mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
agent: Improve sidebar design and behavior (#51763)
- Selection/focus improvements (reduces flickers, move selection more correctly throughout the list) - Adds open folder button in the sidebar header - Fixes sidebar header design, including the thread view one, too - Fixes behavior of cmd-n when focused in the sidebar - Changes the design for the "new thread" button in the sidebar and adds a preview of the prompt on it - Rename items in the "start thread in" dropdown Release Notes: - N/A --------- Co-authored-by: cameron <cameron.studdstreet@gmail.com>
This commit is contained in:
parent
65b80ff689
commit
9f3e3be65f
13 changed files with 915 additions and 549 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -15870,6 +15870,7 @@ dependencies = [
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"project",
|
"project",
|
||||||
"prompt_store",
|
"prompt_store",
|
||||||
|
"recent_projects",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"theme",
|
"theme",
|
||||||
|
|
|
||||||
|
|
@ -674,7 +674,7 @@
|
||||||
"context": "ThreadsSidebar",
|
"context": "ThreadsSidebar",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-n": "multi_workspace::NewWorkspaceInWindow",
|
"ctrl-n": "agents_sidebar::NewThreadInGroup",
|
||||||
"left": "agents_sidebar::CollapseSelectedEntry",
|
"left": "agents_sidebar::CollapseSelectedEntry",
|
||||||
"right": "agents_sidebar::ExpandSelectedEntry",
|
"right": "agents_sidebar::ExpandSelectedEntry",
|
||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
|
|
|
||||||
|
|
@ -742,7 +742,7 @@
|
||||||
"context": "ThreadsSidebar",
|
"context": "ThreadsSidebar",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-n": "multi_workspace::NewWorkspaceInWindow",
|
"cmd-n": "agents_sidebar::NewThreadInGroup",
|
||||||
"left": "agents_sidebar::CollapseSelectedEntry",
|
"left": "agents_sidebar::CollapseSelectedEntry",
|
||||||
"right": "agents_sidebar::ExpandSelectedEntry",
|
"right": "agents_sidebar::ExpandSelectedEntry",
|
||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
|
|
|
||||||
|
|
@ -678,7 +678,7 @@
|
||||||
"context": "ThreadsSidebar",
|
"context": "ThreadsSidebar",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-n": "multi_workspace::NewWorkspaceInWindow",
|
"ctrl-n": "agents_sidebar::NewThreadInGroup",
|
||||||
"left": "agents_sidebar::CollapseSelectedEntry",
|
"left": "agents_sidebar::CollapseSelectedEntry",
|
||||||
"right": "agents_sidebar::ExpandSelectedEntry",
|
"right": "agents_sidebar::ExpandSelectedEntry",
|
||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
|
|
|
||||||
|
|
@ -601,8 +601,8 @@ impl From<Agent> for AgentType {
|
||||||
impl StartThreadIn {
|
impl StartThreadIn {
|
||||||
fn label(&self) -> SharedString {
|
fn label(&self) -> SharedString {
|
||||||
match self {
|
match self {
|
||||||
Self::LocalProject => "Current Project".into(),
|
Self::LocalProject => "Current Worktree".into(),
|
||||||
Self::NewWorktree => "New Worktree".into(),
|
Self::NewWorktree => "New Git Worktree".into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1951,6 +1951,21 @@ impl AgentPanel {
|
||||||
self.background_threads.contains_key(session_id)
|
self.background_threads.contains_key(session_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cancel_thread(&self, session_id: &acp::SessionId, cx: &mut Context<Self>) -> bool {
|
||||||
|
let conversation_views = self
|
||||||
|
.active_conversation_view()
|
||||||
|
.into_iter()
|
||||||
|
.chain(self.background_threads.values());
|
||||||
|
|
||||||
|
for conversation_view in conversation_views {
|
||||||
|
if let Some(thread_view) = conversation_view.read(cx).thread_view(session_id) {
|
||||||
|
thread_view.update(cx, |view, cx| view.cancel_generation(cx));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// active thread plus any background threads that are still running or
|
/// active thread plus any background threads that are still running or
|
||||||
/// completed but unseen.
|
/// completed but unseen.
|
||||||
pub fn parent_threads(&self, cx: &App) -> Vec<Entity<ThreadView>> {
|
pub fn parent_threads(&self, cx: &App) -> Vec<Entity<ThreadView>> {
|
||||||
|
|
@ -3551,7 +3566,7 @@ impl AgentPanel {
|
||||||
|
|
||||||
menu.header("Start Thread In…")
|
menu.header("Start Thread In…")
|
||||||
.item(
|
.item(
|
||||||
ContextMenuEntry::new("Current Project")
|
ContextMenuEntry::new("Current Worktree")
|
||||||
.toggleable(IconPosition::End, is_local_selected)
|
.toggleable(IconPosition::End, is_local_selected)
|
||||||
.documentation_aside(documentation_side, move |_| {
|
.documentation_aside(documentation_side, move |_| {
|
||||||
HoldForDefault::new(is_local_default)
|
HoldForDefault::new(is_local_default)
|
||||||
|
|
@ -3579,7 +3594,7 @@ impl AgentPanel {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.item({
|
.item({
|
||||||
let entry = ContextMenuEntry::new("New Worktree")
|
let entry = ContextMenuEntry::new("New Git Worktree")
|
||||||
.toggleable(IconPosition::End, is_new_worktree_selected)
|
.toggleable(IconPosition::End, is_new_worktree_selected)
|
||||||
.disabled(new_worktree_disabled)
|
.disabled(new_worktree_disabled)
|
||||||
.handler({
|
.handler({
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use theme::ActiveTheme;
|
||||||
use ui::{
|
use ui::{
|
||||||
ButtonLike, CommonAnimationExt, ContextMenu, ContextMenuEntry, HighlightedLabel, ListItem,
|
ButtonLike, CommonAnimationExt, ContextMenu, ContextMenuEntry, HighlightedLabel, ListItem,
|
||||||
PopoverMenu, PopoverMenuHandle, Tab, TintColor, Tooltip, WithScrollbar, prelude::*,
|
PopoverMenu, PopoverMenuHandle, Tab, TintColor, Tooltip, WithScrollbar, prelude::*,
|
||||||
|
utils::platform_title_bar_height,
|
||||||
};
|
};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
use zed_actions::editor::{MoveDown, MoveUp};
|
use zed_actions::editor::{MoveDown, MoveUp};
|
||||||
|
|
@ -676,32 +677,56 @@ impl ThreadsArchiveView {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_header(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_header(&self, window: &Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let has_query = !self.filter_editor.read(cx).text(cx).is_empty();
|
let has_query = !self.filter_editor.read(cx).text(cx).is_empty();
|
||||||
|
let traffic_lights = cfg!(target_os = "macos") && !window.is_fullscreen();
|
||||||
|
let header_height = platform_title_bar_height(window);
|
||||||
|
|
||||||
h_flex()
|
v_flex()
|
||||||
.h(Tab::container_height(cx))
|
|
||||||
.px_1()
|
|
||||||
.justify_between()
|
|
||||||
.border_b_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.flex_1()
|
.h(header_height)
|
||||||
.w_full()
|
.mt_px()
|
||||||
.gap_1p5()
|
.pb_px()
|
||||||
|
.when(traffic_lights, |this| {
|
||||||
|
this.pl(px(ui::utils::TRAFFIC_LIGHT_PADDING))
|
||||||
|
})
|
||||||
|
.pr_1p5()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.justify_between()
|
||||||
.child(
|
.child(
|
||||||
IconButton::new("back", IconName::ArrowLeft)
|
h_flex()
|
||||||
.icon_size(IconSize::Small)
|
.gap_1p5()
|
||||||
.tooltip(Tooltip::text("Back to Sidebar"))
|
.child(
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
IconButton::new("back", IconName::ArrowLeft)
|
||||||
this.go_back(window, cx);
|
.icon_size(IconSize::Small)
|
||||||
})),
|
.tooltip(Tooltip::text("Back to Sidebar"))
|
||||||
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
this.go_back(window, cx);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(Label::new("Threads Archive").size(LabelSize::Small).mb_px()),
|
||||||
|
)
|
||||||
|
.child(self.render_agent_picker(cx)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.h(Tab::container_height(cx))
|
||||||
|
.p_2()
|
||||||
|
.pr_1p5()
|
||||||
|
.gap_1p5()
|
||||||
|
.border_b_1()
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
|
.child(
|
||||||
|
Icon::new(IconName::MagnifyingGlass)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
.child(self.filter_editor.clone())
|
.child(self.filter_editor.clone())
|
||||||
.when(has_query, |this| {
|
.when(has_query, |this| {
|
||||||
this.border_r_1().child(
|
this.child(
|
||||||
IconButton::new("clear_archive_filter", IconName::Close)
|
IconButton::new("clear_filter", IconName::Close)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.tooltip(Tooltip::text("Clear Search"))
|
.tooltip(Tooltip::text("Clear Search"))
|
||||||
.on_click(cx.listener(|this, _, window, cx| {
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
|
|
@ -711,7 +736,6 @@ impl ThreadsArchiveView {
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(self.render_agent_picker(cx))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -783,8 +807,7 @@ impl Render for ThreadsArchiveView {
|
||||||
.on_action(cx.listener(Self::confirm))
|
.on_action(cx.listener(Self::confirm))
|
||||||
.on_action(cx.listener(Self::remove_selected_thread))
|
.on_action(cx.listener(Self::remove_selected_thread))
|
||||||
.size_full()
|
.size_full()
|
||||||
.bg(cx.theme().colors().surface_background)
|
.child(self.render_header(window, cx))
|
||||||
.child(self.render_header(cx))
|
|
||||||
.child(content)
|
.child(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ fs.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
|
recent_projects.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -41,8 +41,8 @@ use std::sync::Arc;
|
||||||
use theme::ActiveTheme;
|
use theme::ActiveTheme;
|
||||||
use title_bar_settings::TitleBarSettings;
|
use title_bar_settings::TitleBarSettings;
|
||||||
use ui::{
|
use ui::{
|
||||||
Avatar, ButtonLike, ContextMenu, IconWithIndicator, Indicator, PopoverMenu, PopoverMenuHandle,
|
Avatar, ButtonLike, ContextMenu, Divider, IconWithIndicator, Indicator, PopoverMenu,
|
||||||
TintColor, Tooltip, prelude::*, utils::platform_title_bar_height,
|
PopoverMenuHandle, TintColor, Tooltip, prelude::*, utils::platform_title_bar_height,
|
||||||
};
|
};
|
||||||
use update_version::UpdateVersion;
|
use update_version::UpdateVersion;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
|
@ -169,6 +169,7 @@ impl Render for TitleBar {
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.h_full()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
.map(|title_bar| {
|
.map(|title_bar| {
|
||||||
let mut render_project_items = title_bar_settings.show_branch_name
|
let mut render_project_items = title_bar_settings.show_branch_name
|
||||||
|
|
@ -705,23 +706,29 @@ impl TitleBar {
|
||||||
let has_notifications = self.platform_titlebar.read(cx).sidebar_has_notifications();
|
let has_notifications = self.platform_titlebar.read(cx).sidebar_has_notifications();
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
IconButton::new(
|
h_flex()
|
||||||
"toggle-workspace-sidebar",
|
.h_full()
|
||||||
IconName::ThreadsSidebarLeftClosed,
|
.gap_0p5()
|
||||||
)
|
.child(
|
||||||
.icon_size(IconSize::Small)
|
IconButton::new(
|
||||||
.when(has_notifications, |button| {
|
"toggle-workspace-sidebar",
|
||||||
button
|
IconName::ThreadsSidebarLeftClosed,
|
||||||
.indicator(Indicator::dot().color(Color::Accent))
|
)
|
||||||
.indicator_border_color(Some(cx.theme().colors().title_bar_background))
|
.icon_size(IconSize::Small)
|
||||||
})
|
.when(has_notifications, |button| {
|
||||||
.tooltip(move |_, cx| {
|
button
|
||||||
Tooltip::for_action("Open Threads Sidebar", &ToggleWorkspaceSidebar, cx)
|
.indicator(Indicator::dot().color(Color::Accent))
|
||||||
})
|
.indicator_border_color(Some(cx.theme().colors().title_bar_background))
|
||||||
.on_click(|_, window, cx| {
|
})
|
||||||
window.dispatch_action(ToggleWorkspaceSidebar.boxed_clone(), cx);
|
.tooltip(move |_, cx| {
|
||||||
})
|
Tooltip::for_action("Open Threads Sidebar", &ToggleWorkspaceSidebar, cx)
|
||||||
.into_any_element(),
|
})
|
||||||
|
.on_click(|_, window, cx| {
|
||||||
|
window.dispatch_action(ToggleWorkspaceSidebar.boxed_clone(), cx);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(Divider::vertical().color(ui::DividerColor::Border))
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -741,6 +748,14 @@ impl TitleBar {
|
||||||
"Open Recent Project".to_string()
|
"Open Recent Project".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let is_sidebar_open = self.platform_titlebar.read(cx).is_workspace_sidebar_open();
|
||||||
|
|
||||||
|
if is_sidebar_open {
|
||||||
|
return self
|
||||||
|
.render_project_name_with_sidebar_popover(display_name, is_project_selected, cx)
|
||||||
|
.into_any_element();
|
||||||
|
}
|
||||||
|
|
||||||
let focus_handle = workspace
|
let focus_handle = workspace
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.map(|w| w.read(cx).focus_handle(cx))
|
.map(|w| w.read(cx).focus_handle(cx))
|
||||||
|
|
@ -782,6 +797,53 @@ impl TitleBar {
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When the sidebar is open, the title bar's project name button becomes a
|
||||||
|
/// plain button that toggles the sidebar's popover (so the popover is always
|
||||||
|
/// anchored to the sidebar). Both buttons show their selected state together.
|
||||||
|
fn render_project_name_with_sidebar_popover(
|
||||||
|
&self,
|
||||||
|
display_name: String,
|
||||||
|
is_project_selected: bool,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let multi_workspace = self.multi_workspace.clone();
|
||||||
|
|
||||||
|
let is_popover_deployed = multi_workspace
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|mw| mw.upgrade())
|
||||||
|
.map(|mw| mw.read(cx).is_recent_projects_popover_deployed(cx))
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
Button::new("project_name_trigger", display_name)
|
||||||
|
.label_size(LabelSize::Small)
|
||||||
|
.when(self.worktree_count(cx) > 1, |this| {
|
||||||
|
this.end_icon(
|
||||||
|
Icon::new(IconName::ChevronDown)
|
||||||
|
.size(IconSize::XSmall)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.toggle_state(is_popover_deployed)
|
||||||
|
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
|
.when(!is_project_selected, |s| s.color(Color::Muted))
|
||||||
|
.tooltip(move |_window, cx| {
|
||||||
|
Tooltip::for_action(
|
||||||
|
"Recent Projects",
|
||||||
|
&zed_actions::OpenRecent {
|
||||||
|
create_new_window: false,
|
||||||
|
},
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.on_click(move |_, window, cx| {
|
||||||
|
if let Some(mw) = multi_workspace.as_ref().and_then(|mw| mw.upgrade()) {
|
||||||
|
mw.update(cx, |mw, cx| {
|
||||||
|
mw.toggle_recent_projects_popover(window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_project_branch(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
|
pub fn render_project_branch(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
|
||||||
let effective_worktree = self.effective_active_worktree(cx)?;
|
let effective_worktree = self.effective_active_worktree(cx)?;
|
||||||
let repository = self.get_repository_for_worktree(&effective_worktree, cx)?;
|
let repository = self.get_repository_for_worktree(&effective_worktree, cx)?;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
mod configured_api_card;
|
mod configured_api_card;
|
||||||
mod thread_item;
|
mod thread_item;
|
||||||
mod thread_sidebar_toggle;
|
|
||||||
|
|
||||||
pub use configured_api_card::*;
|
pub use configured_api_card::*;
|
||||||
pub use thread_item::*;
|
pub use thread_item::*;
|
||||||
pub use thread_sidebar_toggle::*;
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@ use crate::{
|
||||||
IconDecorationKind, prelude::*,
|
IconDecorationKind, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gpui::{Animation, AnimationExt, AnyView, ClickEvent, Hsla, SharedString, pulsating_between};
|
use gpui::{
|
||||||
|
Animation, AnimationExt, AnyView, ClickEvent, Hsla, MouseButton, SharedString,
|
||||||
|
pulsating_between,
|
||||||
|
};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
|
@ -36,6 +39,7 @@ pub struct ThreadItem {
|
||||||
worktree_highlight_positions: Vec<usize>,
|
worktree_highlight_positions: Vec<usize>,
|
||||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||||
on_hover: Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>,
|
on_hover: Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>,
|
||||||
|
title_label_color: Option<Color>,
|
||||||
action_slot: Option<AnyElement>,
|
action_slot: Option<AnyElement>,
|
||||||
tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
|
tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +66,7 @@ impl ThreadItem {
|
||||||
worktree_highlight_positions: Vec::new(),
|
worktree_highlight_positions: Vec::new(),
|
||||||
on_click: None,
|
on_click: None,
|
||||||
on_hover: Box::new(|_, _, _| {}),
|
on_hover: Box::new(|_, _, _| {}),
|
||||||
|
title_label_color: None,
|
||||||
action_slot: None,
|
action_slot: None,
|
||||||
tooltip: None,
|
tooltip: None,
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +160,11 @@ impl ThreadItem {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn title_label_color(mut self, color: Color) -> Self {
|
||||||
|
self.title_label_color = Some(color);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn action_slot(mut self, element: impl IntoElement) -> Self {
|
pub fn action_slot(mut self, element: impl IntoElement) -> Self {
|
||||||
self.action_slot = Some(element.into_any_element());
|
self.action_slot = Some(element.into_any_element());
|
||||||
self
|
self
|
||||||
|
|
@ -230,7 +240,7 @@ impl RenderOnce for ThreadItem {
|
||||||
let title = self.title;
|
let title = self.title;
|
||||||
let highlight_positions = self.highlight_positions;
|
let highlight_positions = self.highlight_positions;
|
||||||
let title_label = if self.generating_title {
|
let title_label = if self.generating_title {
|
||||||
Label::new("New Thread…")
|
Label::new(title)
|
||||||
.color(Color::Muted)
|
.color(Color::Muted)
|
||||||
.with_animation(
|
.with_animation(
|
||||||
"generating-title",
|
"generating-title",
|
||||||
|
|
@ -241,15 +251,31 @@ impl RenderOnce for ThreadItem {
|
||||||
)
|
)
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
} else if highlight_positions.is_empty() {
|
} else if highlight_positions.is_empty() {
|
||||||
Label::new(title).into_any_element()
|
let label = Label::new(title);
|
||||||
|
let label = if let Some(color) = self.title_label_color {
|
||||||
|
label.color(color)
|
||||||
|
} else {
|
||||||
|
label
|
||||||
|
};
|
||||||
|
label.into_any_element()
|
||||||
} else {
|
} else {
|
||||||
HighlightedLabel::new(title, highlight_positions).into_any_element()
|
let label = HighlightedLabel::new(title, highlight_positions);
|
||||||
|
let label = if let Some(color) = self.title_label_color {
|
||||||
|
label.color(color)
|
||||||
|
} else {
|
||||||
|
label
|
||||||
|
};
|
||||||
|
label.into_any_element()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let b_bg = color
|
||||||
|
.title_bar_background
|
||||||
|
.blend(color.panel_background.opacity(0.8));
|
||||||
|
|
||||||
let base_bg = if self.selected {
|
let base_bg = if self.selected {
|
||||||
color.element_active
|
color.element_active
|
||||||
} else {
|
} else {
|
||||||
color.panel_background
|
b_bg
|
||||||
};
|
};
|
||||||
|
|
||||||
let gradient_overlay =
|
let gradient_overlay =
|
||||||
|
|
@ -314,7 +340,15 @@ impl RenderOnce for ThreadItem {
|
||||||
.gradient_stop(0.75)
|
.gradient_stop(0.75)
|
||||||
.group_name("thread-item");
|
.group_name("thread-item");
|
||||||
|
|
||||||
this.child(h_flex().relative().child(overlay).child(slot))
|
this.child(
|
||||||
|
h_flex()
|
||||||
|
.relative()
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, _, cx| {
|
||||||
|
cx.stop_propagation()
|
||||||
|
})
|
||||||
|
.child(overlay)
|
||||||
|
.child(slot),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
use gpui::{AnyView, ClickEvent};
|
|
||||||
use ui_macros::RegisterComponent;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use crate::{IconButton, IconName, Tooltip};
|
|
||||||
|
|
||||||
#[derive(IntoElement, RegisterComponent)]
|
|
||||||
pub struct ThreadSidebarToggle {
|
|
||||||
sidebar_selected: bool,
|
|
||||||
thread_selected: bool,
|
|
||||||
flipped: bool,
|
|
||||||
sidebar_tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
|
|
||||||
thread_tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView + 'static>>,
|
|
||||||
on_sidebar_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
|
||||||
on_thread_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThreadSidebarToggle {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
sidebar_selected: false,
|
|
||||||
thread_selected: false,
|
|
||||||
flipped: false,
|
|
||||||
sidebar_tooltip: None,
|
|
||||||
thread_tooltip: None,
|
|
||||||
on_sidebar_click: None,
|
|
||||||
on_thread_click: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sidebar_selected(mut self, selected: bool) -> Self {
|
|
||||||
self.sidebar_selected = selected;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn thread_selected(mut self, selected: bool) -> Self {
|
|
||||||
self.thread_selected = selected;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flipped(mut self, flipped: bool) -> Self {
|
|
||||||
self.flipped = flipped;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sidebar_tooltip(
|
|
||||||
mut self,
|
|
||||||
tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.sidebar_tooltip = Some(Box::new(tooltip));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn thread_tooltip(
|
|
||||||
mut self,
|
|
||||||
tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.thread_tooltip = Some(Box::new(tooltip));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_sidebar_click(
|
|
||||||
mut self,
|
|
||||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.on_sidebar_click = Some(Box::new(handler));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_thread_click(
|
|
||||||
mut self,
|
|
||||||
handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.on_thread_click = Some(Box::new(handler));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for ThreadSidebarToggle {
|
|
||||||
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
let sidebar_icon = match (self.sidebar_selected, self.flipped) {
|
|
||||||
(true, false) => IconName::ThreadsSidebarLeftOpen,
|
|
||||||
(false, false) => IconName::ThreadsSidebarLeftClosed,
|
|
||||||
(true, true) => IconName::ThreadsSidebarRightOpen,
|
|
||||||
(false, true) => IconName::ThreadsSidebarRightClosed,
|
|
||||||
};
|
|
||||||
|
|
||||||
h_flex()
|
|
||||||
.min_w_0()
|
|
||||||
.rounded_sm()
|
|
||||||
.gap_px()
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.when(self.flipped, |this| this.flex_row_reverse())
|
|
||||||
.child(
|
|
||||||
IconButton::new("sidebar-toggle", sidebar_icon)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.toggle_state(self.sidebar_selected)
|
|
||||||
.when_some(self.sidebar_tooltip, |this, tooltip| this.tooltip(tooltip))
|
|
||||||
.when_some(self.on_sidebar_click, |this, handler| {
|
|
||||||
this.on_click(handler)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(div().h_4().w_px().bg(cx.theme().colors().border))
|
|
||||||
.child(
|
|
||||||
IconButton::new("thread-toggle", IconName::Thread)
|
|
||||||
.icon_size(IconSize::Small)
|
|
||||||
.toggle_state(self.thread_selected)
|
|
||||||
.when_some(self.thread_tooltip, |this, tooltip| this.tooltip(tooltip))
|
|
||||||
.when_some(self.on_thread_click, |this, handler| this.on_click(handler)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for ThreadSidebarToggle {
|
|
||||||
fn scope() -> ComponentScope {
|
|
||||||
ComponentScope::Agent
|
|
||||||
}
|
|
||||||
|
|
||||||
fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
|
|
||||||
let container = || div().p_2().bg(cx.theme().colors().status_bar_background);
|
|
||||||
|
|
||||||
let examples = vec![
|
|
||||||
single_example(
|
|
||||||
"Both Unselected",
|
|
||||||
container()
|
|
||||||
.child(ThreadSidebarToggle::new())
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Sidebar Selected",
|
|
||||||
container()
|
|
||||||
.child(ThreadSidebarToggle::new().sidebar_selected(true))
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Thread Selected",
|
|
||||||
container()
|
|
||||||
.child(ThreadSidebarToggle::new().thread_selected(true))
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Both Selected",
|
|
||||||
container()
|
|
||||||
.child(
|
|
||||||
ThreadSidebarToggle::new()
|
|
||||||
.sidebar_selected(true)
|
|
||||||
.thread_selected(true),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Flipped",
|
|
||||||
container()
|
|
||||||
.child(
|
|
||||||
ThreadSidebarToggle::new()
|
|
||||||
.sidebar_selected(true)
|
|
||||||
.thread_selected(true)
|
|
||||||
.flipped(true),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"With Tooltips",
|
|
||||||
container()
|
|
||||||
.child(
|
|
||||||
ThreadSidebarToggle::new()
|
|
||||||
.sidebar_tooltip(Tooltip::text("Toggle Sidebar"))
|
|
||||||
.thread_tooltip(Tooltip::text("Toggle Thread")),
|
|
||||||
)
|
|
||||||
.into_any_element(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
Some(example_group(examples).into_any_element())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -45,6 +45,8 @@ pub trait Sidebar: Focusable + Render + Sized {
|
||||||
fn width(&self, cx: &App) -> Pixels;
|
fn width(&self, cx: &App) -> Pixels;
|
||||||
fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>);
|
fn set_width(&mut self, width: Option<Pixels>, cx: &mut Context<Self>);
|
||||||
fn has_notifications(&self, cx: &App) -> bool;
|
fn has_notifications(&self, cx: &App) -> bool;
|
||||||
|
fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App);
|
||||||
|
fn is_recent_projects_popover_deployed(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait SidebarHandle: 'static + Send + Sync {
|
pub trait SidebarHandle: 'static + Send + Sync {
|
||||||
|
|
@ -55,6 +57,8 @@ pub trait SidebarHandle: 'static + Send + Sync {
|
||||||
fn has_notifications(&self, cx: &App) -> bool;
|
fn has_notifications(&self, cx: &App) -> bool;
|
||||||
fn to_any(&self) -> AnyView;
|
fn to_any(&self) -> AnyView;
|
||||||
fn entity_id(&self) -> EntityId;
|
fn entity_id(&self) -> EntityId;
|
||||||
|
fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App);
|
||||||
|
fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -95,6 +99,16 @@ impl<T: Sidebar> SidebarHandle for Entity<T> {
|
||||||
fn entity_id(&self) -> EntityId {
|
fn entity_id(&self) -> EntityId {
|
||||||
Entity::entity_id(self)
|
Entity::entity_id(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) {
|
||||||
|
self.update(cx, |this, cx| {
|
||||||
|
this.toggle_recent_projects_popover(window, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool {
|
||||||
|
self.read(cx).is_recent_projects_popover_deployed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MultiWorkspace {
|
pub struct MultiWorkspace {
|
||||||
|
|
@ -167,6 +181,18 @@ impl MultiWorkspace {
|
||||||
.map_or(false, |s| s.has_notifications(cx))
|
.map_or(false, |s| s.has_notifications(cx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_recent_projects_popover(&self, window: &mut Window, cx: &mut App) {
|
||||||
|
if let Some(sidebar) = &self.sidebar {
|
||||||
|
sidebar.toggle_recent_projects_popover(window, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_recent_projects_popover_deployed(&self, cx: &App) -> bool {
|
||||||
|
self.sidebar
|
||||||
|
.as_ref()
|
||||||
|
.map_or(false, |s| s.is_recent_projects_popover_deployed(cx))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn multi_workspace_enabled(&self, cx: &App) -> bool {
|
pub fn multi_workspace_enabled(&self, cx: &App) -> bool {
|
||||||
cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
|
cx.has_flag::<AgentV2FeatureFlag>() && !DisableAiSettings::get_global(cx).disable_ai
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue