From 10f042acab7735408ad069111f3848a909d2023a Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 11 Dec 2024 09:11:30 +0700 Subject: [PATCH] fix clippy issues --- crates/ui/src/accordion.rs | 14 +- crates/ui/src/animation.rs | 3 +- crates/ui/src/breadcrumb.rs | 11 +- crates/ui/src/button.rs | 21 +- crates/ui/src/button_group.rs | 11 +- crates/ui/src/checkbox.rs | 4 +- crates/ui/src/clipboard.rs | 7 +- crates/ui/src/context_menu.rs | 20 +- crates/ui/src/dock/mod.rs | 24 +- crates/ui/src/dock/panel.rs | 34 +- crates/ui/src/dock/stack_panel.rs | 2 +- crates/ui/src/dock/tab_panel.rs | 5 +- crates/ui/src/drawer.rs | 5 +- crates/ui/src/dropdown.rs | 8 +- crates/ui/src/focusable.rs | 3 +- crates/ui/src/history.rs | 11 +- crates/ui/src/indicator.rs | 6 + crates/ui/src/input/clear_button.rs | 1 + crates/ui/src/input/element.rs | 11 +- crates/ui/src/input/input.rs | 65 ++-- crates/ui/src/input/mod.rs | 1 + crates/ui/src/input/otp_input.rs | 2 +- crates/ui/src/link.rs | 4 +- crates/ui/src/list/list_item.rs | 10 +- crates/ui/src/list/mod.rs | 1 + crates/ui/src/modal.rs | 6 +- crates/ui/src/notification.rs | 4 +- crates/ui/src/popover.rs | 11 +- crates/ui/src/popup_menu.rs | 31 +- crates/ui/src/progress.rs | 6 + crates/ui/src/radio.rs | 4 +- crates/ui/src/resizable/panel.rs | 5 +- crates/ui/src/root.rs | 15 +- crates/ui/src/scroll/scrollable.rs | 14 +- crates/ui/src/scroll/scrollable_mask.rs | 16 +- crates/ui/src/scroll/scrollbar.rs | 403 ++++++++++++++++-------- crates/ui/src/sidebar/footer.rs | 12 + crates/ui/src/sidebar/group.rs | 2 + crates/ui/src/sidebar/header.rs | 12 + crates/ui/src/sidebar/menu.rs | 13 +- crates/ui/src/sidebar/mod.rs | 12 +- crates/ui/src/skeleton.rs | 6 + crates/ui/src/styled.rs | 22 +- crates/ui/src/svg_img.rs | 6 + crates/ui/src/switch.rs | 4 +- crates/ui/src/tab.rs | 1 + crates/ui/src/theme.rs | 53 +++- crates/ui/src/title_bar.rs | 37 ++- crates/ui/src/tooltip.rs | 1 + 49 files changed, 661 insertions(+), 319 deletions(-) diff --git a/crates/ui/src/accordion.rs b/crates/ui/src/accordion.rs index bbf9d2d..0148011 100644 --- a/crates/ui/src/accordion.rs +++ b/crates/ui/src/accordion.rs @@ -8,6 +8,8 @@ use gpui::{ use crate::{h_flex, theme::ActiveTheme as _, v_flex, Icon, IconName, Sizable, Size}; +type OnToggleClick = Option>; + /// An AccordionGroup is a container for multiple Accordion elements. #[derive(IntoElement)] pub struct Accordion { @@ -18,7 +20,7 @@ pub struct Accordion { bordered: bool, disabled: bool, children: Vec, - on_toggle_click: Option>, + on_toggle_click: OnToggleClick, } impl Accordion { @@ -138,6 +140,8 @@ impl RenderOnce for Accordion { } } +type OnToggle = Option>; + /// An Accordion is a vertically stacked list of items, each of which can be expanded to reveal the content associated with it. #[derive(IntoElement)] pub struct AccordionItem { @@ -148,7 +152,7 @@ pub struct AccordionItem { size: Size, bordered: bool, disabled: bool, - on_toggle_click: Option>, + on_toggle_click: OnToggle, } impl AccordionItem { @@ -204,6 +208,12 @@ impl AccordionItem { } } +impl Default for AccordionItem { + fn default() -> Self { + Self::new() + } +} + impl Sizable for AccordionItem { fn with_size(mut self, size: impl Into) -> Self { self.size = size.into(); diff --git a/crates/ui/src/animation.rs b/crates/ui/src/animation.rs index be3539e..afb1926 100644 --- a/crates/ui/src/animation.rs +++ b/crates/ui/src/animation.rs @@ -12,8 +12,7 @@ pub fn cubic_bezier(x1: f32, y1: f32, x2: f32, y2: f32) -> impl Fn(f32) -> f32 { // The Bezier curve function for x and y, where x0 = 0, y0 = 0, x3 = 1, y3 = 1 let _x = 3.0 * x1 * one_t2 * t + 3.0 * x2 * one_t * t2 + t3; - let y = 3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3; - y + 3.0 * y1 * one_t2 * t + 3.0 * y2 * one_t * t2 + t3 } } diff --git a/crates/ui/src/breadcrumb.rs b/crates/ui/src/breadcrumb.rs index 4751e93..6337c80 100644 --- a/crates/ui/src/breadcrumb.rs +++ b/crates/ui/src/breadcrumb.rs @@ -12,11 +12,13 @@ pub struct Breadcrumb { items: Vec, } +type OnClick = Option>; + #[derive(IntoElement)] pub struct BreadcrumbItem { id: ElementId, text: SharedString, - on_click: Option>, + on_click: OnClick, disabled: bool, is_last: bool, } @@ -46,6 +48,7 @@ impl BreadcrumbItem { } /// For internal use only. + #[allow(clippy::wrong_self_convention)] fn is_last(mut self, is_last: bool) -> Self { self.is_last = is_last; self @@ -84,6 +87,12 @@ impl Breadcrumb { } } +impl Default for Breadcrumb { + fn default() -> Self { + Self::new() + } +} + #[derive(IntoElement)] struct BreadcrumbSeparator; impl RenderOnce for BreadcrumbSeparator { diff --git a/crates/ui/src/button.rs b/crates/ui/src/button.rs index 53883bf..5311dce 100644 --- a/crates/ui/src/button.rs +++ b/crates/ui/src/button.rs @@ -150,6 +150,8 @@ impl ButtonVariant { } } +type OnClick = Option>; + /// A Button element. #[derive(IntoElement)] pub struct Button { @@ -167,7 +169,7 @@ pub struct Button { size: Size, compact: bool, tooltip: Option, - on_click: Option>, + on_click: OnClick, pub(crate) stop_propagation: bool, loading: bool, loading_icon: Option, @@ -509,10 +511,7 @@ impl ButtonVariant { } fn underline(&self, _: &WindowContext) -> bool { - match self { - ButtonVariant::Link => true, - _ => false, - } + matches!(self, ButtonVariant::Link) } fn shadow(&self, _: &WindowContext) -> bool { @@ -576,7 +575,11 @@ impl ButtonVariant { let bg = match self { ButtonVariant::Primary => cx.theme().primary_active, ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => { - cx.theme().secondary_active + if cx.theme().mode.is_dark() { + cx.theme().secondary.lighten(0.2).opacity(0.8) + } else { + cx.theme().secondary.darken(0.2).opacity(0.8) + } } ButtonVariant::Danger => cx.theme().destructive_active, ButtonVariant::Link => cx.theme().transparent, @@ -605,7 +608,11 @@ impl ButtonVariant { let bg = match self { ButtonVariant::Primary => cx.theme().primary_active, ButtonVariant::Secondary | ButtonVariant::Outline | ButtonVariant::Ghost => { - cx.theme().secondary_active + if cx.theme().mode.is_dark() { + cx.theme().secondary.lighten(0.2).opacity(0.8) + } else { + cx.theme().secondary.darken(0.2).opacity(0.8) + } } ButtonVariant::Danger => cx.theme().destructive_active, ButtonVariant::Link => cx.theme().transparent, diff --git a/crates/ui/src/button_group.rs b/crates/ui/src/button_group.rs index 49be076..6de3041 100644 --- a/crates/ui/src/button_group.rs +++ b/crates/ui/src/button_group.rs @@ -9,6 +9,8 @@ use crate::{ Disableable, Sizable, Size, }; +type OnClick = Option, &mut WindowContext) + 'static>>; + /// A ButtonGroup element, to wrap multiple buttons in a group. #[derive(IntoElement)] pub struct ButtonGroup { @@ -23,7 +25,7 @@ pub struct ButtonGroup { variant: Option, size: Option, - on_click: Option, &mut WindowContext) + 'static>>, + on_click: OnClick, } impl Disableable for ButtonGroup { @@ -118,7 +120,8 @@ impl RenderOnce for ButtonGroup { .enumerate() .map(|(child_index, child)| { let state = Rc::clone(&state); - let child = if children_len == 1 { + + if children_len == 1 { child } else if child_index == 0 { // First @@ -167,9 +170,7 @@ impl RenderOnce for ButtonGroup { .when_some(self.compact, |this, _| this.compact()) .on_click(move |_, _| { state.set(Some(child_index)); - }); - - child + }) }), ) .when_some( diff --git a/crates/ui/src/checkbox.rs b/crates/ui/src/checkbox.rs index 2f79646..55916a4 100644 --- a/crates/ui/src/checkbox.rs +++ b/crates/ui/src/checkbox.rs @@ -5,6 +5,8 @@ use gpui::{ WindowContext, }; +type OnClick = Option>; + /// A Checkbox element. #[derive(IntoElement)] pub struct Checkbox { @@ -12,7 +14,7 @@ pub struct Checkbox { label: Option, checked: bool, disabled: bool, - on_click: Option>, + on_click: OnClick, } impl Checkbox { diff --git a/crates/ui/src/clipboard.rs b/crates/ui/src/clipboard.rs index e605546..4a2728f 100644 --- a/crates/ui/src/clipboard.rs +++ b/crates/ui/src/clipboard.rs @@ -10,11 +10,14 @@ use crate::{ h_flex, IconName, Sizable as _, }; +type ContentBuilder = Option AnyElement>>; +type CopiedCallback = Option>; + pub struct Clipboard { id: ElementId, value: SharedString, - content_builder: Option AnyElement>>, - copied_callback: Option>, + content_builder: ContentBuilder, + copied_callback: CopiedCallback, } impl Clipboard { diff --git a/crates/ui/src/context_menu.rs b/crates/ui/src/context_menu.rs index 83633d5..0337dee 100644 --- a/crates/ui/src/context_menu.rs +++ b/crates/ui/src/context_menu.rs @@ -21,10 +21,12 @@ pub trait ContextMenuExt: ParentElement + Sized { impl ContextMenuExt for Stateful where E: ParentElement {} impl ContextMenuExt for Focusable where E: ParentElement {} +type Menu = Option) -> PopupMenu + 'static>>; + /// A context menu that can be shown on right-click. pub struct ContextMenu { id: ElementId, - menu: Option) -> PopupMenu + 'static>>, + menu: Menu, anchor: AnchorCorner, } @@ -99,13 +101,17 @@ impl Element for ContextMenu { id: Option<&gpui::GlobalElementId>, cx: &mut WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); // Set the layout style relative to the table view to get same size. - 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: gpui::Size { + width: relative(1.).into(), + height: relative(1.).into(), + }, + ..Default::default() + }; let anchor = self.anchor; diff --git a/crates/ui/src/dock/mod.rs b/crates/ui/src/dock/mod.rs index 672ccee..29659dd 100644 --- a/crates/ui/src/dock/mod.rs +++ b/crates/ui/src/dock/mod.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] mod dock; mod invalid_panel; mod panel; @@ -6,7 +7,7 @@ mod state; mod tab_panel; use anyhow::Result; -pub use dock::*; + use gpui::{ actions, canvas, div, prelude::FluentBuilder, AnyElement, AnyView, AppContext, Axis, Bounds, Edges, Entity as _, EntityId, EventEmitter, InteractiveElement as _, IntoElement, @@ -15,11 +16,14 @@ use gpui::{ }; use std::sync::Arc; +pub use dock::*; pub use panel::*; pub use stack_panel::*; pub use state::*; pub use tab_panel::*; +use crate::theme::ActiveTheme; + pub fn init(cx: &mut AppContext) { cx.set_global(PanelRegistry::new()); } @@ -232,7 +236,7 @@ impl DockItem { } Self::Split { view, items, .. } => { // Iter items to add panel to the first tabs - for item in items.into_iter() { + for item in items.iter_mut() { if let DockItem::Tabs { view, .. } = item { view.update(cx, |tab_panel, cx| { tab_panel.add_panel(panel.clone(), cx); @@ -636,12 +640,12 @@ impl DockArea { } self._subscriptions - .push(cx.subscribe(view, move |_, _, event, cx| match event { - PanelEvent::LayoutChanged => { + .push(cx.subscribe(view, move |_, _, event, cx| { + if let PanelEvent::LayoutChanged = event { let dock_area = cx.view().clone(); cx.spawn(|_, mut cx| async move { let _ = cx.update(|cx| { - let _ = dock_area.update(cx, |view, cx| { + dock_area.update(cx, |view, cx| { view.update_toggle_button_tab_panels(cx) }); }); @@ -649,7 +653,6 @@ impl DockArea { .detach(); cx.emit(DockEvent::LayoutChanged); } - _ => {} })); } DockItem::Tabs { .. } => { @@ -673,7 +676,7 @@ impl DockArea { let panel = panel.clone(); cx.spawn(|_, mut cx| async move { let _ = cx.update(|cx| { - let _ = dock_area.update(cx, |dock, cx| { + dock_area.update(cx, |dock, cx| { dock.set_zoomed_in(panel, cx); cx.notify(); }); @@ -685,7 +688,7 @@ impl DockArea { let dock_area = cx.view().clone(); cx.spawn(|_, mut cx| async move { let _ = cx.update(|cx| { - let _ = dock_area.update(cx, |view, cx| view.set_zoomed_out(cx)); + dock_area.update(cx, |view, cx| view.set_zoomed_out(cx)); }); }) .detach() @@ -694,8 +697,7 @@ impl DockArea { let dock_area = cx.view().clone(); cx.spawn(|_, mut cx| async move { let _ = cx.update(|cx| { - let _ = dock_area - .update(cx, |view, cx| view.update_toggle_button_tab_panels(cx)); + dock_area.update(cx, |view, cx| view.update_toggle_button_tab_panels(cx)); }); }) .detach(); @@ -780,6 +782,8 @@ impl Render for DockArea { // Left dock .when_some(self.left_dock.clone(), |this, dock| { this.child(div().flex().flex_none().child(dock)) + .bg(cx.theme().sidebar) + .text_color(cx.theme().sidebar_foreground) }) // Center .child( diff --git a/crates/ui/src/dock/panel.rs b/crates/ui/src/dock/panel.rs index a67e967..50dd626 100644 --- a/crates/ui/src/dock/panel.rs +++ b/crates/ui/src/dock/panel.rs @@ -143,19 +143,22 @@ impl PartialEq for dyn PanelView { } } -pub struct PanelRegistry { - pub(super) items: HashMap< - String, - Arc< - dyn Fn( - WeakView, - &DockItemState, - &DockItemInfo, - &mut WindowContext, - ) -> Box, - >, +type PanelRegistryItem = HashMap< + String, + Arc< + dyn Fn( + WeakView, + &DockItemState, + &DockItemInfo, + &mut WindowContext, + ) -> Box, >, +>; + +pub struct PanelRegistry { + pub(super) items: PanelRegistryItem, } + impl PanelRegistry { pub fn new() -> Self { Self { @@ -163,6 +166,13 @@ impl PanelRegistry { } } } + +impl Default for PanelRegistry { + fn default() -> Self { + Self::new() + } +} + impl Global for PanelRegistry {} /// Register the Panel init by panel_name to global registry. @@ -176,7 +186,7 @@ where ) -> Box + 'static, { - if let None = cx.try_global::() { + if cx.try_global::().is_none() { cx.set_global(PanelRegistry::new()); } diff --git a/crates/ui/src/dock/stack_panel.rs b/crates/ui/src/dock/stack_panel.rs index d162718..c90a542 100644 --- a/crates/ui/src/dock/stack_panel.rs +++ b/crates/ui/src/dock/stack_panel.rs @@ -184,7 +184,7 @@ impl StackPanel { cx: &mut ViewContext, ) { // If the panel is already in the stack, return. - if let Some(_) = self.index_of_panel(panel.clone()) { + if self.index_of_panel(panel.clone()).is_some() { return; } diff --git a/crates/ui/src/dock/tab_panel.rs b/crates/ui/src/dock/tab_panel.rs index ec4a6b7..61eb1d5 100644 --- a/crates/ui/src/dock/tab_panel.rs +++ b/crates/ui/src/dock/tab_panel.rs @@ -456,7 +456,7 @@ impl TabPanel { let right_dock_button = self.render_dock_toggle_button(DockPlacement::Right, cx); if self.panels.len() == 1 && panel_style == PanelStyle::Default { - let panel = self.panels.get(0).unwrap(); + let panel = self.panels.first().unwrap(); let title_style = panel.title_style(cx); return h_flex() @@ -694,6 +694,7 @@ impl TabPanel { // If target is same tab, and it is only one panel, do nothing. if is_same_tab && ix.is_none() { + #[allow(clippy::if_same_then_else)] if self.will_split_placement.is_none() { return; } else if self.panels.len() == 1 { @@ -708,7 +709,7 @@ impl TabPanel { if is_same_tab { self.detach_panel(panel.clone(), cx); } else { - let _ = drag.tab_panel.update(cx, |view, cx| { + drag.tab_panel.update(cx, |view, cx| { view.detach_panel(panel.clone(), cx); view.remove_self_if_empty(cx); }); diff --git a/crates/ui/src/drawer.rs b/crates/ui/src/drawer.rs index b64623c..66d3985 100644 --- a/crates/ui/src/drawer.rs +++ b/crates/ui/src/drawer.rs @@ -21,17 +21,20 @@ use crate::{ actions!(drawer, [Escape]); const CONTEXT: &str = "Drawer"; + pub fn init(cx: &mut AppContext) { cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))]) } +type OnClose = Rc; + #[derive(IntoElement)] pub struct Drawer { pub(crate) focus_handle: FocusHandle, placement: Placement, size: DefiniteLength, resizable: bool, - on_close: Rc, + on_close: OnClose, title: Option, footer: Option, content: Div, diff --git a/crates/ui/src/dropdown.rs b/crates/ui/src/dropdown.rs index ab2e174..8927855 100644 --- a/crates/ui/src/dropdown.rs +++ b/crates/ui/src/dropdown.rs @@ -41,7 +41,7 @@ impl DropdownItem for String { } fn value(&self) -> &Self::Value { - &self + self } } @@ -53,7 +53,7 @@ impl DropdownItem for SharedString { } fn value(&self) -> &Self::Value { - &self + self } } @@ -211,6 +211,8 @@ pub enum DropdownEvent { Confirm(Option<::Value>), } +type Empty = Option AnyElement + 'static>>; + /// A Dropdown element. pub struct Dropdown { id: ElementId, @@ -223,7 +225,7 @@ pub struct Dropdown { placeholder: Option, title_prefix: Option, selected_value: Option<::Value>, - empty: Option AnyElement + 'static>>, + empty: Empty, width: Length, menu_width: Length, /// Store the bounds of the input diff --git a/crates/ui/src/focusable.rs b/crates/ui/src/focusable.rs index 70b0cd9..19cf660 100644 --- a/crates/ui/src/focusable.rs +++ b/crates/ui/src/focusable.rs @@ -30,8 +30,7 @@ pub trait FocusableCycle { let target_focus_handle = handles .into_iter() .skip_while(|handle| Some(handle) != focused_handle.as_ref()) - .skip(1) - .next() + .nth(1) .unwrap_or(fallback_handle); target_focus_handle.focus(cx); diff --git a/crates/ui/src/history.rs b/crates/ui/src/history.rs index 6a854b7..7319a7c 100644 --- a/crates/ui/src/history.rs +++ b/crates/ui/src/history.rs @@ -124,6 +124,15 @@ where } } +impl Default for History +where + I: HistoryItem, +{ + fn default() -> Self { + Self::new() + } +} + #[cfg(test)] mod tests { use super::*; @@ -192,6 +201,6 @@ mod tests { let changes = history.undo().unwrap(); assert_eq!(changes[0].tab_index, 0); - assert_eq!(history.undo().is_none(), true); + assert!(history.undo().is_none()); } } diff --git a/crates/ui/src/indicator.rs b/crates/ui/src/indicator.rs index a62529a..dd545a5 100644 --- a/crates/ui/src/indicator.rs +++ b/crates/ui/src/indicator.rs @@ -35,6 +35,12 @@ impl Indicator { } } +impl Default for Indicator { + fn default() -> Self { + Self::new() + } +} + impl Sizable for Indicator { fn with_size(mut self, size: impl Into) -> Self { self.size = size.into(); diff --git a/crates/ui/src/input/clear_button.rs b/crates/ui/src/input/clear_button.rs index 7f31f04..6de6617 100644 --- a/crates/ui/src/input/clear_button.rs +++ b/crates/ui/src/input/clear_button.rs @@ -9,6 +9,7 @@ use crate::{ pub(crate) struct ClearButton {} impl ClearButton { + #[allow(clippy::new_ret_no_self)] pub fn new(cx: &mut WindowContext) -> Button { Button::new("clean") .icon(Icon::new(IconName::CircleX).text_color(cx.theme().muted_foreground)) diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 5cf77ee..2947b65 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -134,7 +134,7 @@ impl TextElement { } } - bounds.origin = bounds.origin + scroll_offset; + bounds.origin += scroll_offset; if input.show_cursor(cx) { // cursor blink @@ -268,7 +268,7 @@ impl TextElement { // print_points_as_svg_path(&line_corners, &points); - let first_p = *points.get(0).unwrap(); + let first_p = *points.first().unwrap(); let mut path = gpui::Path::new(bounds.origin + first_p); for p in points.iter().skip(1) { path.line_to(bounds.origin + *p); @@ -295,10 +295,7 @@ impl IntoElement for TextElement { /// A debug function to print points as SVG path. #[allow(unused)] -fn print_points_as_svg_path( - line_corners: &Vec>>, - points: &Vec>, -) { +fn print_points_as_svg_path(line_corners: &Vec>>, points: &[Point]) { for corners in line_corners { println!( "tl: ({}, {}), tr: ({}, {}), bl: ({}, {}), br: ({}, {})", @@ -313,7 +310,7 @@ fn print_points_as_svg_path( ); } - if points.len() > 0 { + if !points.is_empty() { println!("M{},{}", points[0].x.0 as i32, points[0].y.0 as i32); for p in points.iter().skip(1) { println!("L{},{}", p.x.0 as i32, p.y.0 as i32); diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 46274b5..c527300 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -145,14 +145,17 @@ pub fn init(cx: &mut AppContext) { ]); } +type Affixes = Option) -> AnyElement + 'static>>; +type Validate = Option bool + 'static>>; + pub struct TextInput { pub(super) focus_handle: FocusHandle, pub(super) text: SharedString, multi_line: bool, pub(super) history: History, pub(super) blink_cursor: Model, - pub(super) prefix: Option) -> AnyElement + 'static>>, - pub(super) suffix: Option) -> AnyElement + 'static>>, + pub(super) prefix: Affixes, + pub(super) suffix: Affixes, pub(super) loading: bool, pub(super) placeholder: SharedString, pub(super) selected_range: Range, @@ -177,7 +180,7 @@ pub struct TextInput { pub(super) size: Size, pub(super) rows: usize, pattern: Option, - validate: Option bool + 'static>>, + validate: Validate, pub(crate) scroll_handle: ScrollHandle, scrollbar_state: Rc>, /// The size of the scrollable content. @@ -506,13 +509,12 @@ impl TextInput { } let offset = self.previous_boundary(self.cursor_offset()); - let line = self - .text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, cx) + + self.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, cx) .unwrap_or_default() .rfind('\n') .map(|i| i + 1) - .unwrap_or(0); - line + .unwrap_or(0) } /// Get end of line @@ -531,17 +533,15 @@ impl TextInput { return offset; } - let line = self - .text_for_range( - self.range_to_utf16(&(offset..self.text.len())), - &mut None, - cx, - ) - .unwrap_or_default() - .find('\n') - .map(|i| i + offset) - .unwrap_or(self.text.len()); - line + self.text_for_range( + self.range_to_utf16(&(offset..self.text.len())), + &mut None, + cx, + ) + .unwrap_or_default() + .find('\n') + .map(|i| i + offset) + .unwrap_or(self.text.len()) } fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext) { @@ -675,7 +675,7 @@ impl TextInput { } let old_text = self - .text_for_range(self.range_to_utf16(&range), &mut None, cx) + .text_for_range(self.range_to_utf16(range), &mut None, cx) .unwrap_or("".to_string()); let new_range = range.start..range.start + new_text.len(); @@ -753,7 +753,7 @@ impl TextInput { let mut y_offset = px(0.); for line in lines.iter() { - let line_origin = self.line_origin_with_y_offset(&mut y_offset, &line, line_height); + let line_origin = self.line_origin_with_y_offset(&mut y_offset, line, line_height); let mut pos = inner_position - line_origin; // Ignore the y position in single line mode, only check x position. if self.is_single_line() { @@ -765,7 +765,10 @@ impl TextInput { // Add 1 for place cursor after the character. index += v + 1; break; - } else if let Ok(_) = line.index_for_position(point(px(0.), pos.y), line_height) { + } else if line + .index_for_position(point(px(0.), pos.y), line_height) + .is_ok() + { // Click in the this line but not in the text, move cursor to the end of the line. // The fallback index is saved in Err from `index_for_position` method. index += index_result.unwrap_err(); @@ -809,7 +812,7 @@ impl TextInput { if self.is_multi_line() { let p = point(px(0.), *y_offset); let height = line_height + line.wrap_boundaries.len() as f32 * line_height; - *y_offset = *y_offset + height; + *y_offset += height; p } else { point(px(0.), px(0.)) @@ -859,7 +862,7 @@ impl TextInput { let prev_chars = prev_text.chars().rev().peekable(); let next_chars = next_text.chars().peekable(); - for (_, c) in prev_chars.enumerate() { + for c in prev_chars { if !is_word(c) { break; } @@ -867,7 +870,7 @@ impl TextInput { start -= c.len_utf16(); } - for (_, c) in next_chars.enumerate() { + for c in next_chars { if !is_word(c) { break; } @@ -1177,12 +1180,8 @@ impl Render for TextInput { .on_action(cx.listener(Self::delete_to_end_of_line)) .on_action(cx.listener(Self::enter)) }) - .on_action(cx.listener(Self::up)) - .on_action(cx.listener(Self::down)) .on_action(cx.listener(Self::left)) .on_action(cx.listener(Self::right)) - .on_action(cx.listener(Self::select_up)) - .on_action(cx.listener(Self::select_down)) .on_action(cx.listener(Self::select_left)) .on_action(cx.listener(Self::select_right)) .on_action(cx.listener(Self::select_all)) @@ -1206,7 +1205,13 @@ impl Render for TextInput { .input_py(self.size) .input_h(self.size) .cursor_text() - .when(self.multi_line, |this| this.h_auto()) + .when(self.multi_line, |this| { + this.on_action(cx.listener(Self::up)) + .on_action(cx.listener(Self::down)) + .on_action(cx.listener(Self::select_up)) + .on_action(cx.listener(Self::select_down)) + .h_auto() + }) .when(self.appearance, |this| { this.bg(if self.disabled { cx.theme().muted @@ -1249,7 +1254,7 @@ impl Render for TextInput { .absolute() .top_0() .left_0() - .right_0() + .right(px(1.)) .bottom_0() .child( Scrollbar::vertical( diff --git a/crates/ui/src/input/mod.rs b/crates/ui/src/input/mod.rs index 2495af9..779dd67 100644 --- a/crates/ui/src/input/mod.rs +++ b/crates/ui/src/input/mod.rs @@ -2,6 +2,7 @@ mod blink_cursor; mod change; mod clear_button; mod element; +#[allow(clippy::module_inception)] mod input; mod otp_input; diff --git a/crates/ui/src/input/otp_input.rs b/crates/ui/src/input/otp_input.rs index bb7054b..fc7fd2e 100644 --- a/crates/ui/src/input/otp_input.rs +++ b/crates/ui/src/input/otp_input.rs @@ -127,7 +127,7 @@ impl OtpInput { } _ => { let c = key.chars().next().unwrap(); - if !matches!(c, '0'..='9') { + if !c.is_ascii_digit() { return; } if ix >= self.length { diff --git a/crates/ui/src/link.rs b/crates/ui/src/link.rs index 82db78c..81a95f2 100644 --- a/crates/ui/src/link.rs +++ b/crates/ui/src/link.rs @@ -5,13 +5,15 @@ use gpui::{ use crate::theme::ActiveTheme as _; +type OnClick = Option>; + /// A Link element like a `` tag in HTML. #[derive(IntoElement)] pub struct Link { base: Stateful
, href: Option, disabled: bool, - on_click: Option>, + on_click: OnClick, } impl Link { diff --git a/crates/ui/src/list/list_item.rs b/crates/ui/src/list/list_item.rs index a35a291..06c6838 100644 --- a/crates/ui/src/list/list_item.rs +++ b/crates/ui/src/list/list_item.rs @@ -6,6 +6,10 @@ use gpui::{ }; use smallvec::SmallVec; +type OnClick = Option>; +type OnMouseEnter = Option>; +type Suffix = Option AnyElement + 'static>>; + #[derive(IntoElement)] pub struct ListItem { id: ElementId, @@ -14,9 +18,9 @@ pub struct ListItem { selected: bool, confirmed: bool, check_icon: Option, - on_click: Option>, - on_mouse_enter: Option>, - suffix: Option AnyElement + 'static>>, + on_click: OnClick, + on_mouse_enter: OnMouseEnter, + suffix: Suffix, children: SmallVec<[AnyElement; 2]>, } diff --git a/crates/ui/src/list/mod.rs b/crates/ui/src/list/mod.rs index 9122575..c797298 100644 --- a/crates/ui/src/list/mod.rs +++ b/crates/ui/src/list/mod.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] mod list; mod list_item; diff --git a/crates/ui/src/modal.rs b/crates/ui/src/modal.rs index 7685ae3..921ce4d 100644 --- a/crates/ui/src/modal.rs +++ b/crates/ui/src/modal.rs @@ -17,10 +17,13 @@ use crate::{ actions!(modal, [Escape]); const CONTEXT: &str = "Modal"; + pub fn init(cx: &mut AppContext) { cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))]) } +type OnClose = Rc; + #[derive(IntoElement)] pub struct Modal { base: Div, @@ -30,8 +33,7 @@ pub struct Modal { width: Pixels, max_width: Option, margin_top: Option, - - on_close: Rc, + on_close: OnClose, show_close: bool, overlay: bool, keyboard: bool, diff --git a/crates/ui/src/notification.rs b/crates/ui/src/notification.rs index 1b51aeb..c8934f5 100644 --- a/crates/ui/src/notification.rs +++ b/crates/ui/src/notification.rs @@ -40,6 +40,8 @@ impl From<(TypeId, ElementId)> for NotificationId { } } +type OnClick = Option>; + /// A notification element. pub struct Notification { /// The id is used make the notification unique. @@ -52,7 +54,7 @@ pub struct Notification { message: SharedString, icon: Option, autohide: bool, - on_click: Option>, + on_click: OnClick, closing: bool, } diff --git a/crates/ui/src/popover.rs b/crates/ui/src/popover.rs index 33fd3d6..3667f31 100644 --- a/crates/ui/src/popover.rs +++ b/crates/ui/src/popover.rs @@ -17,9 +17,11 @@ pub fn init(cx: &mut AppContext) { cx.bind_keys([KeyBinding::new("escape", Escape, Some(CONTEXT))]) } +type Content = Rc) -> AnyElement>; + pub struct PopoverContent { focus_handle: FocusHandle, - content: Rc) -> AnyElement>, + content: Content, max_width: Option, } @@ -62,11 +64,14 @@ impl Render for PopoverContent { } } +type Trigger = Option AnyElement + 'static>>; +type ViewContent = Option View + 'static>>; + pub struct Popover { id: ElementId, anchor: AnchorCorner, - trigger: Option AnyElement + 'static>>, - content: Option View + 'static>>, + trigger: Trigger, + content: ViewContent, /// Style for trigger element. /// This is used for hotfix the trigger element style to support w_full. trigger_style: Option, diff --git a/crates/ui/src/popup_menu.rs b/crates/ui/src/popup_menu.rs index 9bc9da0..9f2a080 100644 --- a/crates/ui/src/popup_menu.rs +++ b/crates/ui/src/popup_menu.rs @@ -264,7 +264,7 @@ impl PopupMenu { // If the actions are listened on the `PanelContent`, // it can't receive the actions from the `PopupMenu`, unless we focus on `PanelContent`. if let Some(handle) = action_focus_handle.as_ref() { - cx.focus(&handle); + cx.focus(handle); } cx.dispatch_action(action.boxed_clone()); @@ -367,22 +367,19 @@ impl PopupMenu { } fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { - match self.selected_index { - Some(index) => { - let item = self.menu_items.get(index); - match item { - Some(PopupMenuItem::Item { handler, .. }) => { - handler(cx); - self.dismiss(&Dismiss, cx) - } - Some(PopupMenuItem::ElementItem { handler, .. }) => { - handler(cx); - self.dismiss(&Dismiss, cx) - } - _ => {} + if let Some(index) = self.selected_index { + let item = self.menu_items.get(index); + match item { + Some(PopupMenuItem::Item { handler, .. }) => { + handler(cx); + self.dismiss(&Dismiss, cx) } + Some(PopupMenuItem::ElementItem { handler, .. }) => { + handler(cx); + self.dismiss(&Dismiss, cx) + } + _ => {} } - _ => {} } } @@ -435,7 +432,7 @@ impl PopupMenu { let el = div().text_color(cx.theme().muted_foreground).children( keybinding .keystrokes() - .into_iter() + .iter() .map(|key| key_shortcut(key.clone())), ); @@ -443,7 +440,7 @@ impl PopupMenu { } } - return None; + None } fn render_icon( diff --git a/crates/ui/src/progress.rs b/crates/ui/src/progress.rs index c3eeba9..cbe72dd 100644 --- a/crates/ui/src/progress.rs +++ b/crates/ui/src/progress.rs @@ -25,6 +25,12 @@ impl Progress { } } +impl Default for Progress { + fn default() -> Self { + Self::new() + } +} + impl RenderOnce for Progress { fn render(self, cx: &mut WindowContext) -> impl IntoElement { let rounded = px(self.height / 2.); diff --git a/crates/ui/src/radio.rs b/crates/ui/src/radio.rs index e10157e..9d6051e 100644 --- a/crates/ui/src/radio.rs +++ b/crates/ui/src/radio.rs @@ -4,6 +4,8 @@ use gpui::{ ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WindowContext, }; +type OnClick = Option>; + /// A Radio element. /// /// This is not included the Radio group implementation, you can manage the group by yourself. @@ -13,7 +15,7 @@ pub struct Radio { label: Option, checked: bool, disabled: bool, - on_click: Option>, + on_click: OnClick, } impl Radio { diff --git a/crates/ui/src/resizable/panel.rs b/crates/ui/src/resizable/panel.rs index 3221e16..011bca4 100644 --- a/crates/ui/src/resizable/panel.rs +++ b/crates/ui/src/resizable/panel.rs @@ -274,6 +274,8 @@ impl Render for ResizablePanelGroup { } } +type ContentBuilder = Option AnyElement>>; + pub struct ResizablePanel { group: Option>, /// Initial size is the size that the panel has when it is created. @@ -283,7 +285,7 @@ pub struct ResizablePanel { /// the size ratio that the panel has relative to its group size_ratio: Option, axis: Axis, - content_builder: Option AnyElement>>, + content_builder: ContentBuilder, content_view: Option, /// The bounds of the resizable panel, when render the bounds will be updated. bounds: Bounds, @@ -435,7 +437,6 @@ impl Element for ResizePanelGroupElement { _: &mut Self::RequestLayoutState, _: &mut WindowContext, ) -> Self::PrepaintState { - () } fn paint( diff --git a/crates/ui/src/root.rs b/crates/ui/src/root.rs index 3a52680..5be3966 100644 --- a/crates/ui/src/root.rs +++ b/crates/ui/src/root.rs @@ -69,7 +69,7 @@ impl ContextModal for WindowContext<'_> { } fn has_active_drawer(&self) -> bool { - Root::read(&self).active_drawer.is_some() + Root::read(self).active_drawer.is_some() } fn close_drawer(&mut self) { @@ -87,7 +87,7 @@ impl ContextModal for WindowContext<'_> { Root::update(self, move |root, cx| { // Only save focus handle if there are no active modals. // This is used to restore focus when all modals are closed. - if root.active_modals.len() == 0 { + if root.active_modals.is_empty() { root.previous_focus_handle = cx.focused(); } @@ -103,7 +103,7 @@ impl ContextModal for WindowContext<'_> { } fn has_active_modal(&self) -> bool { - Root::read(&self).active_modals.len() > 0 + !Root::read(self).active_modals.is_empty() } fn close_modal(&mut self) { @@ -145,7 +145,7 @@ impl ContextModal for WindowContext<'_> { } fn notifications(&self) -> Rc>> { - Rc::new(Root::read(&self).notification.read(&self).notifications()) + Rc::new(Root::read(self).notification.read(self).notifications()) } } impl ContextModal for ViewContext<'_, V> { @@ -211,16 +211,19 @@ pub struct Root { view: AnyView, } +type DrawerBuilder = Rc Drawer + 'static>; +type ModelBuilder = Rc Modal + 'static>; + #[derive(Clone)] struct ActiveDrawer { focus_handle: FocusHandle, - builder: Rc Drawer + 'static>, + builder: DrawerBuilder, } #[derive(Clone)] struct ActiveModal { focus_handle: FocusHandle, - builder: Rc Modal + 'static>, + builder: ModelBuilder, } impl Root { diff --git a/crates/ui/src/scroll/scrollable.rs b/crates/ui/src/scroll/scrollable.rs index d3915f4..bfa47f8 100644 --- a/crates/ui/src/scroll/scrollable.rs +++ b/crates/ui/src/scroll/scrollable.rs @@ -150,11 +150,15 @@ where id: Option<&gpui::GlobalElementId>, cx: &mut gpui::WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - style.flex_grow = 1.0; - style.position = Position::Relative; - style.size.width = relative(1.0).into(); - style.size.height = relative(1.0).into(); + let style = Style { + flex_grow: 1.0, + position: Position::Relative, + size: Size { + width: relative(1.0).into(), + height: relative(1.0).into(), + }, + ..Default::default() + }; let axis = self.axis; let view_id = self.view_id; diff --git a/crates/ui/src/scroll/scrollable_mask.rs b/crates/ui/src/scroll/scrollable_mask.rs index a6f29ba..5e7b168 100644 --- a/crates/ui/src/scroll/scrollable_mask.rs +++ b/crates/ui/src/scroll/scrollable_mask.rs @@ -70,13 +70,17 @@ impl Element for ScrollableMask { _: Option<&GlobalElementId>, cx: &mut WindowContext, ) -> (LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); // Set the layout style relative to the table view to get same size. - 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: gpui::Size { + width: relative(1.).into(), + height: relative(1.).into(), + }, + ..Default::default() + }; (cx.request_layout(style, None), ()) } diff --git a/crates/ui/src/scroll/scrollbar.rs b/crates/ui/src/scroll/scrollbar.rs index 295afbe..9e30774 100644 --- a/crates/ui/src/scroll/scrollbar.rs +++ b/crates/ui/src/scroll/scrollbar.rs @@ -2,14 +2,31 @@ use std::{cell::Cell, rc::Rc, time::Instant}; use crate::theme::ActiveTheme; use gpui::{ - fill, point, px, relative, Bounds, ContentMask, Edges, Element, EntityId, Hitbox, IntoElement, - MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, Position, ScrollHandle, - ScrollWheelEvent, Style, UniformListScrollHandle, + fill, point, px, relative, AppContext, Bounds, ContentMask, CursorStyle, Edges, Element, + EntityId, Hitbox, Hsla, IntoElement, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, + Pixels, Point, Position, ScrollHandle, ScrollWheelEvent, Style, UniformListScrollHandle, }; +use serde::{Deserialize, Serialize}; + +/// Scrollbar show mode. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, Default)] +pub enum ScrollbarShow { + #[default] + Scrolling, + Hover, +} + +impl ScrollbarShow { + fn is_hover(&self) -> bool { + matches!(self, Self::Hover) + } +} const MIN_THUMB_SIZE: f32 = 80.; const THUMB_RADIUS: Pixels = Pixels(3.0); const THUMB_INSET: Pixels = Pixels(4.); +const FADE_OUT_DURATION: f32 = 3.0; +const FADE_OUT_DELAY: f32 = 2.0; pub trait ScrollHandleOffsetable { fn offset(&self) -> Point; @@ -92,6 +109,9 @@ impl ScrollbarState { fn with_hovered(&self, axis: Option) -> Self { let mut state = *self; state.hovered_axis = axis; + if self.is_scrollbar_visible() { + state.last_scroll_time = Some(Instant::now()); + } state } @@ -111,6 +131,21 @@ impl ScrollbarState { state.last_scroll_time = last_scroll_time; state } + + fn with_last_scroll_time(&self, t: Option) -> Self { + let mut state = *self; + state.last_scroll_time = t; + state + } + + fn is_scrollbar_visible(&self) -> bool { + if let Some(last_time) = self.last_scroll_time { + let elapsed = Instant::now().duration_since(last_time).as_secs_f32(); + elapsed < FADE_OUT_DURATION + } else { + false + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -257,6 +292,52 @@ impl Scrollbar { self.axis = axis; self } + + fn style_for_active(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { + ( + cx.theme().scrollbar_thumb_hover, + cx.theme().scrollbar, + cx.theme().border, + THUMB_INSET - px(1.), + THUMB_RADIUS, + ) + } + + fn style_for_hovered_thumb(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { + ( + cx.theme().scrollbar_thumb_hover, + cx.theme().scrollbar, + cx.theme().border, + THUMB_INSET - px(1.), + THUMB_RADIUS, + ) + } + + fn style_for_hovered_bar(cx: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { + let (inset, radius) = if cx.theme().scrollbar_show.is_hover() { + (THUMB_INSET, THUMB_RADIUS - px(1.)) + } else { + (THUMB_INSET - px(1.), THUMB_RADIUS) + }; + + ( + cx.theme().scrollbar_thumb, + cx.theme().scrollbar, + gpui::transparent_black(), + inset, + radius, + ) + } + + fn style_for_idle(_: &AppContext) -> (Hsla, Hsla, Hsla, Pixels, Pixels) { + ( + gpui::transparent_black(), + gpui::transparent_black(), + gpui::transparent_black(), + THUMB_INSET, + THUMB_RADIUS - px(1.), + ) + } } impl IntoElement for Scrollbar { @@ -267,10 +348,31 @@ impl IntoElement for Scrollbar { } } +pub struct PrepaintState { + hitbox: Hitbox, + states: Vec, +} + +pub struct AxisPrepaintState { + axis: ScrollbarAxis, + bar_hitbox: Hitbox, + bounds: Bounds, + border_width: Pixels, + radius: Pixels, + bg: Hsla, + border: Hsla, + thumb_bounds: Bounds, + thumb_bg: Hsla, + scroll_size: Pixels, + container_size: Pixels, + thumb_size: Pixels, + margin_end: Pixels, +} + impl Element for Scrollbar { type RequestLayoutState = (); - type PrepaintState = Hitbox; + type PrepaintState = PrepaintState; fn id(&self) -> Option { None @@ -281,12 +383,16 @@ impl Element for Scrollbar { _: Option<&gpui::GlobalElementId>, cx: &mut gpui::WindowContext, ) -> (gpui::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: gpui::Size { + width: relative(1.).into(), + height: relative(1.).into(), + }, + ..Default::default() + }; (cx.request_layout(style, None), ()) } @@ -298,36 +404,26 @@ impl Element for Scrollbar { _: &mut Self::RequestLayoutState, cx: &mut gpui::WindowContext, ) -> Self::PrepaintState { - cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + let hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| { cx.insert_hitbox(bounds, false) - }) - } + }); + + let mut states = vec![]; - fn paint( - &mut self, - _: Option<&gpui::GlobalElementId>, - _: Bounds, - _: &mut Self::RequestLayoutState, - hitbox: &mut Self::PrepaintState, - cx: &mut gpui::WindowContext, - ) { - let hitbox_bounds = hitbox.bounds; let mut has_both = self.axis.is_both(); for axis in self.axis.all().into_iter() { - const NORMAL_OPACITY: f32 = 0.6; - let is_vertical = axis.is_vertical(); let (scroll_area_size, container_size, scroll_position) = if is_vertical { ( self.scroll_size.height, - hitbox_bounds.size.height, + hitbox.size.height, self.scroll_handle.offset().y, ) } else { ( self.scroll_size.width, - hitbox_bounds.size.width, + hitbox.size.width, self.scroll_handle.offset().x, ) }; @@ -354,23 +450,23 @@ impl Element for Scrollbar { let bounds = Bounds { origin: if is_vertical { point( - hitbox_bounds.origin.x + hitbox_bounds.size.width - self.width, - hitbox_bounds.origin.y, + hitbox.origin.x + hitbox.size.width - self.width, + hitbox.origin.y, ) } else { point( - hitbox_bounds.origin.x, - hitbox_bounds.origin.y + hitbox_bounds.size.height - self.width, + hitbox.origin.x, + hitbox.origin.y + hitbox.size.height - self.width, ) }, size: gpui::Size { width: if is_vertical { self.width } else { - hitbox_bounds.size.width + hitbox.size.width }, height: if is_vertical { - hitbox_bounds.size.height + hitbox.size.height } else { self.width }, @@ -378,49 +474,46 @@ impl Element for Scrollbar { }; let state = self.state.clone(); + let is_hover_to_show = cx.theme().scrollbar_show.is_hover(); + let is_hovered_on_bar = state.get().hovered_axis == Some(axis); + let is_hovered_on_thumb = state.get().hovered_on_thumb == Some(axis); + let (thumb_bg, bar_bg, bar_border, inset, radius) = if state.get().dragged_axis == Some(axis) { - ( - cx.theme().scrollbar_thumb, - cx.theme().scrollbar, - cx.theme().border, - THUMB_INSET - px(1.), - THUMB_RADIUS, - ) - } else if state.get().hovered_axis == Some(axis) { - if state.get().hovered_on_thumb == Some(axis) { - ( - cx.theme().scrollbar_thumb, - cx.theme().scrollbar, - cx.theme().border, - THUMB_INSET - px(1.), - THUMB_RADIUS, - ) + Self::style_for_active(cx) + } else if is_hover_to_show && is_hovered_on_bar { + if is_hovered_on_thumb { + Self::style_for_hovered_thumb(cx) } else { - ( - cx.theme().scrollbar_thumb.opacity(NORMAL_OPACITY), - gpui::transparent_black(), - gpui::transparent_black(), - THUMB_INSET, - THUMB_RADIUS, - ) + Self::style_for_hovered_bar(cx) } } else { - let mut idle_state = ( - gpui::transparent_black(), - gpui::transparent_black(), - gpui::transparent_black(), - THUMB_INSET, - THUMB_RADIUS - px(1.), - ); + let mut idle_state = Self::style_for_idle(cx); + // Delay 2s to fade out the scrollbar thumb (in 1s) if let Some(last_time) = state.get().last_scroll_time { let elapsed = Instant::now().duration_since(last_time).as_secs_f32(); - if elapsed < 1.0 { - let y_value = NORMAL_OPACITY - elapsed.powi(10); // y = 1 - x^10 - idle_state.0 = cx.theme().scrollbar_thumb.opacity(y_value); - cx.request_animation_frame(); + if elapsed < FADE_OUT_DURATION { + if is_hovered_on_bar { + state.set(state.get().with_last_scroll_time(Some(Instant::now()))); + idle_state = if is_hovered_on_thumb { + Self::style_for_hovered_thumb(cx) + } else { + Self::style_for_hovered_bar(cx) + }; + } else { + if elapsed < FADE_OUT_DELAY { + idle_state.0 = cx.theme().scrollbar_thumb; + } else { + // opacity = 1 - (x - 2)^10 + let opacity = 1.0 - (elapsed - FADE_OUT_DELAY).powi(10); + idle_state.0 = cx.theme().scrollbar_thumb.opacity(opacity); + }; + + cx.request_animation_frame(); + } } } + idle_state }; @@ -449,8 +542,57 @@ impl Element for Scrollbar { ) }; + let bar_hitbox = cx.with_content_mask(Some(ContentMask { bounds }), |cx| { + cx.insert_hitbox(bounds, false) + }); + + states.push(AxisPrepaintState { + axis, + bar_hitbox, + bounds, + border_width, + radius, + bg: bar_bg, + border: bar_border, + thumb_bounds, + thumb_bg, + scroll_size: scroll_area_size, + container_size, + thumb_size: thumb_length, + margin_end, + }) + } + + PrepaintState { hitbox, states } + } + + fn paint( + &mut self, + _: Option<&gpui::GlobalElementId>, + _: Bounds, + _: &mut Self::RequestLayoutState, + prepaint: &mut Self::PrepaintState, + cx: &mut gpui::WindowContext, + ) { + let hitbox_bounds = prepaint.hitbox.bounds; + let is_visible = self.state.get().is_scrollbar_visible(); + let is_hover_to_show = cx.theme().scrollbar_show.is_hover(); + + for state in prepaint.states.iter() { + let axis = state.axis; + let radius = state.radius; + let bounds = state.bounds; + let thumb_bounds = state.thumb_bounds; + let scroll_area_size = state.scroll_size; + let container_size = state.container_size; + let thumb_size = state.thumb_size; + let margin_end = state.margin_end; + let is_vertical = axis.is_vertical(); + + cx.set_cursor_style(CursorStyle::default(), &state.bar_hitbox); + cx.paint_layer(hitbox_bounds, |cx| { - cx.paint_quad(fill(bounds, bar_bg)); + cx.paint_quad(fill(state.bounds, state.bg)); cx.paint_quad(PaintQuad { bounds, @@ -461,20 +603,20 @@ impl Element for Scrollbar { top: px(0.), right: px(0.), bottom: px(0.), - left: border_width, + left: state.border_width, } } else { Edges { - top: border_width, + top: state.border_width, right: px(0.), bottom: px(0.), left: px(0.), } }, - border_color: bar_border, + border_color: state.border, }); - cx.paint_quad(fill(thumb_bounds, thumb_bg).corner_radii(radius)); + cx.paint_quad(fill(thumb_bounds, state.thumb_bg).corner_radii(radius)); }); cx.on_mouse_event({ @@ -483,67 +625,70 @@ 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(Some(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(Some(view_id)); } } }); let safe_range = (-scroll_area_size + container_size)..px(0.); - cx.on_mouse_event({ - let state = self.state.clone(); - let view_id = self.view_id; - let scroll_handle = self.scroll_handle.clone(); + if is_hover_to_show || is_visible { + cx.on_mouse_event({ + let state = self.state.clone(); + let view_id = self.view_id; + let scroll_handle = self.scroll_handle.clone(); - move |event: &MouseDownEvent, phase, cx| { - if phase.bubble() && bounds.contains(&event.position) { - cx.stop_propagation(); + move |event: &MouseDownEvent, phase, cx| { + if phase.bubble() && bounds.contains(&event.position) { + cx.stop_propagation(); - if thumb_bounds.contains(&event.position) { - // click on the thumb bar, set the drag position - let pos = event.position - thumb_bounds.origin; + if thumb_bounds.contains(&event.position) { + // click on the thumb bar, set the drag position + let pos = event.position - thumb_bounds.origin; - state.set(state.get().with_drag_pos(axis, pos)); + state.set(state.get().with_drag_pos(axis, pos)); - cx.notify(Some(view_id)); - } else { - // click on the scrollbar, jump to the position - // Set the thumb bar center to the click position - let offset = scroll_handle.offset(); - let percentage = if is_vertical { - (event.position.y - thumb_length / 2. - bounds.origin.y) - / (bounds.size.height - thumb_length) + cx.notify(Some(view_id)); } else { - (event.position.x - thumb_length / 2. - bounds.origin.x) - / (bounds.size.width - thumb_length) - } - .min(1.); + // click on the scrollbar, jump to the position + // Set the thumb bar center to the click position + let offset = scroll_handle.offset(); + let percentage = if is_vertical { + (event.position.y - thumb_size / 2. - bounds.origin.y) + / (bounds.size.height - thumb_size) + } else { + (event.position.x - thumb_size / 2. - bounds.origin.x) + / (bounds.size.width - thumb_size) + } + .min(1.); - if is_vertical { - scroll_handle.set_offset(point( - offset.x, - (-scroll_area_size * percentage) - .clamp(safe_range.start, safe_range.end), - )); - } else { - scroll_handle.set_offset(point( - (-scroll_area_size * percentage) - .clamp(safe_range.start, safe_range.end), - offset.y, - )); + if is_vertical { + scroll_handle.set_offset(point( + offset.x, + (-scroll_area_size * percentage) + .clamp(safe_range.start, safe_range.end), + )); + } else { + scroll_handle.set_offset(point( + (-scroll_area_size * percentage) + .clamp(safe_range.start, safe_range.end), + offset.y, + )); + } } } } - } - }); + }); + } cx.on_mouse_event({ let scroll_handle = self.scroll_handle.clone(); @@ -557,13 +702,11 @@ impl Element for Scrollbar { state.set(state.get().with_hovered(Some(axis))); cx.notify(Some(view_id)); } - } else { - if state.get().hovered_axis == Some(axis) { - if state.get().hovered_axis.is_some() { - state.set(state.get().with_hovered(None)); - cx.notify(Some(view_id)); - } - } + } else if state.get().hovered_axis == Some(axis) + && state.get().hovered_axis.is_some() + { + state.set(state.get().with_hovered(None)); + cx.notify(Some(view_id)); } // Update hovered state for scrollbar thumb @@ -572,11 +715,9 @@ impl Element for Scrollbar { state.set(state.get().with_hovered_on_thumb(Some(axis))); cx.notify(Some(view_id)); } - } else { - if state.get().hovered_on_thumb == Some(axis) { - state.set(state.get().with_hovered_on_thumb(None)); - cx.notify(Some(view_id)); - } + } else if state.get().hovered_on_thumb == Some(axis) { + state.set(state.get().with_hovered_on_thumb(None)); + cx.notify(Some(view_id)); } // Move thumb position on dragging @@ -587,10 +728,10 @@ impl Element for Scrollbar { let percentage = (if is_vertical { (event.position.y - drag_pos.y - bounds.origin.y) - / (bounds.size.height - thumb_length) + / (bounds.size.height - thumb_size) } else { (event.position.x - drag_pos.x - bounds.origin.x) - / (bounds.size.width - thumb_length - margin_end) + / (bounds.size.width - thumb_size - margin_end) }) .clamp(0., 1.); diff --git a/crates/ui/src/sidebar/footer.rs b/crates/ui/src/sidebar/footer.rs index 1237cbe..aa4b650 100644 --- a/crates/ui/src/sidebar/footer.rs +++ b/crates/ui/src/sidebar/footer.rs @@ -23,6 +23,13 @@ impl SidebarFooter { } } } + +impl Default for SidebarFooter { + fn default() -> Self { + Self::new() + } +} + impl Selectable for SidebarFooter { fn selected(mut self, selected: bool) -> Self { self.selected = selected; @@ -33,6 +40,7 @@ impl Selectable for SidebarFooter { &self.id } } + impl Collapsible for SidebarFooter { fn is_collapsed(&self) -> bool { self.is_collapsed @@ -43,17 +51,21 @@ impl Collapsible for SidebarFooter { self } } + impl ParentElement for SidebarFooter { fn extend(&mut self, elements: impl IntoIterator) { self.base.extend(elements); } } + impl Styled for SidebarFooter { fn style(&mut self) -> &mut gpui::StyleRefinement { self.base.style() } } + impl PopupMenuExt for SidebarFooter {} + impl RenderOnce for SidebarFooter { fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement { h_flex() diff --git a/crates/ui/src/sidebar/group.rs b/crates/ui/src/sidebar/group.rs index 0f22bf9..e336a06 100644 --- a/crates/ui/src/sidebar/group.rs +++ b/crates/ui/src/sidebar/group.rs @@ -33,6 +33,7 @@ impl SidebarGroup { self } } + impl Collapsible for SidebarGroup { fn is_collapsed(&self) -> bool { self.is_collapsed @@ -43,6 +44,7 @@ impl Collapsible for SidebarGroup { self } } + impl RenderOnce for SidebarGroup { fn render(self, cx: &mut WindowContext) -> impl IntoElement { v_flex() diff --git a/crates/ui/src/sidebar/header.rs b/crates/ui/src/sidebar/header.rs index 4841a6c..a1e21fb 100644 --- a/crates/ui/src/sidebar/header.rs +++ b/crates/ui/src/sidebar/header.rs @@ -23,6 +23,13 @@ impl SidebarHeader { } } } + +impl Default for SidebarHeader { + fn default() -> Self { + Self::new() + } +} + impl Selectable for SidebarHeader { fn selected(mut self, selected: bool) -> Self { self.selected = selected; @@ -33,6 +40,7 @@ impl Selectable for SidebarHeader { &self.id } } + impl Collapsible for SidebarHeader { fn is_collapsed(&self) -> bool { self.is_collapsed @@ -43,17 +51,21 @@ impl Collapsible for SidebarHeader { self } } + impl ParentElement for SidebarHeader { fn extend(&mut self, elements: impl IntoIterator) { self.base.extend(elements); } } + impl Styled for SidebarHeader { fn style(&mut self) -> &mut gpui::StyleRefinement { self.base.style() } } + impl PopupMenuExt for SidebarHeader {} + impl RenderOnce for SidebarHeader { fn render(self, cx: &mut gpui::WindowContext) -> impl gpui::IntoElement { h_flex() diff --git a/crates/ui/src/sidebar/menu.rs b/crates/ui/src/sidebar/menu.rs index d262ea2..079e948 100644 --- a/crates/ui/src/sidebar/menu.rs +++ b/crates/ui/src/sidebar/menu.rs @@ -58,6 +58,13 @@ impl SidebarMenu { self } } + +impl Default for SidebarMenu { + fn default() -> Self { + Self::new() + } +} + impl Collapsible for SidebarMenu { fn is_collapsed(&self) -> bool { self.is_collapsed @@ -84,20 +91,22 @@ impl RenderOnce for SidebarMenu { } } +type Handler = Rc; + /// A sidebar menu item #[derive(IntoElement)] enum SidebarMenuItem { Item { icon: Option, label: SharedString, - handler: Rc, + handler: Handler, active: bool, is_collapsed: bool, }, Submenu { icon: Option, label: SharedString, - handler: Rc, + handler: Handler, items: Vec, is_open: bool, is_collapsed: bool, diff --git a/crates/ui/src/sidebar/mod.rs b/crates/ui/src/sidebar/mod.rs index 2090d4b..41031b1 100644 --- a/crates/ui/src/sidebar/mod.rs +++ b/crates/ui/src/sidebar/mod.rs @@ -106,13 +106,15 @@ impl Sidebar { } } +type OnClick = Option>; + /// Sidebar collapse button with Icon. #[derive(IntoElement)] pub struct SidebarToggleButton { btn: Button, is_collapsed: bool, side: Side, - on_click: Option>, + on_click: OnClick, } impl SidebarToggleButton { @@ -158,12 +160,10 @@ impl RenderOnce for SidebarToggleButton { } else { IconName::PanelRightOpen } + } else if self.side.is_left() { + IconName::PanelLeftClose } else { - if self.side.is_left() { - IconName::PanelLeftClose - } else { - IconName::PanelRightClose - } + IconName::PanelRightClose }; self.btn diff --git a/crates/ui/src/skeleton.rs b/crates/ui/src/skeleton.rs index 266db5a..61ab040 100644 --- a/crates/ui/src/skeleton.rs +++ b/crates/ui/src/skeleton.rs @@ -18,6 +18,12 @@ impl Skeleton { } } +impl Default for Skeleton { + fn default() -> Self { + Self::new() + } +} + impl Styled for Skeleton { fn style(&mut self) -> &mut gpui::StyleRefinement { self.base.style() diff --git a/crates/ui/src/styled.rs b/crates/ui/src/styled.rs index b70955a..56b08fa 100644 --- a/crates/ui/src/styled.rs +++ b/crates/ui/src/styled.rs @@ -350,17 +350,17 @@ impl StyleSized for T { } pub trait AxisExt { - fn is_horizontal(self) -> bool; - fn is_vertical(self) -> bool; + fn is_horizontal(&self) -> bool; + fn is_vertical(&self) -> bool; } impl AxisExt for Axis { - fn is_horizontal(self) -> bool { - self == Axis::Horizontal + fn is_horizontal(&self) -> bool { + self == &Axis::Horizontal } - fn is_vertical(self) -> bool { - self == Axis::Vertical + fn is_vertical(&self) -> bool { + self == &Axis::Vertical } } @@ -385,17 +385,11 @@ impl Display for Placement { impl Placement { pub fn is_horizontal(&self) -> bool { - match self { - Placement::Left | Placement::Right => true, - _ => false, - } + matches!(self, Placement::Left | Placement::Right) } pub fn is_vertical(&self) -> bool { - match self { - Placement::Top | Placement::Bottom => true, - _ => false, - } + matches!(self, Placement::Top | Placement::Bottom) } pub fn axis(&self) -> Axis { diff --git a/crates/ui/src/svg_img.rs b/crates/ui/src/svg_img.rs index 94f8337..550e536 100644 --- a/crates/ui/src/svg_img.rs +++ b/crates/ui/src/svg_img.rs @@ -184,6 +184,12 @@ impl SvgImg { } } +impl Default for SvgImg { + fn default() -> Self { + Self::new() + } +} + impl IntoElement for SvgImg { type Element = Self; diff --git a/crates/ui/src/switch.rs b/crates/ui/src/switch.rs index 3f5a46a..11a6427 100644 --- a/crates/ui/src/switch.rs +++ b/crates/ui/src/switch.rs @@ -6,13 +6,15 @@ use gpui::{ }; use std::{cell::RefCell, rc::Rc, time::Duration}; +type OnClick = Option>; + pub struct Switch { id: ElementId, checked: bool, disabled: bool, label: Option, label_side: Side, - on_click: Option>, + on_click: OnClick, size: Size, } diff --git a/crates/ui/src/tab.rs b/crates/ui/src/tab.rs index 1906121..bff7e60 100644 --- a/crates/ui/src/tab.rs +++ b/crates/ui/src/tab.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] mod tab; mod tab_bar; diff --git a/crates/ui/src/theme.rs b/crates/ui/src/theme.rs index fb7d287..59405f8 100644 --- a/crates/ui/src/theme.rs +++ b/crates/ui/src/theme.rs @@ -1,9 +1,10 @@ -use std::ops::{Deref, DerefMut}; - use gpui::{ hsla, point, AppContext, BoxShadow, Global, Hsla, ModelContext, Pixels, SharedString, ViewContext, WindowAppearance, WindowContext, }; +use std::ops::{Deref, DerefMut}; + +use crate::scroll::ScrollbarShow; pub fn init(cx: &mut AppContext) { Theme::sync_system_appearance(cx) @@ -117,15 +118,19 @@ impl Colorize for Hsla { } /// Return a new color with the lightness increased by the given factor. + /// + /// factor range: 0.0 .. 1.0 fn lighten(&self, factor: f32) -> Hsla { - let l = self.l + (1.0 - self.l) * factor.clamp(0.0, 1.0).min(1.0); + let l = self.l * (1.0 + factor.clamp(0.0, 1.0)); Hsla { l, ..*self } } /// Return a new color with the darkness increased by the given factor. + /// + /// factor range: 0.0 .. 1.0 fn darken(&self, factor: f32) -> Hsla { - let l = self.l * (1.0 - factor.clamp(0.0, 1.0).min(1.0)); + let l = self.l * (1.0 - factor.clamp(0.0, 1.0)); Hsla { l, ..*self } } @@ -181,6 +186,7 @@ pub struct ThemeColor { pub ring: Hsla, pub scrollbar: Hsla, pub scrollbar_thumb: Hsla, + pub scrollbar_thumb_hover: Hsla, pub secondary: Hsla, pub secondary_active: Hsla, pub secondary_foreground: Hsla, @@ -253,8 +259,9 @@ impl ThemeColor { primary_hover: hsl(223.0, 5.9, 15.0), progress_bar: hsl(223.0, 5.9, 10.0), ring: hsl(240.0, 5.9, 65.0), - scrollbar: hsl(0., 0., 97.).opacity(0.3), - scrollbar_thumb: hsl(0., 0., 69.), + scrollbar: hsl(0., 0., 97.).opacity(0.75), + scrollbar_thumb: hsl(0., 0., 69.).opacity(0.9), + scrollbar_thumb_hover: hsl(0., 0., 59.), secondary: hsl(240.0, 5.9, 96.9), secondary_active: hsl(240.0, 5.9, 93.), secondary_foreground: hsl(240.0, 59.0, 10.), @@ -327,8 +334,9 @@ impl ThemeColor { primary_hover: hsl(223.0, 0.0, 90.0), progress_bar: hsl(223.0, 0.0, 98.0), ring: hsl(240.0, 4.9, 83.9), - scrollbar: hsl(240., 1., 15.).opacity(0.3), - scrollbar_thumb: hsl(0., 0., 68.), + scrollbar: hsl(240., 1., 15.).opacity(0.75), + scrollbar_thumb: hsl(0., 0., 48.).opacity(0.9), + scrollbar_thumb_hover: hsl(0., 0., 68.), secondary: hsl(240.0, 0., 13.0), secondary_active: hsl(240.0, 0., 10.), secondary_foreground: hsl(0.0, 0.0, 78.0), @@ -373,6 +381,8 @@ pub struct Theme { pub radius: f32, pub shadow: bool, pub transparent: Hsla, + /// Show the scrollbar mode, default: Scrolling + pub scrollbar_show: ScrollbarShow, } impl Deref for Theme { @@ -434,6 +444,7 @@ impl Theme { // self.selection = self.selection.apply(mask_color); self.scrollbar = self.scrollbar.apply(mask_color); self.scrollbar_thumb = self.scrollbar_thumb.apply(mask_color); + self.scrollbar_thumb_hover = self.scrollbar_thumb_hover.apply(mask_color); self.panel = self.panel.apply(mask_color); self.drag_border = self.drag_border.apply(mask_color); self.drop_target = self.drop_target.apply(mask_color); @@ -518,6 +529,7 @@ impl From for Theme { }, radius: 4.0, shadow: true, + scrollbar_show: ScrollbarShow::default(), colors, } } @@ -535,3 +547,28 @@ impl ThemeMode { matches!(self, Self::Dark) } } + +#[cfg(test)] +mod tests { + use crate::theme::Colorize as _; + + #[test] + fn test_lighten() { + let color = super::hsl(240.0, 5.0, 30.0); + let color = color.lighten(0.5); + assert_eq!(color.l, 0.45000002); + let color = color.lighten(0.5); + assert_eq!(color.l, 0.675); + let color = color.lighten(0.1); + assert_eq!(color.l, 0.7425); + } + + #[test] + fn test_darken() { + let color = super::hsl(240.0, 5.0, 96.0); + let color = color.darken(0.5); + assert_eq!(color.l, 0.48); + let color = color.darken(0.5); + assert_eq!(color.l, 0.24); + } +} diff --git a/crates/ui/src/title_bar.rs b/crates/ui/src/title_bar.rs index f2c3d2f..8643609 100644 --- a/crates/ui/src/title_bar.rs +++ b/crates/ui/src/title_bar.rs @@ -13,6 +13,8 @@ const TITLE_BAR_LEFT_PADDING: Pixels = px(80.); #[cfg(not(target_os = "macos"))] const TITLE_BAR_LEFT_PADDING: Pixels = px(12.); +type OnCloseWindow = Option>>; + /// TitleBar used to customize the appearance of the title bar. /// /// We can put some elements inside the title bar. @@ -20,7 +22,7 @@ const TITLE_BAR_LEFT_PADDING: Pixels = px(12.); pub struct TitleBar { base: Stateful
, children: Vec, - on_close_window: Option>>, + on_close_window: OnCloseWindow, } impl TitleBar { @@ -45,6 +47,12 @@ impl TitleBar { } } +impl Default for TitleBar { + fn default() -> Self { + Self::new() + } +} + // The Windows control buttons have a fixed width of 35px. // // We don't need implementation the click event for the control buttons. @@ -54,9 +62,7 @@ enum ControlIcon { Minimize, Restore, Maximize, - Close { - on_close_window: Option>>, - }, + Close { on_close_window: OnCloseWindow }, } impl ControlIcon { @@ -72,7 +78,7 @@ impl ControlIcon { Self::Maximize } - fn close(on_close_window: Option>>) -> Self { + fn close(on_close_window: OnCloseWindow) -> Self { Self::Close { on_close_window } } @@ -173,7 +179,7 @@ impl RenderOnce for ControlIcon { #[derive(IntoElement)] struct WindowControls { - on_close_window: Option>>, + on_close_window: OnCloseWindow, } impl RenderOnce for WindowControls { @@ -230,9 +236,9 @@ impl RenderOnce for TitleBar { .items_center() .justify_between() .h(HEIGHT) - .border_b_1() - .border_color(cx.theme().title_bar_border) .bg(cx.theme().title_bar) + .border_b_1() + .border_color(cx.theme().title_bar_border.opacity(0.7)) .when(cx.is_fullscreen(), |this| this.pl(px(12.))) .on_double_click(|_, cx| cx.zoom_window()) .child( @@ -286,13 +292,18 @@ impl Element for TitleBarElement { _: Option<&gpui::GlobalElementId>, cx: &mut WindowContext, ) -> (gpui::LayoutId, Self::RequestLayoutState) { - let mut style = Style::default(); - 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 { + flex_grow: 1.0, + flex_shrink: 1.0, + size: gpui::Size { + width: relative(1.).into(), + height: relative(1.).into(), + }, + ..Default::default() + }; let id = cx.request_layout(style, []); + (id, ()) } diff --git a/crates/ui/src/tooltip.rs b/crates/ui/src/tooltip.rs index 7866a70..2ae6a65 100644 --- a/crates/ui/src/tooltip.rs +++ b/crates/ui/src/tooltip.rs @@ -10,6 +10,7 @@ pub struct Tooltip { } impl Tooltip { + #[allow(clippy::new_ret_no_self)] pub fn new(text: impl Into, cx: &mut WindowContext) -> AnyView { cx.new_view(|_| Self { text: text.into() }).into() }