agent: Improve pickers and their triggers styles in the panel (#40284)

Danilo Leal created

Making all triggers have the same style when the picker is open
(including changing the icon when the picker opens on top of the
trigger). Also removed the footer from the ACP model selector given
there's nothing to consider when that's the case; users can only
configure LLM providers when using Zed's built-in agent.

Release Notes:

- N/A

Change summary

crates/agent_ui/src/acp/mode_selector.rs          |  9 +++
crates/agent_ui/src/acp/model_selector.rs         | 38 +---------------
crates/agent_ui/src/acp/model_selector_popover.rs | 14 ++----
crates/agent_ui/src/agent_model_selector.rs       | 20 ++++++--
crates/agent_ui/src/profile_selector.rs           |  8 +++
crates/agent_ui/src/text_thread_editor.rs         | 27 +++++------
crates/picker/src/popover_menu.rs                 | 21 ++++++---
7 files changed, 65 insertions(+), 72 deletions(-)

Detailed changes

crates/agent_ui/src/acp/mode_selector.rs 🔗

@@ -174,11 +174,16 @@ impl Render for ModeSelector {
 
         let this = cx.entity();
 
+        let icon = if self.menu_handle.is_deployed() {
+            IconName::ChevronUp
+        } else {
+            IconName::ChevronDown
+        };
+
         let trigger_button = Button::new("mode-selector-trigger", current_mode_name)
             .label_size(LabelSize::Small)
-            .style(ButtonStyle::Subtle)
             .color(Color::Muted)
-            .icon(IconName::ChevronDown)
+            .icon(icon)
             .icon_size(IconSize::XSmall)
             .icon_position(IconPosition::End)
             .icon_color(Color::Muted)

crates/agent_ui/src/acp/model_selector.rs 🔗

@@ -5,12 +5,12 @@ use anyhow::Result;
 use collections::IndexMap;
 use futures::FutureExt;
 use fuzzy::{StringMatchCandidate, match_strings};
-use gpui::{Action, AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
+use gpui::{AsyncWindowContext, BackgroundExecutor, DismissEvent, Task, WeakEntity};
 use ordered_float::OrderedFloat;
 use picker::{Picker, PickerDelegate};
 use ui::{
-    AnyElement, App, Context, DocumentationAside, DocumentationEdge, DocumentationSide,
-    IntoElement, ListItem, ListItemSpacing, SharedString, Window, prelude::*, rems,
+    DocumentationAside, DocumentationEdge, DocumentationSide, IntoElement, ListItem,
+    ListItemSpacing, prelude::*,
 };
 use util::ResultExt;
 
@@ -278,36 +278,6 @@ impl PickerDelegate for AcpModelPickerDelegate {
         }
     }
 
-    fn render_footer(
-        &self,
-        _: &mut Window,
-        cx: &mut Context<Picker<Self>>,
-    ) -> Option<gpui::AnyElement> {
-        Some(
-            h_flex()
-                .w_full()
-                .border_t_1()
-                .border_color(cx.theme().colors().border_variant)
-                .p_1()
-                .gap_4()
-                .justify_between()
-                .child(
-                    Button::new("configure", "Configure")
-                        .icon(IconName::Settings)
-                        .icon_size(IconSize::Small)
-                        .icon_color(Color::Muted)
-                        .icon_position(IconPosition::Start)
-                        .on_click(|_, window, cx| {
-                            window.dispatch_action(
-                                zed_actions::agent::OpenSettings.boxed_clone(),
-                                cx,
-                            );
-                        }),
-                )
-                .into_any(),
-        )
-    }
-
     fn documentation_aside(
         &self,
         _window: &mut Window,
@@ -317,7 +287,7 @@ impl PickerDelegate for AcpModelPickerDelegate {
             let description = description.clone();
             DocumentationAside::new(
                 DocumentationSide::Left,
-                DocumentationEdge::Bottom,
+                DocumentationEdge::Top,
                 Rc::new(move |_| Label::new(description.clone()).into_any_element()),
             )
         })

crates/agent_ui/src/acp/model_selector_popover.rs 🔗

@@ -57,30 +57,26 @@ impl Render for AcpModelSelectorPopover {
 
         let focus_handle = self.focus_handle.clone();
 
-        let color = if self.menu_handle.is_deployed() {
-            Color::Accent
+        let (color, icon) = if self.menu_handle.is_deployed() {
+            (Color::Accent, IconName::ChevronUp)
         } else {
-            Color::Muted
+            (Color::Muted, IconName::ChevronDown)
         };
 
         PickerPopoverMenu::new(
             self.selector.clone(),
             ButtonLike::new("active-model")
+                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
                 .when_some(model_icon, |this, icon| {
                     this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
                 })
-                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
                 .child(
                     Label::new(model_name)
                         .color(color)
                         .size(LabelSize::Small)
                         .ml_0p5(),
                 )
-                .child(
-                    Icon::new(IconName::ChevronDown)
-                        .color(Color::Muted)
-                        .size(IconSize::XSmall),
-                ),
+                .child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall)),
             move |window, cx| {
                 Tooltip::for_action_in(
                     "Change Model",

crates/agent_ui/src/agent_model_selector.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{Entity, FocusHandle, SharedString};
 use picker::popover_menu::PickerPopoverMenu;
 use settings::update_settings_file;
 use std::sync::Arc;
-use ui::{ButtonLike, PopoverMenuHandle, Tooltip, prelude::*};
+use ui::{ButtonLike, PopoverMenuHandle, TintColor, Tooltip, prelude::*};
 use zed_actions::agent::ToggleModelSelector;
 
 pub struct AgentModelSelector {
@@ -70,6 +70,11 @@ impl Render for AgentModelSelector {
             .unwrap_or_else(|| SharedString::from("Select a Model"));
 
         let provider_icon = model.as_ref().map(|model| model.provider.icon());
+        let color = if self.menu_handle.is_deployed() {
+            Color::Accent
+        } else {
+            Color::Muted
+        };
 
         let focus_handle = self.focus_handle.clone();
 
@@ -77,17 +82,18 @@ impl Render for AgentModelSelector {
             self.selector.clone(),
             ButtonLike::new("active-model")
                 .when_some(provider_icon, |this, icon| {
-                    this.child(Icon::new(icon).color(Color::Muted).size(IconSize::XSmall))
+                    this.child(Icon::new(icon).color(color).size(IconSize::XSmall))
                 })
+                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
                 .child(
                     Label::new(model_name)
-                        .color(Color::Muted)
+                        .color(color)
                         .size(LabelSize::Small)
                         .ml_0p5(),
                 )
                 .child(
                     Icon::new(IconName::ChevronDown)
-                        .color(Color::Muted)
+                        .color(color)
                         .size(IconSize::XSmall),
                 ),
             move |window, cx| {
@@ -99,10 +105,14 @@ impl Render for AgentModelSelector {
                     cx,
                 )
             },
-            gpui::Corner::BottomRight,
+            gpui::Corner::TopRight,
             cx,
         )
         .with_handle(self.menu_handle.clone())
+        .offset(gpui::Point {
+            x: px(0.0),
+            y: px(2.0),
+        })
         .render(window, cx)
     }
 }

crates/agent_ui/src/profile_selector.rs 🔗

@@ -144,10 +144,16 @@ impl Render for ProfileSelector {
             .unwrap_or_else(|| "Unknown".into());
         let focus_handle = self.focus_handle.clone();
 
+        let icon = if self.picker_handle.is_deployed() {
+            IconName::ChevronUp
+        } else {
+            IconName::ChevronDown
+        };
+
         let trigger_button = Button::new("profile-selector", selected_profile)
             .label_size(LabelSize::Small)
             .color(Color::Muted)
-            .icon(IconName::ChevronDown)
+            .icon(icon)
             .icon_size(IconSize::XSmall)
             .icon_position(IconPosition::End)
             .icon_color(Color::Muted)

crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -1977,7 +1977,9 @@ impl TextThreadEditor {
             cx.entity().downgrade(),
             IconButton::new("trigger", IconName::Plus)
                 .icon_size(IconSize::Small)
-                .icon_color(Color::Muted),
+                .icon_color(Color::Muted)
+                .selected_icon_color(Color::Accent)
+                .selected_style(ButtonStyle::Filled),
             move |window, cx| {
                 Tooltip::with_meta(
                     "Add Context",
@@ -2052,30 +2054,27 @@ impl TextThreadEditor {
         };
 
         let focus_handle = self.editor().focus_handle(cx);
+        let (color, icon) = if self.language_model_selector_menu_handle.is_deployed() {
+            (Color::Accent, IconName::ChevronUp)
+        } else {
+            (Color::Muted, IconName::ChevronDown)
+        };
 
         PickerPopoverMenu::new(
             self.language_model_selector.clone(),
             ButtonLike::new("active-model")
-                .style(ButtonStyle::Subtle)
+                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
                 .child(
                     h_flex()
                         .gap_0p5()
-                        .child(
-                            Icon::new(provider_icon)
-                                .color(Color::Muted)
-                                .size(IconSize::XSmall),
-                        )
+                        .child(Icon::new(provider_icon).color(color).size(IconSize::XSmall))
                         .child(
                             Label::new(model_name)
-                                .color(Color::Muted)
+                                .color(color)
                                 .size(LabelSize::Small)
                                 .ml_0p5(),
                         )
-                        .child(
-                            Icon::new(IconName::ChevronDown)
-                                .color(Color::Muted)
-                                .size(IconSize::XSmall),
-                        ),
+                        .child(Icon::new(icon).color(color).size(IconSize::XSmall)),
                 ),
             move |window, cx| {
                 Tooltip::for_action_in(
@@ -2086,7 +2085,7 @@ impl TextThreadEditor {
                     cx,
                 )
             },
-            gpui::Corner::BottomLeft,
+            gpui::Corner::BottomRight,
             cx,
         )
         .with_handle(self.language_model_selector_menu_handle.clone())

crates/picker/src/popover_menu.rs 🔗

@@ -1,9 +1,9 @@
 use gpui::{
-    AnyView, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
+    AnyView, Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Pixels, Point,
+    Subscription,
 };
 use ui::{
-    App, ButtonCommon, FluentBuilder as _, IntoElement, PopoverMenu, PopoverMenuHandle,
-    PopoverTrigger, RenderOnce, Window, px,
+    FluentBuilder as _, IntoElement, PopoverMenu, PopoverMenuHandle, PopoverTrigger, prelude::*,
 };
 
 use crate::{Picker, PickerDelegate};
@@ -19,6 +19,7 @@ where
     tooltip: TT,
     handle: Option<PopoverMenuHandle<Picker<P>>>,
     anchor: Corner,
+    offset: Option<Point<Pixels>>,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -43,6 +44,10 @@ where
             trigger,
             tooltip,
             handle: None,
+            offset: Some(Point {
+                x: px(0.0),
+                y: px(-2.0),
+            }),
             anchor,
         }
     }
@@ -51,6 +56,11 @@ where
         self.handle = Some(handle);
         self
     }
+
+    pub fn offset(mut self, offset: Point<Pixels>) -> Self {
+        self.offset = Some(offset);
+        self
+    }
 }
 
 impl<T, TT, P> EventEmitter<DismissEvent> for PickerPopoverMenu<T, TT, P>
@@ -86,9 +96,6 @@ where
             .trigger_with_tooltip(self.trigger, self.tooltip)
             .anchor(self.anchor)
             .when_some(self.handle, |menu, handle| menu.with_handle(handle))
-            .offset(gpui::Point {
-                x: px(0.0),
-                y: px(-2.0),
-            })
+            .when_some(self.offset, |menu, offset| menu.offset(offset))
     }
 }