From 932457e71140a7eb9f252f975f2f1f15fd1be326 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Fri, 9 May 2025 13:44:03 +0200 Subject: [PATCH 1/4] Add a new end_slot type in the list item --- crates/ui/src/components/list/list_item.rs | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index 9570301e2bf93a..26b5f2d44a9b6a 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -30,6 +30,9 @@ pub struct ListItem { /// A slot for content that appears on hover after the children /// It will obscure the `end_slot` when visible. end_hover_slot: Option, + /// A slot for content that only renders on hover or focus, + /// This slot doesn't use any `visibility`-related approach to rendering elements inside of it. + end_slot_conditional: Option, toggle: Option, inset: bool, on_click: Option>, @@ -43,6 +46,7 @@ pub struct ListItem { rounded: bool, overflow_x: bool, focused: Option, + hovered: bool, } impl ListItem { @@ -58,6 +62,7 @@ impl ListItem { start_slot: None, end_slot: None, end_hover_slot: None, + end_slot_conditional: None, toggle: None, inset: false, on_click: None, @@ -71,6 +76,7 @@ impl ListItem { rounded: false, overflow_x: false, focused: None, + hovered: false, } } @@ -158,6 +164,11 @@ impl ListItem { self } + pub fn end_slot_conditional(mut self, element: impl Into>) -> Self { + self.end_slot_conditional = element.into().map(IntoElement::into_any_element); + self + } + pub fn outlined(mut self) -> Self { self.outlined = true; self @@ -177,6 +188,11 @@ impl ListItem { self.focused = Some(focused); self } + + pub fn hovered(mut self, hovered: bool) -> Self { + self.hovered = hovered; + self + } } impl Disableable for ListItem { @@ -233,6 +249,27 @@ impl RenderOnce for ListItem { }) }) .when(self.rounded, |this| this.rounded_sm()) + // .on_hover(move |this, hovered, cx| { + // this.hovered = *hovered; + // // cx.notify(); + // this + // }) + // .on_hover(cx.listener(|this, hovered, window, cx| { + // if *hovered { + // this.hovered = true; + // } else { + // this.hovered = false; + // } + // cx.notify(); + // this + // })) + // .on_hover(move |_this, hovered, _cx| { + // // Simply use the hovered state directly + // let is_hovered = *hovered; + // // Update the condition for end_slot_conditional + // // This will be checked later in the render tree + // is_hovered + // }) .child( h_flex() .id("inner_list_item") @@ -340,6 +377,10 @@ impl RenderOnce for ListItem { .visible_on_hover("list_item") .child(end_hover_slot), ) + }) + .when_some(self.end_slot_conditional, |this, container| { + let should_show = self.selected || self.focused.is_some() || self.hovered; + this.when(should_show, |this| this.child(container)) }), ) } From e2e6bf851e0facc9666d9b065ee1dce43230c584 Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Fri, 9 May 2025 13:44:14 +0200 Subject: [PATCH 2/4] Add it to the past thread elements --- crates/agent/src/agent_panel.rs | 2 +- crates/agent/src/thread_history.rs | 113 ++++++++++++++--------------- 2 files changed, 57 insertions(+), 58 deletions(-) diff --git a/crates/agent/src/agent_panel.rs b/crates/agent/src/agent_panel.rs index fa203a1409cdd2..4bbada79680e59 100644 --- a/crates/agent/src/agent_panel.rs +++ b/crates/agent/src/agent_panel.rs @@ -2212,7 +2212,7 @@ impl AgentPanel { .border_b_1() .border_color(cx.theme().colors().border_variant) .child( - Label::new("Past Interactions") + Label::new("Recent") .size(LabelSize::Small) .color(Color::Muted), ) diff --git a/crates/agent/src/thread_history.rs b/crates/agent/src/thread_history.rs index d8fdddc2c5931a..1cde0c5cf46082 100644 --- a/crates/agent/src/thread_history.rs +++ b/crates/agent/src/thread_history.rs @@ -664,41 +664,41 @@ impl RenderOnce for PastThread { .toggle_state(self.selected) .spacing(ListItemSpacing::Sparse) .start_slot( - div().max_w_4_5().child( - HighlightedLabel::new(summary, self.highlight_positions) - .size(LabelSize::Small) - .truncate(), - ), - ) - .end_slot( h_flex() - .gap_1p5() + .w_full() + .gap_2() + .justify_between() + .child( + HighlightedLabel::new(summary, self.highlight_positions) + .size(LabelSize::Small) + .truncate(), + ) .child( Label::new(thread_timestamp) .color(Color::Muted) .size(LabelSize::XSmall), - ) - .child( - IconButton::new("delete", IconName::TrashAlt) - .shape(IconButtonShape::Square) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .tooltip(move |window, cx| { - Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) - }) - .on_click({ - let agent_panel = self.agent_panel.clone(); - let id = self.thread.id.clone(); - move |_event, _window, cx| { - agent_panel - .update(cx, |this, cx| { - this.delete_thread(&id, cx).detach_and_log_err(cx); - }) - .ok(); - } - }), ), ) + .end_slot_conditional( + IconButton::new("delete", IconName::TrashAlt) + .shape(IconButtonShape::Square) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .tooltip(move |window, cx| { + Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) + }) + .on_click({ + let agent_panel = self.agent_panel.clone(); + let id = self.thread.id.clone(); + move |_event, _window, cx| { + agent_panel + .update(cx, |this, cx| { + this.delete_thread(&id, cx).detach_and_log_err(cx); + }) + .ok(); + } + }), + ) .on_click({ let agent_panel = self.agent_panel.clone(); let id = self.thread.id.clone(); @@ -757,42 +757,41 @@ impl RenderOnce for PastContext { .toggle_state(self.selected) .spacing(ListItemSpacing::Sparse) .start_slot( - div().max_w_4_5().child( - HighlightedLabel::new(summary, self.highlight_positions) - .size(LabelSize::Small) - .truncate(), - ), - ) - .end_slot( h_flex() - .gap_1p5() + .w_full() + .gap_2() + .justify_between() + .child( + HighlightedLabel::new(summary, self.highlight_positions) + .size(LabelSize::Small) + .truncate(), + ) .child( Label::new(context_timestamp) .color(Color::Muted) .size(LabelSize::XSmall), - ) - .child( - IconButton::new("delete", IconName::TrashAlt) - .shape(IconButtonShape::Square) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .tooltip(move |window, cx| { - Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) - }) - .on_click({ - let agent_panel = self.agent_panel.clone(); - let path = self.context.path.clone(); - move |_event, _window, cx| { - agent_panel - .update(cx, |this, cx| { - this.delete_context(path.clone(), cx) - .detach_and_log_err(cx); - }) - .ok(); - } - }), ), ) + .end_slot_conditional( + IconButton::new("delete", IconName::TrashAlt) + .shape(IconButtonShape::Square) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .tooltip(move |window, cx| { + Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) + }) + .on_click({ + let agent_panel = self.agent_panel.clone(); + let path = self.context.path.clone(); + move |_event, _window, cx| { + agent_panel + .update(cx, |this, cx| { + this.delete_context(path.clone(), cx).detach_and_log_err(cx); + }) + .ok(); + } + }), + ) .on_click({ let agent_panel = self.agent_panel.clone(); let path = self.context.path.clone(); From 815476720207561dad6bdf51eef705438065befd Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Fri, 9 May 2025 13:46:27 +0200 Subject: [PATCH 3/4] Improve documentation --- crates/ui/src/components/list/list_item.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index 26b5f2d44a9b6a..d5438efbc2cf83 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -31,7 +31,7 @@ pub struct ListItem { /// It will obscure the `end_slot` when visible. end_hover_slot: Option, /// A slot for content that only renders on hover or focus, - /// This slot doesn't use any `visibility`-related approach to rendering elements inside of it. + /// This slot doesn't use visibility to render elements inside of it. end_slot_conditional: Option, toggle: Option, inset: bool, From 2c5838ee13727125bb16746b533a92431ded841f Mon Sep 17 00:00:00 2001 From: Danilo Leal Date: Fri, 9 May 2025 17:00:30 +0200 Subject: [PATCH 4/4] Refactor `PastThread` and `PastContext` into `HistoryElementEntry` Co-authored-by: Agus Zubiaga --- crates/agent/src/agent_panel.rs | 30 +- crates/agent/src/thread_history.rs | 345 ++++++++++----------- crates/ui/src/components/list/list_item.rs | 49 +-- 3 files changed, 181 insertions(+), 243 deletions(-) diff --git a/crates/agent/src/agent_panel.rs b/crates/agent/src/agent_panel.rs index 4bbada79680e59..14b926674c7042 100644 --- a/crates/agent/src/agent_panel.rs +++ b/crates/agent/src/agent_panel.rs @@ -55,10 +55,10 @@ use zed_llm_client::UsageLimit; use crate::active_thread::{self, ActiveThread, ActiveThreadEvent}; use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent}; use crate::agent_diff::AgentDiff; -use crate::history_store::{HistoryEntry, HistoryStore, RecentEntry}; +use crate::history_store::{HistoryStore, RecentEntry}; use crate::message_editor::{MessageEditor, MessageEditorEvent}; use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio}; -use crate::thread_history::{EntryTimeFormat, PastContext, PastThread, ThreadHistory}; +use crate::thread_history::{HistoryEntryElement, ThreadHistory}; use crate::thread_store::ThreadStore; use crate::ui::AgentOnboardingModal; use crate::{ @@ -356,6 +356,7 @@ pub struct AgentPanel { previous_view: Option, history_store: Entity, history: Entity, + hovered_recent_history_item: Option, assistant_dropdown_menu_handle: PopoverMenuHandle, assistant_navigation_menu_handle: PopoverMenuHandle, assistant_navigation_menu: Option>, @@ -696,6 +697,7 @@ impl AgentPanel { previous_view: None, history_store: history_store.clone(), history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)), + hovered_recent_history_item: None, assistant_dropdown_menu_handle: PopoverMenuHandle::default(), assistant_navigation_menu_handle: PopoverMenuHandle::default(), assistant_navigation_menu: None, @@ -2237,18 +2239,20 @@ impl AgentPanel { v_flex() .gap_1() .children( - recent_history.into_iter().map(|entry| { + recent_history.into_iter().enumerate().map(|(index, entry)| { // TODO: Add keyboard navigation. - match entry { - HistoryEntry::Thread(thread) => { - PastThread::new(thread, cx.entity().downgrade(), false, vec![], EntryTimeFormat::DateAndTime) - .into_any_element() - } - HistoryEntry::Context(context) => { - PastContext::new(context, cx.entity().downgrade(), false, vec![], EntryTimeFormat::DateAndTime) - .into_any_element() - } - } + let is_hovered = self.hovered_recent_history_item == Some(index); + HistoryEntryElement::new(entry.clone(), cx.entity().downgrade()) + .hovered(is_hovered) + .on_hover(cx.listener(move |this, is_hovered, _window, cx| { + if *is_hovered { + this.hovered_recent_history_item = Some(index); + } else if this.hovered_recent_history_item == Some(index) { + this.hovered_recent_history_item = None; + } + cx.notify(); + })) + .into_any_element() }), ) ) diff --git a/crates/agent/src/thread_history.rs b/crates/agent/src/thread_history.rs index 1cde0c5cf46082..73c5b47001d37f 100644 --- a/crates/agent/src/thread_history.rs +++ b/crates/agent/src/thread_history.rs @@ -2,12 +2,11 @@ use std::fmt::Display; use std::ops::Range; use std::sync::Arc; -use assistant_context_editor::SavedContextMetadata; use chrono::{Datelike as _, Local, NaiveDate, TimeDelta}; use editor::{Editor, EditorEvent}; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ - App, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task, + App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task, UniformListScrollHandle, WeakEntity, Window, uniform_list, }; use time::{OffsetDateTime, UtcOffset}; @@ -18,7 +17,6 @@ use ui::{ use util::ResultExt; use crate::history_store::{HistoryEntry, HistoryStore}; -use crate::thread_store::SerializedThreadMetadata; use crate::{AgentPanel, RemoveSelectedThread}; pub struct ThreadHistory { @@ -26,11 +24,12 @@ pub struct ThreadHistory { history_store: Entity, scroll_handle: UniformListScrollHandle, selected_index: usize, + hovered_index: Option, search_editor: Entity, all_entries: Arc>, // When the search is empty, we display date separators between history entries // This vector contains an enum of either a separator or an actual entry - separated_items: Vec, + separated_items: Vec, // Maps entry indexes to list item indexes separated_item_indexes: Vec, _separated_items_task: Option>, @@ -52,7 +51,7 @@ enum SearchState { }, } -enum HistoryListItem { +enum ListItemType { BucketSeparator(TimeBucket), Entry { index: usize, @@ -60,11 +59,11 @@ enum HistoryListItem { }, } -impl HistoryListItem { +impl ListItemType { fn entry_index(&self) -> Option { match self { - HistoryListItem::BucketSeparator(_) => None, - HistoryListItem::Entry { index, .. } => Some(*index), + ListItemType::BucketSeparator(_) => None, + ListItemType::Entry { index, .. } => Some(*index), } } } @@ -102,6 +101,7 @@ impl ThreadHistory { history_store, scroll_handle, selected_index: 0, + hovered_index: None, search_state: SearchState::Empty, all_entries: Default::default(), separated_items: Default::default(), @@ -160,11 +160,11 @@ impl ThreadHistory { if Some(entry_bucket) != bucket { bucket = Some(entry_bucket); - items.push(HistoryListItem::BucketSeparator(entry_bucket)); + items.push(ListItemType::BucketSeparator(entry_bucket)); } indexes.push(items.len() as u32); - items.push(HistoryListItem::Entry { + items.push(ListItemType::Entry { index, format: entry_bucket.into(), }); @@ -467,7 +467,7 @@ impl ThreadHistory { .map(|(ix, m)| { self.render_list_item( Some(range_start + ix), - &HistoryListItem::Entry { + &ListItemType::Entry { index: m.candidate_id, format: EntryTimeFormat::DateAndTime, }, @@ -485,25 +485,36 @@ impl ThreadHistory { fn render_list_item( &self, list_entry_ix: Option, - item: &HistoryListItem, + item: &ListItemType, highlight_positions: Vec, - cx: &App, + cx: &Context, ) -> AnyElement { match item { - HistoryListItem::Entry { index, format } => match self.all_entries.get(*index) { + ListItemType::Entry { index, format } => match self.all_entries.get(*index) { Some(entry) => h_flex() .w_full() .pb_1() - .child(self.render_history_entry( - entry, - list_entry_ix == Some(self.selected_index), - highlight_positions, - *format, - )) + .child( + HistoryEntryElement::new(entry.clone(), self.agent_panel.clone()) + .highlight_positions(highlight_positions) + .timestamp_format(*format) + .selected(list_entry_ix == Some(self.selected_index)) + .hovered(list_entry_ix == self.hovered_index) + .on_hover(cx.listener(move |this, is_hovered, _window, cx| { + if *is_hovered { + this.hovered_index = list_entry_ix; + } else if this.hovered_index == list_entry_ix { + this.hovered_index = None; + } + + cx.notify(); + })) + .into_any_element(), + ) .into_any(), None => Empty.into_any_element(), }, - HistoryListItem::BucketSeparator(bucket) => div() + ListItemType::BucketSeparator(bucket) => div() .px(DynamicSpacing::Base06.rems(cx)) .pt_2() .pb_1() @@ -515,33 +526,6 @@ impl ThreadHistory { .into_any_element(), } } - - fn render_history_entry( - &self, - entry: &HistoryEntry, - is_active: bool, - highlight_positions: Vec, - format: EntryTimeFormat, - ) -> AnyElement { - match entry { - HistoryEntry::Thread(thread) => PastThread::new( - thread.clone(), - self.agent_panel.clone(), - is_active, - highlight_positions, - format, - ) - .into_any_element(), - HistoryEntry::Context(context) => PastContext::new( - context.clone(), - self.agent_panel.clone(), - is_active, - highlight_positions, - format, - ) - .into_any_element(), - } - } } impl Focusable for ThreadHistory { @@ -623,43 +607,75 @@ impl Render for ThreadHistory { } #[derive(IntoElement)] -pub struct PastThread { - thread: SerializedThreadMetadata, +pub struct HistoryEntryElement { + entry: HistoryEntry, agent_panel: WeakEntity, selected: bool, + hovered: bool, highlight_positions: Vec, timestamp_format: EntryTimeFormat, + on_hover: Box, } -impl PastThread { - pub fn new( - thread: SerializedThreadMetadata, - agent_panel: WeakEntity, - selected: bool, - highlight_positions: Vec, - timestamp_format: EntryTimeFormat, - ) -> Self { +impl HistoryEntryElement { + pub fn new(entry: HistoryEntry, agent_panel: WeakEntity) -> Self { Self { - thread, + entry, agent_panel, - selected, - highlight_positions, - timestamp_format, + selected: false, + hovered: false, + highlight_positions: vec![], + timestamp_format: EntryTimeFormat::DateAndTime, + on_hover: Box::new(|_, _, _| {}), } } + + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + pub fn hovered(mut self, hovered: bool) -> Self { + self.hovered = hovered; + self + } + + pub fn highlight_positions(mut self, positions: Vec) -> Self { + self.highlight_positions = positions; + self + } + + pub fn on_hover(mut self, on_hover: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { + self.on_hover = Box::new(on_hover); + self + } + + pub fn timestamp_format(mut self, format: EntryTimeFormat) -> Self { + self.timestamp_format = format; + self + } } -impl RenderOnce for PastThread { +impl RenderOnce for HistoryEntryElement { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let summary = self.thread.summary; + let (id, summary, timestamp) = match &self.entry { + HistoryEntry::Thread(thread) => ( + thread.id.to_string(), + thread.summary.clone(), + thread.updated_at.timestamp(), + ), + HistoryEntry::Context(context) => ( + context.path.to_string_lossy().to_string(), + context.title.clone().into(), + context.mtime.timestamp(), + ), + }; - let thread_timestamp = self.timestamp_format.format_timestamp( - &self.agent_panel, - self.thread.updated_at.timestamp(), - cx, - ); + let thread_timestamp = + self.timestamp_format + .format_timestamp(&self.agent_panel, timestamp, cx); - ListItem::new(SharedString::from(self.thread.id.to_string())) + ListItem::new(SharedString::from(id)) .rounded() .toggle_state(self.selected) .spacing(ListItemSpacing::Sparse) @@ -679,131 +695,82 @@ impl RenderOnce for PastThread { .size(LabelSize::XSmall), ), ) - .end_slot_conditional( - IconButton::new("delete", IconName::TrashAlt) - .shape(IconButtonShape::Square) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .tooltip(move |window, cx| { - Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) - }) - .on_click({ - let agent_panel = self.agent_panel.clone(); - let id = self.thread.id.clone(); - move |_event, _window, cx| { + .on_hover(self.on_hover) + .end_slot::(if self.hovered || self.selected { + Some( + IconButton::new("delete", IconName::TrashAlt) + .shape(IconButtonShape::Square) + .icon_size(IconSize::XSmall) + .icon_color(Color::Muted) + .tooltip(move |window, cx| { + Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) + }) + .on_click({ + let agent_panel = self.agent_panel.clone(); + + let f: Box = + match &self.entry { + HistoryEntry::Thread(thread) => { + let id = thread.id.clone(); + + Box::new(move |_event, _window, cx| { + agent_panel + .update(cx, |this, cx| { + this.delete_thread(&id, cx) + .detach_and_log_err(cx); + }) + .ok(); + }) + } + HistoryEntry::Context(context) => { + let path = context.path.clone(); + + Box::new(move |_event, _window, cx| { + agent_panel + .update(cx, |this, cx| { + this.delete_context(path.clone(), cx) + .detach_and_log_err(cx); + }) + .ok(); + }) + } + }; + f + }), + ) + } else { + None + }) + .on_click({ + let agent_panel = self.agent_panel.clone(); + + let f: Box = match &self.entry + { + HistoryEntry::Thread(thread) => { + let id = thread.id.clone(); + Box::new(move |_event, window, cx| { agent_panel .update(cx, |this, cx| { - this.delete_thread(&id, cx).detach_and_log_err(cx); + this.open_thread_by_id(&id, window, cx) + .detach_and_log_err(cx); }) .ok(); - } - }), - ) - .on_click({ - let agent_panel = self.agent_panel.clone(); - let id = self.thread.id.clone(); - move |_event, window, cx| { - agent_panel - .update(cx, |this, cx| { - this.open_thread_by_id(&id, window, cx) - .detach_and_log_err(cx); }) - .ok(); - } - }) - } -} - -#[derive(IntoElement)] -pub struct PastContext { - context: SavedContextMetadata, - agent_panel: WeakEntity, - selected: bool, - highlight_positions: Vec, - timestamp_format: EntryTimeFormat, -} - -impl PastContext { - pub fn new( - context: SavedContextMetadata, - agent_panel: WeakEntity, - selected: bool, - highlight_positions: Vec, - timestamp_format: EntryTimeFormat, - ) -> Self { - Self { - context, - agent_panel, - selected, - highlight_positions, - timestamp_format, - } - } -} - -impl RenderOnce for PastContext { - fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let summary = self.context.title; - let context_timestamp = self.timestamp_format.format_timestamp( - &self.agent_panel, - self.context.mtime.timestamp(), - cx, - ); - - ListItem::new(SharedString::from( - self.context.path.to_string_lossy().to_string(), - )) - .rounded() - .toggle_state(self.selected) - .spacing(ListItemSpacing::Sparse) - .start_slot( - h_flex() - .w_full() - .gap_2() - .justify_between() - .child( - HighlightedLabel::new(summary, self.highlight_positions) - .size(LabelSize::Small) - .truncate(), - ) - .child( - Label::new(context_timestamp) - .color(Color::Muted) - .size(LabelSize::XSmall), - ), - ) - .end_slot_conditional( - IconButton::new("delete", IconName::TrashAlt) - .shape(IconButtonShape::Square) - .icon_size(IconSize::XSmall) - .icon_color(Color::Muted) - .tooltip(move |window, cx| { - Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx) - }) - .on_click({ - let agent_panel = self.agent_panel.clone(); - let path = self.context.path.clone(); - move |_event, _window, cx| { - agent_panel - .update(cx, |this, cx| { - this.delete_context(path.clone(), cx).detach_and_log_err(cx); - }) - .ok(); } - }), - ) - .on_click({ - let agent_panel = self.agent_panel.clone(); - let path = self.context.path.clone(); - move |_event, window, cx| { - agent_panel - .update(cx, |this, cx| { - this.open_saved_prompt_editor(path.clone(), window, cx) - .detach_and_log_err(cx); - }) - .ok(); - } - }) + HistoryEntry::Context(context) => { + let path = context.path.clone(); + Box::new(move |_event, window, cx| { + agent_panel + .update(cx, |this, cx| { + this.open_saved_prompt_editor(path.clone(), window, cx) + .detach_and_log_err(cx); + }) + .ok(); + }) + } + }; + f + }) } } diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index d5438efbc2cf83..6356930aee8cc3 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -30,12 +30,10 @@ pub struct ListItem { /// A slot for content that appears on hover after the children /// It will obscure the `end_slot` when visible. end_hover_slot: Option, - /// A slot for content that only renders on hover or focus, - /// This slot doesn't use visibility to render elements inside of it. - end_slot_conditional: Option, toggle: Option, inset: bool, on_click: Option>, + on_hover: Option>, on_toggle: Option>, tooltip: Option AnyView + 'static>>, on_secondary_mouse_down: Option>, @@ -46,7 +44,6 @@ pub struct ListItem { rounded: bool, overflow_x: bool, focused: Option, - hovered: bool, } impl ListItem { @@ -62,12 +59,12 @@ impl ListItem { start_slot: None, end_slot: None, end_hover_slot: None, - end_slot_conditional: None, toggle: None, inset: false, on_click: None, on_secondary_mouse_down: None, on_toggle: None, + on_hover: None, tooltip: None, children: SmallVec::new(), selectable: true, @@ -76,7 +73,6 @@ impl ListItem { rounded: false, overflow_x: false, focused: None, - hovered: false, } } @@ -108,6 +104,11 @@ impl ListItem { self } + pub fn on_hover(mut self, handler: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self { + self.on_hover = Some(Box::new(handler)); + self + } + pub fn on_secondary_mouse_down( mut self, handler: impl Fn(&MouseDownEvent, &mut Window, &mut App) + 'static, @@ -164,11 +165,6 @@ impl ListItem { self } - pub fn end_slot_conditional(mut self, element: impl Into>) -> Self { - self.end_slot_conditional = element.into().map(IntoElement::into_any_element); - self - } - pub fn outlined(mut self) -> Self { self.outlined = true; self @@ -188,11 +184,6 @@ impl ListItem { self.focused = Some(focused); self } - - pub fn hovered(mut self, hovered: bool) -> Self { - self.hovered = hovered; - self - } } impl Disableable for ListItem { @@ -249,27 +240,7 @@ impl RenderOnce for ListItem { }) }) .when(self.rounded, |this| this.rounded_sm()) - // .on_hover(move |this, hovered, cx| { - // this.hovered = *hovered; - // // cx.notify(); - // this - // }) - // .on_hover(cx.listener(|this, hovered, window, cx| { - // if *hovered { - // this.hovered = true; - // } else { - // this.hovered = false; - // } - // cx.notify(); - // this - // })) - // .on_hover(move |_this, hovered, _cx| { - // // Simply use the hovered state directly - // let is_hovered = *hovered; - // // Update the condition for end_slot_conditional - // // This will be checked later in the render tree - // is_hovered - // }) + .when_some(self.on_hover, |this, on_hover| this.on_hover(on_hover)) .child( h_flex() .id("inner_list_item") @@ -377,10 +348,6 @@ impl RenderOnce for ListItem { .visible_on_hover("list_item") .child(end_hover_slot), ) - }) - .when_some(self.end_slot_conditional, |this, container| { - let should_show = self.selected || self.focused.is_some() || self.hovered; - this.when(should_show, |this| this.child(container)) }), ) }