From 3a0d34c0240f4421737a6a08761f99d6f8140d02 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 4 Mar 2023 05:37:11 +0100 Subject: Create `iced_widget` subcrate and re-organize the whole codebase --- widget/src/overlay/menu.rs | 519 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 widget/src/overlay/menu.rs (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs new file mode 100644 index 00000000..c322c8ba --- /dev/null +++ b/widget/src/overlay/menu.rs @@ -0,0 +1,519 @@ +//! Build and show dropdown menus. +use crate::container::{self, Container}; +use crate::core::alignment; +use crate::core::event::{self, Event}; +use crate::core::layout::{self, Layout}; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::text::{self, Text}; +use crate::core::touch; +use crate::core::widget::Tree; +use crate::core::{ + Clipboard, Color, Length, Padding, Pixels, Point, Rectangle, Size, Vector, +}; +use crate::core::{Element, Shell, Widget}; +use crate::scrollable::{self, Scrollable}; + +pub use iced_style::menu::{Appearance, StyleSheet}; + +/// A list of selectable options. +#[allow(missing_debug_implementations)] +pub struct Menu<'a, T, Renderer = crate::Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + state: &'a mut State, + options: &'a [T], + hovered_option: &'a mut Option, + last_selection: &'a mut Option, + width: f32, + padding: Padding, + text_size: Option, + font: Option, + style: ::Style, +} + +impl<'a, T, Renderer> Menu<'a, T, Renderer> +where + T: ToString + Clone, + Renderer: text::Renderer + 'a, + Renderer::Theme: + StyleSheet + container::StyleSheet + scrollable::StyleSheet, +{ + /// Creates a new [`Menu`] with the given [`State`], a list of options, and + /// the message to produced when an option is selected. + pub fn new( + state: &'a mut State, + options: &'a [T], + hovered_option: &'a mut Option, + last_selection: &'a mut Option, + ) -> Self { + Menu { + state, + options, + hovered_option, + last_selection, + width: 0.0, + padding: Padding::ZERO, + text_size: None, + font: None, + style: Default::default(), + } + } + + /// Sets the width of the [`Menu`]. + pub fn width(mut self, width: f32) -> Self { + self.width = width; + self + } + + /// Sets the [`Padding`] of the [`Menu`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the text size of the [`Menu`]. + pub fn text_size(mut self, text_size: impl Into) -> Self { + self.text_size = Some(text_size.into().0); + self + } + + /// Sets the font of the [`Menu`]. + pub fn font(mut self, font: impl Into) -> Self { + self.font = Some(font.into()); + self + } + + /// Sets the style of the [`Menu`]. + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } + + /// Turns the [`Menu`] into an overlay [`Element`] at the given target + /// position. + /// + /// The `target_height` will be used to display the menu either on top + /// of the target or under it, depending on the screen position and the + /// dimensions of the [`Menu`]. + pub fn overlay( + self, + position: Point, + target_height: f32, + ) -> overlay::Element<'a, Message, Renderer> { + overlay::Element::new( + position, + Box::new(Overlay::new(self, target_height)), + ) + } +} + +/// The local state of a [`Menu`]. +#[derive(Debug)] +pub struct State { + tree: Tree, +} + +impl State { + /// Creates a new [`State`] for a [`Menu`]. + pub fn new() -> Self { + Self { + tree: Tree::empty(), + } + } +} + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +struct Overlay<'a, Message, Renderer> +where + Renderer: crate::core::Renderer, + Renderer::Theme: StyleSheet + container::StyleSheet, +{ + state: &'a mut Tree, + container: Container<'a, Message, Renderer>, + width: f32, + target_height: f32, + style: ::Style, +} + +impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> +where + Message: 'a, + Renderer: 'a, + Renderer: text::Renderer, + Renderer::Theme: + StyleSheet + container::StyleSheet + scrollable::StyleSheet, +{ + pub fn new(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self + where + T: Clone + ToString, + { + let Menu { + state, + options, + hovered_option, + last_selection, + width, + padding, + font, + text_size, + style, + } = menu; + + let container = Container::new(Scrollable::new(List { + options, + hovered_option, + last_selection, + font, + text_size, + padding, + style: style.clone(), + })); + + state.tree.diff(&container as &dyn Widget<_, _>); + + Self { + state: &mut state.tree, + container, + width, + target_height, + style, + } + } +} + +impl<'a, Message, Renderer> crate::core::Overlay + for Overlay<'a, Message, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet + container::StyleSheet, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + let space_below = bounds.height - (position.y + self.target_height); + let space_above = position.y; + + let limits = layout::Limits::new( + Size::ZERO, + Size::new( + bounds.width - position.x, + if space_below > space_above { + space_below + } else { + space_above + }, + ), + ) + .width(self.width); + + let mut node = self.container.layout(renderer, &limits); + + node.move_to(if space_below > space_above { + position + Vector::new(0.0, self.target_height) + } else { + position - Vector::new(0.0, node.size().height) + }); + + node + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.container.on_event( + self.state, + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.container.mouse_interaction( + self.state, + layout, + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + let appearance = theme.appearance(&self.style); + let bounds = layout.bounds(); + + renderer.fill_quad( + renderer::Quad { + bounds, + border_color: appearance.border_color, + border_width: appearance.border_width, + border_radius: appearance.border_radius.into(), + }, + appearance.background, + ); + + self.container.draw( + self.state, + renderer, + theme, + style, + layout, + cursor_position, + &bounds, + ); + } +} + +struct List<'a, T, Renderer> +where + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + options: &'a [T], + hovered_option: &'a mut Option, + last_selection: &'a mut Option, + padding: Padding, + text_size: Option, + font: Option, + style: ::Style, +} + +impl<'a, T, Message, Renderer> Widget + for List<'a, T, Renderer> +where + T: Clone + ToString, + Renderer: text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + use std::f32; + + let limits = limits.width(Length::Fill).height(Length::Shrink); + let text_size = + self.text_size.unwrap_or_else(|| renderer.default_size()); + + let size = { + let intrinsic = Size::new( + 0.0, + (text_size * 1.2 + self.padding.vertical()) + * self.options.len() as f32, + ); + + limits.resolve(intrinsic) + }; + + layout::Node::new(size) + } + + fn on_event( + &mut self, + _state: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + _shell: &mut Shell<'_, Message>, + ) -> event::Status { + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + if let Some(index) = *self.hovered_option { + if let Some(option) = self.options.get(index) { + *self.last_selection = Some(option.clone()); + } + } + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let text_size = self + .text_size + .unwrap_or_else(|| renderer.default_size()); + + *self.hovered_option = Some( + ((cursor_position.y - bounds.y) + / (text_size * 1.2 + self.padding.vertical())) + as usize, + ); + } + } + Event::Touch(touch::Event::FingerPressed { .. }) => { + let bounds = layout.bounds(); + + if bounds.contains(cursor_position) { + let text_size = self + .text_size + .unwrap_or_else(|| renderer.default_size()); + + *self.hovered_option = Some( + ((cursor_position.y - bounds.y) + / (text_size * 1.2 + self.padding.vertical())) + as usize, + ); + + if let Some(index) = *self.hovered_option { + if let Some(option) = self.options.get(index) { + *self.last_selection = Some(option.clone()); + } + } + } + } + _ => {} + } + + event::Status::Ignored + } + + fn mouse_interaction( + &self, + _state: &Tree, + layout: Layout<'_>, + cursor_position: Point, + _viewport: &Rectangle, + _renderer: &Renderer, + ) -> mouse::Interaction { + let is_mouse_over = layout.bounds().contains(cursor_position); + + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + } + } + + fn draw( + &self, + _state: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor_position: Point, + viewport: &Rectangle, + ) { + let appearance = theme.appearance(&self.style); + let bounds = layout.bounds(); + + let text_size = + self.text_size.unwrap_or_else(|| renderer.default_size()); + let option_height = + (text_size * 1.2 + self.padding.vertical()) as usize; + + let offset = viewport.y - bounds.y; + let start = (offset / option_height as f32) as usize; + let end = + ((offset + viewport.height) / option_height as f32).ceil() as usize; + + let visible_options = &self.options[start..end.min(self.options.len())]; + + for (i, option) in visible_options.iter().enumerate() { + let i = start + i; + let is_selected = *self.hovered_option == Some(i); + + let bounds = Rectangle { + x: bounds.x, + y: bounds.y + (option_height * i) as f32, + width: bounds.width, + height: text_size * 1.2 + self.padding.vertical(), + }; + + if is_selected { + renderer.fill_quad( + renderer::Quad { + bounds, + border_color: Color::TRANSPARENT, + border_width: 0.0, + border_radius: appearance.border_radius.into(), + }, + appearance.selected_background, + ); + } + + renderer.fill_text(Text { + content: &option.to_string(), + bounds: Rectangle { + x: bounds.x + self.padding.left, + y: bounds.center_y(), + width: f32::INFINITY, + ..bounds + }, + size: text_size, + font: self.font.unwrap_or_else(|| renderer.default_font()), + color: if is_selected { + appearance.selected_text_color + } else { + appearance.text_color + }, + horizontal_alignment: alignment::Horizontal::Left, + vertical_alignment: alignment::Vertical::Center, + }); + } + } +} + +impl<'a, T, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + T: ToString + Clone, + Message: 'a, + Renderer: 'a + text::Renderer, + Renderer::Theme: StyleSheet, +{ + fn from(list: List<'a, T, Renderer>) -> Self { + Element::new(list) + } +} -- cgit From 33b5a900197e2798a393d6d9a0834039666eddbb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 19 Apr 2023 01:19:56 +0200 Subject: Make basic text shaping the default shaping strategy --- widget/src/overlay/menu.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index c322c8ba..c904730d 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -500,6 +500,7 @@ where }, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, + advanced_shape: false, }); } } -- cgit From 4bd290afe7d81d9aaf7467b3ce91491f6600261a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 19 Apr 2023 02:00:45 +0200 Subject: Introduce `text::Shaping` enum and replace magic boolean --- widget/src/overlay/menu.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index c904730d..7de3cbae 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -31,6 +31,7 @@ where width: f32, padding: Padding, text_size: Option, + text_shaping: text::Shaping, font: Option, style: ::Style, } @@ -58,6 +59,7 @@ where width: 0.0, padding: Padding::ZERO, text_size: None, + text_shaping: text::Shaping::Basic, font: None, style: Default::default(), } @@ -81,6 +83,12 @@ where self } + /// Sets the [`text::Shaping`] strategy of the [`Menu`]. + pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { + self.text_shaping = shaping; + self + } + /// Sets the font of the [`Menu`]. pub fn font(mut self, font: impl Into) -> Self { self.font = Some(font.into()); @@ -168,6 +176,7 @@ where padding, font, text_size, + text_shaping, style, } = menu; @@ -177,6 +186,7 @@ where last_selection, font, text_size, + text_shaping, padding, style: style.clone(), })); @@ -311,6 +321,7 @@ where last_selection: &'a mut Option, padding: Padding, text_size: Option, + text_shaping: text::Shaping, font: Option, style: ::Style, } @@ -500,7 +511,7 @@ where }, horizontal_alignment: alignment::Horizontal::Left, vertical_alignment: alignment::Vertical::Center, - advanced_shape: false, + shaping: self.text_shaping, }); } } -- cgit From 9499a8f9e6f9971dedfae563cb133232aa3cebc2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 4 May 2023 13:00:16 +0200 Subject: Support configurable `LineHeight` in text widgets --- widget/src/overlay/menu.rs | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 7de3cbae..dfb6a22a 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -31,6 +31,7 @@ where width: f32, padding: Padding, text_size: Option, + text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, style: ::Style, @@ -59,6 +60,7 @@ where width: 0.0, padding: Padding::ZERO, text_size: None, + text_line_height: text::LineHeight::default(), text_shaping: text::Shaping::Basic, font: None, style: Default::default(), @@ -83,6 +85,15 @@ where self } + /// Sets the text [`LineHeight`] of the [`Menu`]. + pub fn text_line_height( + mut self, + line_height: impl Into, + ) -> Self { + self.text_line_height = line_height.into(); + self + } + /// Sets the [`text::Shaping`] strategy of the [`Menu`]. pub fn text_shaping(mut self, shaping: text::Shaping) -> Self { self.text_shaping = shaping; @@ -176,6 +187,7 @@ where padding, font, text_size, + text_line_height, text_shaping, style, } = menu; @@ -186,6 +198,7 @@ where last_selection, font, text_size, + text_line_height, text_shaping, padding, style: style.clone(), @@ -321,6 +334,7 @@ where last_selection: &'a mut Option, padding: Padding, text_size: Option, + text_line_height: text::LineHeight, text_shaping: text::Shaping, font: Option, style: ::Style, @@ -352,10 +366,13 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); + let text_line_height = + self.text_line_height.to_absolute(Pixels(text_size)); + let size = { let intrinsic = Size::new( 0.0, - (text_size * 1.2 + self.padding.vertical()) + (f32::from(text_line_height) + self.padding.vertical()) * self.options.len() as f32, ); @@ -395,9 +412,12 @@ where .text_size .unwrap_or_else(|| renderer.default_size()); + let option_height = f32::from( + self.text_line_height.to_absolute(Pixels(text_size)), + ) + self.padding.vertical(); + *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size * 1.2 + self.padding.vertical())) + ((cursor_position.y - bounds.y) / option_height) as usize, ); } @@ -410,9 +430,12 @@ where .text_size .unwrap_or_else(|| renderer.default_size()); + let option_height = f32::from( + self.text_line_height.to_absolute(Pixels(text_size)), + ) + self.padding.vertical(); + *self.hovered_option = Some( - ((cursor_position.y - bounds.y) - / (text_size * 1.2 + self.padding.vertical())) + ((cursor_position.y - bounds.y) / option_height) as usize, ); @@ -462,12 +485,12 @@ where let text_size = self.text_size.unwrap_or_else(|| renderer.default_size()); let option_height = - (text_size * 1.2 + self.padding.vertical()) as usize; + f32::from(self.text_line_height.to_absolute(Pixels(text_size))) + + self.padding.vertical(); let offset = viewport.y - bounds.y; - let start = (offset / option_height as f32) as usize; - let end = - ((offset + viewport.height) / option_height as f32).ceil() as usize; + let start = (offset / option_height) as usize; + let end = ((offset + viewport.height) / option_height).ceil() as usize; let visible_options = &self.options[start..end.min(self.options.len())]; @@ -477,9 +500,9 @@ where let bounds = Rectangle { x: bounds.x, - y: bounds.y + (option_height * i) as f32, + y: bounds.y + (option_height * i as f32), width: bounds.width, - height: text_size * 1.2 + self.padding.vertical(), + height: option_height, }; if is_selected { @@ -503,6 +526,7 @@ where ..bounds }, size: text_size, + line_height: self.text_line_height, font: self.font.unwrap_or_else(|| renderer.default_font()), color: if is_selected { appearance.selected_text_color -- cgit From 1234d528121265698f9f426ca89fc687dc95dc01 Mon Sep 17 00:00:00 2001 From: Casper Storm Date: Tue, 23 May 2023 15:28:45 +0200 Subject: clippy --- widget/src/overlay/menu.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 0acc6f79..84cc800c 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -307,7 +307,7 @@ where bounds, border_color: appearance.border_color, border_width: appearance.border_width, - border_radius: appearance.border_radius.into(), + border_radius: appearance.border_radius, }, appearance.background, ); @@ -515,7 +515,7 @@ where }, border_color: Color::TRANSPARENT, border_width: 0.0, - border_radius: appearance.border_radius.into(), + border_radius: appearance.border_radius, }, appearance.selected_background, ); -- cgit From 34451bff185d8875f55747ee97ed746828e30f40 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 8 Jun 2023 20:11:59 +0200 Subject: Implement basic cursor availability --- widget/src/overlay/menu.rs | 68 ++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 45 deletions(-) (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 84cc800c..fe1175ac 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -259,36 +259,25 @@ where &mut self, event: Event, layout: Layout<'_>, - cursor_position: Point, + cursor: mouse::Cursor, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { self.container.on_event( - self.state, - event, - layout, - cursor_position, - renderer, - clipboard, - shell, + self.state, event, layout, cursor, renderer, clipboard, shell, ) } fn mouse_interaction( &self, layout: Layout<'_>, - cursor_position: Point, + cursor: mouse::Cursor, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { - self.container.mouse_interaction( - self.state, - layout, - cursor_position, - viewport, - renderer, - ) + self.container + .mouse_interaction(self.state, layout, cursor, viewport, renderer) } fn draw( @@ -297,7 +286,7 @@ where theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, - cursor_position: Point, + cursor: mouse::Cursor, ) { let appearance = theme.appearance(&self.style); let bounds = layout.bounds(); @@ -312,15 +301,8 @@ where appearance.background, ); - self.container.draw( - self.state, - renderer, - theme, - style, - layout, - cursor_position, - &bounds, - ); + self.container + .draw(self.state, renderer, theme, style, layout, cursor, &bounds); } } @@ -387,7 +369,7 @@ where _state: &mut Tree, event: Event, layout: Layout<'_>, - cursor_position: Point, + cursor: mouse::Cursor, renderer: &Renderer, _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, @@ -396,7 +378,7 @@ where Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let bounds = layout.bounds(); - if bounds.contains(cursor_position) { + if cursor.is_over(&bounds) { if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { *self.last_selection = Some(option.clone()); @@ -405,9 +387,9 @@ where } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { + if let Some(cursor_position) = + cursor.position_in(&layout.bounds()) + { let text_size = self .text_size .unwrap_or_else(|| renderer.default_size()); @@ -416,16 +398,14 @@ where self.text_line_height.to_absolute(Pixels(text_size)), ) + self.padding.vertical(); - *self.hovered_option = Some( - ((cursor_position.y - bounds.y) / option_height) - as usize, - ); + *self.hovered_option = + Some((cursor_position.y / option_height) as usize); } } Event::Touch(touch::Event::FingerPressed { .. }) => { - let bounds = layout.bounds(); - - if bounds.contains(cursor_position) { + if let Some(cursor_position) = + cursor.position_in(&layout.bounds()) + { let text_size = self .text_size .unwrap_or_else(|| renderer.default_size()); @@ -434,10 +414,8 @@ where self.text_line_height.to_absolute(Pixels(text_size)), ) + self.padding.vertical(); - *self.hovered_option = Some( - ((cursor_position.y - bounds.y) / option_height) - as usize, - ); + *self.hovered_option = + Some((cursor_position.y / option_height) as usize); if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { @@ -456,11 +434,11 @@ where &self, _state: &Tree, layout: Layout<'_>, - cursor_position: Point, + cursor: mouse::Cursor, _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - let is_mouse_over = layout.bounds().contains(cursor_position); + let is_mouse_over = cursor.is_over(&layout.bounds()); if is_mouse_over { mouse::Interaction::Pointer @@ -476,7 +454,7 @@ where theme: &Renderer::Theme, _style: &renderer::Style, layout: Layout<'_>, - _cursor_position: Point, + _cursor: mouse::Cursor, viewport: &Rectangle, ) { let appearance = theme.appearance(&self.style); -- cgit From 5c8cfb411ed0a9a6e55bd1193cd7e97252e63d28 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 8 Jun 2023 20:16:46 +0200 Subject: Take `Rectangle` by value in `Cursor` API --- widget/src/overlay/menu.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index fe1175ac..b699def0 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -376,9 +376,7 @@ where ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { - let bounds = layout.bounds(); - - if cursor.is_over(&bounds) { + if cursor.is_over(layout.bounds()) { if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { *self.last_selection = Some(option.clone()); @@ -388,7 +386,7 @@ where } Event::Mouse(mouse::Event::CursorMoved { .. }) => { if let Some(cursor_position) = - cursor.position_in(&layout.bounds()) + cursor.position_in(layout.bounds()) { let text_size = self .text_size @@ -404,7 +402,7 @@ where } Event::Touch(touch::Event::FingerPressed { .. }) => { if let Some(cursor_position) = - cursor.position_in(&layout.bounds()) + cursor.position_in(layout.bounds()) { let text_size = self .text_size @@ -438,7 +436,7 @@ where _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { - let is_mouse_over = cursor.is_over(&layout.bounds()); + let is_mouse_over = cursor.is_over(layout.bounds()); if is_mouse_over { mouse::Interaction::Pointer -- cgit From 87db76a11ffd0e3e2350c2b2531fde1c56ffeea6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 14 Jun 2023 11:25:05 +0200 Subject: Make `overlay::Menu` publish messages on selection --- widget/src/overlay/menu.rs | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'widget/src/overlay/menu.rs') diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b699def0..ccf4dfb5 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -19,7 +19,7 @@ pub use iced_style::menu::{Appearance, StyleSheet}; /// A list of selectable options. #[allow(missing_debug_implementations)] -pub struct Menu<'a, T, Renderer = crate::Renderer> +pub struct Menu<'a, T, Message, Renderer = crate::Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, @@ -27,7 +27,7 @@ where state: &'a mut State, options: &'a [T], hovered_option: &'a mut Option, - last_selection: &'a mut Option, + on_selected: Box Message + 'a>, width: f32, padding: Padding, text_size: Option, @@ -37,9 +37,10 @@ where style: ::Style, } -impl<'a, T, Renderer> Menu<'a, T, Renderer> +impl<'a, T, Message, Renderer> Menu<'a, T, Message, Renderer> where T: ToString + Clone, + Message: 'a, Renderer: text::Renderer + 'a, Renderer::Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet, @@ -50,13 +51,13 @@ where state: &'a mut State, options: &'a [T], hovered_option: &'a mut Option, - last_selection: &'a mut Option, + on_selected: impl FnMut(T) -> Message + 'a, ) -> Self { Menu { state, options, hovered_option, - last_selection, + on_selected: Box::new(on_selected), width: 0.0, padding: Padding::ZERO, text_size: None, @@ -121,7 +122,7 @@ where /// The `target_height` will be used to display the menu either on top /// of the target or under it, depending on the screen position and the /// dimensions of the [`Menu`]. - pub fn overlay( + pub fn overlay( self, position: Point, target_height: f32, @@ -174,7 +175,10 @@ where Renderer::Theme: StyleSheet + container::StyleSheet + scrollable::StyleSheet, { - pub fn new(menu: Menu<'a, T, Renderer>, target_height: f32) -> Self + pub fn new( + menu: Menu<'a, T, Message, Renderer>, + target_height: f32, + ) -> Self where T: Clone + ToString, { @@ -182,7 +186,7 @@ where state, options, hovered_option, - last_selection, + on_selected, width, padding, font, @@ -195,7 +199,7 @@ where let container = Container::new(Scrollable::new(List { options, hovered_option, - last_selection, + on_selected, font, text_size, text_line_height, @@ -306,14 +310,14 @@ where } } -struct List<'a, T, Renderer> +struct List<'a, T, Message, Renderer> where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { options: &'a [T], hovered_option: &'a mut Option, - last_selection: &'a mut Option, + on_selected: Box Message + 'a>, padding: Padding, text_size: Option, text_line_height: text::LineHeight, @@ -323,7 +327,7 @@ where } impl<'a, T, Message, Renderer> Widget - for List<'a, T, Renderer> + for List<'a, T, Message, Renderer> where T: Clone + ToString, Renderer: text::Renderer, @@ -372,14 +376,15 @@ where cursor: mouse::Cursor, renderer: &Renderer, _clipboard: &mut dyn Clipboard, - _shell: &mut Shell<'_, Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if cursor.is_over(layout.bounds()) { if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); + shell.publish((self.on_selected)(option.clone())); + return event::Status::Captured; } } } @@ -417,7 +422,8 @@ where if let Some(index) = *self.hovered_option { if let Some(option) = self.options.get(index) { - *self.last_selection = Some(option.clone()); + shell.publish((self.on_selected)(option.clone())); + return event::Status::Captured; } } } @@ -521,7 +527,7 @@ where } } -impl<'a, T, Message, Renderer> From> +impl<'a, T, Message, Renderer> From> for Element<'a, Message, Renderer> where T: ToString + Clone, @@ -529,7 +535,7 @@ where Renderer: 'a + text::Renderer, Renderer::Theme: StyleSheet, { - fn from(list: List<'a, T, Renderer>) -> Self { + fn from(list: List<'a, T, Message, Renderer>) -> Self { Element::new(list) } } -- cgit