diff --git a/crates/auto_update/src/lib.rs b/crates/auto_update/src/lib.rs index f680a0a..3bb10a6 100644 --- a/crates/auto_update/src/lib.rs +++ b/crates/auto_update/src/lib.rs @@ -231,13 +231,13 @@ impl AutoUpdater { fn subscribe_to_updates(cx: &App) -> Task<()> { let nostr = NostrRegistry::global(cx); - let client = nostr.read(cx).client(); + let _client = nostr.read(cx).client(); cx.background_spawn(async move { - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + let _opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let app_pubkey = PublicKey::parse(APP_PUBKEY).unwrap(); - let filter = Filter::new() + let _filter = Filter::new() .kind(Kind::ReleaseArtifactSet) .author(app_pubkey) .limit(1); @@ -253,7 +253,7 @@ impl AutoUpdater { }); cx.background_spawn(async move { - let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); + let _opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let app_pubkey = PublicKey::parse(APP_PUBKEY).unwrap(); let filter = Filter::new() @@ -274,7 +274,7 @@ impl AutoUpdater { // Get all file metadata event ids let ids: Vec = event.tags.event_ids().copied().collect(); - let filter = Filter::new() + let _filter = Filter::new() .kind(Kind::FileMetadata) .author(app_pubkey) .ids(ids.clone()); diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index dbd13d9..4c6caf5 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -16,6 +16,7 @@ use crate::{ChatRegistry, NewMessage}; #[derive(Debug, Clone)] pub struct SendReport { pub receiver: PublicKey, + pub gift_wrap_id: Option, pub error: Option, pub output: Option>, } @@ -24,12 +25,18 @@ impl SendReport { pub fn new(receiver: PublicKey) -> Self { Self { receiver, - + gift_wrap_id: None, error: None, output: None, } } + /// Set the gift wrap ID. + pub fn gift_wrap_id(mut self, gift_wrap_id: EventId) -> Self { + self.gift_wrap_id = Some(gift_wrap_id); + self + } + /// Set the output. pub fn output(mut self, output: Output) -> Self { self.output = Some(output); @@ -53,7 +60,7 @@ impl SendReport { /// Returns true if the send was successful. pub fn success(&self) -> bool { if let Some(output) = self.output.as_ref() { - !output.failed.is_empty() + !output.success.is_empty() } else { false } @@ -533,7 +540,11 @@ impl Room { .await { Ok(output) => { - reports.push(SendReport::new(member.public_key()).output(output)); + reports.push( + SendReport::new(member.public_key()) + .gift_wrap_id(event.id) + .output(output), + ); } Err(e) => { reports.push( diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 2a6f21c..37d8113 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -30,6 +30,7 @@ use ui::indicator::Indicator; use ui::input::{InputEvent, InputState, TextInput}; use ui::menu::{ContextMenuExt, DropdownMenu}; use ui::notification::Notification; +use ui::scroll::Scrollbar; use ui::{ h_flex, v_flex, Disableable, Icon, IconName, InteractiveElementExt, Sizable, StyledExt, WindowExtension, @@ -62,11 +63,14 @@ pub struct ChatPanel { rendered_texts_by_id: BTreeMap, /// Mapping message (rumor event) ids to their reports - reports_by_id: Arc>>>, + reports_by_id: Entity>>, /// Input state input: Entity, + /// Sent message ids + sent_ids: Arc>>, + /// Replies to replies_to: Entity>, @@ -88,6 +92,7 @@ impl ChatPanel { // Define attachments and replies_to entities let attachments = cx.new(|_| vec![]); let replies_to = cx.new(|_| HashSet::new()); + let reports_by_id = cx.new(|_| BTreeMap::new()); // Define list of messages let messages = BTreeSet::from([Message::system()]); @@ -140,7 +145,8 @@ impl ChatPanel { replies_to, attachments, rendered_texts_by_id: BTreeMap::new(), - reports_by_id: Arc::new(RwLock::new(BTreeMap::new())), + reports_by_id, + sent_ids: Arc::new(RwLock::new(Vec::new())), uploading: false, subscriptions, tasks: vec![], @@ -151,7 +157,9 @@ impl ChatPanel { fn handle_notifications(&mut self, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let reports = self.reports_by_id.clone(); + let sent_ids = self.sent_ids.clone(); + + let (tx, rx) = flume::bounded::<(EventId, RelayUrl)>(256); self.tasks.push(cx.background_spawn(async move { let mut notifications = client.notifications(); @@ -162,18 +170,33 @@ impl ChatPanel { relay_url, } = notification { - let mut writer = reports.write().await; + let sent_ids = sent_ids.read().await; - for reports in writer.values_mut() { - for report in reports.iter_mut() { - if let Some(output) = report.output.as_mut() { - if output.id() == &event_id { - output.success.insert(relay_url.clone()); + if sent_ids.contains(&event_id) { + tx.send_async((event_id, relay_url)).await.ok(); + } + } + } + + Ok(()) + })); + + self.tasks.push(cx.spawn(async move |this, cx| { + while let Ok((event_id, relay_url)) = rx.recv_async().await { + this.update(cx, |this, cx| { + this.reports_by_id.update(cx, |this, cx| { + for reports in this.values_mut() { + for report in reports.iter_mut() { + if let Some(output) = report.output.as_mut() { + if output.id() == &event_id { + output.success.insert(relay_url.clone()); + cx.notify(); + } } } } - } - } + }); + })?; } Ok(()) @@ -301,6 +324,7 @@ impl ChatPanel { /// Send message in the background and wait for the response fn send_and_wait(&mut self, rumor: UnsignedEvent, window: &mut Window, cx: &mut Context) { + let sent_ids = self.sent_ids.clone(); // This can't fail, because we already ensured that the ID is set let id = rumor.id.unwrap(); @@ -316,6 +340,10 @@ impl ChatPanel { self.tasks.push(cx.spawn_in(window, async move |this, cx| { let outputs = task.await; + // Add sent IDs to the list + let mut sent_ids = sent_ids.write().await; + sent_ids.extend(outputs.iter().filter_map(|output| output.gift_wrap_id)); + // Update the state this.update(cx, |this, cx| { this.insert_reports(id, outputs, cx); @@ -342,10 +370,12 @@ impl ChatPanel { }) } - /// Synchronously insert reports + /// Insert reports fn insert_reports(&mut self, id: EventId, reports: Vec, cx: &mut Context) { - self.reports_by_id.write_blocking().insert(id, reports); - cx.notify(); + self.reports_by_id.update(cx, |this, cx| { + this.insert(id, reports); + cx.notify(); + }); } /// Insert a message into the chat panel @@ -379,32 +409,32 @@ impl ChatPanel { } /// Check if a message is pending - fn sent_pending(&self, id: &EventId) -> bool { + fn sent_pending(&self, id: &EventId, cx: &App) -> bool { self.reports_by_id - .read_blocking() + .read(cx) .get(id) .is_some_and(|reports| reports.iter().any(|r| r.pending())) } /// Check if a message was sent successfully by its ID - fn sent_success(&self, id: &EventId) -> bool { + fn sent_success(&self, id: &EventId, cx: &App) -> bool { self.reports_by_id - .read_blocking() + .read(cx) .get(id) .is_some_and(|reports| reports.iter().any(|r| r.success())) } /// Check if a message failed to send by its ID - fn sent_failed(&self, id: &EventId) -> bool { + fn sent_failed(&self, id: &EventId, cx: &App) -> Option { self.reports_by_id - .read_blocking() + .read(cx) .get(id) - .is_some_and(|reports| reports.iter().all(|r| !r.success())) + .map(|reports| reports.iter().all(|r| !r.success())) } /// Get all sent reports for a message by its ID - fn sent_reports(&self, id: &EventId) -> Option> { - self.reports_by_id.read_blocking().get(id).cloned() + fn sent_reports(&self, id: &EventId, cx: &App) -> Option> { + self.reports_by_id.read(cx).get(id).cloned() } /// Get a message by its ID @@ -624,13 +654,13 @@ impl ChatPanel { let has_replies = !replies.is_empty(); // Check if message is sent failed - let sent_pending = self.sent_pending(&id); + let sent_pending = self.sent_pending(&id, cx); // Check if message is sent successfully - let sent_success = self.sent_success(&id); + let sent_success = self.sent_success(&id, cx); // Check if message is sent failed - let sent_failed = self.sent_failed(&id); + let sent_failed = self.sent_failed(&id, cx); // Hide avatar setting let hide_avatar = AppSettings::get_hide_avatar(cx); @@ -689,8 +719,10 @@ impl ChatPanel { this.children(self.render_message_replies(replies, cx)) }) .child(rendered_text) - .when(sent_failed, |this| { - this.child(deferred(self.render_message_reports(&id, cx))) + .when_some(sent_failed, |this, failed| { + this.when(failed, |this| { + this.child(deferred(self.render_message_reports(&id, cx))) + }) }), ), ) @@ -755,11 +787,11 @@ impl ChatPanel { items } - fn render_sent_indicator(&self, id: &EventId, _cx: &Context) -> impl IntoElement { + fn render_sent_indicator(&self, id: &EventId, cx: &Context) -> impl IntoElement { div() .id(SharedString::from(id.to_hex())) .child(SharedString::from("• Sent")) - .when_some(self.sent_reports(id), |this, reports| { + .when_some(self.sent_reports(id, cx), |this, reports| { this.on_click(move |_e, window, cx| { let reports = reports.clone(); @@ -791,7 +823,7 @@ impl ChatPanel { .child(SharedString::from( "Failed to send message. Click to see details.", )) - .when_some(self.sent_reports(id), |this, reports| { + .when_some(self.sent_reports(id, cx), |this, reports| { this.on_click(move |_e, window, cx| { let reports = reports.clone(); @@ -1155,15 +1187,19 @@ impl Render for ChatPanel { .on_action(cx.listener(Self::on_command)) .size_full() .child( - list( - self.list_state.clone(), - cx.processor(|this, ix, window, cx| { - // Get and render message by index - this.render_message(ix, window, cx) - }), - ) - .flex_1() - .size_full(), + div() + .flex_1() + .size_full() + .child( + list( + self.list_state.clone(), + cx.processor(move |this, ix, window, cx| { + this.render_message(ix, window, cx) + }), + ) + .size_full(), + ) + .child(Scrollbar::vertical(&self.list_state)), ) .child( v_flex() @@ -1206,7 +1242,7 @@ impl Render for ChatPanel { .dropdown_menu_with_anchor( gpui::Corner::BottomLeft, move |this, _window, _cx| { - this//.axis(gpui::Axis::Horizontal) + this.horizontal() .menu("👍", Box::new(Command::Insert("👍"))) .menu("👎", Box::new(Command::Insert("👎"))) .menu("😄", Box::new(Command::Insert("😄"))) diff --git a/crates/coop/src/sidebar/mod.rs b/crates/coop/src/sidebar/mod.rs index e9a8de2..b7b7d6a 100644 --- a/crates/coop/src/sidebar/mod.rs +++ b/crates/coop/src/sidebar/mod.rs @@ -11,7 +11,7 @@ use gpui::prelude::FluentBuilder; use gpui::{ div, uniform_list, App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement, ParentElement, Render, RetainAllImageCache, SharedString, Styled, Subscription, - Task, Window, + Task, UniformListScrollHandle, Window, }; use nostr_sdk::prelude::*; use person::PersonRegistry; @@ -23,6 +23,7 @@ use ui::divider::Divider; use ui::indicator::Indicator; use ui::input::{InputEvent, InputState, TextInput}; use ui::notification::Notification; +use ui::scroll::Scrollbar; use ui::{ h_flex, v_flex, Disableable, Icon, IconName, Selectable, Sizable, StyledExt, WindowExtension, }; @@ -39,6 +40,7 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity { pub struct Sidebar { name: SharedString, focus_handle: FocusHandle, + scroll_handle: UniformListScrollHandle, /// Image cache image_cache: Entity, @@ -143,6 +145,7 @@ impl Sidebar { Self { name: "Sidebar".into(), focus_handle: cx.focus_handle(), + scroll_handle: UniformListScrollHandle::new(), image_cache: RetainAllImageCache::new(cx), find_input, find_debouncer: DebouncedDelay::new(), @@ -690,9 +693,11 @@ impl Render for Sidebar { this.render_list_items(range, cx) }), ) + .track_scroll(&self.scroll_handle) .flex_1() .h_full(), ) + .child(Scrollbar::vertical(&self.scroll_handle)) }), ) .when(!self.selected_pkeys.read(cx).is_empty(), |this| { diff --git a/crates/theme/src/scrollbar_mode.rs b/crates/theme/src/scrollbar_mode.rs index e9fbf76..ab60e03 100644 --- a/crates/theme/src/scrollbar_mode.rs +++ b/crates/theme/src/scrollbar_mode.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize, JsonSchema)] pub enum ScrollbarMode { #[default] - Scrolling, Hover, + Scrolling, Always, } diff --git a/crates/ui/src/geometry.rs b/crates/ui/src/geometry.rs index 6f967bd..4f6fbe7 100644 --- a/crates/ui/src/geometry.rs +++ b/crates/ui/src/geometry.rs @@ -32,18 +32,12 @@ impl Display for Placement { impl Placement { #[inline] pub fn is_horizontal(&self) -> bool { - match self { - Placement::Left | Placement::Right => true, - _ => false, - } + matches!(self, Placement::Left | Placement::Right) } #[inline] pub fn is_vertical(&self) -> bool { - match self { - Placement::Top | Placement::Bottom => true, - _ => false, - } + matches!(self, Placement::Top | Placement::Bottom) } #[inline] @@ -217,7 +211,9 @@ impl Side { /// A trait to extend the [`Axis`] enum with utility methods. pub trait AxisExt { + #[allow(clippy::wrong_self_convention)] fn is_horizontal(self) -> bool; + #[allow(clippy::wrong_self_convention)] fn is_vertical(self) -> bool; } @@ -236,6 +232,7 @@ impl AxisExt for Axis { /// A trait for converting [`Pixels`] to `f32` and `f64`. pub trait PixelsExt { fn as_f32(&self) -> f32; + #[allow(clippy::wrong_self_convention)] fn as_f64(self) -> f64; } impl PixelsExt for Pixels { diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 1c2870a..5aab352 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -152,6 +152,7 @@ where } /// Return true if either the list or the search input is focused. + #[allow(dead_code)] pub(crate) fn is_focused(&self, window: &Window, cx: &App) -> bool { self.focus_handle.is_focused(window) || self.query_input.focus_handle(cx).is_focused(window) } @@ -330,6 +331,7 @@ where } } + #[allow(dead_code)] pub(crate) fn reset_on_cancel(mut self, reset: bool) -> Self { self.reset_on_cancel = reset; self diff --git a/crates/ui/src/list/list_item.rs b/crates/ui/src/list/list_item.rs index c016c4b..d2d872a 100644 --- a/crates/ui/src/list/list_item.rs +++ b/crates/ui/src/list/list_item.rs @@ -33,8 +33,11 @@ pub struct ListItem { secondary_selected: bool, confirmed: bool, check_icon: Option, + #[allow(clippy::type_complexity)] on_click: Option>, + #[allow(clippy::type_complexity)] on_mouse_enter: Option>, + #[allow(clippy::type_complexity)] suffix: Option AnyElement + 'static>>, children: SmallVec<[AnyElement; 2]>, } @@ -157,8 +160,10 @@ impl RenderOnce for ListItem { let corner_radii = self.style.corner_radii.clone(); - let mut selected_style = StyleRefinement::default(); - selected_style.corner_radii = corner_radii; + let _selected_style = StyleRefinement { + corner_radii, + ..Default::default() + }; let is_selectable = !(self.disabled || self.mode.is_separator()); diff --git a/crates/ui/src/list/mod.rs b/crates/ui/src/list/mod.rs index dbba734..11105c1 100644 --- a/crates/ui/src/list/mod.rs +++ b/crates/ui/src/list/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod cache; mod delegate; +#[allow(clippy::module_inception)] mod list; mod list_item; mod loading; diff --git a/crates/ui/src/list/separator_item.rs b/crates/ui/src/list/separator_item.rs index 4e73f7e..b419a4e 100644 --- a/crates/ui/src/list/separator_item.rs +++ b/crates/ui/src/list/separator_item.rs @@ -18,6 +18,12 @@ impl ListSeparatorItem { } } +impl Default for ListSeparatorItem { + fn default() -> Self { + Self::new() + } +} + impl ParentElement for ListSeparatorItem { fn extend(&mut self, elements: impl IntoIterator) { self.children.extend(elements); diff --git a/crates/ui/src/menu/app_menu_bar.rs b/crates/ui/src/menu/app_menu_bar.rs index 5ca920b..ea90217 100644 --- a/crates/ui/src/menu/app_menu_bar.rs +++ b/crates/ui/src/menu/app_menu_bar.rs @@ -192,7 +192,7 @@ impl AppMenu { ) { let is_selected = self.menu_bar.read(cx).selected_index == Some(self.ix); - _ = self.menu_bar.update(cx, |state, cx| { + self.menu_bar.update(cx, |state, cx| { let new_ix = if is_selected { None } else { Some(self.ix) }; state.set_selected_index(new_ix, window, cx); }); @@ -208,7 +208,7 @@ impl AppMenu { return; } - _ = self.menu_bar.update(cx, |state, cx| { + self.menu_bar.update(cx, |state, cx| { state.set_selected_index(Some(self.ix), window, cx); }); } diff --git a/crates/ui/src/menu/context_menu.rs b/crates/ui/src/menu/context_menu.rs index 1a492fb..679c4c6 100644 --- a/crates/ui/src/menu/context_menu.rs +++ b/crates/ui/src/menu/context_menu.rs @@ -37,6 +37,7 @@ impl ContextMenuExt for E {} pub struct ContextMenu { id: ElementId, element: Option, + #[allow(clippy::type_complexity)] menu: Option) -> PopupMenu>>, // This is not in use, just for style refinement forwarding. _ignore_style: StyleRefinement, diff --git a/crates/ui/src/menu/dropdown_menu.rs b/crates/ui/src/menu/dropdown_menu.rs index 7e422d8..c5938cc 100644 --- a/crates/ui/src/menu/dropdown_menu.rs +++ b/crates/ui/src/menu/dropdown_menu.rs @@ -41,6 +41,7 @@ pub struct DropdownMenuPopover { style: StyleRefinement, anchor: Corner, trigger: T, + #[allow(clippy::type_complexity)] builder: Rc) -> PopupMenu>, } diff --git a/crates/ui/src/menu/menu_item.rs b/crates/ui/src/menu/menu_item.rs index f387100..cb905d1 100644 --- a/crates/ui/src/menu/menu_item.rs +++ b/crates/ui/src/menu/menu_item.rs @@ -16,7 +16,9 @@ pub(crate) struct MenuItemElement { style: StyleRefinement, disabled: bool, selected: bool, + #[allow(clippy::type_complexity)] on_click: Option>, + #[allow(clippy::type_complexity)] on_hover: Option>, children: SmallVec<[AnyElement; 2]>, } @@ -104,12 +106,12 @@ impl RenderOnce for MenuItemElement { }) .when(!self.disabled, |this| { this.group_hover(self.group_name, |this| { - this.bg(cx.theme().element_background) - .text_color(cx.theme().element_foreground) + this.bg(cx.theme().secondary_background) + .text_color(cx.theme().secondary_foreground) }) .when(self.selected, |this| { - this.bg(cx.theme().element_background) - .text_color(cx.theme().element_foreground) + this.bg(cx.theme().secondary_background) + .text_color(cx.theme().secondary_foreground) }) .when_some(self.on_click, |this, on_click| { this.on_mouse_down(MouseButton::Left, move |_, _, cx| { diff --git a/crates/ui/src/menu/popup_menu.rs b/crates/ui/src/menu/popup_menu.rs index f177e44..a092eab 100644 --- a/crates/ui/src/menu/popup_menu.rs +++ b/crates/ui/src/menu/popup_menu.rs @@ -2,8 +2,8 @@ use std::rc::Rc; use gpui::prelude::FluentBuilder; use gpui::{ - anchored, div, px, rems, Action, AnyElement, App, AppContext, Bounds, ClickEvent, Context, - Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, Half, + anchored, div, px, rems, Action, AnyElement, App, AppContext, Axis, Bounds, ClickEvent, + Context, Corner, DismissEvent, Edges, Entity, EventEmitter, FocusHandle, Focusable, Half, InteractiveElement, IntoElement, KeyBinding, MouseDownEvent, OwnedMenuItem, ParentElement, Pixels, Point, Render, ScrollHandle, SharedString, StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, @@ -44,6 +44,7 @@ pub enum PopupMenuItem { is_link: bool, action: Option>, // For link item + #[allow(clippy::type_complexity)] handler: Option>, }, /// A menu item with custom element render. @@ -52,7 +53,9 @@ pub enum PopupMenuItem { disabled: bool, checked: bool, action: Option>, + #[allow(clippy::type_complexity)] render: Box AnyElement + 'static>, + #[allow(clippy::type_complexity)] handler: Option>, }, /// A submenu item that opens another popup menu. @@ -275,8 +278,11 @@ impl PopupMenuItem { pub struct PopupMenu { pub(crate) focus_handle: FocusHandle, pub(crate) menu_items: Vec, + /// The focus handle of Entity to handle actions. pub(crate) action_context: Option, + + axis: Axis, selected_index: Option, min_width: Option, max_width: Option, @@ -290,7 +296,8 @@ pub struct PopupMenu { scrollable: bool, external_link_icon: bool, scroll_handle: ScrollHandle, - // This will update on render + + /// This will update on render submenu_anchor: (Corner, Pixels), _subscriptions: Vec, @@ -304,6 +311,7 @@ impl PopupMenu { parent_menu: None, menu_items: Vec::new(), selected_index: None, + axis: Axis::Vertical, min_width: None, max_width: None, max_height: None, @@ -354,6 +362,12 @@ impl PopupMenu { self } + /// Set the axis of children to horizontal. + pub fn horizontal(mut self) -> Self { + self.axis = Axis::Horizontal; + self + } + /// Set the menu to be scrollable to show vertical scrollbar. /// /// NOTE: If this is true, the sub-menus will cannot be support. @@ -643,6 +657,7 @@ impl PopupMenu { } /// Use small size, the menu item will have smaller height. + #[allow(dead_code)] pub(crate) fn small(mut self) -> Self { self.size = Size::Small; self @@ -734,41 +749,38 @@ impl PopupMenu { } fn confirm(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context) { - match self.selected_index { - Some(index) => { - let item = self.menu_items.get(index); - match item { - Some(PopupMenuItem::Item { - handler, action, .. - }) => { - if let Some(handler) = handler { - handler(&ClickEvent::default(), window, cx); - } else if let Some(action) = action.as_ref() { - self.dispatch_confirm_action(action, window, cx); - } + if let Some(index) = self.selected_index { + let item = self.menu_items.get(index); + match item { + Some(PopupMenuItem::Item { + handler, action, .. + }) => { + if let Some(handler) = handler { + handler(&ClickEvent::default(), window, cx); + } else if let Some(action) = action.as_ref() { + self.dispatch_confirm_action(action.as_ref(), window, cx); + } - self.dismiss(&Cancel, window, cx) - } - Some(PopupMenuItem::ElementItem { - handler, action, .. - }) => { - if let Some(handler) = handler { - handler(&ClickEvent::default(), window, cx); - } else if let Some(action) = action.as_ref() { - self.dispatch_confirm_action(action, window, cx); - } - self.dismiss(&Cancel, window, cx) - } - _ => {} + self.dismiss(&Cancel, window, cx) } + Some(PopupMenuItem::ElementItem { + handler, action, .. + }) => { + if let Some(handler) = handler { + handler(&ClickEvent::default(), window, cx); + } else if let Some(action) = action.as_ref() { + self.dispatch_confirm_action(action.as_ref(), window, cx); + } + self.dismiss(&Cancel, window, cx) + } + _ => {} } - _ => {} } } fn dispatch_confirm_action( &self, - action: &Box, + action: &dyn Action, window: &mut Window, cx: &mut Context, ) { @@ -878,8 +890,7 @@ impl PopupMenu { cx.notify(); return true; } - - return false; + false } fn _unselect_submenu(&mut self, _: &mut Window, cx: &mut Context) -> bool { @@ -890,8 +901,7 @@ impl PopupMenu { }); return true; } - - return false; + false } fn _focus_parent_menu(&mut self, window: &mut Window, cx: &mut Context) { @@ -1248,7 +1258,9 @@ impl PopupMenu { } impl FluentBuilder for PopupMenu {} + impl EventEmitter for PopupMenu {} + impl Focusable for PopupMenu { fn focus_handle(&self, _: &App) -> FocusHandle { self.focus_handle.clone() @@ -1302,13 +1314,17 @@ impl Render for PopupMenu { .relative() .occlude() .child( - v_flex() + div() .id("items") .p_1() .gap_y_0p5() .min_w(rems(8.)) .when_some(self.min_width, |this, min_width| this.min_w(min_width)) .max_w(max_width) + .map(|this| match self.axis { + Axis::Horizontal => this.flex().flex_row().items_center(), + Axis::Vertical => this.flex().flex_col(), + }) .when(self.scrollable, |this| { this.max_h(max_height) .overflow_y_scroll() diff --git a/crates/ui/src/popover.rs b/crates/ui/src/popover.rs index fd76050..9f5d826 100644 --- a/crates/ui/src/popover.rs +++ b/crates/ui/src/popover.rs @@ -26,7 +26,9 @@ pub struct Popover { default_open: bool, open: Option, tracked_focus_handle: Option, + #[allow(clippy::type_complexity)] trigger: Option AnyElement + 'static>>, + #[allow(clippy::type_complexity)] content: Option< Rc< dyn Fn(&mut PopoverState, &mut Window, &mut Context) -> AnyElement @@ -40,6 +42,7 @@ pub struct Popover { mouse_button: MouseButton, appearance: bool, overlay_closable: bool, + #[allow(clippy::type_complexity)] on_open_change: Option>, } @@ -204,6 +207,7 @@ pub struct PopoverState { pub(crate) tracked_focus_handle: Option, trigger_bounds: Bounds, open: bool, + #[allow(clippy::type_complexity)] on_open_change: Option>, _dismiss_subscription: Option, diff --git a/crates/ui/src/scroll/scrollbar.rs b/crates/ui/src/scroll/scrollbar.rs index 16d37ba..e78a3b7 100644 --- a/crates/ui/src/scroll/scrollbar.rs +++ b/crates/ui/src/scroll/scrollbar.rs @@ -16,16 +16,16 @@ use theme::{ActiveTheme, ScrollbarMode}; use crate::AxisExt; /// The width of the scrollbar (THUMB_ACTIVE_INSET * 2 + THUMB_ACTIVE_WIDTH) -const WIDTH: Pixels = px(4. * 2. + 8.); +const WIDTH: Pixels = px(1. * 2. + 8.); const MIN_THUMB_SIZE: f32 = 48.; const THUMB_WIDTH: Pixels = px(6.); const THUMB_RADIUS: Pixels = px(6. / 2.); -const THUMB_INSET: Pixels = px(4.); +const THUMB_INSET: Pixels = px(1.); const THUMB_ACTIVE_WIDTH: Pixels = px(8.); const THUMB_ACTIVE_RADIUS: Pixels = px(8. / 2.); -const THUMB_ACTIVE_INSET: Pixels = px(4.); +const THUMB_ACTIVE_INSET: Pixels = px(1.); const FADE_OUT_DURATION: f32 = 3.0; const FADE_OUT_DELAY: f32 = 2.0; @@ -167,10 +167,8 @@ impl ScrollbarStateInner { fn with_hovered_on_thumb(&self, axis: Option) -> Self { let mut state = *self; state.hovered_on_thumb = axis; - if self.is_scrollbar_visible() { - if axis.is_some() { - state.last_scroll_time = Some(std::time::Instant::now()); - } + if self.is_scrollbar_visible() && axis.is_some() { + state.last_scroll_time = Some(std::time::Instant::now()); } state } @@ -358,12 +356,14 @@ impl Scrollbar { /// If you have very high CPU usage, consider reducing this value to improve performance. /// /// Available values: 30..120 + #[allow(dead_code)] pub(crate) fn max_fps(mut self, max_fps: usize) -> Self { self.max_fps = max_fps.clamp(30, 120); self } // Get the width of the scrollbar. + #[allow(dead_code)] pub(crate) const fn width() -> Pixels { WIDTH } @@ -488,12 +488,16 @@ impl Element for Scrollbar { window: &mut Window, cx: &mut App, ) -> (LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - style.position = Position::Absolute; - style.flex_grow = 1.0; - style.flex_shrink = 1.0; - style.size.width = relative(1.).into(); - style.size.height = relative(1.).into(); + let style = Style { + position: Position::Absolute, + flex_grow: 1.0, + flex_shrink: 1.0, + size: Size { + width: relative(1.).into(), + height: relative(1.).into(), + }, + ..Default::default() + }; (window.request_layout(style, None, cx), ()) } @@ -757,20 +761,11 @@ impl Element for Scrollbar { bounds, corner_radii: (0.).into(), background: gpui::transparent_black().into(), - border_widths: if is_vertical { - Edges { - top: px(0.), - right: px(0.), - bottom: px(0.), - left: px(0.), - } - } else { - Edges { - top: px(0.), - right: px(0.), - bottom: px(0.), - left: px(0.), - } + border_widths: Edges { + top: px(0.), + right: px(0.), + bottom: px(0.), + left: px(0.), }, border_color: state.border, border_style: BorderStyle::default(), @@ -786,14 +781,15 @@ impl Element for Scrollbar { let scroll_handle = self.scroll_handle.clone(); move |event: &ScrollWheelEvent, phase, _, cx| { - if phase.bubble() && hitbox_bounds.contains(&event.position) { - if scroll_handle.offset() != state.get().last_scroll_offset { - state.set(state.get().with_last_scroll( - scroll_handle.offset(), - Some(Instant::now()), - )); - cx.notify(view_id); - } + if phase.bubble() + && hitbox_bounds.contains(&event.position) + && scroll_handle.offset() != state.get().last_scroll_offset + { + state.set(state.get().with_last_scroll( + scroll_handle.offset(), + Some(Instant::now()), + )); + cx.notify(view_id); } } }); @@ -866,13 +862,9 @@ impl Element for Scrollbar { if state.get().hovered_axis != Some(axis) { notify = true; } - } else { - if state.get().hovered_axis == Some(axis) { - if state.get().hovered_axis.is_some() { - state.set(state.get().with_hovered(None)); - notify = true; - } - } + } else if state.get().hovered_axis == Some(axis) { + state.set(state.get().with_hovered(None)); + notify = true; } // Update hovered state for scrollbar thumb @@ -881,11 +873,9 @@ impl Element for Scrollbar { state.set(state.get().with_hovered_on_thumb(Some(axis))); notify = true; } - } else { - if state.get().hovered_on_thumb == Some(axis) { - state.set(state.get().with_hovered_on_thumb(None)); - notify = true; - } + } else if state.get().hovered_on_thumb == Some(axis) { + state.set(state.get().with_hovered_on_thumb(None)); + notify = true; } // Move thumb position on dragging