mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
agent_ui: Add more refinements for v2 flag (#50968)
- Fixes message editor in empty state not consuming the whole height of the panel - Remove duped focused method in the `ListItem` - Remove duped "new thread" buttons when group is empty - Add some UI adjustments like removing labels and fading out truncated items Release Notes: - N/A
This commit is contained in:
parent
1dd09c3e76
commit
1dd80ac28f
6 changed files with 202 additions and 139 deletions
|
|
@ -3685,7 +3685,7 @@ impl AgentPanel {
|
|||
h_flex()
|
||||
.gap_1()
|
||||
.child(agent_icon_element)
|
||||
.child(Label::new(selected_agent_label).color(label_color))
|
||||
.child(Label::new(selected_agent_label).color(label_color).ml_0p5())
|
||||
.child(
|
||||
Icon::new(chevron_icon)
|
||||
.color(icon_color)
|
||||
|
|
|
|||
|
|
@ -2715,6 +2715,31 @@ impl ThreadView {
|
|||
(IconName::Maximize, "Expand Message Editor")
|
||||
};
|
||||
|
||||
if v2_empty_state {
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_mode(
|
||||
EditorMode::Full {
|
||||
scale_ui_elements_with_buffer_font_size: false,
|
||||
show_active_line_background: false,
|
||||
sizing_behavior: SizingBehavior::Default,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_mode(
|
||||
EditorMode::AutoHeight {
|
||||
min_lines: AgentSettings::get_global(cx).message_editor_min_lines,
|
||||
max_lines: Some(
|
||||
AgentSettings::get_global(cx).set_message_editor_max_lines(),
|
||||
),
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.on_action(cx.listener(Self::expand_message_editor))
|
||||
.p_2()
|
||||
|
|
@ -2731,6 +2756,7 @@ impl ThreadView {
|
|||
v_flex()
|
||||
.relative()
|
||||
.size_full()
|
||||
.when(v2_empty_state, |this| this.flex_1())
|
||||
.pt_1()
|
||||
.pr_2p5()
|
||||
.child(self.message_editor.clone())
|
||||
|
|
|
|||
|
|
@ -1222,8 +1222,10 @@ impl MessageEditor {
|
|||
|
||||
pub fn set_mode(&mut self, mode: EditorMode, cx: &mut Context<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_mode(mode);
|
||||
cx.notify()
|
||||
if *editor.mode() != mode {
|
||||
editor.set_mode(mode);
|
||||
cx.notify()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ enum ListEntry {
|
|||
label: SharedString,
|
||||
workspace: Entity<Workspace>,
|
||||
highlight_positions: Vec<usize>,
|
||||
has_threads: bool,
|
||||
},
|
||||
Thread {
|
||||
session_info: acp_thread::AgentSessionInfo,
|
||||
|
|
@ -322,10 +323,15 @@ impl Sidebar {
|
|||
window,
|
||||
|this, agent_panel, event: &AgentPanelEvent, _window, cx| match event {
|
||||
AgentPanelEvent::ActiveViewChanged => {
|
||||
if let Some(thread) = agent_panel.read(cx).active_connection_view()
|
||||
&& let Some(session_id) = thread.read(cx).parent_id(cx)
|
||||
{
|
||||
this.focused_thread = Some(session_id);
|
||||
match agent_panel.read(cx).active_connection_view() {
|
||||
Some(thread) => {
|
||||
if let Some(session_id) = thread.read(cx).parent_id(cx) {
|
||||
this.focused_thread = Some(session_id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
this.focused_thread = None;
|
||||
}
|
||||
}
|
||||
this.update_entries(cx);
|
||||
}
|
||||
|
|
@ -334,7 +340,7 @@ impl Sidebar {
|
|||
.read(cx)
|
||||
.active_connection_view()
|
||||
.and_then(|thread| thread.read(cx).parent_id(cx));
|
||||
if new_focused != this.focused_thread {
|
||||
if new_focused.is_some() && new_focused != this.focused_thread {
|
||||
this.focused_thread = new_focused;
|
||||
this.update_entries(cx);
|
||||
}
|
||||
|
|
@ -522,6 +528,7 @@ impl Sidebar {
|
|||
}
|
||||
|
||||
if !query.is_empty() {
|
||||
let has_threads = !threads.is_empty();
|
||||
let mut matched_threads = Vec::new();
|
||||
for mut thread in threads {
|
||||
if let ListEntry::Thread {
|
||||
|
|
@ -554,14 +561,17 @@ impl Sidebar {
|
|||
label,
|
||||
workspace: workspace.clone(),
|
||||
highlight_positions: workspace_highlight_positions,
|
||||
has_threads,
|
||||
});
|
||||
entries.extend(matched_threads);
|
||||
} else {
|
||||
let has_threads = !threads.is_empty();
|
||||
entries.push(ListEntry::ProjectHeader {
|
||||
path_list: path_list.clone(),
|
||||
label,
|
||||
workspace: workspace.clone(),
|
||||
highlight_positions: Vec::new(),
|
||||
has_threads,
|
||||
});
|
||||
|
||||
if is_collapsed {
|
||||
|
|
@ -677,12 +687,14 @@ impl Sidebar {
|
|||
label,
|
||||
workspace,
|
||||
highlight_positions,
|
||||
has_threads,
|
||||
} => self.render_project_header(
|
||||
ix,
|
||||
path_list,
|
||||
label,
|
||||
workspace,
|
||||
highlight_positions,
|
||||
*has_threads,
|
||||
is_selected,
|
||||
cx,
|
||||
),
|
||||
|
|
@ -736,12 +748,12 @@ impl Sidebar {
|
|||
label: &SharedString,
|
||||
workspace: &Entity<Workspace>,
|
||||
highlight_positions: &[usize],
|
||||
has_threads: bool,
|
||||
is_selected: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> AnyElement {
|
||||
let id = SharedString::from(format!("project-header-{}", ix));
|
||||
let ib_id = SharedString::from(format!("project-header-new-thread-{}", ix));
|
||||
let group = SharedString::from(format!("group-{}", ix));
|
||||
|
||||
let is_collapsed = self.collapsed_groups.contains(path_list);
|
||||
let disclosure_icon = if is_collapsed {
|
||||
|
|
@ -774,20 +786,19 @@ impl Sidebar {
|
|||
.into_any_element()
|
||||
};
|
||||
|
||||
// TODO: if is_selected, draw a blue border around the item.
|
||||
|
||||
ListItem::new(id)
|
||||
.selection_outlined(is_selected)
|
||||
.group_name(&group)
|
||||
.toggle_state(is_active_workspace)
|
||||
.focused(is_selected)
|
||||
.child(
|
||||
h_flex().px_1().py_1p5().gap_0p5().child(label).child(
|
||||
div().visible_on_hover(group).child(
|
||||
h_flex()
|
||||
.p_1()
|
||||
.gap_1p5()
|
||||
.child(
|
||||
Icon::new(disclosure_icon)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
.color(Color::Custom(cx.theme().colors().icon_muted.opacity(0.6))),
|
||||
)
|
||||
.child(label),
|
||||
)
|
||||
.end_hover_slot(
|
||||
h_flex()
|
||||
|
|
@ -808,18 +819,21 @@ impl Sidebar {
|
|||
)),
|
||||
)
|
||||
})
|
||||
.child(
|
||||
IconButton::new(ib_id, IconName::NewThread)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(Tooltip::text("New Thread"))
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selection = None;
|
||||
this.create_new_thread(&workspace_for_new_thread, window, cx);
|
||||
})),
|
||||
),
|
||||
.when(has_threads, |this| {
|
||||
this.child(
|
||||
IconButton::new(ib_id, IconName::NewThread)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(Tooltip::text("New Thread"))
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selection = None;
|
||||
this.create_new_thread(&workspace_for_new_thread, window, cx);
|
||||
})),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selection = None;
|
||||
this.toggle_collapse(&path_list_for_toggle, window, cx);
|
||||
}))
|
||||
// TODO: Decide if we really want the header to be activating different workspaces
|
||||
|
|
@ -887,12 +901,7 @@ impl Sidebar {
|
|||
self.update_entries(cx);
|
||||
}
|
||||
|
||||
fn focus_in(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selection.is_none() && !self.contents.entries.is_empty() {
|
||||
self.selection = Some(0);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
fn focus_in(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {}
|
||||
|
||||
fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.reset_filter_editor_text(window, cx) {
|
||||
|
|
@ -1122,7 +1131,7 @@ impl Sidebar {
|
|||
.status(status)
|
||||
.notified(has_notification)
|
||||
.selected(self.focused_thread.as_ref() == Some(&session_info.session_id))
|
||||
.outlined(is_selected)
|
||||
.focused(is_selected)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.selection = None;
|
||||
this.activate_thread(session_info.clone(), &workspace, window, cx);
|
||||
|
|
@ -1168,7 +1177,7 @@ impl Sidebar {
|
|||
let count = format!("({})", remaining_count);
|
||||
|
||||
ListItem::new(id)
|
||||
.selection_outlined(is_selected)
|
||||
.focused(is_selected)
|
||||
.child(
|
||||
h_flex()
|
||||
.px_1()
|
||||
|
|
@ -1319,52 +1328,45 @@ impl Render for Sidebar {
|
|||
.justify_between()
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child({
|
||||
let focus_handle_toggle = self.focus_handle.clone();
|
||||
let focus_handle_focus = self.focus_handle.clone();
|
||||
IconButton::new("close-sidebar", IconName::WorkspaceNavOpen)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::element(move |_, cx| {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(Label::new("Close Sidebar"))
|
||||
.child(KeyBinding::for_action_in(
|
||||
&ToggleWorkspaceSidebar,
|
||||
&focus_handle_toggle,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.pt_1()
|
||||
.gap_2()
|
||||
.border_t_1()
|
||||
.border_color(
|
||||
cx.theme().colors().border_variant,
|
||||
)
|
||||
.justify_between()
|
||||
.child(Label::new(focus_tooltip_label))
|
||||
.child(KeyBinding::for_action_in(
|
||||
&FocusWorkspaceSidebar,
|
||||
&focus_handle_focus,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.into_any_element()
|
||||
}))
|
||||
.on_click(cx.listener(|_this, _, _window, cx| {
|
||||
cx.emit(SidebarEvent::Close);
|
||||
}))
|
||||
})
|
||||
.child(Label::new("Threads").size(LabelSize::Small)),
|
||||
)
|
||||
.child({
|
||||
let focus_handle_toggle = self.focus_handle.clone();
|
||||
let focus_handle_focus = self.focus_handle.clone();
|
||||
IconButton::new("close-sidebar", IconName::WorkspaceNavOpen)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(Tooltip::element(move |_, cx| {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(Label::new("Close Sidebar"))
|
||||
.child(KeyBinding::for_action_in(
|
||||
&ToggleWorkspaceSidebar,
|
||||
&focus_handle_toggle,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.pt_1()
|
||||
.gap_2()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.justify_between()
|
||||
.child(Label::new(focus_tooltip_label))
|
||||
.child(KeyBinding::for_action_in(
|
||||
&FocusWorkspaceSidebar,
|
||||
&focus_handle_focus,
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
.into_any_element()
|
||||
}))
|
||||
.on_click(cx.listener(|_this, _, _window, cx| {
|
||||
cx.emit(SidebarEvent::Close);
|
||||
}))
|
||||
})
|
||||
.child(
|
||||
IconButton::new("open-project", IconName::OpenFolder)
|
||||
.icon_size(IconSize::Small)
|
||||
|
|
@ -1852,6 +1854,7 @@ mod tests {
|
|||
label: "expanded-project".into(),
|
||||
workspace: workspace.clone(),
|
||||
highlight_positions: Vec::new(),
|
||||
has_threads: true,
|
||||
},
|
||||
// Thread with default (Completed) status, not active
|
||||
ListEntry::Thread {
|
||||
|
|
@ -1954,6 +1957,7 @@ mod tests {
|
|||
label: "collapsed-project".into(),
|
||||
workspace: workspace.clone(),
|
||||
highlight_positions: Vec::new(),
|
||||
has_threads: true,
|
||||
},
|
||||
];
|
||||
// Select the Running thread (index 2)
|
||||
|
|
@ -2014,11 +2018,16 @@ mod tests {
|
|||
cx.run_until_parked();
|
||||
|
||||
// Entries: [header, thread3, thread2, thread1]
|
||||
// Focusing the sidebar triggers focus_in, which selects the first entry
|
||||
// Focusing the sidebar does not set a selection; select_next/select_previous
|
||||
// handle None gracefully by starting from the first or last entry.
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), None);
|
||||
|
||||
// First SelectNext from None starts at index 0
|
||||
cx.dispatch_action(SelectNext);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
|
||||
|
||||
// Move down through all entries
|
||||
// Move down through remaining entries
|
||||
cx.dispatch_action(SelectNext);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(1));
|
||||
|
||||
|
|
@ -2072,7 +2081,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_keyboard_focus_in_selects_first(cx: &mut TestAppContext) {
|
||||
async fn test_keyboard_focus_in_does_not_set_selection(cx: &mut TestAppContext) {
|
||||
let project = init_test_project("/my-project", cx).await;
|
||||
let (multi_workspace, cx) =
|
||||
cx.add_window_view(|window, cx| MultiWorkspace::test_new(project, window, cx));
|
||||
|
|
@ -2081,11 +2090,16 @@ mod tests {
|
|||
// Initially no selection
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), None);
|
||||
|
||||
// Open the sidebar so it's rendered, then focus it to trigger focus_in
|
||||
// Open the sidebar so it's rendered, then focus it to trigger focus_in.
|
||||
// focus_in no longer sets a default selection.
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), None);
|
||||
|
||||
// Manually set a selection, blur, then refocus — selection should be preserved
|
||||
sidebar.update_in(cx, |sidebar, _window, _cx| {
|
||||
sidebar.selection = Some(0);
|
||||
});
|
||||
|
||||
// Blur the sidebar, then refocus — existing selection should be preserved
|
||||
cx.update(|window, _cx| {
|
||||
window.blur();
|
||||
});
|
||||
|
|
@ -2135,9 +2149,11 @@ mod tests {
|
|||
1
|
||||
);
|
||||
|
||||
// Focus the sidebar — focus_in selects the header (index 0)
|
||||
// Focus the sidebar and manually select the header (index 0)
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
|
||||
sidebar.update_in(cx, |sidebar, _window, _cx| {
|
||||
sidebar.selection = Some(0);
|
||||
});
|
||||
|
||||
// Press confirm on project header (workspace 0) to activate it.
|
||||
cx.dispatch_action(Confirm);
|
||||
|
|
@ -2176,9 +2192,9 @@ mod tests {
|
|||
assert_eq!(entries.len(), 7);
|
||||
assert!(entries.iter().any(|e| e.contains("View More (3)")));
|
||||
|
||||
// Focus sidebar (selects index 0), then navigate down to the "View More" entry (index 6)
|
||||
// Focus sidebar (selection starts at None), then navigate down to the "View More" entry (index 6)
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
for _ in 0..6 {
|
||||
for _ in 0..7 {
|
||||
cx.dispatch_action(SelectNext);
|
||||
}
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(6));
|
||||
|
|
@ -2210,9 +2226,11 @@ mod tests {
|
|||
vec!["v [my-project]", " Thread 1"]
|
||||
);
|
||||
|
||||
// Focus sidebar — focus_in selects the header (index 0). Press left to collapse.
|
||||
// Focus sidebar and manually select the header (index 0). Press left to collapse.
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
|
||||
sidebar.update_in(cx, |sidebar, _window, _cx| {
|
||||
sidebar.selection = Some(0);
|
||||
});
|
||||
|
||||
cx.dispatch_action(CollapseSelectedEntry);
|
||||
cx.run_until_parked();
|
||||
|
|
@ -2248,9 +2266,10 @@ mod tests {
|
|||
multi_workspace.update_in(cx, |_, _window, cx| cx.notify());
|
||||
cx.run_until_parked();
|
||||
|
||||
// Focus sidebar (selects header at index 0), then navigate down to the thread (child)
|
||||
// Focus sidebar (selection starts at None), then navigate down to the thread (child)
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
cx.dispatch_action(SelectNext);
|
||||
cx.dispatch_action(SelectNext);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(1));
|
||||
|
||||
assert_eq!(
|
||||
|
|
@ -2282,8 +2301,12 @@ mod tests {
|
|||
vec!["v [empty-project]", " [+ New Thread]"]
|
||||
);
|
||||
|
||||
// Focus sidebar — focus_in selects the first entry (header at 0)
|
||||
// Focus sidebar — focus_in does not set a selection
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), None);
|
||||
|
||||
// First SelectNext from None starts at index 0 (header)
|
||||
cx.dispatch_action(SelectNext);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
|
||||
|
||||
// SelectNext moves to the new thread button
|
||||
|
|
@ -2311,9 +2334,10 @@ mod tests {
|
|||
multi_workspace.update_in(cx, |_, _window, cx| cx.notify());
|
||||
cx.run_until_parked();
|
||||
|
||||
// Focus sidebar (selects header at 0), navigate down to the thread (index 1)
|
||||
// Focus sidebar (selection starts at None), navigate down to the thread (index 1)
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
cx.dispatch_action(SelectNext);
|
||||
cx.dispatch_action(SelectNext);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(1));
|
||||
|
||||
// Collapse the group, which removes the thread from the list
|
||||
|
|
@ -2935,9 +2959,11 @@ mod tests {
|
|||
cx.run_until_parked();
|
||||
|
||||
// User focuses the sidebar and collapses the group using keyboard:
|
||||
// select the header, then press CollapseSelectedEntry to collapse.
|
||||
// manually select the header, then press CollapseSelectedEntry to collapse.
|
||||
open_and_focus_sidebar(&sidebar, &multi_workspace, cx);
|
||||
assert_eq!(sidebar.read_with(cx, |s, _| s.selection), Some(0));
|
||||
sidebar.update_in(cx, |sidebar, _window, _cx| {
|
||||
sidebar.selection = Some(0);
|
||||
});
|
||||
cx.dispatch_action(CollapseSelectedEntry);
|
||||
cx.run_until_parked();
|
||||
|
||||
|
|
@ -3151,15 +3177,12 @@ mod tests {
|
|||
});
|
||||
assert_eq!(sidebar.read_with(cx, |sidebar, _| sidebar.selection), None);
|
||||
|
||||
// When the user tabs back into the sidebar, focus_in restores
|
||||
// selection to the first entry for keyboard navigation.
|
||||
// When the user tabs back into the sidebar, focus_in no longer
|
||||
// restores selection — it stays None.
|
||||
sidebar.update_in(cx, |sidebar, window, cx| {
|
||||
sidebar.focus_in(window, cx);
|
||||
});
|
||||
assert_eq!(
|
||||
sidebar.read_with(cx, |sidebar, _| sidebar.selection),
|
||||
Some(0)
|
||||
);
|
||||
assert_eq!(sidebar.read_with(cx, |sidebar, _| sidebar.selection), None);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
prelude::*,
|
||||
};
|
||||
|
||||
use gpui::{AnyView, ClickEvent, Hsla, SharedString};
|
||||
use gpui::{AnyView, ClickEvent, Hsla, SharedString, linear_color_stop, linear_gradient};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum AgentThreadStatus {
|
||||
|
|
@ -24,7 +24,7 @@ pub struct ThreadItem {
|
|||
notified: bool,
|
||||
status: AgentThreadStatus,
|
||||
selected: bool,
|
||||
outlined: bool,
|
||||
focused: bool,
|
||||
hovered: bool,
|
||||
added: Option<usize>,
|
||||
removed: Option<usize>,
|
||||
|
|
@ -48,7 +48,7 @@ impl ThreadItem {
|
|||
notified: false,
|
||||
status: AgentThreadStatus::default(),
|
||||
selected: false,
|
||||
outlined: false,
|
||||
focused: false,
|
||||
hovered: false,
|
||||
added: None,
|
||||
removed: None,
|
||||
|
|
@ -92,8 +92,8 @@ impl ThreadItem {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn outlined(mut self, outlined: bool) -> Self {
|
||||
self.outlined = outlined;
|
||||
pub fn focused(mut self, focused: bool) -> Self {
|
||||
self.focused = focused;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +153,7 @@ impl ThreadItem {
|
|||
|
||||
impl RenderOnce for ThreadItem {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let clr = cx.theme().colors();
|
||||
let color = cx.theme().colors();
|
||||
// let dot_separator = || {
|
||||
// Label::new("•")
|
||||
// .size(LabelSize::Small)
|
||||
|
|
@ -161,7 +161,7 @@ impl RenderOnce for ThreadItem {
|
|||
// .alpha(0.5)
|
||||
// };
|
||||
|
||||
let icon_container = || h_flex().size_4().justify_center();
|
||||
let icon_container = || h_flex().size_4().flex_none().justify_center();
|
||||
let agent_icon = if let Some(custom_svg) = self.custom_icon_from_external_svg {
|
||||
Icon::from_external_svg(custom_svg)
|
||||
.color(Color::Muted)
|
||||
|
|
@ -189,7 +189,7 @@ impl RenderOnce for ThreadItem {
|
|||
} else if self.status == AgentThreadStatus::Error {
|
||||
Some(decoration(IconDecorationKind::X, cx.theme().status().error))
|
||||
} else if self.notified {
|
||||
Some(decoration(IconDecorationKind::Dot, clr.text_accent))
|
||||
Some(decoration(IconDecorationKind::Dot, color.text_accent))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
@ -209,15 +209,41 @@ impl RenderOnce for ThreadItem {
|
|||
let title = self.title;
|
||||
let highlight_positions = self.highlight_positions;
|
||||
let title_label = if highlight_positions.is_empty() {
|
||||
Label::new(title).truncate().into_any_element()
|
||||
Label::new(title).into_any_element()
|
||||
} else {
|
||||
HighlightedLabel::new(title, highlight_positions)
|
||||
.truncate()
|
||||
.into_any_element()
|
||||
HighlightedLabel::new(title, highlight_positions).into_any_element()
|
||||
};
|
||||
|
||||
let base_bg = if self.selected {
|
||||
color.element_active
|
||||
} else {
|
||||
color.panel_background
|
||||
};
|
||||
|
||||
let gradient_overlay = div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.right(px(-10.0))
|
||||
.w_12()
|
||||
.h_full()
|
||||
.bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(base_bg, 0.6),
|
||||
linear_color_stop(base_bg.opacity(0.0), 0.),
|
||||
))
|
||||
.group_hover("thread-item", |s| {
|
||||
s.bg(linear_gradient(
|
||||
90.,
|
||||
linear_color_stop(color.element_hover, 0.6),
|
||||
linear_color_stop(color.element_hover.opacity(0.0), 0.),
|
||||
))
|
||||
});
|
||||
|
||||
v_flex()
|
||||
.id(self.id.clone())
|
||||
.group("thread-item")
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.cursor_pointer()
|
||||
.w_full()
|
||||
.map(|this| {
|
||||
|
|
@ -227,11 +253,11 @@ impl RenderOnce for ThreadItem {
|
|||
this.px_2().py_1()
|
||||
}
|
||||
})
|
||||
.when(self.selected, |s| s.bg(clr.element_active))
|
||||
.when(self.selected, |s| s.bg(color.element_active))
|
||||
.border_1()
|
||||
.border_color(gpui::transparent_black())
|
||||
.when(self.outlined, |s| s.border_color(clr.panel_focused_border))
|
||||
.hover(|s| s.bg(clr.element_hover))
|
||||
.when(self.focused, |s| s.border_color(color.panel_focused_border))
|
||||
.hover(|s| s.bg(color.element_hover))
|
||||
.on_hover(self.on_hover)
|
||||
.child(
|
||||
h_flex()
|
||||
|
|
@ -249,6 +275,7 @@ impl RenderOnce for ThreadItem {
|
|||
.child(title_label)
|
||||
.when_some(self.tooltip, |this, tooltip| this.tooltip(tooltip)),
|
||||
)
|
||||
.child(gradient_overlay)
|
||||
.when(running_or_action, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
|
|
@ -271,7 +298,6 @@ impl RenderOnce for ThreadItem {
|
|||
Label::new(worktree)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.truncate_start()
|
||||
.into_any_element()
|
||||
} else {
|
||||
HighlightedLabel::new(worktree, worktree_highlight_positions)
|
||||
|
|
@ -420,25 +446,25 @@ impl Component for ThreadItem {
|
|||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Outlined Item (Keyboard Selection)",
|
||||
"Focused Item (Keyboard Selection)",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-7", "Implement keyboard navigation")
|
||||
.icon(IconName::AiClaude)
|
||||
.timestamp("4:00 PM")
|
||||
.outlined(true),
|
||||
.focused(true),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected + Outlined",
|
||||
"Selected + Focused",
|
||||
container()
|
||||
.child(
|
||||
ThreadItem::new("ti-8", "Active and keyboard-focused thread")
|
||||
.icon(IconName::AiGemini)
|
||||
.timestamp("5:00 PM")
|
||||
.selected(true)
|
||||
.outlined(true),
|
||||
.focused(true),
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,6 @@ pub struct ListItem {
|
|||
selectable: bool,
|
||||
always_show_disclosure_icon: bool,
|
||||
outlined: bool,
|
||||
selection_outlined: Option<bool>,
|
||||
rounded: bool,
|
||||
overflow_x: bool,
|
||||
focused: Option<bool>,
|
||||
|
|
@ -72,7 +71,6 @@ impl ListItem {
|
|||
selectable: true,
|
||||
always_show_disclosure_icon: false,
|
||||
outlined: false,
|
||||
selection_outlined: None,
|
||||
rounded: false,
|
||||
overflow_x: false,
|
||||
focused: None,
|
||||
|
|
@ -173,11 +171,6 @@ impl ListItem {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn selection_outlined(mut self, outlined: bool) -> Self {
|
||||
self.selection_outlined = Some(outlined);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn rounded(mut self) -> Self {
|
||||
self.rounded = true;
|
||||
self
|
||||
|
|
@ -248,13 +241,6 @@ impl RenderOnce for ListItem {
|
|||
})
|
||||
})
|
||||
.when(self.rounded, |this| this.rounded_sm())
|
||||
.when_some(self.selection_outlined, |this, outlined| {
|
||||
this.border_1()
|
||||
.border_color(gpui::transparent_black())
|
||||
.when(outlined, |this| {
|
||||
this.border_color(cx.theme().colors().panel_focused_border)
|
||||
})
|
||||
})
|
||||
.when_some(self.on_hover, |this, on_hover| this.on_hover(on_hover))
|
||||
.child(
|
||||
h_flex()
|
||||
|
|
|
|||
Loading…
Reference in a new issue