diff --git a/core/src/overlay.rs b/core/src/overlay.rs index 1fa994e4d8..2e05db9366 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -91,9 +91,23 @@ where /// /// By default, it returns true if the bounds of the `layout` contain /// the `cursor_position`. - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + _renderer: &Renderer, + cursor_position: Point, + ) -> bool { layout.bounds().contains(cursor_position) } + + /// Returns the nested overlay of the [`Overlay`], if there is any. + fn overlay<'a>( + &'a mut self, + _layout: Layout<'_>, + _renderer: &Renderer, + ) -> Option> { + None + } } /// Returns a [`Group`] of overlay [`Element`] children. diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index be67fc76e0..c2134343db 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -112,8 +112,22 @@ where } /// Returns true if the cursor is over the [`Element`]. - pub fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { - self.overlay.is_over(layout, cursor_position) + pub fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { + self.overlay.is_over(layout, renderer, cursor_position) + } + + /// Returns the nested overlay of the [`Element`], if there is any. + pub fn overlay<'b>( + &'b mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.overlay.overlay(layout, renderer) } } @@ -248,7 +262,22 @@ where self.content.draw(renderer, theme, style, layout, cursor) } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { - self.content.is_over(layout, cursor_position) + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { + self.content.is_over(layout, renderer, cursor_position) + } + + fn overlay<'b>( + &'b mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content + .overlay(layout, renderer) + .map(|overlay| overlay.map(self.mapper)) } } diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index e770abf838..deffaad030 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -147,11 +147,33 @@ where }); } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { self.children .iter() .zip(layout.children()) - .any(|(child, layout)| child.is_over(layout, cursor_position)) + .any(|(child, layout)| { + child.is_over(layout, renderer, cursor_position) + }) + } + + fn overlay<'b>( + &'b mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + let children = self + .children + .iter_mut() + .zip(layout.children()) + .filter_map(|(child, layout)| child.overlay(layout, renderer)) + .collect::>(); + + (!children.is_empty()).then(|| Group::with_children(children).overlay()) } } diff --git a/examples/modal/src/main.rs b/examples/modal/src/main.rs index 5c43c20340..7fcbbfe4ff 100644 --- a/examples/modal/src/main.rs +++ b/examples/modal/src/main.rs @@ -3,11 +3,13 @@ use iced::keyboard; use iced::subscription::{self, Subscription}; use iced::theme; use iced::widget::{ - self, button, column, container, horizontal_space, row, text, text_input, + self, button, column, container, horizontal_space, pick_list, row, text, + text_input, }; use iced::{Alignment, Application, Command, Element, Event, Length, Settings}; -use self::modal::Modal; +use modal::Modal; +use std::fmt; pub fn main() -> iced::Result { App::run(Settings::default()) @@ -18,6 +20,7 @@ struct App { show_modal: bool, email: String, password: String, + plan: Plan, } #[derive(Debug, Clone)] @@ -26,6 +29,7 @@ enum Message { HideModal, Email(String), Password(String), + Plan(Plan), Submit, Event(Event), } @@ -66,6 +70,10 @@ impl Application for App { self.password = password; Command::none() } + Message::Plan(plan) => { + self.plan = plan; + Command::none() + } Message::Submit => { if !self.email.is_empty() && !self.password.is_empty() { self.hide_modal(); @@ -149,6 +157,16 @@ impl Application for App { .padding(5), ] .spacing(5), + column![ + text("Plan").size(12), + pick_list( + Plan::ALL, + Some(self.plan), + Message::Plan + ) + .padding(5), + ] + .spacing(5), button(text("Submit")).on_press(Message::HideModal), ] .spacing(10) @@ -176,6 +194,29 @@ impl App { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +enum Plan { + #[default] + Basic, + Pro, + Enterprise, +} + +impl Plan { + pub const ALL: &[Self] = &[Self::Basic, Self::Pro, Self::Enterprise]; +} + +impl fmt::Display for Plan { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Plan::Basic => "Basic", + Plan::Pro => "Pro", + Plan::Enterprise => "Enterprise", + } + .fmt(f) + } +} + mod modal { use iced::advanced::layout::{self, Layout}; use iced::advanced::overlay; @@ -469,6 +510,18 @@ mod modal { renderer, ) } + + fn overlay<'c>( + &'c mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + self.tree, + layout.children().next().unwrap(), + renderer, + ) + } } impl<'a, Message, Renderer> From> diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 395cbc1075..4282ddcf61 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -650,7 +650,12 @@ mod toast { .unwrap_or_default() } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + _renderer: &Renderer, + cursor_position: Point, + ) -> bool { layout .children() .any(|layout| layout.bounds().contains(cursor_position)) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 50abf7b27e..4bbf9687bc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -47,6 +47,7 @@ pub mod clipboard; pub mod command; pub mod font; pub mod keyboard; +pub mod overlay; pub mod program; pub mod system; pub mod user_interface; diff --git a/runtime/src/overlay.rs b/runtime/src/overlay.rs new file mode 100644 index 0000000000..03390980bb --- /dev/null +++ b/runtime/src/overlay.rs @@ -0,0 +1,4 @@ +//! Overlays for user interfaces. +mod nested; + +pub use nested::Nested; diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs new file mode 100644 index 0000000000..cd258ab687 --- /dev/null +++ b/runtime/src/overlay/nested.rs @@ -0,0 +1,353 @@ +use crate::core::event; +use crate::core::layout; +use crate::core::mouse; +use crate::core::overlay; +use crate::core::renderer; +use crate::core::widget; +use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; + +/// An [`Overlay`] container that displays nested overlays +#[allow(missing_debug_implementations)] +pub struct Nested<'a, Message, Renderer> { + overlay: overlay::Element<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Nested<'a, Message, Renderer> +where + Renderer: renderer::Renderer, +{ + /// Creates a nested overlay from the provided [`overlay::Element`] + pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self { + Self { overlay: element } + } + + /// Returns the position of the [`Nested`] overlay. + pub fn position(&self) -> Point { + self.overlay.position() + } + + /// Returns the layout [`Node`] of the [`Nested`] overlay. + pub fn layout( + &mut self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node + where + Renderer: renderer::Renderer, + { + let translation = position - element.position(); + + let node = element.layout(renderer, bounds, translation); + + if let Some(mut nested) = + element.overlay(Layout::new(&node), renderer) + { + layout::Node::with_children( + node.size(), + vec![ + node, + recurse(&mut nested, renderer, bounds, position), + ], + ) + } else { + layout::Node::with_children(node.size(), vec![node]) + } + } + + recurse(&mut self.overlay, renderer, bounds, position) + } + + /// Draws the [`Nested`] overlay using the associated `Renderer`. + pub fn draw( + &mut self, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &mut Renderer, + theme: &::Theme, + style: &renderer::Style, + cursor: mouse::Cursor, + ) where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + let nested_layout = layouts.next(); + + let is_over = cursor + .position() + .zip(nested_layout) + .and_then(|(cursor_position, nested_layout)| { + element.overlay(layout, renderer).map(|nested| { + nested.is_over( + nested_layout.children().next().unwrap(), + renderer, + cursor_position, + ) + }) + }) + .unwrap_or_default(); + + renderer.with_layer(layout.bounds(), |renderer| { + element.draw( + renderer, + theme, + style, + layout, + if is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + ); + }); + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(nested_layout) + { + recurse( + &mut nested, + nested_layout, + renderer, + theme, + style, + cursor, + ); + } + } + } + + recurse(&mut self.overlay, layout, renderer, theme, style, cursor); + } + + /// Applies a [`widget::Operation`] to the [`Nested`] overlay. + pub fn operate( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + element.operate(layout, renderer, operation); + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse(&mut nested, nested_layout, renderer, operation); + } + } + } + + recurse(&mut self.overlay, layout, renderer, operation) + } + + /// Processes a runtime [`Event`]. + pub fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + event: Event, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> (event::Status, bool) + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + let (nested_status, nested_is_over) = + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + event.clone(), + cursor, + renderer, + clipboard, + shell, + ) + } else { + (event::Status::Ignored, false) + }; + + if matches!(nested_status, event::Status::Ignored) { + let is_over = nested_is_over + || cursor + .position() + .map(|cursor_position| { + element.is_over( + layout, + renderer, + cursor_position, + ) + }) + .unwrap_or_default(); + + ( + element.on_event( + event, + layout, + if nested_is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + renderer, + clipboard, + shell, + ), + is_over, + ) + } else { + (nested_status, nested_is_over) + } + } else { + (event::Status::Ignored, false) + } + } + + let (status, _) = recurse( + &mut self.overlay, + layout, + event, + cursor, + renderer, + clipboard, + shell, + ); + + status + } + + /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay. + pub fn mouse_interaction( + &mut self, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> Option + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + let layout = layouts.next()?; + let cursor_position = cursor.position()?; + + if !element.is_over(layout, renderer, cursor_position) { + return None; + } + + Some( + element + .overlay(layout, renderer) + .zip(layouts.next()) + .and_then(|(mut overlay, layout)| { + recurse( + &mut overlay, + layout, + cursor, + viewport, + renderer, + ) + }) + .unwrap_or_else(|| { + element.mouse_interaction( + layout, cursor, viewport, renderer, + ) + }), + ) + } + + recurse(&mut self.overlay, layout, cursor, viewport, renderer) + .unwrap_or_default() + } + + /// Returns true if the cursor is over the [`Nested`] overlay. + pub fn is_over( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { + fn recurse( + element: &mut overlay::Element<'_, Message, Renderer>, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool + where + Renderer: renderer::Renderer, + { + let mut layouts = layout.children(); + + if let Some(layout) = layouts.next() { + if element.is_over(layout, renderer, cursor_position) { + return true; + } + + if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + renderer, + cursor_position, + ) + } else { + false + } + } else { + false + } + } + + recurse(&mut self.overlay, layout, renderer, cursor_position) + } +} diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 68ff6158f0..619423fdf6 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -5,8 +5,8 @@ use crate::core::mouse; use crate::core::renderer; use crate::core::widget; use crate::core::window; -use crate::core::{Clipboard, Rectangle, Size, Vector}; -use crate::core::{Element, Layout, Shell}; +use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; +use crate::overlay; /// A set of interactive graphical elements with a specific [`Layout`]. /// @@ -185,18 +185,18 @@ where let mut outdated = false; let mut redraw_request = None; - let mut manual_overlay = - ManuallyDrop::new(self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - )); + let mut manual_overlay = ManuallyDrop::new( + self.root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new), + ); let (base_cursor, overlay_statuses) = if manual_overlay.is_some() { let bounds = self.bounds; let mut overlay = manual_overlay.as_mut().unwrap(); - let mut layout = overlay.layout(renderer, bounds, Vector::ZERO); + let mut layout = overlay.layout(renderer, bounds, Point::ORIGIN); let mut event_statuses = Vec::new(); for event in events.iter().cloned() { @@ -231,12 +231,16 @@ where &layout::Limits::new(Size::ZERO, self.bounds), ); - manual_overlay = - ManuallyDrop::new(self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - )); + manual_overlay = ManuallyDrop::new( + self.root + .as_widget_mut() + .overlay( + &mut self.state, + Layout::new(&self.base), + renderer, + ) + .map(overlay::Nested::new), + ); if manual_overlay.is_none() { break; @@ -245,7 +249,8 @@ where overlay = manual_overlay.as_mut().unwrap(); shell.revalidate_layout(|| { - layout = overlay.layout(renderer, bounds, Vector::ZERO); + layout = + overlay.layout(renderer, bounds, Point::ORIGIN); }); } @@ -254,19 +259,23 @@ where } } - let base_cursor = manual_overlay - .as_ref() - .filter(|overlay| { - cursor - .position() - .map(|cursor_position| { - overlay - .is_over(Layout::new(&layout), cursor_position) - }) - .unwrap_or_default() + let base_cursor = if manual_overlay + .as_mut() + .and_then(|overlay| { + cursor.position().map(|cursor_position| { + overlay.is_over( + Layout::new(&layout), + renderer, + cursor_position, + ) + }) }) - .map(|_| mouse::Cursor::Unavailable) - .unwrap_or(cursor); + .unwrap_or_default() + { + mouse::Cursor::Unavailable + } else { + cursor + }; self.overlay = Some(layout); @@ -424,20 +433,24 @@ where let viewport = Rectangle::with_size(self.bounds); - let base_cursor = if let Some(overlay) = self + let base_cursor = if let Some(mut overlay) = self .root .as_widget_mut() .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new) { let overlay_layout = self.overlay.take().unwrap_or_else(|| { - overlay.layout(renderer, self.bounds, Vector::ZERO) + overlay.layout(renderer, self.bounds, Point::ORIGIN) }); let cursor = if cursor .position() .map(|cursor_position| { - overlay - .is_over(Layout::new(&overlay_layout), cursor_position) + overlay.is_over( + Layout::new(&overlay_layout), + renderer, + cursor_position, + ) }) .unwrap_or_default() { @@ -488,7 +501,8 @@ where .and_then(|layout| { root.as_widget_mut() .overlay(&mut self.state, Layout::new(base), renderer) - .map(|overlay| { + .map(overlay::Nested::new) + .map(|mut overlay| { let overlay_interaction = overlay.mouse_interaction( Layout::new(layout), cursor, @@ -513,6 +527,7 @@ where .map(|cursor_position| { overlay.is_over( Layout::new(layout), + renderer, cursor_position, ) }) @@ -540,14 +555,15 @@ where operation, ); - if let Some(mut overlay) = self.root.as_widget_mut().overlay( - &mut self.state, - Layout::new(&self.base), - renderer, - ) { + if let Some(mut overlay) = self + .root + .as_widget_mut() + .overlay(&mut self.state, Layout::new(&self.base), renderer) + .map(overlay::Nested::new) + { if self.overlay.is_none() { self.overlay = - Some(overlay.layout(renderer, self.bounds, Vector::ZERO)); + Some(overlay.layout(renderer, self.bounds, Point::ORIGIN)); } overlay.operate( diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 92a611c30e..4903d0ed12 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -20,6 +20,7 @@ use crate::core::Element; use crate::core::{ self, Clipboard, Hasher, Length, Point, Rectangle, Shell, Size, }; +use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::RefCell; @@ -260,14 +261,17 @@ where .unwrap(), tree: &mut tree.children[0], overlay_builder: |element, tree| { - element.as_widget_mut().overlay(tree, layout, renderer) + element + .as_widget_mut() + .overlay(tree, layout, renderer) + .map(|overlay| RefCell::new(Nested::new(overlay))) }, } .build(), )); - let has_overlay = overlay - .with_overlay_maybe(|overlay| overlay::Element::position(overlay)); + let has_overlay = + overlay.with_overlay_maybe(|overlay| overlay.position()); has_overlay .map(|position| overlay::Element::new(position, Box::new(overlay))) @@ -285,8 +289,8 @@ where tree: &'a mut Tree, #[borrows(mut element, mut tree)] - #[covariant] - overlay: Option>, + #[not_covariant] + overlay: Option>>, } struct Overlay<'a, Message, Renderer>(Option>); @@ -301,19 +305,20 @@ impl<'a, Message, Renderer> Drop for Overlay<'a, Message, Renderer> { impl<'a, Message, Renderer> Overlay<'a, Message, Renderer> { fn with_overlay_maybe( &self, - f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option { - self.0.as_ref().unwrap().borrow_overlay().as_ref().map(f) + self.0.as_ref().unwrap().with_overlay(|overlay| { + overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) + }) } fn with_overlay_mut_maybe( &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option { - self.0 - .as_mut() - .unwrap() - .with_overlay_mut(|overlay| overlay.as_mut().map(f)) + self.0.as_mut().unwrap().with_overlay_mut(|overlay| { + overlay.as_mut().map(|nested| (f)(nested.get_mut())) + }) } } @@ -329,9 +334,7 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let translation = position - overlay.position(); - - overlay.layout(renderer, bounds, translation) + overlay.layout(renderer, bounds, position) }) .unwrap_or_default() } @@ -377,9 +380,14 @@ where .unwrap_or(event::Status::Ignored) } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { self.with_overlay_maybe(|overlay| { - overlay.is_over(layout, cursor_position) + overlay.is_over(layout, renderer, cursor_position) }) .unwrap_or_default() } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index f462c8cfdb..f955d9dd65 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -9,6 +9,7 @@ use crate::core::widget::tree::{self, Tree}; use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; +use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::RefCell; @@ -455,11 +456,18 @@ where overlay_builder: |instance, tree| { instance.state.get_mut().as_mut().unwrap().with_element_mut( move |element| { - element.as_mut().unwrap().as_widget_mut().overlay( - &mut tree.children[0], - layout, - renderer, - ) + element + .as_mut() + .unwrap() + .as_widget_mut() + .overlay( + &mut tree.children[0], + layout, + renderer, + ) + .map(|overlay| { + RefCell::new(Nested::new(overlay)) + }) }, ) }, @@ -468,7 +476,7 @@ where )); let has_overlay = overlay.0.as_ref().unwrap().with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) + overlay.as_ref().map(|nested| nested.borrow().position()) }); has_overlay.map(|position| { @@ -503,8 +511,8 @@ struct Inner<'a, 'b, Message, Renderer, Event, S> { types: PhantomData<(Message, Event, S)>, #[borrows(mut instance, mut tree)] - #[covariant] - overlay: Option>, + #[not_covariant] + overlay: Option>>, } struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> { @@ -516,7 +524,7 @@ impl<'a, 'b, Message, Renderer, Event, S> { fn with_overlay_maybe( &self, - f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T, ) -> Option { self.overlay .as_ref() @@ -524,14 +532,14 @@ impl<'a, 'b, Message, Renderer, Event, S> .0 .as_ref() .unwrap() - .borrow_overlay() - .as_ref() - .map(f) + .with_overlay(|overlay| { + overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) + }) } fn with_overlay_mut_maybe( &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Event, Renderer>) -> T, ) -> Option { self.overlay .as_mut() @@ -539,7 +547,9 @@ impl<'a, 'b, Message, Renderer, Event, S> .0 .as_mut() .unwrap() - .with_overlay_mut(|overlay| overlay.as_mut().map(f)) + .with_overlay_mut(|overlay| { + overlay.as_mut().map(|nested| (f)(nested.get_mut())) + }) } } @@ -556,9 +566,7 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let translation = position - overlay.position(); - - overlay.layout(renderer, bounds, translation) + overlay.layout(renderer, bounds, position) }) .unwrap_or_default() } @@ -655,9 +663,14 @@ where event_status } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { self.with_overlay_maybe(|overlay| { - overlay.is_over(layout, cursor_position) + overlay.is_over(layout, renderer, cursor_position) }) .unwrap_or_default() } diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index bd6385bcec..07300857ab 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -9,6 +9,7 @@ use crate::core::{ self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use crate::horizontal_space; +use crate::runtime::overlay::Nested; use ouroboros::self_referencing; use std::cell::{RefCell, RefMut}; @@ -298,13 +299,13 @@ where element .as_widget_mut() .overlay(tree, content_layout, renderer) + .map(|overlay| RefCell::new(Nested::new(overlay))) }, } .build(); - let has_overlay = overlay.with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) - }); + let has_overlay = + overlay.with_overlay_maybe(|overlay| overlay.position()); has_overlay .map(|position| overlay::Element::new(position, Box::new(overlay))) @@ -329,23 +330,27 @@ struct Overlay<'a, 'b, Message, Renderer> { types: PhantomData, #[borrows(mut content, mut tree)] - #[covariant] - overlay: Option>, + #[not_covariant] + overlay: Option>>, } impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { fn with_overlay_maybe( &self, - f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option { - self.borrow_overlay().as_ref().map(f) + self.with_overlay(|overlay| { + overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut())) + }) } fn with_overlay_mut_maybe( &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + f: impl FnOnce(&mut Nested<'_, Message, Renderer>) -> T, ) -> Option { - self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) + self.with_overlay_mut(|overlay| { + overlay.as_mut().map(|nested| (f)(nested.get_mut())) + }) } } @@ -361,9 +366,7 @@ where position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { - let translation = position - overlay.position(); - - overlay.layout(renderer, bounds, translation) + overlay.layout(renderer, bounds, position) }) .unwrap_or_default() } @@ -409,9 +412,14 @@ where .unwrap_or(event::Status::Ignored) } - fn is_over(&self, layout: Layout<'_>, cursor_position: Point) -> bool { + fn is_over( + &self, + layout: Layout<'_>, + renderer: &Renderer, + cursor_position: Point, + ) -> bool { self.with_overlay_maybe(|overlay| { - overlay.is_over(layout, cursor_position) + overlay.is_over(layout, renderer, cursor_position) }) .unwrap_or_default() } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b699def0e6..ccf4dfb56d 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) } } diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index dc53a9c86f..832aae6b60 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -157,11 +157,11 @@ where From<::Style>, { fn tag(&self) -> tree::Tag { - tree::Tag::of::>() + tree::Tag::of::() } fn state(&self) -> tree::State { - tree::State::new(State::::new()) + tree::State::new(State::new()) } fn width(&self) -> Length { @@ -209,7 +209,7 @@ where self.on_selected.as_ref(), self.selected.as_ref(), &self.options, - || tree.state.downcast_mut::>(), + || tree.state.downcast_mut::(), ) } @@ -249,7 +249,7 @@ where self.selected.as_ref(), &self.handle, &self.style, - || tree.state.downcast_ref::>(), + || tree.state.downcast_ref::(), ) } @@ -259,7 +259,7 @@ where layout: Layout<'_>, renderer: &Renderer, ) -> Option> { - let state = tree.state.downcast_mut::>(); + let state = tree.state.downcast_mut::(); overlay( layout, @@ -269,6 +269,7 @@ where self.text_shaping, self.font.unwrap_or_else(|| renderer.default_font()), &self.options, + &self.on_selected, self.style.clone(), ) } @@ -295,15 +296,14 @@ where /// The local state of a [`PickList`]. #[derive(Debug)] -pub struct State { +pub struct State { menu: menu::State, keyboard_modifiers: keyboard::Modifiers, is_open: bool, hovered_option: Option, - last_selection: Option, } -impl State { +impl State { /// Creates a new [`State`] for a [`PickList`]. pub fn new() -> Self { Self { @@ -311,12 +311,11 @@ impl State { keyboard_modifiers: keyboard::Modifiers::default(), is_open: bool::default(), hovered_option: Option::default(), - last_selection: Option::default(), } } } -impl Default for State { +impl Default for State { fn default() -> Self { Self::new() } @@ -436,7 +435,7 @@ pub fn update<'a, T, Message>( on_selected: &dyn Fn(T) -> Message, selected: Option<&T>, options: &[T], - state: impl FnOnce() -> &'a mut State, + state: impl FnOnce() -> &'a mut State, ) -> event::Status where T: PartialEq + Clone + 'a, @@ -446,7 +445,7 @@ where | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state(); - let event_status = if state.is_open { + 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; @@ -460,16 +459,6 @@ where event::Status::Captured } else { event::Status::Ignored - }; - - if let Some(last_selection) = state.last_selection.take() { - shell.publish((on_selected)(last_selection)); - - state.is_open = false; - - event::Status::Captured - } else { - event_status } } Event::Mouse(mouse::Event::WheelScrolled { @@ -544,12 +533,13 @@ pub fn mouse_interaction( /// Returns the current overlay of a [`PickList`]. pub fn overlay<'a, T, Message, Renderer>( layout: Layout<'_>, - state: &'a mut State, + 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 @@ -570,7 +560,11 @@ where &mut state.menu, options, &mut state.hovered_option, - &mut state.last_selection, + |option| { + state.is_open = false; + + (on_selected)(option) + }, ) .width(bounds.width) .padding(padding) @@ -603,7 +597,7 @@ pub fn draw<'a, T, Renderer>( selected: Option<&T>, handle: &Handle, style: &::Style, - state: impl FnOnce() -> &'a State, + state: impl FnOnce() -> &'a State, ) where Renderer: text::Renderer, Renderer::Theme: StyleSheet,