diff options
author | 2021-12-02 17:47:44 +0700 | |
---|---|---|
committer | 2021-12-02 17:47:44 +0700 | |
commit | 26d053ab3887b73f854a1174864d0fec221c5c9b (patch) | |
tree | a6cf758c934638e3dd9586781dfb62e1cf028572 | |
parent | d9f970ffd5af6dafb5e696ad317d9ea7b997eb4b (diff) | |
parent | bbd9355450bc2df3a2c0e37cc900ba00b26255af (diff) | |
download | iced-26d053ab3887b73f854a1174864d0fec221c5c9b.tar.gz iced-26d053ab3887b73f854a1174864d0fec221c5c9b.tar.bz2 iced-26d053ab3887b73f854a1174864d0fec221c5c9b.zip |
Merge pull request #1131 from iced-rs/component-trait
`iced_lazy` and `Component` trait
31 files changed, 605 insertions, 121 deletions
@@ -57,6 +57,7 @@ members = [ "graphics", "glow", "glutin", + "lazy", "native", "style", "web", @@ -65,6 +66,7 @@ members = [ "examples/bezier_tool", "examples/clock", "examples/color_palette", + "examples/component", "examples/counter", "examples/custom_widget", "examples/download_progress", diff --git a/examples/component/Cargo.toml b/examples/component/Cargo.toml new file mode 100644 index 00000000..5761db9f --- /dev/null +++ b/examples/component/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "component" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_native = { path = "../../native" } +iced_lazy = { path = "../../lazy" } diff --git a/examples/component/src/main.rs b/examples/component/src/main.rs new file mode 100644 index 00000000..39335cf1 --- /dev/null +++ b/examples/component/src/main.rs @@ -0,0 +1,180 @@ +use iced::{Container, Element, Length, Sandbox, Settings}; +use numeric_input::NumericInput; + +pub fn main() -> iced::Result { + Component::run(Settings::default()) +} + +#[derive(Default)] +struct Component { + numeric_input: numeric_input::State, + value: Option<u32>, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + NumericInputChanged(Option<u32>), +} + +impl Sandbox for Component { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Component - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::NumericInputChanged(value) => { + self.value = value; + } + } + } + + fn view(&mut self) -> Element<Message> { + Container::new(NumericInput::new( + &mut self.numeric_input, + self.value, + Message::NumericInputChanged, + )) + .padding(20) + .height(Length::Fill) + .center_y() + .into() + } +} + +mod numeric_input { + use iced_lazy::component::{self, Component}; + use iced_native::alignment::{self, Alignment}; + use iced_native::text; + use iced_native::widget::button::{self, Button}; + use iced_native::widget::text_input::{self, TextInput}; + use iced_native::widget::{Row, Text}; + use iced_native::{Element, Length}; + + pub struct NumericInput<'a, Message> { + state: &'a mut State, + value: Option<u32>, + on_change: Box<dyn Fn(Option<u32>) -> Message>, + } + + #[derive(Default)] + pub struct State { + input: text_input::State, + decrement_button: button::State, + increment_button: button::State, + } + + #[derive(Debug, Clone)] + pub enum Event { + InputChanged(String), + IncrementPressed, + DecrementPressed, + } + + impl<'a, Message> NumericInput<'a, Message> { + pub fn new( + state: &'a mut State, + value: Option<u32>, + on_change: impl Fn(Option<u32>) -> Message + 'static, + ) -> Self { + Self { + state, + value, + on_change: Box::new(on_change), + } + } + } + + impl<'a, Message, Renderer> Component<Message, Renderer> + for NumericInput<'a, Message> + where + Renderer: 'a + text::Renderer, + { + type Event = Event; + + fn update(&mut self, event: Event) -> Option<Message> { + match event { + Event::IncrementPressed => Some((self.on_change)(Some( + self.value.unwrap_or_default().saturating_add(1), + ))), + Event::DecrementPressed => Some((self.on_change)(Some( + self.value.unwrap_or_default().saturating_sub(1), + ))), + Event::InputChanged(value) => { + if value.is_empty() { + Some((self.on_change)(None)) + } else { + value + .parse() + .ok() + .map(Some) + .map(self.on_change.as_ref()) + } + } + } + } + + fn view(&mut self) -> Element<Event, Renderer> { + let button = |state, label, on_press| { + Button::new( + state, + Text::new(label) + .width(Length::Fill) + .height(Length::Fill) + .horizontal_alignment(alignment::Horizontal::Center) + .vertical_alignment(alignment::Vertical::Center), + ) + .width(Length::Units(50)) + .on_press(on_press) + }; + + Row::with_children(vec![ + button( + &mut self.state.decrement_button, + "-", + Event::DecrementPressed, + ) + .into(), + TextInput::new( + &mut self.state.input, + "Type a number", + self.value + .as_ref() + .map(u32::to_string) + .as_ref() + .map(String::as_str) + .unwrap_or(""), + Event::InputChanged, + ) + .padding(10) + .into(), + button( + &mut self.state.increment_button, + "+", + Event::IncrementPressed, + ) + .into(), + ]) + .align_items(Alignment::Fill) + .spacing(10) + .into() + } + } + + impl<'a, Message, Renderer> From<NumericInput<'a, Message>> + for Element<'a, Message, Renderer> + where + Message: 'a, + Renderer: text::Renderer + 'a, + { + fn from(numeric_input: NumericInput<'a, Message>) -> Self { + component::view(numeric_input) + } + } +} diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 639c2a9b..97846d65 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -9,8 +9,8 @@ use crate::{Backend, Primitive}; use iced_native::layout; use iced_native::mouse; use iced_native::{ - Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, - Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Shell, Size, + Vector, Widget, }; use std::hash::Hash; use std::marker::PhantomData; @@ -158,7 +158,7 @@ where cursor_position: Point, _renderer: &Renderer<B>, _clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); @@ -179,7 +179,7 @@ where self.program.update(canvas_event, bounds, cursor); if let Some(message) = message { - messages.push(message); + shell.publish(message); } return event_status; diff --git a/lazy/Cargo.toml b/lazy/Cargo.toml new file mode 100644 index 00000000..b840de50 --- /dev/null +++ b/lazy/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "iced_lazy" +version = "0.1.0" +edition = "2021" + +[dependencies] +ouroboros = "0.13" + +[dependencies.iced_native] +version = "0.4" +path = "../native" diff --git a/lazy/src/component.rs b/lazy/src/component.rs new file mode 100644 index 00000000..aa5b847e --- /dev/null +++ b/lazy/src/component.rs @@ -0,0 +1,186 @@ +use iced_native::event; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::{ + Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Widget, +}; + +use ouroboros::self_referencing; +use std::marker::PhantomData; + +pub fn view<'a, C, Message, Renderer>( + component: C, +) -> Element<'a, Message, Renderer> +where + C: Component<Message, Renderer> + 'a, + Message: 'a, + Renderer: iced_native::Renderer + 'a, +{ + Element::new(Instance { + state: Some( + StateBuilder { + component: Box::new(component), + cache_builder: |state| Cache { + element: state.view(), + message: PhantomData, + }, + } + .build(), + ), + }) +} + +pub trait Component<Message, Renderer> { + type Event; + + fn update(&mut self, event: Self::Event) -> Option<Message>; + + fn view(&mut self) -> Element<Self::Event, Renderer>; +} + +struct Instance<'a, Message, Renderer, Event> { + state: Option<State<'a, Message, Renderer, Event>>, +} + +#[self_referencing] +struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> { + component: Box<dyn Component<Message, Renderer, Event = Event> + 'a>, + + #[borrows(mut component)] + #[covariant] + cache: Cache<'this, Message, Renderer, Event>, +} + +struct Cache<'a, Message, Renderer, Event> { + element: Element<'a, Event, Renderer>, + message: PhantomData<Message>, +} + +impl<'a, Message, Renderer, Event> Widget<Message, Renderer> + for Instance<'a, Message, Renderer, Event> +where + Renderer: iced_native::Renderer, +{ + fn width(&self) -> Length { + self.state.as_ref().unwrap().borrow_cache().element.width() + } + + fn height(&self) -> Length { + self.state.as_ref().unwrap().borrow_cache().element.width() + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.state + .as_ref() + .unwrap() + .borrow_cache() + .element + .layout(renderer, limits) + } + + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); + + let event_status = + self.state.as_mut().unwrap().with_cache_mut(|cache| { + cache.element.on_event( + event, + layout, + cursor_position, + renderer, + clipboard, + &mut local_shell, + ) + }); + + if !local_messages.is_empty() { + let mut component = + self.state.take().unwrap().into_heads().component; + + for message in local_messages + .into_iter() + .filter_map(|message| component.update(message)) + { + shell.publish(message); + } + + self.state = Some( + StateBuilder { + component, + cache_builder: |state| Cache { + element: state.view(), + message: PhantomData, + }, + } + .build(), + ); + + shell.invalidate_layout(); + } + + event_status + } + + fn draw( + &self, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.state.as_ref().unwrap().borrow_cache().element.draw( + renderer, + style, + layout, + cursor_position, + viewport, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + self.state + .as_ref() + .unwrap() + .borrow_cache() + .element + .hash_layout(state) + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) -> mouse::Interaction { + self.state + .as_ref() + .unwrap() + .borrow_cache() + .element + .mouse_interaction(layout, cursor_position, viewport) + } + + fn overlay( + &mut self, + _layout: Layout<'_>, + ) -> Option<overlay::Element<'_, Message, Renderer>> { + // TODO: Rethink overlay composability + None + } +} diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs new file mode 100644 index 00000000..42e5f587 --- /dev/null +++ b/lazy/src/lib.rs @@ -0,0 +1,3 @@ +pub mod component; + +pub use component::Component; diff --git a/native/src/element.rs b/native/src/element.rs index ee404a1c..7e806b08 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -4,7 +4,7 @@ use crate::mouse; use crate::overlay; use crate::renderer; use crate::{ - Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Widget, + Clipboard, Color, Hasher, Layout, Length, Point, Rectangle, Shell, Widget, }; /// A generic [`Widget`]. @@ -228,7 +228,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.widget.on_event( event, @@ -236,7 +236,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) } @@ -327,9 +327,10 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<B>, + shell: &mut Shell<'_, B>, ) -> event::Status { - let mut original_messages = Vec::new(); + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); let status = self.widget.on_event( event, @@ -337,12 +338,10 @@ where cursor_position, renderer, clipboard, - &mut original_messages, + &mut local_shell, ); - original_messages - .drain(..) - .for_each(|message| messages.push((self.mapper)(message))); + shell.merge(local_shell, &self.mapper); status } @@ -427,7 +426,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.element.widget.on_event( event, @@ -435,7 +434,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) } diff --git a/native/src/lib.rs b/native/src/lib.rs index 51b232e9..f340ec14 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -53,6 +53,7 @@ pub mod window; mod element; mod hasher; mod runtime; +mod shell; mod user_interface; // We disable debug capabilities on release builds unless the `debug` feature @@ -85,6 +86,7 @@ pub use overlay::Overlay; pub use program::Program; pub use renderer::Renderer; pub use runtime::Runtime; +pub use shell::Shell; pub use subscription::Subscription; pub use user_interface::{Cache, UserInterface}; pub use widget::Widget; diff --git a/native/src/overlay.rs b/native/src/overlay.rs index 1ac3cea5..e66d421a 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -10,7 +10,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size}; +use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Shell, Size}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay<Message, Renderer> @@ -71,7 +71,7 @@ where _cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - _messages: &mut Vec<Message>, + _shell: &mut Shell<'_, Message>, ) -> event::Status { event::Status::Ignored } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index f418a518..70cb417e 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -4,7 +4,7 @@ use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; -use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Size, Vector}; +use crate::{Clipboard, Hasher, Layout, Point, Rectangle, Shell, Size, Vector}; /// A generic [`Overlay`]. #[allow(missing_debug_implementations)] @@ -25,6 +25,11 @@ where Self { position, overlay } } + /// Returns the position of the [`Element`]. + pub fn position(&self) -> Point { + self.position + } + /// Translates the [`Element`]. pub fn translate(mut self, translation: Vector) -> Self { self.position = self.position + translation; @@ -57,7 +62,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.overlay.on_event( event, @@ -65,7 +70,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) } @@ -131,9 +136,10 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<B>, + shell: &mut Shell<'_, B>, ) -> event::Status { - let mut original_messages = Vec::new(); + let mut local_messages = Vec::new(); + let mut local_shell = Shell::new(&mut local_messages); let event_status = self.content.on_event( event, @@ -141,12 +147,10 @@ where cursor_position, renderer, clipboard, - &mut original_messages, + &mut local_shell, ); - original_messages - .drain(..) - .for_each(|message| messages.push((self.mapper)(message))); + shell.merge(local_shell, self.mapper); event_status } diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index ee3bee6e..6776a3ee 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -11,7 +11,7 @@ use crate::widget::scrollable::{self, Scrollable}; use crate::widget::Container; use crate::{ Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Size, Vector, Widget, + Rectangle, Shell, Size, Vector, Widget, }; pub use iced_style::menu::Style; @@ -222,7 +222,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.container.on_event( event.clone(), @@ -230,7 +230,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) } @@ -333,7 +333,7 @@ where cursor_position: Point, renderer: &Renderer, _clipboard: &mut dyn Clipboard, - _messages: &mut Vec<Message>, + _shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { diff --git a/native/src/shell.rs b/native/src/shell.rs new file mode 100644 index 00000000..e916f52d --- /dev/null +++ b/native/src/shell.rs @@ -0,0 +1,54 @@ +/// A connection to the state of a shell. +/// +/// A [`Widget`] can leverage a [`Shell`] to trigger changes in an application, +/// like publishing messages or invalidating the current layout. +/// +/// [`Widget`]: crate::Widget +#[derive(Debug)] +pub struct Shell<'a, Message> { + messages: &'a mut Vec<Message>, + is_layout_invalid: bool, +} + +impl<'a, Message> Shell<'a, Message> { + /// Creates a new [`Shell`] with the provided buffer of messages. + pub fn new(messages: &'a mut Vec<Message>) -> Self { + Self { + messages, + is_layout_invalid: false, + } + } + + /// Triggers the given function if the layout is invalid, cleaning it in the + /// process. + pub fn with_invalid_layout(&mut self, f: impl FnOnce()) { + if self.is_layout_invalid { + self.is_layout_invalid = false; + + f() + } + } + + /// Publish the given `Message` for an application to process it. + pub fn publish(&mut self, message: Message) { + self.messages.push(message); + } + + /// Invalidates the current application layout. + /// + /// The shell will relayout the application widgets. + pub fn invalidate_layout(&mut self) { + self.is_layout_invalid = true; + } + + /// Merges the current [`Shell`] with another one by applying the given + /// function to the messages of the latter. + /// + /// This method is useful for composition. + pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { + self.messages.extend(other.messages.drain(..).map(f)); + + self.is_layout_invalid = + self.is_layout_invalid || other.is_layout_invalid; + } +} diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index f44ed1fb..5d24bf4f 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -3,7 +3,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Element, Layout, Point, Rectangle, Size}; +use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size}; use std::hash::Hasher; @@ -179,7 +179,7 @@ where /// let event_statuses = user_interface.update( /// &events, /// cursor_position, - /// &renderer, + /// &mut renderer, /// &mut clipboard, /// &mut messages /// ); @@ -196,16 +196,18 @@ where &mut self, events: &[Event], cursor_position: Point, - renderer: &Renderer, + renderer: &mut Renderer, clipboard: &mut dyn Clipboard, messages: &mut Vec<Message>, ) -> Vec<event::Status> { let (base_cursor, overlay_statuses) = if let Some(mut overlay) = self.root.overlay(Layout::new(&self.base.layout)) { - let layer = Self::overlay_layer( + let bounds = self.bounds; + + let mut layer = Self::overlay_layer( self.overlay.take(), - self.bounds, + bounds, &mut overlay, renderer, ); @@ -214,14 +216,27 @@ where .iter() .cloned() .map(|event| { - overlay.on_event( + let mut shell = Shell::new(messages); + + let event_status = overlay.on_event( event, Layout::new(&layer.layout), cursor_position, renderer, clipboard, - messages, - ) + &mut shell, + ); + + shell.with_invalid_layout(|| { + layer = Self::overlay_layer( + None, + bounds, + &mut overlay, + renderer, + ); + }); + + event_status }) .collect(); @@ -245,15 +260,34 @@ where .cloned() .zip(overlay_statuses.into_iter()) .map(|(event, overlay_status)| { + let mut shell = Shell::new(messages); + let event_status = self.root.widget.on_event( event, Layout::new(&self.base.layout), base_cursor, renderer, clipboard, - messages, + &mut shell, ); + shell.with_invalid_layout(|| { + let hash = { + let hasher = &mut crate::Hasher::default(); + self.root.hash_layout(hasher); + + hasher.finish() + }; + + let layout = renderer.layout( + &self.root, + &layout::Limits::new(Size::ZERO, self.bounds), + ); + + self.base = Layer { layout, hash }; + self.overlay = None; + }); + event_status.merge(overlay_status) }) .collect() @@ -313,7 +347,7 @@ where /// let event_statuses = user_interface.update( /// &events, /// cursor_position, - /// &renderer, + /// &mut renderer, /// &mut clipboard, /// &mut messages /// ); diff --git a/native/src/widget.rs b/native/src/widget.rs index 07214b16..e2613e67 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -75,7 +75,7 @@ use crate::layout; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle}; +use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle, Shell}; /// A component that displays information and allows interaction. /// @@ -163,7 +163,7 @@ where _cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - _messages: &mut Vec<Message>, + _shell: &mut Shell<'_, Message>, ) -> event::Status { event::Status::Ignored } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 1d785f35..686289e4 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -9,7 +9,7 @@ use crate::renderer; use crate::touch; use crate::{ Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding, - Point, Rectangle, Vector, Widget, + Point, Rectangle, Shell, Vector, Widget, }; use std::hash::Hash; @@ -197,7 +197,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { if let event::Status::Captured = self.content.on_event( event.clone(), @@ -205,7 +205,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) { return event::Status::Captured; } @@ -232,7 +232,7 @@ where self.state.is_pressed = false; if bounds.contains(cursor_position) { - messages.push(on_press); + shell.publish(on_press); } return event::Status::Captured; diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 81611426..2af2d60c 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -11,7 +11,7 @@ use crate::touch; use crate::widget::{self, Row, Text}; use crate::{ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point, - Rectangle, Widget, + Rectangle, Shell, Widget, }; pub use iced_style::checkbox::{Style, StyleSheet}; @@ -171,7 +171,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) @@ -179,7 +179,7 @@ where let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { - messages.push((self.on_toggle)(!self.is_checked)); + shell.publish((self.on_toggle)(!self.is_checked)); return event::Status::Captured; } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 0d4d6fa7..4c43c8c8 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -8,7 +8,7 @@ use crate::overlay; use crate::renderer; use crate::{ Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Widget, + Rectangle, Shell, Widget, }; use std::u32; @@ -146,7 +146,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.children .iter_mut() @@ -158,7 +158,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) }) .fold(event::Status::Ignored, event::Status::merge) diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 596af7fd..756d9d1b 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -9,7 +9,7 @@ use crate::overlay; use crate::renderer; use crate::{ Background, Clipboard, Color, Element, Hasher, Layout, Length, Padding, - Point, Rectangle, Widget, + Point, Rectangle, Shell, Widget, }; use std::u32; @@ -167,7 +167,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.content.widget.on_event( event, @@ -175,7 +175,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) } diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 95e5c6e4..0fc766c3 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -5,8 +5,8 @@ use crate::layout; use crate::mouse; use crate::renderer; use crate::{ - Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Vector, - Widget, + Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Shell, Size, + Vector, Widget, }; use std::hash::Hash; @@ -169,7 +169,7 @@ where cursor_position: Point, renderer: &Renderer, _clipboard: &mut dyn Clipboard, - _messages: &mut Vec<Message>, + _shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3637822b..24b87eed 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -34,8 +34,8 @@ use crate::overlay; use crate::renderer; use crate::touch; use crate::{ - Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, - Vector, Widget, + Clipboard, Color, Element, Hasher, Layout, Length, Point, Rectangle, Shell, + Size, Vector, Widget, }; pub use iced_style::pane_grid::{Line, StyleSheet}; @@ -205,7 +205,7 @@ where &mut self, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) { let mut clicked_region = self.elements.iter().zip(layout.children()).filter( @@ -214,7 +214,7 @@ where if let Some(((pane, content), layout)) = clicked_region.next() { if let Some(on_click) = &self.on_click { - messages.push(on_click(*pane)); + shell.publish(on_click(*pane)); } if let Some(on_drag) = &self.on_drag { @@ -226,7 +226,7 @@ where self.state.pick_pane(pane, origin); - messages.push(on_drag(DragEvent::Picked { pane: *pane })); + shell.publish(on_drag(DragEvent::Picked { pane: *pane })); } } } @@ -236,7 +236,7 @@ where &mut self, layout: Layout<'_>, cursor_position: Point, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { if let Some((_, on_resize)) = &self.on_resize { if let Some((split, _)) = self.state.picked_split() { @@ -263,7 +263,7 @@ where } }; - messages.push(on_resize(ResizeEvent { split, ratio })); + shell.publish(on_resize(ResizeEvent { split, ratio })); return event::Status::Captured; } @@ -362,7 +362,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { let mut event_status = event::Status::Ignored; @@ -395,15 +395,11 @@ where if let Some((split, axis, _)) = clicked_split { self.state.pick_split(&split, axis); } else { - self.click_pane( - layout, - cursor_position, - messages, - ); + self.click_pane(layout, cursor_position, shell); } } None => { - self.click_pane(layout, cursor_position, messages); + self.click_pane(layout, cursor_position, shell); } } } @@ -430,7 +426,7 @@ where _ => DragEvent::Canceled { pane }, }; - messages.push(on_drag(event)); + shell.publish(on_drag(event)); } self.state.idle(); @@ -445,7 +441,7 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { event_status = - self.trigger_resize(layout, cursor_position, messages); + self.trigger_resize(layout, cursor_position, shell); } _ => {} } @@ -464,7 +460,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, is_picked, ) }) diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index c44506dd..533827b7 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -5,7 +5,9 @@ use crate::overlay; use crate::renderer; use crate::widget::container; use crate::widget::pane_grid::TitleBar; -use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size}; +use crate::{ + Clipboard, Element, Hasher, Layout, Point, Rectangle, Shell, Size, +}; /// The content of a [`Pane`]. /// @@ -160,7 +162,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, is_picked: bool, ) -> event::Status { let mut event_status = event::Status::Ignored; @@ -174,7 +176,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ); children.next().unwrap() @@ -191,7 +193,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) }; diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 070cf404..353e1ce9 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -5,7 +5,7 @@ use crate::overlay; use crate::renderer; use crate::widget::container; use crate::{ - Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size, + Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Shell, Size, }; /// The title bar of a [`Pane`]. @@ -218,7 +218,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { let mut children = layout.children(); let padded = children.next().unwrap(); @@ -235,7 +235,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) } else { event::Status::Ignored @@ -247,7 +247,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ); control_status.merge(title_status) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 9d1a86ec..cb781a89 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -11,7 +11,7 @@ use crate::text::{self, Text}; use crate::touch; use crate::{ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle, - Size, Widget, + Shell, Size, Widget, }; use std::borrow::Cow; @@ -245,7 +245,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) @@ -271,7 +271,7 @@ where }; if let Some(last_selection) = self.last_selection.take() { - messages.push((self.on_selected)(last_selection)); + shell.publish((self.on_selected)(last_selection)); *self.is_open = false; @@ -312,7 +312,7 @@ where }; if let Some(next_option) = next_option { - messages.push((self.on_selected)(next_option.clone())); + shell.publish((self.on_selected)(next_option.clone())); } event::Status::Captured diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 86ad4c4e..523773bd 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -11,7 +11,7 @@ use crate::touch; use crate::widget::{self, Row, Text}; use crate::{ Alignment, Clipboard, Color, Element, Hasher, Layout, Length, Point, - Rectangle, Widget, + Rectangle, Shell, Widget, }; pub use iced_style::radio::{Style, StyleSheet}; @@ -187,13 +187,13 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { if layout.bounds().contains(cursor_position) { - messages.push(self.on_click.clone()); + shell.publish(self.on_click.clone()); return event::Status::Captured; } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 6fe3284b..a0174f79 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -6,7 +6,7 @@ use crate::overlay; use crate::renderer; use crate::{ Alignment, Clipboard, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Widget, + Rectangle, Shell, Widget, }; use std::hash::Hash; @@ -145,7 +145,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.children .iter_mut() @@ -157,7 +157,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) }) .fold(event::Status::Ignored, event::Status::merge) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 2bf2ea5e..fac8af9e 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -8,7 +8,7 @@ use crate::touch; use crate::widget::Column; use crate::{ Alignment, Background, Clipboard, Color, Element, Hasher, Layout, Length, - Padding, Point, Rectangle, Size, Vector, Widget, + Padding, Point, Rectangle, Shell, Size, Vector, Widget, }; use std::{f32, hash::Hash, u32}; @@ -144,14 +144,14 @@ impl<'a, Message, Renderer: crate::Renderer> Scrollable<'a, Message, Renderer> { &self, bounds: Rectangle, content_bounds: Rectangle, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) { if content_bounds.height <= bounds.height { return; } if let Some(on_scroll) = &self.on_scroll { - messages.push(on_scroll( + shell.publish(on_scroll( self.state.offset.absolute(bounds, content_bounds) / (content_bounds.height - bounds.height), )); @@ -251,7 +251,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); @@ -286,7 +286,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) }; @@ -307,7 +307,7 @@ where } } - self.notify_on_scroll(bounds, content_bounds, messages); + self.notify_on_scroll(bounds, content_bounds, shell); return event::Status::Captured; } @@ -336,7 +336,7 @@ where self.notify_on_scroll( bounds, content_bounds, - messages, + shell, ); } } @@ -377,7 +377,7 @@ where content_bounds, ); - self.notify_on_scroll(bounds, content_bounds, messages); + self.notify_on_scroll(bounds, content_bounds, shell); return event::Status::Captured; } @@ -409,7 +409,7 @@ where self.notify_on_scroll( bounds, content_bounds, - messages, + shell, ); return event::Status::Captured; diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 3ce53f6c..65632a5c 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -8,7 +8,7 @@ use crate::renderer; use crate::touch; use crate::{ Background, Clipboard, Color, Element, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + Rectangle, Shell, Size, Widget, }; use std::hash::Hash; @@ -191,7 +191,7 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { let is_dragging = self.state.is_dragging; @@ -220,7 +220,7 @@ where }; if (self.value.into() - new_value.into()).abs() > f64::EPSILON { - messages.push((self.on_change)(new_value)); + shell.publish((self.on_change)(new_value)); self.value = new_value; } @@ -241,7 +241,7 @@ where | Event::Touch(touch::Event::FingerLost { .. }) => { if is_dragging { if let Some(on_release) = self.on_release.clone() { - messages.push(on_release); + shell.publish(on_release); } self.state.is_dragging = false; diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 40c6c573..5e1726db 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -21,7 +21,7 @@ use crate::text::{self, Text}; use crate::touch; use crate::{ Clipboard, Color, Element, Hasher, Layout, Length, Padding, Point, - Rectangle, Size, Vector, Widget, + Rectangle, Shell, Size, Vector, Widget, }; use std::u32; @@ -384,7 +384,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) @@ -509,7 +509,7 @@ where editor.insert(c); let message = (self.on_change)(editor.contents()); - messages.push(message); + shell.publish(message); return event::Status::Captured; } @@ -521,7 +521,7 @@ where match key_code { keyboard::KeyCode::Enter => { if let Some(on_submit) = self.on_submit.clone() { - messages.push(on_submit); + shell.publish(on_submit); } } keyboard::KeyCode::Backspace => { @@ -551,7 +551,7 @@ where editor.backspace(); let message = (self.on_change)(editor.contents()); - messages.push(message); + shell.publish(message); } keyboard::KeyCode::Delete => { if platform::is_jump_modifier_pressed(modifiers) @@ -582,7 +582,7 @@ where editor.delete(); let message = (self.on_change)(editor.contents()); - messages.push(message); + shell.publish(message); } keyboard::KeyCode::Left => { if platform::is_jump_modifier_pressed(modifiers) @@ -674,7 +674,7 @@ where editor.delete(); let message = (self.on_change)(editor.contents()); - messages.push(message); + shell.publish(message); } keyboard::KeyCode::V => { if self.state.keyboard_modifiers.command() { @@ -700,7 +700,7 @@ where editor.paste(content.clone()); let message = (self.on_change)(editor.contents()); - messages.push(message); + shell.publish(message); self.state.is_pasting = Some(content); } else { diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 2dcc3ffe..6cecc7e1 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -10,7 +10,7 @@ use crate::text; use crate::widget::{Row, Text}; use crate::{ Alignment, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Widget, + Rectangle, Shell, Widget, }; pub use iced_style::toggler::{Style, StyleSheet}; @@ -173,14 +173,14 @@ where cursor_position: Point, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { - messages.push((self.on_toggle)(!self.is_active)); + shell.publish((self.on_toggle)(!self.is_active)); event::Status::Captured } else { diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index c35005e0..79a57824 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -11,8 +11,8 @@ use crate::text; use crate::widget::container; use crate::widget::text::Text; use crate::{ - Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Size, - Vector, Widget, + Clipboard, Element, Event, Hasher, Layout, Length, Padding, Point, Shell, + Size, Vector, Widget, }; /// An element to display a widget over another. @@ -130,7 +130,7 @@ where cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, - messages: &mut Vec<Message>, + shell: &mut Shell<'_, Message>, ) -> event::Status { self.content.widget.on_event( event, @@ -138,7 +138,7 @@ where cursor_position, renderer, clipboard, - messages, + shell, ) } |