From 29326215ccf13e1d1e25bf3bf5ada007856bff69 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 03:48:08 +0100 Subject: Simplify theming for `Container` widget --- widget/src/pick_list.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 1f20e2bc..4d6ca695 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -64,7 +64,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet, + + container::Style, ::Style: From<::Style>, Renderer: text::Renderer, { @@ -179,7 +179,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet, + + container::Style, ::Style: From<::Style>, Renderer: text::Renderer + 'a, { @@ -320,7 +320,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet + + container::Style + 'a, ::Style: From<::Style>, Renderer: text::Renderer + 'a, @@ -630,7 +630,7 @@ where Theme: StyleSheet + scrollable::StyleSheet + menu::StyleSheet - + container::StyleSheet + + container::Style + 'a, ::Style: From<::Style>, Renderer: text::Renderer + 'a, -- cgit From d681aaa57e3106cf0ce90b74ade040ca7bb97832 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 04:42:25 +0100 Subject: Simplify theming for `Scrollable` widget --- widget/src/pick_list.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 4d6ca695..b75baa74 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -62,7 +62,7 @@ where V: Borrow + 'a, Message: Clone, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style, ::Style: From<::Style>, @@ -177,7 +177,7 @@ where V: Borrow, Message: Clone + 'a, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style, ::Style: From<::Style>, @@ -318,7 +318,7 @@ where V: Borrow + 'a, Message: Clone + 'a, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style + 'a, @@ -628,7 +628,7 @@ where T: Clone + ToString, Message: 'a, Theme: StyleSheet - + scrollable::StyleSheet + + scrollable::Tradition + menu::StyleSheet + container::Style + 'a, -- cgit From 704ec9cb5cdc1d44f2df2f15de700b0af330b1d7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Mar 2024 15:53:59 +0100 Subject: Simplify theming for `TextInput` widget --- widget/src/pick_list.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index b75baa74..aeb0f246 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -61,10 +61,7 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone, - Theme: StyleSheet - + scrollable::Tradition - + menu::StyleSheet - + container::Style, + Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, ::Style: From<::Style>, Renderer: text::Renderer, { @@ -176,10 +173,7 @@ where L: Borrow<[T]>, V: Borrow, Message: Clone + 'a, - Theme: StyleSheet - + scrollable::Tradition - + menu::StyleSheet - + container::Style, + Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, ::Style: From<::Style>, Renderer: text::Renderer + 'a, { @@ -318,7 +312,7 @@ where V: Borrow + 'a, Message: Clone + 'a, Theme: StyleSheet - + scrollable::Tradition + + scrollable::Style + menu::StyleSheet + container::Style + 'a, @@ -628,7 +622,7 @@ where T: Clone + ToString, Message: 'a, Theme: StyleSheet - + scrollable::Tradition + + scrollable::Style + menu::StyleSheet + container::Style + 'a, -- cgit From 597a41cea73f078eda04eb3ff40cfda5d37d6135 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 17:08:28 +0100 Subject: Simplify theming for `PickList`, `ComboBox`, and `Menu` widgets --- widget/src/pick_list.rs | 449 ++++++++++++++++++++++++------------------------ 1 file changed, 226 insertions(+), 223 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index aeb0f246..546bf294 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -11,16 +11,15 @@ use crate::core::text::{self, Paragraph as _, Text}; use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ - Clipboard, Element, Layout, Length, Padding, Pixels, Point, Rectangle, - Shell, Size, Vector, Widget, + Background, Border, Clipboard, Color, Element, Layout, Length, Padding, + Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; use crate::overlay::menu::{self, Menu}; use crate::scrollable; +use crate::style::Theme; use std::borrow::Borrow; -pub use crate::style::pick_list::{Appearance, StyleSheet}; - /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] pub struct PickList< @@ -35,7 +34,6 @@ pub struct PickList< T: ToString + PartialEq + Clone, L: Borrow<[T]> + 'a, V: Borrow + 'a, - Theme: StyleSheet, Renderer: text::Renderer, { on_select: Box Message + 'a>, @@ -51,7 +49,7 @@ pub struct PickList< text_shaping: text::Shaping, font: Option, handle: Handle, - style: Theme::Style, + style: Style, } impl<'a, T, L, V, Message, Theme, Renderer> @@ -61,8 +59,6 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone, - Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, - ::Style: From<::Style>, Renderer: text::Renderer, { /// The default padding of a [`PickList`]. @@ -74,7 +70,10 @@ where options: L, selected: Option, on_select: impl Fn(T) -> Message + 'a, - ) -> Self { + ) -> Self + where + Style: Default, + { Self { on_select: Box::new(on_select), on_open: None, @@ -89,7 +88,7 @@ where text_shaping: text::Shaping::Basic, font: None, handle: Handle::default(), - style: Default::default(), + style: Style::default(), } } @@ -157,10 +156,7 @@ where } /// Sets the style of the [`PickList`]. - pub fn style( - mut self, - style: impl Into<::Style>, - ) -> Self { + pub fn style(mut self, style: impl Into>) -> Self { self.style = style.into(); self } @@ -173,8 +169,7 @@ where L: Borrow<[T]>, V: Borrow, Message: Clone + 'a, - Theme: StyleSheet + scrollable::Style + menu::StyleSheet + container::Style, - ::Style: From<::Style>, + Theme: scrollable::Style + container::Style, Renderer: text::Renderer + 'a, { fn tag(&self) -> tree::Tag { @@ -260,23 +255,124 @@ where viewport: &Rectangle, ) { let font = self.font.unwrap_or_else(|| renderer.default_font()); - draw( - renderer, - theme, - layout, - cursor, - self.padding, - self.text_size, - self.text_line_height, - self.text_shaping, - font, - self.placeholder.as_deref(), - self.selected.as_ref().map(Borrow::borrow), - &self.handle, - &self.style, - || tree.state.downcast_ref::>(), - viewport, + let selected = self.selected.as_ref().map(Borrow::borrow); + let state = tree.state.downcast_ref::>(); + + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + let is_selected = selected.is_some(); + + let status = if state.is_open { + Status::Opened + } else if is_mouse_over { + Status::Hovered + } else { + Status::Active + }; + + let appearance = (self.style.pick_list)(theme, status); + + renderer.fill_quad( + renderer::Quad { + bounds, + border: appearance.border, + ..renderer::Quad::default() + }, + appearance.background, ); + + let handle = match &self.handle { + Handle::Arrow { size } => Some(( + Renderer::ICON_FONT, + Renderer::ARROW_DOWN_ICON, + *size, + text::LineHeight::default(), + text::Shaping::Basic, + )), + Handle::Static(Icon { + font, + code_point, + size, + line_height, + shaping, + }) => Some((*font, *code_point, *size, *line_height, *shaping)), + Handle::Dynamic { open, closed } => { + if state.is_open { + Some(( + open.font, + open.code_point, + open.size, + open.line_height, + open.shaping, + )) + } else { + Some(( + closed.font, + closed.code_point, + closed.size, + closed.line_height, + closed.shaping, + )) + } + } + Handle::None => None, + }; + + if let Some((font, code_point, size, line_height, shaping)) = handle { + let size = size.unwrap_or_else(|| renderer.default_size()); + + renderer.fill_text( + Text { + content: &code_point.to_string(), + size, + line_height, + font, + bounds: Size::new( + bounds.width, + f32::from(line_height.to_absolute(size)), + ), + horizontal_alignment: alignment::Horizontal::Right, + vertical_alignment: alignment::Vertical::Center, + shaping, + }, + Point::new( + bounds.x + bounds.width - self.padding.horizontal(), + bounds.center_y(), + ), + appearance.handle_color, + *viewport, + ); + } + + let label = selected.map(ToString::to_string); + + if let Some(label) = label.as_deref().or(self.placeholder.as_deref()) { + let text_size = + self.text_size.unwrap_or_else(|| renderer.default_size()); + + renderer.fill_text( + Text { + content: label, + size: text_size, + line_height: self.text_line_height, + font, + bounds: Size::new( + bounds.width - self.padding.horizontal(), + f32::from(self.text_line_height.to_absolute(text_size)), + ), + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, + }, + Point::new(bounds.x + self.padding.left, bounds.center_y()), + if is_selected { + appearance.text_color + } else { + appearance.placeholder_color + }, + *viewport, + ); + } } fn overlay<'b>( @@ -287,19 +383,38 @@ where translation: Vector, ) -> Option> { let state = tree.state.downcast_mut::>(); + let font = self.font.unwrap_or_else(|| renderer.default_font()); - overlay( - layout, - translation, - state, - self.padding, - self.text_size, - self.text_shaping, - self.font.unwrap_or_else(|| renderer.default_font()), - self.options.borrow(), - &self.on_select, - self.style.clone(), - ) + if state.is_open { + let bounds = layout.bounds(); + + let on_select = &self.on_select; + + let mut menu = Menu::with_style( + &mut state.menu, + self.options.borrow(), + &mut state.hovered_option, + |option| { + state.is_open = false; + + (on_select)(option) + }, + None, + self.style.menu, + ) + .width(bounds.width) + .padding(self.padding) + .font(font) + .text_shaping(self.text_shaping); + + if let Some(text_size) = self.text_size { + menu = menu.text_size(text_size); + } + + Some(menu.overlay(layout.position() + translation, bounds.height)) + } else { + None + } } } @@ -311,12 +426,7 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone + 'a, - Theme: StyleSheet - + scrollable::Style - + menu::StyleSheet - + container::Style - + 'a, - ::Style: From<::Style>, + Theme: scrollable::Style + container::Style + 'a, Renderer: text::Renderer + 'a, { fn from( @@ -605,190 +715,83 @@ pub fn mouse_interaction( } } -/// Returns the current overlay of a [`PickList`]. -pub fn overlay<'a, T, Message, Theme, Renderer>( - layout: Layout<'_>, - translation: Vector, - state: &'a mut State, - padding: Padding, - text_size: Option, - text_shaping: text::Shaping, - font: Renderer::Font, - options: &'a [T], - on_selected: &'a dyn Fn(T) -> Message, - style: ::Style, -) -> Option> -where - T: Clone + ToString, - Message: 'a, - Theme: StyleSheet - + scrollable::Style - + menu::StyleSheet - + container::Style - + 'a, - ::Style: From<::Style>, - Renderer: text::Renderer + 'a, -{ - if state.is_open { - let bounds = layout.bounds(); +/// The possible status of a [`PickList`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Status { + /// The [`PickList`] can be interacted with. + Active, + /// The [`PickList`] is being hovered. + Hovered, + /// The [`PickList`] is open. + Opened, +} - let mut menu = Menu::new( - &mut state.menu, - options, - &mut state.hovered_option, - |option| { - state.is_open = false; +/// The appearance of a pick list. +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The text [`Color`] of the pick list. + pub text_color: Color, + /// The placeholder [`Color`] of the pick list. + pub placeholder_color: Color, + /// The handle [`Color`] of the pick list. + pub handle_color: Color, + /// The [`Background`] of the pick list. + pub background: Background, + /// The [`Border`] of the pick list. + pub border: Border, +} - (on_selected)(option) - }, - None, - ) - .width(bounds.width) - .padding(padding) - .font(font) - .text_shaping(text_shaping) - .style(style); - - if let Some(text_size) = text_size { - menu = menu.text_size(text_size); - } +/// The different styles of a [`PickList`]. +#[derive(Debug, PartialEq, Eq)] +pub struct Style { + /// The style of the [`PickList`] itself. + pub pick_list: fn(&Theme, Status) -> Appearance, - Some(menu.overlay(layout.position() + translation, bounds.height)) - } else { - None - } + /// The style of the [`Menu`] of the pick list. + pub menu: menu::Style, } -/// Draws a [`PickList`]. -pub fn draw<'a, T, Theme, Renderer>( - renderer: &mut Renderer, - theme: &Theme, - layout: Layout<'_>, - cursor: mouse::Cursor, - padding: Padding, - text_size: Option, - text_line_height: text::LineHeight, - text_shaping: text::Shaping, - font: Renderer::Font, - placeholder: Option<&str>, - selected: Option<&T>, - handle: &Handle, - style: &Theme::Style, - state: impl FnOnce() -> &'a State, - viewport: &Rectangle, -) where - Renderer: text::Renderer, - Theme: StyleSheet, - T: ToString + 'a, -{ - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - let is_selected = selected.is_some(); +impl Clone for Style { + fn clone(&self) -> Self { + *self + } +} - let style = if is_mouse_over { - theme.hovered(style) - } else { - theme.active(style) - }; +impl Copy for Style {} - renderer.fill_quad( - renderer::Quad { - bounds, - border: style.border, - ..renderer::Quad::default() - }, - style.background, - ); - - let handle = match handle { - Handle::Arrow { size } => Some(( - Renderer::ICON_FONT, - Renderer::ARROW_DOWN_ICON, - *size, - text::LineHeight::default(), - text::Shaping::Basic, - )), - Handle::Static(Icon { - font, - code_point, - size, - line_height, - shaping, - }) => Some((*font, *code_point, *size, *line_height, *shaping)), - Handle::Dynamic { open, closed } => { - if state().is_open { - Some(( - open.font, - open.code_point, - open.size, - open.line_height, - open.shaping, - )) - } else { - Some(( - closed.font, - closed.code_point, - closed.size, - closed.line_height, - closed.shaping, - )) - } +impl Default for Style { + fn default() -> Self { + Self { + pick_list: default, + menu: menu::Style::default(), } - Handle::None => None, - }; - - if let Some((font, code_point, size, line_height, shaping)) = handle { - let size = size.unwrap_or_else(|| renderer.default_size()); - - renderer.fill_text( - Text { - content: &code_point.to_string(), - size, - line_height, - font, - bounds: Size::new( - bounds.width, - f32::from(line_height.to_absolute(size)), - ), - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Center, - shaping, - }, - Point::new( - bounds.x + bounds.width - padding.horizontal(), - bounds.center_y(), - ), - style.handle_color, - *viewport, - ); } +} - let label = selected.map(ToString::to_string); - - if let Some(label) = label.as_deref().or(placeholder) { - let text_size = text_size.unwrap_or_else(|| renderer.default_size()); +/// The default style of a [`PickList`]. +pub fn default(theme: &Theme, status: Status) -> Appearance { + let palette = theme.extended_palette(); + + let active = Appearance { + text_color: palette.background.weak.text, + background: palette.background.weak.color.into(), + placeholder_color: palette.background.strong.color, + handle_color: palette.background.weak.text, + border: Border { + radius: 2.0.into(), + width: 1.0, + color: palette.background.strong.color, + }, + }; - renderer.fill_text( - Text { - content: label, - size: text_size, - line_height: text_line_height, - font, - bounds: Size::new( - bounds.width - padding.horizontal(), - f32::from(text_line_height.to_absolute(text_size)), - ), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text_shaping, - }, - Point::new(bounds.x + padding.left, bounds.center_y()), - if is_selected { - style.text_color - } else { - style.placeholder_color + match status { + Status::Active => active, + Status::Hovered | Status::Opened => Appearance { + border: Border { + color: palette.primary.strong.color, + ..active.border }, - *viewport, - ); + ..active + }, } } -- cgit From 34e7c6593a9e0f56cee5db18b7258717cf6bc11b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 6 Mar 2024 20:30:58 +0100 Subject: Use `Style` struct pattern instead of trait for all widgets --- widget/src/pick_list.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 546bf294..49daa89c 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -1,5 +1,4 @@ //! Display a dropdown list of selectable values. -use crate::container; use crate::core::alignment; use crate::core::event::{self, Event}; use crate::core::keyboard; @@ -15,7 +14,6 @@ use crate::core::{ Pixels, Point, Rectangle, Shell, Size, Vector, Widget, }; use crate::overlay::menu::{self, Menu}; -use crate::scrollable; use crate::style::Theme; use std::borrow::Borrow; @@ -169,7 +167,6 @@ where L: Borrow<[T]>, V: Borrow, Message: Clone + 'a, - Theme: scrollable::Style + container::Style, Renderer: text::Renderer + 'a, { fn tag(&self) -> tree::Tag { @@ -426,7 +423,7 @@ where L: Borrow<[T]> + 'a, V: Borrow + 'a, Message: Clone + 'a, - Theme: scrollable::Style + container::Style + 'a, + Theme: 'a, Renderer: text::Renderer + 'a, { fn from( -- cgit From 905f2160e6eb7504f52d9bd62c7bfa42c8ec2902 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 00:14:41 +0100 Subject: Move `Theme` type to `iced_core` --- widget/src/pick_list.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 49daa89c..649daafe 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -11,10 +11,9 @@ use crate::core::touch; use crate::core::widget::tree::{self, Tree}; use crate::core::{ Background, Border, Clipboard, Color, Element, Layout, Length, Padding, - Pixels, Point, Rectangle, Shell, Size, Vector, Widget, + Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, }; use crate::overlay::menu::{self, Menu}; -use crate::style::Theme; use std::borrow::Borrow; -- cgit From 833538ee7f3a60a839304762dfc29b0881d19094 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 7 Mar 2024 20:11:32 +0100 Subject: Leverage `DefaultStyle` traits instead of `Default` --- widget/src/pick_list.rs | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 649daafe..cfeabbb7 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -69,7 +69,7 @@ where on_select: impl Fn(T) -> Message + 'a, ) -> Self where - Style: Default, + Theme: DefaultStyle, { Self { on_select: Box::new(on_select), @@ -85,7 +85,7 @@ where text_shaping: text::Shaping::Basic, font: None, handle: Handle::default(), - style: Style::default(), + style: Theme::default_style(), } } @@ -266,7 +266,7 @@ where Status::Active }; - let appearance = (self.style.pick_list)(theme, status); + let appearance = (self.style.field)(theme, status); renderer.fill_quad( renderer::Quad { @@ -737,16 +737,24 @@ pub struct Appearance { pub border: Border, } -/// The different styles of a [`PickList`]. +/// The styles of the different parts of a [`PickList`]. #[derive(Debug, PartialEq, Eq)] pub struct Style { /// The style of the [`PickList`] itself. - pub pick_list: fn(&Theme, Status) -> Appearance, + pub field: fn(&Theme, Status) -> Appearance, /// The style of the [`Menu`] of the pick list. pub menu: menu::Style, } +impl Style { + /// The default style of a [`PickList`] with the built-in [`Theme`]. + pub const DEFAULT: Self = Self { + field: default, + menu: menu::Style::::DEFAULT, + }; +} + impl Clone for Style { fn clone(&self) -> Self { *self @@ -755,16 +763,19 @@ impl Clone for Style { impl Copy for Style {} -impl Default for Style { - fn default() -> Self { - Self { - pick_list: default, - menu: menu::Style::default(), - } +/// The default style of a [`PickList`]. +pub trait DefaultStyle: Sized { + /// Returns the default style of a [`PickList`]. + fn default_style() -> Style; +} + +impl DefaultStyle for Theme { + fn default_style() -> Style { + Style::::DEFAULT } } -/// The default style of a [`PickList`]. +/// The default style of the field of a [`PickList`]. pub fn default(theme: &Theme, status: Status) -> Appearance { let palette = theme.extended_palette(); -- cgit From 1db823b4c528441627dd075d989fca2fa0a44346 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:36:37 +0100 Subject: Make `PickList` padding consistent with `Button` --- widget/src/pick_list.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index cfeabbb7..d98909fa 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -58,9 +58,6 @@ where Message: Clone, Renderer: text::Renderer, { - /// The default padding of a [`PickList`]. - pub const DEFAULT_PADDING: Padding = Padding::new(5.0); - /// Creates a new [`PickList`] with the given list of options, the current /// selected value, and the message to produce when an option is selected. pub fn new( @@ -79,7 +76,7 @@ where placeholder: None, selected, width: Length::Shrink, - padding: Self::DEFAULT_PADDING, + padding: crate::button::DEFAULT_PADDING, text_size: None, text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, -- cgit From f316755cdcaf632d91a9adbc80e93fda744fd16e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 00:37:10 +0100 Subject: Fix handle of `PickList` being rendered too much to the left --- widget/src/pick_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index d98909fa..b8fc6079 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -329,7 +329,7 @@ where shaping, }, Point::new( - bounds.x + bounds.width - self.padding.horizontal(), + bounds.x + bounds.width - self.padding.right, bounds.center_y(), ), appearance.handle_color, -- cgit From 288025f5143f4e3f8bc5af36e86f7afa7f07a4c7 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 8 Mar 2024 13:34:36 +0100 Subject: Inline helper functions in `widget` modules --- widget/src/pick_list.rs | 405 +++++++++++++++++++++--------------------------- 1 file changed, 173 insertions(+), 232 deletions(-) (limited to 'widget/src/pick_list.rs') diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index b8fc6079..beb4e0c1 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -16,6 +16,7 @@ use crate::core::{ use crate::overlay::menu::{self, Menu}; use std::borrow::Borrow; +use std::f32; /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] @@ -186,19 +187,77 @@ where renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { - layout( - tree.state.downcast_mut::>(), - renderer, - limits, - self.width, - self.padding, - self.text_size, - self.text_line_height, - self.text_shaping, - self.font, - self.placeholder.as_deref(), - self.options.borrow(), - ) + let state = tree.state.downcast_mut::>(); + + let font = self.font.unwrap_or_else(|| renderer.default_font()); + let text_size = + self.text_size.unwrap_or_else(|| renderer.default_size()); + let options = self.options.borrow(); + + state.options.resize_with(options.len(), Default::default); + + let option_text = Text { + content: "", + bounds: Size::new( + f32::INFINITY, + self.text_line_height.to_absolute(text_size).into(), + ), + size: text_size, + line_height: self.text_line_height, + font, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + shaping: self.text_shaping, + }; + + for (option, paragraph) in options.iter().zip(state.options.iter_mut()) + { + let label = option.to_string(); + + paragraph.update(Text { + content: &label, + ..option_text + }); + } + + if let Some(placeholder) = &self.placeholder { + state.placeholder.update(Text { + content: placeholder, + ..option_text + }); + } + + let max_width = match self.width { + Length::Shrink => { + let labels_width = + state.options.iter().fold(0.0, |width, paragraph| { + f32::max(width, paragraph.min_width()) + }); + + labels_width.max( + self.placeholder + .as_ref() + .map(|_| state.placeholder.min_width()) + .unwrap_or(0.0), + ) + } + _ => 0.0, + }; + + let size = { + let intrinsic = Size::new( + max_width + text_size.0 + self.padding.left, + f32::from(self.text_line_height.to_absolute(text_size)), + ); + + limits + .width(self.width) + .shrink(self.padding) + .resolve(self.width, Length::Shrink, intrinsic) + .expand(self.padding) + }; + + layout::Node::new(size) } fn on_event( @@ -212,18 +271,98 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - update( - event, - layout, - cursor, - shell, - self.on_select.as_ref(), - self.on_open.as_ref(), - self.on_close.as_ref(), - self.selected.as_ref().map(Borrow::borrow), - self.options.borrow(), - || tree.state.downcast_mut::>(), - ) + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + let state = + tree.state.downcast_mut::>(); + + if state.is_open { + // Event wasn't processed by overlay, so cursor was clicked either outside its + // bounds or on the drop-down, either way we close the overlay. + state.is_open = false; + + if let Some(on_close) = &self.on_close { + shell.publish(on_close.clone()); + } + + event::Status::Captured + } else if cursor.is_over(layout.bounds()) { + let selected = self.selected.as_ref().map(Borrow::borrow); + + state.is_open = true; + state.hovered_option = self + .options + .borrow() + .iter() + .position(|option| Some(option) == selected); + + if let Some(on_open) = &self.on_open { + shell.publish(on_open.clone()); + } + + event::Status::Captured + } else { + event::Status::Ignored + } + } + Event::Mouse(mouse::Event::WheelScrolled { + delta: mouse::ScrollDelta::Lines { y, .. }, + }) => { + let state = + tree.state.downcast_mut::>(); + + if state.keyboard_modifiers.command() + && cursor.is_over(layout.bounds()) + && !state.is_open + { + fn find_next<'a, T: PartialEq>( + selected: &'a T, + mut options: impl Iterator, + ) -> Option<&'a T> { + let _ = options.find(|&option| option == selected); + + options.next() + } + + let options = self.options.borrow(); + let selected = self.selected.as_ref().map(Borrow::borrow); + + let next_option = if y < 0.0 { + if let Some(selected) = selected { + find_next(selected, options.iter()) + } else { + options.first() + } + } else if y > 0.0 { + if let Some(selected) = selected { + find_next(selected, options.iter().rev()) + } else { + options.last() + } + } else { + None + }; + + if let Some(next_option) = next_option { + shell.publish((self.on_select)(next_option.clone())); + } + + event::Status::Captured + } else { + event::Status::Ignored + } + } + Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { + let state = + tree.state.downcast_mut::>(); + + state.keyboard_modifiers = modifiers; + + event::Status::Ignored + } + _ => event::Status::Ignored, + } } fn mouse_interaction( @@ -234,7 +373,14 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - mouse_interaction(layout, cursor) + let bounds = layout.bounds(); + let is_mouse_over = cursor.is_over(bounds); + + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } } fn draw( @@ -429,9 +575,8 @@ where } } -/// The state of a [`PickList`]. #[derive(Debug)] -pub struct State { +struct State { menu: menu::State, keyboard_modifiers: keyboard::Modifiers, is_open: bool, @@ -504,210 +649,6 @@ pub struct Icon { pub shaping: text::Shaping, } -/// Computes the layout of a [`PickList`]. -pub fn layout( - state: &mut State, - renderer: &Renderer, - limits: &layout::Limits, - width: Length, - padding: Padding, - text_size: Option, - text_line_height: text::LineHeight, - text_shaping: text::Shaping, - font: Option, - placeholder: Option<&str>, - options: &[T], -) -> layout::Node -where - Renderer: text::Renderer, - T: ToString, -{ - use std::f32; - - let font = font.unwrap_or_else(|| renderer.default_font()); - let text_size = text_size.unwrap_or_else(|| renderer.default_size()); - - state.options.resize_with(options.len(), Default::default); - - let option_text = Text { - content: "", - bounds: Size::new( - f32::INFINITY, - text_line_height.to_absolute(text_size).into(), - ), - size: text_size, - line_height: text_line_height, - font, - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Center, - shaping: text_shaping, - }; - - for (option, paragraph) in options.iter().zip(state.options.iter_mut()) { - let label = option.to_string(); - - paragraph.update(Text { - content: &label, - ..option_text - }); - } - - if let Some(placeholder) = placeholder { - state.placeholder.update(Text { - content: placeholder, - ..option_text - }); - } - - let max_width = match width { - Length::Shrink => { - let labels_width = - state.options.iter().fold(0.0, |width, paragraph| { - f32::max(width, paragraph.min_width()) - }); - - labels_width.max( - placeholder - .map(|_| state.placeholder.min_width()) - .unwrap_or(0.0), - ) - } - _ => 0.0, - }; - - let size = { - let intrinsic = Size::new( - max_width + text_size.0 + padding.left, - f32::from(text_line_height.to_absolute(text_size)), - ); - - limits - .width(width) - .shrink(padding) - .resolve(width, Length::Shrink, intrinsic) - .expand(padding) - }; - - layout::Node::new(size) -} - -/// Processes an [`Event`] and updates the [`State`] of a [`PickList`] -/// accordingly. -pub fn update<'a, T, P, Message>( - event: Event, - layout: Layout<'_>, - cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - on_select: &dyn Fn(T) -> Message, - on_open: Option<&Message>, - on_close: Option<&Message>, - selected: Option<&T>, - options: &[T], - state: impl FnOnce() -> &'a mut State

, -) -> event::Status -where - T: PartialEq + Clone + 'a, - P: text::Paragraph + 'a, - Message: Clone, -{ - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = state(); - - if state.is_open { - // Event wasn't processed by overlay, so cursor was clicked either outside it's - // bounds or on the drop-down, either way we close the overlay. - state.is_open = false; - - if let Some(on_close) = on_close { - shell.publish(on_close.clone()); - } - - event::Status::Captured - } else if cursor.is_over(layout.bounds()) { - state.is_open = true; - state.hovered_option = - options.iter().position(|option| Some(option) == selected); - - if let Some(on_open) = on_open { - shell.publish(on_open.clone()); - } - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Mouse(mouse::Event::WheelScrolled { - delta: mouse::ScrollDelta::Lines { y, .. }, - }) => { - let state = state(); - - if state.keyboard_modifiers.command() - && cursor.is_over(layout.bounds()) - && !state.is_open - { - fn find_next<'a, T: PartialEq>( - selected: &'a T, - mut options: impl Iterator, - ) -> Option<&'a T> { - let _ = options.find(|&option| option == selected); - - options.next() - } - - let next_option = if y < 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter()) - } else { - options.first() - } - } else if y > 0.0 { - if let Some(selected) = selected { - find_next(selected, options.iter().rev()) - } else { - options.last() - } - } else { - None - }; - - if let Some(next_option) = next_option { - shell.publish((on_select)(next_option.clone())); - } - - event::Status::Captured - } else { - event::Status::Ignored - } - } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = state(); - - state.keyboard_modifiers = modifiers; - - event::Status::Ignored - } - _ => event::Status::Ignored, - } -} - -/// Returns the current [`mouse::Interaction`] of a [`PickList`]. -pub fn mouse_interaction( - layout: Layout<'_>, - cursor: mouse::Cursor, -) -> mouse::Interaction { - let bounds = layout.bounds(); - let is_mouse_over = cursor.is_over(bounds); - - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - } -} - /// The possible status of a [`PickList`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Status { -- cgit