diff options
Diffstat (limited to 'widget')
38 files changed, 1742 insertions, 1415 deletions
| diff --git a/widget/src/action.rs b/widget/src/action.rs new file mode 100644 index 00000000..1dd3a787 --- /dev/null +++ b/widget/src/action.rs @@ -0,0 +1,89 @@ +use crate::core::event; +use crate::core::time::Instant; +use crate::core::window; + +/// A runtime action that can be performed by some widgets. +#[derive(Debug, Clone)] +pub struct Action<Message> { +    message_to_publish: Option<Message>, +    redraw_request: Option<window::RedrawRequest>, +    event_status: event::Status, +} + +impl<Message> Action<Message> { +    fn new() -> Self { +        Self { +            message_to_publish: None, +            redraw_request: None, +            event_status: event::Status::Ignored, +        } +    } + +    /// Creates a new "capturing" [`Action`]. A capturing [`Action`] +    /// will make other widgets consider it final and prevent further +    /// processing. +    /// +    /// Prevents "event bubbling". +    pub fn capture() -> Self { +        Self { +            event_status: event::Status::Captured, +            ..Self::new() +        } +    } + +    /// Creates a new [`Action`] that publishes the given `Message` for +    /// the application to handle. +    /// +    /// Publishing a `Message` always produces a redraw. +    pub fn publish(message: Message) -> Self { +        Self { +            message_to_publish: Some(message), +            ..Self::new() +        } +    } + +    /// Creates a new [`Action`] that requests a redraw to happen as +    /// soon as possible; without publishing any `Message`. +    pub fn request_redraw() -> Self { +        Self { +            redraw_request: Some(window::RedrawRequest::NextFrame), +            ..Self::new() +        } +    } + +    /// Creates a new [`Action`] that requests a redraw to happen at +    /// the given [`Instant`]; without publishing any `Message`. +    /// +    /// This can be useful to efficiently animate content, like a +    /// blinking caret on a text input. +    pub fn request_redraw_at(at: Instant) -> Self { +        Self { +            redraw_request: Some(window::RedrawRequest::At(at)), +            ..Self::new() +        } +    } + +    /// Marks the [`Action`] as "capturing". See [`Self::capture`]. +    pub fn and_capture(mut self) -> Self { +        self.event_status = event::Status::Captured; +        self +    } + +    /// Converts the [`Action`] into its internal parts. +    /// +    /// This method is meant to be used by runtimes, libraries, or internal +    /// widget implementations. +    pub fn into_inner( +        self, +    ) -> ( +        Option<Message>, +        Option<window::RedrawRequest>, +        event::Status, +    ) { +        ( +            self.message_to_publish, +            self.redraw_request, +            self.event_status, +        ) +    } +} diff --git a/widget/src/button.rs b/widget/src/button.rs index a3394a01..9eac2e4c 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -17,7 +17,6 @@  //! }  //! ```  use crate::core::border::{self, Border}; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay; @@ -26,9 +25,10 @@ use crate::core::theme::palette;  use crate::core::touch;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::Operation; +use crate::core::window;  use crate::core::{ -    Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle, -    Shadow, Shell, Size, Theme, Vector, Widget, +    Background, Clipboard, Color, Element, Event, Layout, Length, Padding, +    Rectangle, Shadow, Shell, Size, Theme, Vector, Widget,  };  /// A generic widget that produces a message when pressed. @@ -81,6 +81,7 @@ where      padding: Padding,      clip: bool,      class: Theme::Class<'a>, +    status: Option<Status>,  }  enum OnPress<'a, Message> { @@ -117,6 +118,7 @@ where              padding: DEFAULT_PADDING,              clip: false,              class: Theme::default(), +            status: None,          }      } @@ -270,7 +272,7 @@ where          });      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -280,8 +282,8 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        if let event::Status::Captured = self.content.as_widget_mut().on_event( +    ) { +        self.content.as_widget_mut().update(              &mut tree.children[0],              event.clone(),              layout.children().next().unwrap(), @@ -290,8 +292,10 @@ where              clipboard,              shell,              viewport, -        ) { -            return event::Status::Captured; +        ); + +        if shell.is_event_captured() { +            return;          }          match event { @@ -305,7 +309,7 @@ where                          state.is_pressed = true; -                        return event::Status::Captured; +                        shell.capture_event();                      }                  }              } @@ -324,7 +328,7 @@ where                              shell.publish(on_press);                          } -                        return event::Status::Captured; +                        shell.capture_event();                      }                  }              } @@ -336,7 +340,25 @@ where              _ => {}          } -        event::Status::Ignored +        let current_status = if self.on_press.is_none() { +            Status::Disabled +        } else if cursor.is_over(layout.bounds()) { +            let state = tree.state.downcast_ref::<State>(); + +            if state.is_pressed { +                Status::Pressed +            } else { +                Status::Hovered +            } +        } else { +            Status::Active +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.status = Some(current_status); +        } else if self.status.is_some_and(|status| status != current_status) { +            shell.request_redraw(); +        }      }      fn draw( @@ -351,23 +373,8 @@ where      ) {          let bounds = layout.bounds();          let content_layout = layout.children().next().unwrap(); -        let is_mouse_over = cursor.is_over(bounds); - -        let status = if self.on_press.is_none() { -            Status::Disabled -        } else if is_mouse_over { -            let state = tree.state.downcast_ref::<State>(); - -            if state.is_pressed { -                Status::Pressed -            } else { -                Status::Hovered -            } -        } else { -            Status::Active -        }; - -        let style = theme.style(&self.class, status); +        let style = +            theme.style(&self.class, self.status.unwrap_or(Status::Disabled));          if style.background.is_some()              || style.border.width > 0.0 diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 9fbccf82..23cc3f2b 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -48,24 +48,24 @@  //!     canvas(Circle { radius: 50.0 }).into()  //! }  //! ``` -pub mod event; -  mod program; -pub use event::Event;  pub use program::Program; +pub use crate::core::event::Event;  pub use crate::graphics::cache::Group;  pub use crate::graphics::geometry::{      fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,      LineJoin, Path, Stroke, Style, Text,  }; +pub use crate::Action; -use crate::core; +use crate::core::event;  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::renderer;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{      Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,  }; @@ -148,6 +148,7 @@ where      message_: PhantomData<Message>,      theme_: PhantomData<Theme>,      renderer_: PhantomData<Renderer>, +    last_mouse_interaction: Option<mouse::Interaction>,  }  impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer> @@ -166,6 +167,7 @@ where              message_: PhantomData,              theme_: PhantomData,              renderer_: PhantomData, +            last_mouse_interaction: None,          }      } @@ -213,42 +215,63 @@ where          layout::atomic(limits, self.width, self.height)      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree, -        event: core::Event, +        event: Event,          layout: Layout<'_>,          cursor: mouse::Cursor, -        _renderer: &Renderer, +        renderer: &Renderer,          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>, -        _viewport: &Rectangle, -    ) -> event::Status { +        viewport: &Rectangle, +    ) {          let bounds = layout.bounds(); -        let canvas_event = match event { -            core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), -            core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), -            core::Event::Keyboard(keyboard_event) => { -                Some(Event::Keyboard(keyboard_event)) -            } -            core::Event::Window(_) => None, -        }; - -        if let Some(canvas_event) = canvas_event { -            let state = tree.state.downcast_mut::<P::State>(); +        let state = tree.state.downcast_mut::<P::State>(); +        let is_redraw_request = matches!( +            event, +            Event::Window(window::Event::RedrawRequested(_now)), +        ); -            let (event_status, message) = -                self.program.update(state, canvas_event, bounds, cursor); +        if let Some(action) = self.program.update(state, event, bounds, cursor) +        { +            let (message, redraw_request, event_status) = action.into_inner();              if let Some(message) = message {                  shell.publish(message);              } -            return event_status; +            if let Some(redraw_request) = redraw_request { +                match redraw_request { +                    window::RedrawRequest::NextFrame => { +                        shell.request_redraw(); +                    } +                    window::RedrawRequest::At(at) => { +                        shell.request_redraw_at(at); +                    } +                } +            } + +            if event_status == event::Status::Captured { +                shell.capture_event(); +            }          } -        event::Status::Ignored +        if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { +            let mouse_interaction = self +                .mouse_interaction(tree, layout, cursor, viewport, renderer); + +            if is_redraw_request { +                self.last_mouse_interaction = Some(mouse_interaction); +            } else if self.last_mouse_interaction.is_some_and( +                |last_mouse_interaction| { +                    last_mouse_interaction != mouse_interaction +                }, +            ) { +                shell.request_redraw(); +            } +        }      }      fn mouse_interaction( diff --git a/widget/src/canvas/event.rs b/widget/src/canvas/event.rs deleted file mode 100644 index a8eb47f7..00000000 --- a/widget/src/canvas/event.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! Handle events of a canvas. -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::touch; - -pub use crate::core::event::Status; - -/// A [`Canvas`] event. -/// -/// [`Canvas`]: crate::Canvas -#[derive(Debug, Clone, PartialEq)] -pub enum Event { -    /// A mouse event. -    Mouse(mouse::Event), - -    /// A touch event. -    Touch(touch::Event), - -    /// A keyboard event. -    Keyboard(keyboard::Event), -} diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs index a7ded0f4..c68b2830 100644 --- a/widget/src/canvas/program.rs +++ b/widget/src/canvas/program.rs @@ -1,8 +1,8 @@ -use crate::canvas::event::{self, Event};  use crate::canvas::mouse; -use crate::canvas::Geometry; +use crate::canvas::{Event, Geometry};  use crate::core::Rectangle;  use crate::graphics::geometry; +use crate::Action;  /// The state and logic of a [`Canvas`].  /// @@ -22,8 +22,9 @@ where      /// When a [`Program`] is used in a [`Canvas`], the runtime will call this      /// method for each [`Event`].      /// -    /// This method can optionally return a `Message` to notify an application -    /// of any meaningful interactions. +    /// This method can optionally return an [`Action`] to either notify an +    /// application of any meaningful interactions, capture the event, or +    /// request a redraw.      ///      /// By default, this method does and returns nothing.      /// @@ -34,8 +35,8 @@ where          _event: Event,          _bounds: Rectangle,          _cursor: mouse::Cursor, -    ) -> (event::Status, Option<Message>) { -        (event::Status::Ignored, None) +    ) -> Option<Action<Message>> { +        None      }      /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. @@ -84,7 +85,7 @@ where          event: Event,          bounds: Rectangle,          cursor: mouse::Cursor, -    ) -> (event::Status, Option<Message>) { +    ) -> Option<Action<Message>> {          T::update(self, state, event, bounds, cursor)      } diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 819f0d9d..625dee7c 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -31,7 +31,6 @@  //! ```  //!   use crate::core::alignment; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::renderer; @@ -40,9 +39,10 @@ use crate::core::theme::palette;  use crate::core::touch;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{ -    Background, Border, Clipboard, Color, Element, Layout, Length, Pixels, -    Rectangle, Shell, Size, Theme, Widget, +    Background, Border, Clipboard, Color, Element, Event, Layout, Length, +    Pixels, Rectangle, Shell, Size, Theme, Widget,  };  /// A box that can be checked. @@ -100,6 +100,7 @@ pub struct Checkbox<      font: Option<Renderer::Font>,      icon: Icon<Renderer::Font>,      class: Theme::Class<'a>, +    last_status: Option<Status>,  }  impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> @@ -139,6 +140,7 @@ where                  shaping: text::Shaping::Basic,              },              class: Theme::default(), +            last_status: None,          }      } @@ -300,7 +302,7 @@ where          )      } -    fn on_event( +    fn update(          &mut self,          _tree: &mut Tree,          event: Event, @@ -310,7 +312,7 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          match event {              Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))              | Event::Touch(touch::Event::FingerPressed { .. }) => { @@ -319,14 +321,35 @@ where                  if mouse_over {                      if let Some(on_toggle) = &self.on_toggle {                          shell.publish((on_toggle)(!self.is_checked)); -                        return event::Status::Captured; +                        shell.capture_event();                      }                  }              }              _ => {}          } -        event::Status::Ignored +        let current_status = { +            let is_mouse_over = cursor.is_over(layout.bounds()); +            let is_disabled = self.on_toggle.is_none(); +            let is_checked = self.is_checked; + +            if is_disabled { +                Status::Disabled { is_checked } +            } else if is_mouse_over { +                Status::Hovered { is_checked } +            } else { +                Status::Active { is_checked } +            } +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.last_status = Some(current_status); +        } else if self +            .last_status +            .is_some_and(|status| status != current_status) +        { +            shell.request_redraw(); +        }      }      fn mouse_interaction( @@ -351,24 +374,17 @@ where          theme: &Theme,          defaults: &renderer::Style,          layout: Layout<'_>, -        cursor: mouse::Cursor, +        _cursor: mouse::Cursor,          viewport: &Rectangle,      ) { -        let is_mouse_over = cursor.is_over(layout.bounds()); -        let is_disabled = self.on_toggle.is_none(); -        let is_checked = self.is_checked; -          let mut children = layout.children(); -        let status = if is_disabled { -            Status::Disabled { is_checked } -        } else if is_mouse_over { -            Status::Hovered { is_checked } -        } else { -            Status::Active { is_checked } -        }; - -        let style = theme.style(&self.class, status); +        let style = theme.style( +            &self.class, +            self.last_status.unwrap_or(Status::Disabled { +                is_checked: self.is_checked, +            }), +        );          {              let layout = children.next().unwrap(); diff --git a/widget/src/column.rs b/widget/src/column.rs index 213f68fc..a3efab94 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -1,14 +1,13 @@  //! Distribute content vertically.  use crate::core::alignment::{self, Alignment}; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget::{Operation, Tree};  use crate::core::{ -    Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, Shell, -    Size, Vector, Widget, +    Clipboard, Element, Event, Layout, Length, Padding, Pixels, Rectangle, +    Shell, Size, Vector, Widget,  };  /// A container that distributes its contents vertically. @@ -258,7 +257,7 @@ where          });      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -268,24 +267,24 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        self.children +    ) { +        for ((child, state), layout) in self +            .children              .iter_mut()              .zip(&mut tree.children)              .zip(layout.children()) -            .map(|((child, state), layout)| { -                child.as_widget_mut().on_event( -                    state, -                    event.clone(), -                    layout, -                    cursor, -                    renderer, -                    clipboard, -                    shell, -                    viewport, -                ) -            }) -            .fold(event::Status::Ignored, event::Status::merge) +        { +            child.as_widget_mut().update( +                state, +                event.clone(), +                layout, +                cursor, +                renderer, +                clipboard, +                shell, +                viewport, +            ); +        }      }      fn mouse_interaction( diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index e300f1d0..8b8e895d 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -54,7 +54,6 @@  //!     }  //! }  //! ``` -use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::keyboard::key;  use crate::core::layout::{self, Layout}; @@ -64,8 +63,10 @@ use crate::core::renderer;  use crate::core::text;  use crate::core::time::Instant;  use crate::core::widget::{self, Widget}; +use crate::core::window;  use crate::core::{ -    Clipboard, Element, Length, Padding, Rectangle, Shell, Size, Theme, Vector, +    Clipboard, Element, Event, Length, Padding, Rectangle, Shell, Size, Theme, +    Vector,  };  use crate::overlay::menu;  use crate::text::LineHeight; @@ -509,7 +510,7 @@ where          vec![widget::Tree::new(&self.text_input as &dyn Widget<_, _, _>)]      } -    fn on_event( +    fn update(          &mut self,          tree: &mut widget::Tree,          event: Event, @@ -519,7 +520,7 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { +    ) {          let menu = tree.state.downcast_mut::<Menu<T>>();          let started_focused = { @@ -538,7 +539,7 @@ where          let mut local_shell = Shell::new(&mut local_messages);          // Provide it to the widget -        let mut event_status = self.text_input.on_event( +        self.text_input.update(              &mut tree.children[0],              event.clone(),              layout, @@ -549,13 +550,27 @@ where              viewport,          ); +        if local_shell.is_event_captured() { +            shell.capture_event(); +        } + +        if let Some(redraw_request) = local_shell.redraw_request() { +            match redraw_request { +                window::RedrawRequest::NextFrame => { +                    shell.request_redraw(); +                } +                window::RedrawRequest::At(at) => { +                    shell.request_redraw_at(at); +                } +            } +        } +          // Then finally react to them here          for message in local_messages {              let TextInputEvent::TextChanged(new_value) = message;              if let Some(on_input) = &self.on_input {                  shell.publish((on_input)(new_value.clone())); -                published_message_to_shell = true;              }              // Couple the filtered options with the `ComboBox` @@ -576,6 +591,7 @@ where                  );              });              shell.invalidate_layout(); +            shell.request_redraw();          }          let is_focused = { @@ -619,9 +635,9 @@ where                                  }                              } -                            event_status = event::Status::Captured; +                            shell.capture_event(); +                            shell.request_redraw();                          } -                          (key::Named::ArrowUp, _) | (key::Named::Tab, true) => {                              if let Some(index) = &mut menu.hovered_option {                                  if *index == 0 { @@ -656,7 +672,8 @@ where                                  }                              } -                            event_status = event::Status::Captured; +                            shell.capture_event(); +                            shell.request_redraw();                          }                          (key::Named::ArrowDown, _)                          | (key::Named::Tab, false) @@ -703,7 +720,8 @@ where                                  }                              } -                            event_status = event::Status::Captured; +                            shell.capture_event(); +                            shell.request_redraw();                          }                          _ => {}                      } @@ -724,7 +742,7 @@ where                  published_message_to_shell = true;                  // Unfocus the input -                let _ = self.text_input.on_event( +                self.text_input.update(                      &mut tree.children[0],                      Event::Mouse(mouse::Event::ButtonPressed(                          mouse::Button::Left, @@ -761,8 +779,6 @@ where                  }              }          } - -        event_status      }      fn mouse_interaction( diff --git a/widget/src/container.rs b/widget/src/container.rs index f4993ac9..b7b2b39e 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -21,7 +21,6 @@  //! ```  use crate::core::alignment::{self, Alignment};  use crate::core::border::{self, Border}; -use crate::core::event::{self, Event};  use crate::core::gradient::{self, Gradient};  use crate::core::layout;  use crate::core::mouse; @@ -30,7 +29,7 @@ use crate::core::renderer;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::{self, Operation};  use crate::core::{ -    self, color, Background, Clipboard, Color, Element, Layout, Length, +    self, color, Background, Clipboard, Color, Element, Event, Layout, Length,      Padding, Pixels, Point, Rectangle, Shadow, Shell, Size, Theme, Vector,      Widget,  }; @@ -298,7 +297,7 @@ where          );      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -308,8 +307,8 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        self.content.as_widget_mut().on_event( +    ) { +        self.content.as_widget_mut().update(              tree,              event,              layout.children().next().unwrap(), @@ -318,7 +317,7 @@ where              clipboard,              shell,              viewport, -        ) +        );      }      fn mouse_interaction( diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 52290a54..33dff647 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -363,12 +363,11 @@ where      Theme: 'a,      Renderer: core::Renderer + 'a,  { -    use crate::core::event::{self, Event};      use crate::core::layout::{self, Layout};      use crate::core::mouse;      use crate::core::renderer;      use crate::core::widget::tree::{self, Tree}; -    use crate::core::{Rectangle, Shell, Size}; +    use crate::core::{Event, Rectangle, Shell, Size};      struct Opaque<'a, Message, Theme, Renderer> {          content: Element<'a, Message, Theme, Renderer>, @@ -439,7 +438,7 @@ where                  .operate(state, layout, renderer, operation);          } -        fn on_event( +        fn update(              &mut self,              state: &mut Tree,              event: Event, @@ -449,25 +448,19 @@ where              clipboard: &mut dyn core::Clipboard,              shell: &mut Shell<'_, Message>,              viewport: &Rectangle, -        ) -> event::Status { +        ) {              let is_mouse_press = matches!(                  event,                  core::Event::Mouse(mouse::Event::ButtonPressed(_))              ); -            if let core::event::Status::Captured = -                self.content.as_widget_mut().on_event( -                    state, event, layout, cursor, renderer, clipboard, shell, -                    viewport, -                ) -            { -                return event::Status::Captured; -            } +            self.content.as_widget_mut().update( +                state, event, layout, cursor, renderer, clipboard, shell, +                viewport, +            );              if is_mouse_press && cursor.is_over(layout.bounds()) { -                event::Status::Captured -            } else { -                event::Status::Ignored +                shell.capture_event();              }          } @@ -530,18 +523,18 @@ where      Theme: 'a,      Renderer: core::Renderer + 'a,  { -    use crate::core::event::{self, Event};      use crate::core::layout::{self, Layout};      use crate::core::mouse;      use crate::core::renderer;      use crate::core::widget::tree::{self, Tree}; -    use crate::core::{Rectangle, Shell, Size}; +    use crate::core::{Event, Rectangle, Shell, Size};      struct Hover<'a, Message, Theme, Renderer> {          base: Element<'a, Message, Theme, Renderer>,          top: Element<'a, Message, Theme, Renderer>,          is_top_focused: bool,          is_top_overlay_active: bool, +        is_hovered: bool,      }      impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> @@ -648,7 +641,7 @@ where              }          } -        fn on_event( +        fn update(              &mut self,              tree: &mut Tree,              event: Event, @@ -658,11 +651,13 @@ where              clipboard: &mut dyn core::Clipboard,              shell: &mut Shell<'_, Message>,              viewport: &Rectangle, -        ) -> event::Status { +        ) {              let mut children = layout.children().zip(&mut tree.children);              let (base_layout, base_tree) = children.next().unwrap();              let (top_layout, top_tree) = children.next().unwrap(); +            let is_hovered = cursor.is_over(layout.bounds()); +              if matches!(event, Event::Window(window::Event::RedrawRequested(_)))              {                  let mut count_focused = operation::focusable::count(); @@ -678,19 +673,23 @@ where                      operation::Outcome::Some(count) => count.focused.is_some(),                      _ => false,                  }; + +                self.is_hovered = is_hovered; +            } else if is_hovered != self.is_hovered { +                shell.request_redraw();              } -            let top_status = if matches!( +            if matches!(                  event,                  Event::Mouse(                      mouse::Event::CursorMoved { .. }                          | mouse::Event::ButtonReleased(_)                  ) -            ) || cursor.is_over(layout.bounds()) +            ) || is_hovered                  || self.is_top_focused                  || self.is_top_overlay_active              { -                self.top.as_widget_mut().on_event( +                self.top.as_widget_mut().update(                      top_tree,                      event.clone(),                      top_layout, @@ -699,16 +698,14 @@ where                      clipboard,                      shell,                      viewport, -                ) -            } else { -                event::Status::Ignored +                );              }; -            if top_status == event::Status::Captured { -                return top_status; +            if shell.is_event_captured() { +                return;              } -            self.base.as_widget_mut().on_event( +            self.base.as_widget_mut().update(                  base_tree,                  event.clone(),                  base_layout, @@ -717,7 +714,7 @@ where                  clipboard,                  shell,                  viewport, -            ) +            );          }          fn mouse_interaction( @@ -777,6 +774,7 @@ where          top: top.into(),          is_top_focused: false,          is_top_overlay_active: false, +        is_hovered: false,      })  } diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index b1aad22c..20a7955f 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -1,13 +1,12 @@  //! Zoom and pan on an image. -use crate::core::event::{self, Event};  use crate::core::image::{self, FilterMethod};  use crate::core::layout;  use crate::core::mouse;  use crate::core::renderer;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    Clipboard, ContentFit, Element, Image, Layout, Length, Pixels, Point, -    Radians, Rectangle, Shell, Size, Vector, Widget, +    Clipboard, ContentFit, Element, Event, Image, Layout, Length, Pixels, +    Point, Radians, Rectangle, Shell, Size, Vector, Widget,  };  /// A frame that displays an image with the ability to zoom in/out and pan. @@ -149,7 +148,7 @@ where          layout::Node::new(final_size)      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -157,15 +156,15 @@ where          cursor: mouse::Cursor,          renderer: &Renderer,          _clipboard: &mut dyn Clipboard, -        _shell: &mut Shell<'_, Message>, +        shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let bounds = layout.bounds();          match event {              Event::Mouse(mouse::Event::WheelScrolled { delta }) => {                  let Some(cursor_position) = cursor.position_over(bounds) else { -                    return event::Status::Ignored; +                    return;                  };                  match delta { @@ -216,29 +215,25 @@ where                      }                  } -                event::Status::Captured +                shell.capture_event();              }              Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {                  let Some(cursor_position) = cursor.position_over(bounds) else { -                    return event::Status::Ignored; +                    return;                  };                  let state = tree.state.downcast_mut::<State>();                  state.cursor_grabbed_at = Some(cursor_position);                  state.starting_offset = state.current_offset; - -                event::Status::Captured +                shell.capture_event();              }              Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {                  let state = tree.state.downcast_mut::<State>();                  if state.cursor_grabbed_at.is_some() {                      state.cursor_grabbed_at = None; - -                    event::Status::Captured -                } else { -                    event::Status::Ignored +                    shell.capture_event();                  }              }              Event::Mouse(mouse::Event::CursorMoved { position }) => { @@ -278,13 +273,10 @@ where                      };                      state.current_offset = Vector::new(x, y); - -                    event::Status::Captured -                } else { -                    event::Status::Ignored +                    shell.capture_event();                  }              } -            _ => event::Status::Ignored, +            _ => {}          }      } diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 5852ede1..055e2ea1 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -1,5 +1,4 @@  //! Keyed columns distribute content vertically while keeping continuity. -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay; @@ -7,8 +6,8 @@ use crate::core::renderer;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::Operation;  use crate::core::{ -    Alignment, Clipboard, Element, Layout, Length, Padding, Pixels, Rectangle, -    Shell, Size, Vector, Widget, +    Alignment, Clipboard, Element, Event, Layout, Length, Padding, Pixels, +    Rectangle, Shell, Size, Vector, Widget,  };  /// A container that distributes its contents vertically while keeping continuity. @@ -298,7 +297,7 @@ where          });      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -308,24 +307,24 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        self.children +    ) { +        for ((child, state), layout) in self +            .children              .iter_mut()              .zip(&mut tree.children)              .zip(layout.children()) -            .map(|((child, state), layout)| { -                child.as_widget_mut().on_event( -                    state, -                    event.clone(), -                    layout, -                    cursor, -                    renderer, -                    clipboard, -                    shell, -                    viewport, -                ) -            }) -            .fold(event::Status::Ignored, event::Status::merge) +        { +            child.as_widget_mut().update( +                state, +                event.clone(), +                layout, +                cursor, +                renderer, +                clipboard, +                shell, +                viewport, +            ); +        }      }      fn mouse_interaction( diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 232f254c..07b90c93 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -10,7 +10,6 @@ pub use responsive::Responsive;  mod cache; -use crate::core::event::{self, Event};  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::overlay; @@ -19,7 +18,7 @@ use crate::core::widget::tree::{self, Tree};  use crate::core::widget::{self, Widget};  use crate::core::Element;  use crate::core::{ -    self, Clipboard, Length, Point, Rectangle, Shell, Size, Vector, +    self, Clipboard, Event, Length, Point, Rectangle, Shell, Size, Vector,  };  use crate::runtime::overlay::Nested; @@ -196,7 +195,7 @@ where          });      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -206,9 +205,9 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { +    ) {          self.with_element_mut(|element| { -            element.as_widget_mut().on_event( +            element.as_widget_mut().update(                  &mut tree.children[0],                  event,                  layout, @@ -217,8 +216,8 @@ where                  clipboard,                  shell,                  viewport, -            ) -        }) +            ); +        });      }      fn mouse_interaction( @@ -387,7 +386,7 @@ where          .unwrap_or_default()      } -    fn on_event( +    fn update(          &mut self,          event: Event,          layout: Layout<'_>, @@ -395,11 +394,10 @@ where          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>, -    ) -> event::Status { -        self.with_overlay_mut_maybe(|overlay| { -            overlay.on_event(event, layout, cursor, renderer, clipboard, shell) -        }) -        .unwrap_or(event::Status::Ignored) +    ) { +        let _ = self.with_overlay_mut_maybe(|overlay| { +            overlay.update(event, layout, cursor, renderer, clipboard, shell); +        });      }      fn is_over( diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index c7bc1264..1758b963 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -1,12 +1,12 @@  //! Build and reuse custom widgets using The Elm Architecture.  #![allow(deprecated)] -use crate::core::event;  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{      self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,      Widget, @@ -311,7 +311,7 @@ where          })      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: core::Event, @@ -321,13 +321,13 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { +    ) {          let mut local_messages = Vec::new();          let mut local_shell = Shell::new(&mut local_messages);          let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>(); -        let event_status = self.with_element_mut(|element| { -            element.as_widget_mut().on_event( +        self.with_element_mut(|element| { +            element.as_widget_mut().update(                  &mut t.borrow_mut().as_mut().unwrap().children[0],                  event,                  layout, @@ -336,13 +336,24 @@ where                  clipboard,                  &mut local_shell,                  viewport, -            ) +            );          }); +        if local_shell.is_event_captured() { +            shell.capture_event(); +        } +          local_shell.revalidate_layout(|| shell.invalidate_layout());          if let Some(redraw_request) = local_shell.redraw_request() { -            shell.request_redraw(redraw_request); +            match redraw_request { +                window::RedrawRequest::NextFrame => { +                    shell.request_redraw(); +                } +                window::RedrawRequest::At(at) => { +                    shell.request_redraw_at(at); +                } +            }          }          if !local_messages.is_empty() { @@ -369,8 +380,6 @@ where              shell.invalidate_layout();          } - -        event_status      }      fn operate( @@ -592,7 +601,7 @@ where          .unwrap_or_default()      } -    fn on_event( +    fn update(          &mut self,          event: core::Event,          layout: Layout<'_>, @@ -600,27 +609,36 @@ where          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 -            .with_overlay_mut_maybe(|overlay| { -                overlay.on_event( -                    event, -                    layout, -                    cursor, -                    renderer, -                    clipboard, -                    &mut local_shell, -                ) -            }) -            .unwrap_or(event::Status::Ignored); +        let _ = self.with_overlay_mut_maybe(|overlay| { +            overlay.update( +                event, +                layout, +                cursor, +                renderer, +                clipboard, +                &mut local_shell, +            ); +        }); + +        if local_shell.is_event_captured() { +            shell.capture_event(); +        }          local_shell.revalidate_layout(|| shell.invalidate_layout());          if let Some(redraw_request) = local_shell.redraw_request() { -            shell.request_redraw(redraw_request); +            match redraw_request { +                window::RedrawRequest::NextFrame => { +                    shell.request_redraw(); +                } +                window::RedrawRequest::At(at) => { +                    shell.request_redraw_at(at); +                } +            }          }          if !local_messages.is_empty() { @@ -658,8 +676,6 @@ where              shell.invalidate_layout();          } - -        event_status      }      fn is_over( diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index a6c40ab0..2aef1fa3 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -1,4 +1,3 @@ -use crate::core::event::{self, Event};  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::overlay; @@ -6,8 +5,8 @@ use crate::core::renderer;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector, -    Widget, +    self, Clipboard, Element, Event, Length, Point, Rectangle, Shell, Size, +    Vector, Widget,  };  use crate::horizontal_space;  use crate::runtime::overlay::Nested; @@ -83,18 +82,21 @@ where          new_size: Size,          view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,      ) { -        let is_tree_empty = -            tree.tag == tree::Tag::stateless() && tree.children.is_empty(); +        if self.size != new_size { +            self.element = view(new_size); +            self.size = new_size; +            self.layout = None; -        if !is_tree_empty && self.size == new_size { -            return; -        } - -        self.element = view(new_size); -        self.size = new_size; -        self.layout = None; +            tree.diff(&self.element); +        } else { +            let is_tree_empty = +                tree.tag == tree::Tag::stateless() && tree.children.is_empty(); -        tree.diff(&self.element); +            if is_tree_empty { +                self.layout = None; +                tree.diff(&self.element); +            } +        }      }      fn resolve<R, T>( @@ -183,7 +185,7 @@ where          );      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -193,20 +195,20 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { +    ) {          let state = tree.state.downcast_mut::<State>();          let mut content = self.content.borrow_mut();          let mut local_messages = vec![];          let mut local_shell = Shell::new(&mut local_messages); -        let status = content.resolve( +        content.resolve(              &mut state.tree.borrow_mut(),              renderer,              layout,              &self.view,              |tree, renderer, layout, element| { -                element.as_widget_mut().on_event( +                element.as_widget_mut().update(                      tree,                      event,                      layout, @@ -215,7 +217,7 @@ where                      clipboard,                      &mut local_shell,                      viewport, -                ) +                );              },          ); @@ -224,8 +226,6 @@ where          }          shell.merge(local_shell, std::convert::identity); - -        status      }      fn draw( @@ -417,7 +417,7 @@ where          .unwrap_or_default()      } -    fn on_event( +    fn update(          &mut self,          event: Event,          layout: Layout<'_>, @@ -425,28 +425,20 @@ where          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>, -    ) -> event::Status { +    ) {          let mut is_layout_invalid = false; -        let event_status = self -            .with_overlay_mut_maybe(|overlay| { -                let event_status = overlay.on_event( -                    event, layout, cursor, renderer, clipboard, shell, -                ); - -                is_layout_invalid = shell.is_layout_invalid(); +        let _ = self.with_overlay_mut_maybe(|overlay| { +            overlay.update(event, layout, cursor, renderer, clipboard, shell); -                event_status -            }) -            .unwrap_or(event::Status::Ignored); +            is_layout_invalid = shell.is_layout_invalid(); +        });          if is_layout_invalid {              self.with_overlay_mut(|(_overlay, layout)| {                  **layout = None;              });          } - -        event_status      }      fn is_over( diff --git a/widget/src/lib.rs b/widget/src/lib.rs index a68720d6..776a04a0 100644 --- a/widget/src/lib.rs +++ b/widget/src/lib.rs @@ -8,6 +8,7 @@ pub use iced_renderer::graphics;  pub use iced_runtime as runtime;  pub use iced_runtime::core; +mod action;  mod column;  mod mouse_area;  mod row; @@ -131,4 +132,5 @@ pub use qr_code::QRCode;  pub mod markdown;  pub use crate::core::theme::{self, Theme}; +pub use action::Action;  pub use renderer::Renderer; diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index c5a37ae3..d9215a7b 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -1,5 +1,4 @@  //! A container for capturing mouse events. -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay; @@ -7,8 +6,8 @@ use crate::core::renderer;  use crate::core::touch;  use crate::core::widget::{tree, Operation, Tree};  use crate::core::{ -    Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, -    Widget, +    Clipboard, Element, Event, Layout, Length, Point, Rectangle, Shell, Size, +    Vector, Widget,  };  /// Emit messages on mouse events. @@ -216,7 +215,7 @@ where          );      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -226,8 +225,8 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        if let event::Status::Captured = self.content.as_widget_mut().on_event( +    ) { +        self.content.as_widget_mut().update(              &mut tree.children[0],              event.clone(),              layout, @@ -236,11 +235,13 @@ where              clipboard,              shell,              viewport, -        ) { -            return event::Status::Captured; +        ); + +        if shell.is_event_captured() { +            return;          } -        update(self, tree, event, layout, cursor, shell) +        update(self, tree, event, layout, cursor, shell);      }      fn mouse_interaction( @@ -329,7 +330,7 @@ fn update<Message: Clone, Theme, Renderer>(      layout: Layout<'_>,      cursor: mouse::Cursor,      shell: &mut Shell<'_, Message>, -) -> event::Status { +) {      let state: &mut State = tree.state.downcast_mut();      let cursor_position = cursor.position(); @@ -363,104 +364,71 @@ fn update<Message: Clone, Theme, Renderer>(      }      if !cursor.is_over(layout.bounds()) { -        return event::Status::Ignored; +        return;      } -    if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -    | Event::Touch(touch::Event::FingerPressed { .. }) = event -    { -        let mut captured = false; - -        if let Some(message) = widget.on_press.as_ref() { -            captured = true; -            shell.publish(message.clone()); -        } +    match event { +        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) +        | Event::Touch(touch::Event::FingerPressed { .. }) => { +            if let Some(message) = widget.on_press.as_ref() { +                shell.publish(message.clone()); +                shell.capture_event(); +            } -        if let Some(position) = cursor_position { -            if let Some(message) = widget.on_double_click.as_ref() { -                let new_click = mouse::Click::new( -                    position, -                    mouse::Button::Left, -                    state.previous_click, -                ); +            if let Some(position) = cursor_position { +                if let Some(message) = widget.on_double_click.as_ref() { +                    let new_click = mouse::Click::new( +                        position, +                        mouse::Button::Left, +                        state.previous_click, +                    ); -                if matches!(new_click.kind(), mouse::click::Kind::Double) { -                    shell.publish(message.clone()); -                } +                    if matches!(new_click.kind(), mouse::click::Kind::Double) { +                        shell.publish(message.clone()); +                    } -                state.previous_click = Some(new_click); +                    state.previous_click = Some(new_click); -                // Even if this is not a double click, but the press is nevertheless -                // processed by us and should not be popup to parent widgets. -                captured = true; +                    // Even if this is not a double click, but the press is nevertheless +                    // processed by us and should not be popup to parent widgets. +                    shell.capture_event(); +                }              }          } - -        if captured { -            return event::Status::Captured; -        } -    } - -    if let Some(message) = widget.on_release.as_ref() { -        if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -        | Event::Touch(touch::Event::FingerLifted { .. }) = event -        { -            shell.publish(message.clone()); - -            return event::Status::Captured; +        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) +        | Event::Touch(touch::Event::FingerLifted { .. }) => { +            if let Some(message) = widget.on_release.as_ref() { +                shell.publish(message.clone()); +            }          } -    } - -    if let Some(message) = widget.on_right_press.as_ref() { -        if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = -            event -        { -            shell.publish(message.clone()); - -            return event::Status::Captured; +        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) => { +            if let Some(message) = widget.on_right_press.as_ref() { +                shell.publish(message.clone()); +                shell.capture_event(); +            }          } -    } - -    if let Some(message) = widget.on_right_release.as_ref() { -        if let Event::Mouse(mouse::Event::ButtonReleased( -            mouse::Button::Right, -        )) = event -        { -            shell.publish(message.clone()); - -            return event::Status::Captured; +        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => { +            if let Some(message) = widget.on_right_release.as_ref() { +                shell.publish(message.clone()); +            }          } -    } - -    if let Some(message) = widget.on_middle_press.as_ref() { -        if let Event::Mouse(mouse::Event::ButtonPressed( -            mouse::Button::Middle, -        )) = event -        { -            shell.publish(message.clone()); - -            return event::Status::Captured; +        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)) => { +            if let Some(message) = widget.on_middle_press.as_ref() { +                shell.publish(message.clone()); +                shell.capture_event(); +            }          } -    } - -    if let Some(message) = widget.on_middle_release.as_ref() { -        if let Event::Mouse(mouse::Event::ButtonReleased( -            mouse::Button::Middle, -        )) = event -        { -            shell.publish(message.clone()); - -            return event::Status::Captured; +        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Middle)) => { +            if let Some(message) = widget.on_middle_release.as_ref() { +                shell.publish(message.clone()); +            }          } -    } - -    if let Some(on_scroll) = widget.on_scroll.as_ref() { -        if let Event::Mouse(mouse::Event::WheelScrolled { delta }) = event { -            shell.publish(on_scroll(delta)); - -            return event::Status::Captured; +        Event::Mouse(mouse::Event::WheelScrolled { delta }) => { +            if let Some(on_scroll) = widget.on_scroll.as_ref() { +                shell.publish(on_scroll(delta)); +                shell.capture_event(); +            }          } +        _ => {}      } - -    event::Status::Ignored  } diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b641e8f5..7907ef01 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -1,17 +1,17 @@  //! Build and show dropdown menus.  use crate::core::alignment;  use crate::core::border::{self, Border}; -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::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{ -    Background, Clipboard, Color, Length, Padding, Pixels, Point, Rectangle, -    Size, Theme, Vector, +    Background, Clipboard, Color, Event, Length, Padding, Pixels, Point, +    Rectangle, Size, Theme, Vector,  };  use crate::core::{Element, Shell, Widget};  use crate::scrollable::{self, Scrollable}; @@ -262,7 +262,7 @@ where          })      } -    fn on_event( +    fn update(          &mut self,          event: Event,          layout: Layout<'_>, @@ -270,13 +270,13 @@ where          renderer: &Renderer,          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>, -    ) -> event::Status { +    ) {          let bounds = layout.bounds(); -        self.list.on_event( +        self.list.update(              self.state, event, layout, cursor, renderer, clipboard, shell,              &bounds, -        ) +        );      }      fn mouse_interaction( @@ -334,6 +334,10 @@ where      class: &'a <Theme as Catalog>::Class<'b>,  } +struct ListState { +    is_hovered: Option<bool>, +} +  impl<'a, 'b, T, Message, Theme, Renderer> Widget<Message, Theme, Renderer>      for List<'a, 'b, T, Message, Theme, Renderer>  where @@ -341,6 +345,14 @@ where      Theme: Catalog,      Renderer: text::Renderer,  { +    fn tag(&self) -> tree::Tag { +        tree::Tag::of::<Option<bool>>() +    } + +    fn state(&self) -> tree::State { +        tree::State::new(ListState { is_hovered: None }) +    } +      fn size(&self) -> Size<Length> {          Size {              width: Length::Fill, @@ -374,9 +386,9 @@ where          layout::Node::new(size)      } -    fn on_event( +    fn update(          &mut self, -        _state: &mut Tree, +        tree: &mut Tree,          event: Event,          layout: Layout<'_>,          cursor: mouse::Cursor, @@ -384,14 +396,14 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> 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) {                              shell.publish((self.on_selected)(option.clone())); -                            return event::Status::Captured; +                            shell.capture_event();                          }                      }                  } @@ -411,14 +423,18 @@ where                      let new_hovered_option =                          (cursor_position.y / option_height) as usize; -                    if let Some(on_option_hovered) = self.on_option_hovered { -                        if *self.hovered_option != Some(new_hovered_option) { -                            if let Some(option) = -                                self.options.get(new_hovered_option) +                    if *self.hovered_option != Some(new_hovered_option) { +                        if let Some(option) = +                            self.options.get(new_hovered_option) +                        { +                            if let Some(on_option_hovered) = +                                self.on_option_hovered                              {                                  shell                                      .publish(on_option_hovered(option.clone()));                              } + +                            shell.request_redraw();                          }                      } @@ -443,7 +459,7 @@ where                      if let Some(index) = *self.hovered_option {                          if let Some(option) = self.options.get(index) {                              shell.publish((self.on_selected)(option.clone())); -                            return event::Status::Captured; +                            shell.capture_event();                          }                      }                  } @@ -451,7 +467,15 @@ where              _ => {}          } -        event::Status::Ignored +        let state = tree.state.downcast_mut::<ListState>(); + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            state.is_hovered = Some(cursor.is_over(layout.bounds())); +        } else if state.is_hovered.is_some_and(|is_hovered| { +            is_hovered != cursor.is_over(layout.bounds()) +        }) { +            shell.request_redraw(); +        }      }      fn mouse_interaction( diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index b4ed4b64..7b2956f3 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -79,7 +79,6 @@ pub use state::State;  pub use title_bar::TitleBar;  use crate::container; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay::{self, Group}; @@ -87,8 +86,9 @@ use crate::core::renderer;  use crate::core::touch;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{ -    self, Background, Border, Clipboard, Color, Element, Layout, Length, +    self, Background, Border, Clipboard, Color, Element, Event, Layout, Length,      Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,  }; @@ -167,6 +167,7 @@ pub struct PaneGrid<      on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>,      on_resize: Option<(f32, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,      class: <Theme as Catalog>::Class<'a>, +    last_mouse_interaction: Option<mouse::Interaction>,  }  impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> @@ -203,6 +204,7 @@ where              on_drag: None,              on_resize: None,              class: <Theme as Catalog>::default(), +            last_mouse_interaction: None,          }      } @@ -293,6 +295,52 @@ where              .then(|| self.on_drag.is_some())              .unwrap_or_default()      } + +    fn grid_interaction( +        &self, +        action: &state::Action, +        layout: Layout<'_>, +        cursor: mouse::Cursor, +    ) -> Option<mouse::Interaction> { +        if action.picked_pane().is_some() { +            return Some(mouse::Interaction::Grabbing); +        } + +        let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); +        let node = self.internal.layout(); + +        let resize_axis = +            action.picked_split().map(|(_, axis)| axis).or_else(|| { +                resize_leeway.and_then(|leeway| { +                    let cursor_position = cursor.position()?; +                    let bounds = layout.bounds(); + +                    let splits = +                        node.split_regions(self.spacing, bounds.size()); + +                    let relative_cursor = Point::new( +                        cursor_position.x - bounds.x, +                        cursor_position.y - bounds.y, +                    ); + +                    hovered_split( +                        splits.iter(), +                        self.spacing + leeway, +                        relative_cursor, +                    ) +                    .map(|(_, axis, _)| axis) +                }) +            }); + +        if let Some(resize_axis) = resize_axis { +            return Some(match resize_axis { +                Axis::Horizontal => mouse::Interaction::ResizingVertically, +                Axis::Vertical => mouse::Interaction::ResizingHorizontally, +            }); +        } + +        None +    }  }  #[derive(Default)] @@ -423,7 +471,7 @@ where          });      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -433,9 +481,7 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        let mut event_status = event::Status::Ignored; - +    ) {          let Memory { action, .. } = tree.state.downcast_mut();          let node = self.internal.layout(); @@ -445,13 +491,43 @@ where              &None          }; +        let picked_pane = action.picked_pane().map(|(pane, _)| pane); + +        for (((pane, content), tree), layout) in self +            .panes +            .iter() +            .copied() +            .zip(&mut self.contents) +            .zip(&mut tree.children) +            .zip(layout.children()) +            .filter(|(((pane, _), _), _)| { +                self.internal +                    .maximized() +                    .map_or(true, |maximized| *pane == maximized) +            }) +        { +            let is_picked = picked_pane == Some(pane); + +            content.update( +                tree, +                event.clone(), +                layout, +                cursor, +                renderer, +                clipboard, +                shell, +                viewport, +                is_picked, +            ); +        } +          match event {              Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))              | Event::Touch(touch::Event::FingerPressed { .. }) => {                  let bounds = layout.bounds();                  if let Some(cursor_position) = cursor.position_over(bounds) { -                    event_status = event::Status::Captured; +                    shell.capture_event();                      match &self.on_resize {                          Some((leeway, _)) => { @@ -555,10 +631,6 @@ where                              }                          }                      } - -                    event_status = event::Status::Captured; -                } else if action.picked_split().is_some() { -                    event_status = event::Status::Captured;                  }                  *action = state::Action::Idle; @@ -600,44 +672,48 @@ where                                      ratio,                                  })); -                                event_status = event::Status::Captured; +                                shell.capture_event();                              }                          } +                    } else if action.picked_pane().is_some() { +                        shell.request_redraw();                      }                  }              }              _ => {}          } -        let picked_pane = action.picked_pane().map(|(pane, _)| pane); - -        self.panes -            .iter() -            .copied() -            .zip(&mut self.contents) -            .zip(&mut tree.children) -            .zip(layout.children()) -            .filter(|(((pane, _), _), _)| { -                self.internal -                    .maximized() -                    .map_or(true, |maximized| *pane == maximized) -            }) -            .map(|(((pane, content), tree), layout)| { -                let is_picked = picked_pane == Some(pane); - -                content.on_event( -                    tree, -                    event.clone(), -                    layout, -                    cursor, -                    renderer, -                    clipboard, -                    shell, -                    viewport, -                    is_picked, -                ) -            }) -            .fold(event_status, event::Status::merge) +        if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { +            let interaction = self +                .grid_interaction(action, layout, cursor) +                .or_else(|| { +                    self.panes +                        .iter() +                        .zip(&self.contents) +                        .zip(layout.children()) +                        .filter(|((&pane, _content), _layout)| { +                            self.internal +                                .maximized() +                                .map_or(true, |maximized| pane == maximized) +                        }) +                        .find_map(|((_pane, content), layout)| { +                            content.grid_interaction( +                                layout, +                                cursor, +                                on_drag.is_some(), +                            ) +                        }) +                }) +                .unwrap_or(mouse::Interaction::None); + +            if let Event::Window(window::Event::RedrawRequested(_now)) = event { +                self.last_mouse_interaction = Some(interaction); +            } else if self.last_mouse_interaction.is_some_and( +                |last_mouse_interaction| last_mouse_interaction != interaction, +            ) { +                shell.request_redraw(); +            } +        }      }      fn mouse_interaction( @@ -650,41 +726,10 @@ where      ) -> mouse::Interaction {          let Memory { action, .. } = tree.state.downcast_ref(); -        if action.picked_pane().is_some() { -            return mouse::Interaction::Grabbing; -        } - -        let resize_leeway = self.on_resize.as_ref().map(|(leeway, _)| *leeway); -        let node = self.internal.layout(); - -        let resize_axis = -            action.picked_split().map(|(_, axis)| axis).or_else(|| { -                resize_leeway.and_then(|leeway| { -                    let cursor_position = cursor.position()?; -                    let bounds = layout.bounds(); - -                    let splits = -                        node.split_regions(self.spacing, bounds.size()); - -                    let relative_cursor = Point::new( -                        cursor_position.x - bounds.x, -                        cursor_position.y - bounds.y, -                    ); - -                    hovered_split( -                        splits.iter(), -                        self.spacing + leeway, -                        relative_cursor, -                    ) -                    .map(|(_, axis, _)| axis) -                }) -            }); - -        if let Some(resize_axis) = resize_axis { -            return match resize_axis { -                Axis::Horizontal => mouse::Interaction::ResizingVertically, -                Axis::Vertical => mouse::Interaction::ResizingHorizontally, -            }; +        if let Some(grid_interaction) = +            self.grid_interaction(action, layout, cursor) +        { +            return grid_interaction;          }          self.panes diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index ec0676b1..fa9f7a9f 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -1,12 +1,12 @@  use crate::container; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget::{self, Tree};  use crate::core::{ -    self, Clipboard, Element, Layout, Point, Rectangle, Shell, Size, Vector, +    self, Clipboard, Element, Event, Layout, Point, Rectangle, Shell, Size, +    Vector,  };  use crate::pane_grid::{Draggable, TitleBar}; @@ -239,7 +239,7 @@ where          );      } -    pub(crate) fn on_event( +    pub(crate) fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -250,13 +250,11 @@ where          shell: &mut Shell<'_, Message>,          viewport: &Rectangle,          is_picked: bool, -    ) -> event::Status { -        let mut event_status = event::Status::Ignored; - +    ) {          let body_layout = if let Some(title_bar) = &mut self.title_bar {              let mut children = layout.children(); -            event_status = title_bar.on_event( +            title_bar.update(                  &mut tree.children[1],                  event.clone(),                  children.next().unwrap(), @@ -272,10 +270,8 @@ where              layout          }; -        let body_status = if is_picked { -            event::Status::Ignored -        } else { -            self.body.as_widget_mut().on_event( +        if !is_picked { +            self.body.as_widget_mut().update(                  &mut tree.children[0],                  event,                  body_layout, @@ -284,10 +280,33 @@ where                  clipboard,                  shell,                  viewport, -            ) -        }; +            ); +        } +    } + +    pub(crate) fn grid_interaction( +        &self, +        layout: Layout<'_>, +        cursor: mouse::Cursor, +        drag_enabled: bool, +    ) -> Option<mouse::Interaction> { +        let title_bar = self.title_bar.as_ref()?; + +        let mut children = layout.children(); +        let title_bar_layout = children.next().unwrap(); + +        let is_over_pick_area = cursor +            .position() +            .map(|cursor_position| { +                title_bar.is_over_pick_area(title_bar_layout, cursor_position) +            }) +            .unwrap_or_default(); + +        if is_over_pick_area && drag_enabled { +            return Some(mouse::Interaction::Grab); +        } -        event_status.merge(body_status) +        None      }      pub(crate) fn mouse_interaction( diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 5002b4f7..3f4a651e 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -1,13 +1,12 @@  use crate::container; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget::{self, Tree};  use crate::core::{ -    self, Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size, -    Vector, +    self, Clipboard, Element, Event, Layout, Padding, Point, Rectangle, Shell, +    Size, Vector,  };  use crate::pane_grid::controls::Controls; @@ -428,7 +427,7 @@ where          }      } -    pub(crate) fn on_event( +    pub(crate) fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -438,7 +437,7 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { +    ) {          let mut children = layout.children();          let padded = children.next().unwrap(); @@ -446,15 +445,16 @@ where          let title_layout = children.next().unwrap();          let mut show_title = true; -        let control_status = if let Some(controls) = &mut self.controls { +        if let Some(controls) = &mut self.controls {              let controls_layout = children.next().unwrap(); +              if title_layout.bounds().width + controls_layout.bounds().width                  > padded.bounds().width              {                  if let Some(compact) = controls.compact.as_mut() {                      let compact_layout = children.next().unwrap(); -                    compact.as_widget_mut().on_event( +                    compact.as_widget_mut().update(                          &mut tree.children[2],                          event.clone(),                          compact_layout, @@ -463,11 +463,11 @@ where                          clipboard,                          shell,                          viewport, -                    ) +                    );                  } else {                      show_title = false; -                    controls.full.as_widget_mut().on_event( +                    controls.full.as_widget_mut().update(                          &mut tree.children[1],                          event.clone(),                          controls_layout, @@ -476,10 +476,10 @@ where                          clipboard,                          shell,                          viewport, -                    ) +                    );                  }              } else { -                controls.full.as_widget_mut().on_event( +                controls.full.as_widget_mut().update(                      &mut tree.children[1],                      event.clone(),                      controls_layout, @@ -488,14 +488,12 @@ where                      clipboard,                      shell,                      viewport, -                ) +                );              } -        } else { -            event::Status::Ignored -        }; +        } -        let title_status = if show_title { -            self.content.as_widget_mut().on_event( +        if show_title { +            self.content.as_widget_mut().update(                  &mut tree.children[0],                  event,                  title_layout, @@ -504,12 +502,8 @@ where                  clipboard,                  shell,                  viewport, -            ) -        } else { -            event::Status::Ignored -        }; - -        control_status.merge(title_status) +            ); +        }      }      pub(crate) fn mouse_interaction( diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 4f1e9da9..6708e7cd 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -61,7 +61,6 @@  //! }  //! ```  use crate::core::alignment; -use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::layout;  use crate::core::mouse; @@ -71,9 +70,10 @@ use crate::core::text::paragraph;  use crate::core::text::{self, Text};  use crate::core::touch;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{ -    Background, Border, Clipboard, Color, Element, Layout, Length, Padding, -    Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, +    Background, Border, Clipboard, Color, Element, Event, Layout, Length, +    Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,  };  use crate::overlay::menu::{self, Menu}; @@ -173,6 +173,7 @@ pub struct PickList<      handle: Handle<Renderer::Font>,      class: <Theme as Catalog>::Class<'a>,      menu_class: <Theme as menu::Catalog>::Class<'a>, +    last_status: Option<Status>,  }  impl<'a, T, L, V, Message, Theme, Renderer> @@ -208,6 +209,7 @@ where              handle: Handle::default(),              class: <Theme as Catalog>::default(),              menu_class: <Theme as Catalog>::default_menu(), +            last_status: None,          }      } @@ -425,7 +427,7 @@ where          layout::Node::new(size)      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -435,13 +437,12 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) { +        let state = tree.state.downcast_mut::<State<Renderer::Paragraph>>(); +          match event {              Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))              | Event::Touch(touch::Event::FingerPressed { .. }) => { -                let state = -                    tree.state.downcast_mut::<State<Renderer::Paragraph>>(); -                  if state.is_open {                      // Event wasn't processed by overlay, so cursor was clicked either outside its                      // bounds or on the drop-down, either way we close the overlay. @@ -451,7 +452,7 @@ where                          shell.publish(on_close.clone());                      } -                    event::Status::Captured +                    shell.capture_event();                  } else if cursor.is_over(layout.bounds()) {                      let selected = self.selected.as_ref().map(Borrow::borrow); @@ -466,17 +467,12 @@ where                          shell.publish(on_open.clone());                      } -                    event::Status::Captured -                } else { -                    event::Status::Ignored +                    shell.capture_event();                  }              }              Event::Mouse(mouse::Event::WheelScrolled {                  delta: mouse::ScrollDelta::Lines { y, .. },              }) => { -                let state = -                    tree.state.downcast_mut::<State<Renderer::Paragraph>>(); -                  if state.keyboard_modifiers.command()                      && cursor.is_over(layout.bounds())                      && !state.is_open @@ -513,20 +509,34 @@ where                          shell.publish((self.on_select)(next_option.clone()));                      } -                    event::Status::Captured -                } else { -                    event::Status::Ignored +                    shell.capture_event();                  }              }              Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { -                let state = -                    tree.state.downcast_mut::<State<Renderer::Paragraph>>(); -                  state.keyboard_modifiers = modifiers; +            } +            _ => {} +        }; + +        let status = { +            let is_hovered = cursor.is_over(layout.bounds()); -                event::Status::Ignored +            if state.is_open { +                Status::Opened { is_hovered } +            } else if is_hovered { +                Status::Hovered +            } else { +                Status::Active              } -            _ => event::Status::Ignored, +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.last_status = Some(status); +        } else if self +            .last_status +            .is_some_and(|last_status| last_status != status) +        { +            shell.request_redraw();          }      } @@ -555,7 +565,7 @@ where          theme: &Theme,          _style: &renderer::Style,          layout: Layout<'_>, -        cursor: mouse::Cursor, +        _cursor: mouse::Cursor,          viewport: &Rectangle,      ) {          let font = self.font.unwrap_or_else(|| renderer.default_font()); @@ -563,18 +573,12 @@ where          let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();          let bounds = layout.bounds(); -        let is_mouse_over = cursor.is_over(bounds); -        let is_selected = selected.is_some(); -        let status = if state.is_open { -            Status::Opened -        } else if is_mouse_over { -            Status::Hovered -        } else { -            Status::Active -        }; - -        let style = Catalog::style(theme, &self.class, status); +        let style = Catalog::style( +            theme, +            &self.class, +            self.last_status.unwrap_or(Status::Active), +        );          renderer.fill_quad(              renderer::Quad { @@ -671,7 +675,7 @@ where                      wrapping: text::Wrapping::default(),                  },                  Point::new(bounds.x + self.padding.left, bounds.center_y()), -                if is_selected { +                if selected.is_some() {                      style.text_color                  } else {                      style.placeholder_color @@ -824,7 +828,10 @@ pub enum Status {      /// The [`PickList`] is being hovered.      Hovered,      /// The [`PickList`] is open. -    Opened, +    Opened { +        /// Whether the [`PickList`] is hovered, while open. +        is_hovered: bool, +    },  }  /// The appearance of a pick list. @@ -898,7 +905,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {      match status {          Status::Active => active, -        Status::Hovered | Status::Opened => Style { +        Status::Hovered | Status::Opened { .. } => Style {              border: Border {                  color: palette.primary.strong.color,                  ..active.border diff --git a/widget/src/radio.rs b/widget/src/radio.rs index d2a3bd6a..b38ae6b4 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -58,7 +58,6 @@  //! ```  use crate::core::alignment;  use crate::core::border::{self, Border}; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::renderer; @@ -66,9 +65,10 @@ use crate::core::text;  use crate::core::touch;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{ -    Background, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle, -    Shell, Size, Theme, Widget, +    Background, Clipboard, Color, Element, Event, Layout, Length, Pixels, +    Rectangle, Shell, Size, Theme, Widget,  };  /// A circular button representing a choice. @@ -147,6 +147,7 @@ where      text_wrapping: text::Wrapping,      font: Option<Renderer::Font>,      class: Theme::Class<'a>, +    last_status: Option<Status>,  }  impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer> @@ -192,6 +193,7 @@ where              text_wrapping: text::Wrapping::default(),              font: None,              class: Theme::default(), +            last_status: None,          }      } @@ -321,7 +323,7 @@ where          )      } -    fn on_event( +    fn update(          &mut self,          _state: &mut Tree,          event: Event, @@ -331,20 +333,37 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          match event {              Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))              | Event::Touch(touch::Event::FingerPressed { .. }) => {                  if cursor.is_over(layout.bounds()) {                      shell.publish(self.on_click.clone()); - -                    return event::Status::Captured; +                    shell.capture_event();                  }              }              _ => {}          } -        event::Status::Ignored +        let current_status = { +            let is_mouse_over = cursor.is_over(layout.bounds()); +            let is_selected = self.is_selected; + +            if is_mouse_over { +                Status::Hovered { is_selected } +            } else { +                Status::Active { is_selected } +            } +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.last_status = Some(current_status); +        } else if self +            .last_status +            .is_some_and(|last_status| last_status != current_status) +        { +            shell.request_redraw(); +        }      }      fn mouse_interaction( @@ -369,21 +388,17 @@ where          theme: &Theme,          defaults: &renderer::Style,          layout: Layout<'_>, -        cursor: mouse::Cursor, +        _cursor: mouse::Cursor,          viewport: &Rectangle,      ) { -        let is_mouse_over = cursor.is_over(layout.bounds()); -        let is_selected = self.is_selected; -          let mut children = layout.children(); -        let status = if is_mouse_over { -            Status::Hovered { is_selected } -        } else { -            Status::Active { is_selected } -        }; - -        let style = theme.style(&self.class, status); +        let style = theme.style( +            &self.class, +            self.last_status.unwrap_or(Status::Active { +                is_selected: self.is_selected, +            }), +        );          {              let layout = children.next().unwrap(); diff --git a/widget/src/row.rs b/widget/src/row.rs index 9c0fa97e..96a4ab92 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -1,13 +1,12 @@  //! Distribute content horizontally.  use crate::core::alignment::{self, 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::widget::{Operation, Tree};  use crate::core::{ -    Clipboard, Element, Length, Padding, Pixels, Rectangle, Shell, Size, +    Clipboard, Element, Event, Length, Padding, Pixels, Rectangle, Shell, Size,      Vector, Widget,  }; @@ -254,7 +253,7 @@ where          });      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -264,24 +263,24 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        self.children +    ) { +        for ((child, state), layout) in self +            .children              .iter_mut()              .zip(&mut tree.children)              .zip(layout.children()) -            .map(|((child, state), layout)| { -                child.as_widget_mut().on_event( -                    state, -                    event.clone(), -                    layout, -                    cursor, -                    renderer, -                    clipboard, -                    shell, -                    viewport, -                ) -            }) -            .fold(event::Status::Ignored, event::Status::merge) +        { +            child.as_widget_mut().update( +                state, +                event.clone(), +                layout, +                cursor, +                renderer, +                clipboard, +                shell, +                viewport, +            ); +        }      }      fn mouse_interaction( @@ -493,7 +492,7 @@ where          self.row.operate(tree, layout, renderer, operation);      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -503,10 +502,10 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        self.row.on_event( +    ) { +        self.row.update(              tree, event, layout, cursor, renderer, clipboard, shell, viewport, -        ) +        );      }      fn mouse_interaction( diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 528d63c1..a6a41d9f 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -21,7 +21,6 @@  //! ```  use crate::container;  use crate::core::border::{self, Border}; -use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::layout;  use crate::core::mouse; @@ -34,8 +33,8 @@ use crate::core::widget::operation::{self, Operation};  use crate::core::widget::tree::{self, Tree};  use crate::core::window;  use crate::core::{ -    self, Background, Clipboard, Color, Element, Layout, Length, Padding, -    Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, +    self, Background, Clipboard, Color, Element, Event, Layout, Length, +    Padding, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget,  };  use crate::runtime::task::{self, Task};  use crate::runtime::Action; @@ -81,6 +80,7 @@ pub struct Scrollable<      content: Element<'a, Message, Theme, Renderer>,      on_scroll: Option<Box<dyn Fn(Viewport) -> Message + 'a>>,      class: Theme::Class<'a>, +    last_status: Option<Status>,  }  impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer> @@ -108,6 +108,7 @@ where              content: content.into(),              on_scroll: None,              class: Theme::default(), +            last_status: None,          }          .validate()      } @@ -507,7 +508,7 @@ where          );      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -517,7 +518,7 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let state = tree.state.downcast_mut::<State>();          let bounds = layout.bounds();          let cursor_over_scrollable = cursor.position_over(bounds); @@ -531,6 +532,8 @@ where          let (mouse_over_y_scrollbar, mouse_over_x_scrollbar) =              scrollbars.is_mouse_over(cursor); +        let last_offsets = (state.offset_x, state.offset_y); +          if let Some(last_scrolled) = state.last_scrolled {              let clear_transaction = match event {                  Event::Mouse( @@ -549,335 +552,380 @@ where              }          } -        if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { -            match event { -                Event::Mouse(mouse::Event::CursorMoved { .. }) -                | Event::Touch(touch::Event::FingerMoved { .. }) => { -                    if let Some(scrollbar) = scrollbars.y { -                        let Some(cursor_position) = cursor.position() else { -                            return event::Status::Ignored; -                        }; +        let mut update = || { +            if let Some(scroller_grabbed_at) = state.y_scroller_grabbed_at { +                match event { +                    Event::Mouse(mouse::Event::CursorMoved { .. }) +                    | Event::Touch(touch::Event::FingerMoved { .. }) => { +                        if let Some(scrollbar) = scrollbars.y { +                            let Some(cursor_position) = cursor.position() +                            else { +                                return; +                            }; -                        state.scroll_y_to( -                            scrollbar.scroll_percentage_y( -                                scroller_grabbed_at, -                                cursor_position, -                            ), -                            bounds, -                            content_bounds, -                        ); +                            state.scroll_y_to( +                                scrollbar.scroll_percentage_y( +                                    scroller_grabbed_at, +                                    cursor_position, +                                ), +                                bounds, +                                content_bounds, +                            ); -                        let _ = notify_scroll( -                            state, -                            &self.on_scroll, -                            bounds, -                            content_bounds, -                            shell, -                        ); +                            let _ = notify_scroll( +                                state, +                                &self.on_scroll, +                                bounds, +                                content_bounds, +                                shell, +                            ); -                        return event::Status::Captured; +                            shell.capture_event(); +                        }                      } +                    _ => {}                  } -                _ => {} -            } -        } else if mouse_over_y_scrollbar { -            match event { -                Event::Mouse(mouse::Event::ButtonPressed( -                    mouse::Button::Left, -                )) -                | Event::Touch(touch::Event::FingerPressed { .. }) => { -                    let Some(cursor_position) = cursor.position() else { -                        return event::Status::Ignored; -                    }; +            } else if mouse_over_y_scrollbar { +                match event { +                    Event::Mouse(mouse::Event::ButtonPressed( +                        mouse::Button::Left, +                    )) +                    | Event::Touch(touch::Event::FingerPressed { .. }) => { +                        let Some(cursor_position) = cursor.position() else { +                            return; +                        }; -                    if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( -                        scrollbars.grab_y_scroller(cursor_position), -                        scrollbars.y, -                    ) { -                        state.scroll_y_to( -                            scrollbar.scroll_percentage_y( -                                scroller_grabbed_at, -                                cursor_position, -                            ), -                            bounds, -                            content_bounds, -                        ); +                        if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( +                            scrollbars.grab_y_scroller(cursor_position), +                            scrollbars.y, +                        ) { +                            state.scroll_y_to( +                                scrollbar.scroll_percentage_y( +                                    scroller_grabbed_at, +                                    cursor_position, +                                ), +                                bounds, +                                content_bounds, +                            ); -                        state.y_scroller_grabbed_at = Some(scroller_grabbed_at); +                            state.y_scroller_grabbed_at = +                                Some(scroller_grabbed_at); -                        let _ = notify_scroll( -                            state, -                            &self.on_scroll, -                            bounds, -                            content_bounds, -                            shell, -                        ); -                    } +                            let _ = notify_scroll( +                                state, +                                &self.on_scroll, +                                bounds, +                                content_bounds, +                                shell, +                            ); +                        } -                    return event::Status::Captured; +                        shell.capture_event(); +                    } +                    _ => {}                  } -                _ => {}              } -        } -        if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { -            match event { -                Event::Mouse(mouse::Event::CursorMoved { .. }) -                | Event::Touch(touch::Event::FingerMoved { .. }) => { -                    let Some(cursor_position) = cursor.position() else { -                        return event::Status::Ignored; -                    }; +            if let Some(scroller_grabbed_at) = state.x_scroller_grabbed_at { +                match event { +                    Event::Mouse(mouse::Event::CursorMoved { .. }) +                    | Event::Touch(touch::Event::FingerMoved { .. }) => { +                        let Some(cursor_position) = cursor.position() else { +                            return; +                        }; -                    if let Some(scrollbar) = scrollbars.x { -                        state.scroll_x_to( -                            scrollbar.scroll_percentage_x( -                                scroller_grabbed_at, -                                cursor_position, -                            ), -                            bounds, -                            content_bounds, -                        ); +                        if let Some(scrollbar) = scrollbars.x { +                            state.scroll_x_to( +                                scrollbar.scroll_percentage_x( +                                    scroller_grabbed_at, +                                    cursor_position, +                                ), +                                bounds, +                                content_bounds, +                            ); -                        let _ = notify_scroll( -                            state, -                            &self.on_scroll, -                            bounds, -                            content_bounds, -                            shell, -                        ); -                    } +                            let _ = notify_scroll( +                                state, +                                &self.on_scroll, +                                bounds, +                                content_bounds, +                                shell, +                            ); +                        } -                    return event::Status::Captured; +                        shell.capture_event(); +                    } +                    _ => {}                  } -                _ => {} -            } -        } else if mouse_over_x_scrollbar { -            match event { -                Event::Mouse(mouse::Event::ButtonPressed( -                    mouse::Button::Left, -                )) -                | Event::Touch(touch::Event::FingerPressed { .. }) => { -                    let Some(cursor_position) = cursor.position() else { -                        return event::Status::Ignored; -                    }; +            } else if mouse_over_x_scrollbar { +                match event { +                    Event::Mouse(mouse::Event::ButtonPressed( +                        mouse::Button::Left, +                    )) +                    | Event::Touch(touch::Event::FingerPressed { .. }) => { +                        let Some(cursor_position) = cursor.position() else { +                            return; +                        }; -                    if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( -                        scrollbars.grab_x_scroller(cursor_position), -                        scrollbars.x, -                    ) { -                        state.scroll_x_to( -                            scrollbar.scroll_percentage_x( -                                scroller_grabbed_at, -                                cursor_position, -                            ), -                            bounds, -                            content_bounds, -                        ); +                        if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( +                            scrollbars.grab_x_scroller(cursor_position), +                            scrollbars.x, +                        ) { +                            state.scroll_x_to( +                                scrollbar.scroll_percentage_x( +                                    scroller_grabbed_at, +                                    cursor_position, +                                ), +                                bounds, +                                content_bounds, +                            ); -                        state.x_scroller_grabbed_at = Some(scroller_grabbed_at); +                            state.x_scroller_grabbed_at = +                                Some(scroller_grabbed_at); -                        let _ = notify_scroll( -                            state, -                            &self.on_scroll, -                            bounds, -                            content_bounds, -                            shell, -                        ); +                            let _ = notify_scroll( +                                state, +                                &self.on_scroll, +                                bounds, +                                content_bounds, +                                shell, +                            ); -                        return event::Status::Captured; +                            shell.capture_event(); +                        }                      } +                    _ => {}                  } -                _ => {}              } -        } -        let content_status = if state.last_scrolled.is_some() -            && matches!(event, Event::Mouse(mouse::Event::WheelScrolled { .. })) -        { -            event::Status::Ignored -        } else { -            let cursor = match cursor_over_scrollable { -                Some(cursor_position) -                    if !(mouse_over_x_scrollbar || mouse_over_y_scrollbar) => -                { -                    mouse::Cursor::Available( -                        cursor_position -                            + state.translation( -                                self.direction, -                                bounds, -                                content_bounds, -                            ), -                    ) -                } -                _ => mouse::Cursor::Unavailable, -            }; +            if state.last_scrolled.is_none() +                || !matches!( +                    event, +                    Event::Mouse(mouse::Event::WheelScrolled { .. }) +                ) +            { +                let cursor = match cursor_over_scrollable { +                    Some(cursor_position) +                        if !(mouse_over_x_scrollbar +                            || mouse_over_y_scrollbar) => +                    { +                        mouse::Cursor::Available( +                            cursor_position +                                + state.translation( +                                    self.direction, +                                    bounds, +                                    content_bounds, +                                ), +                        ) +                    } +                    _ => mouse::Cursor::Unavailable, +                }; -            let translation = -                state.translation(self.direction, bounds, content_bounds); +                let translation = +                    state.translation(self.direction, bounds, content_bounds); -            self.content.as_widget_mut().on_event( -                &mut tree.children[0], -                event.clone(), -                content, -                cursor, -                renderer, -                clipboard, -                shell, -                &Rectangle { -                    y: bounds.y + translation.y, -                    x: bounds.x + translation.x, -                    ..bounds -                }, -            ) -        }; +                self.content.as_widget_mut().update( +                    &mut tree.children[0], +                    event.clone(), +                    content, +                    cursor, +                    renderer, +                    clipboard, +                    shell, +                    &Rectangle { +                        y: bounds.y + translation.y, +                        x: bounds.x + translation.x, +                        ..bounds +                    }, +                ); +            }; -        if matches!( -            event, -            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -                | Event::Touch( -                    touch::Event::FingerLifted { .. } -                        | touch::Event::FingerLost { .. } -                ) -        ) { -            state.scroll_area_touched_at = None; -            state.x_scroller_grabbed_at = None; -            state.y_scroller_grabbed_at = None; +            if matches!( +                event, +                Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) +                    | Event::Touch( +                        touch::Event::FingerLifted { .. } +                            | touch::Event::FingerLost { .. } +                    ) +            ) { +                state.scroll_area_touched_at = None; +                state.x_scroller_grabbed_at = None; +                state.y_scroller_grabbed_at = None; -            return content_status; -        } +                return; +            } -        if let event::Status::Captured = content_status { -            return event::Status::Captured; -        } +            if shell.is_event_captured() { +                return; +            } -        if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = -            event -        { -            state.keyboard_modifiers = modifiers; +            if let Event::Keyboard(keyboard::Event::ModifiersChanged( +                modifiers, +            )) = event +            { +                state.keyboard_modifiers = modifiers; -            return event::Status::Ignored; -        } +                return; +            } -        match event { -            Event::Mouse(mouse::Event::WheelScrolled { delta }) => { -                if cursor_over_scrollable.is_none() { -                    return event::Status::Ignored; -                } +            match event { +                Event::Mouse(mouse::Event::WheelScrolled { delta }) => { +                    if cursor_over_scrollable.is_none() { +                        return; +                    } -                let delta = match delta { -                    mouse::ScrollDelta::Lines { x, y } => { -                        let is_shift_pressed = state.keyboard_modifiers.shift(); +                    let delta = match delta { +                        mouse::ScrollDelta::Lines { x, y } => { +                            let is_shift_pressed = +                                state.keyboard_modifiers.shift(); -                        // macOS automatically inverts the axes when Shift is pressed -                        let (x, y) = -                            if cfg!(target_os = "macos") && is_shift_pressed { +                            // macOS automatically inverts the axes when Shift is pressed +                            let (x, y) = if cfg!(target_os = "macos") +                                && is_shift_pressed +                            {                                  (y, x)                              } else {                                  (x, y)                              }; -                        let is_vertical = match self.direction { -                            Direction::Vertical(_) => true, -                            Direction::Horizontal(_) => false, -                            Direction::Both { .. } => !is_shift_pressed, -                        }; - -                        let movement = if is_vertical { -                            Vector::new(x, y) -                        } else { -                            Vector::new(y, x) -                        }; +                            let is_vertical = match self.direction { +                                Direction::Vertical(_) => true, +                                Direction::Horizontal(_) => false, +                                Direction::Both { .. } => !is_shift_pressed, +                            }; -                        // TODO: Configurable speed/friction (?) -                        -movement * 60.0 -                    } -                    mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y), -                }; +                            let movement = if is_vertical { +                                Vector::new(x, y) +                            } else { +                                Vector::new(y, x) +                            }; -                state.scroll( -                    self.direction.align(delta), -                    bounds, -                    content_bounds, -                ); +                            // TODO: Configurable speed/friction (?) +                            -movement * 60.0 +                        } +                        mouse::ScrollDelta::Pixels { x, y } => { +                            -Vector::new(x, y) +                        } +                    }; -                let has_scrolled = notify_scroll( -                    state, -                    &self.on_scroll, -                    bounds, -                    content_bounds, -                    shell, -                ); +                    state.scroll( +                        self.direction.align(delta), +                        bounds, +                        content_bounds, +                    ); -                let in_transaction = state.last_scrolled.is_some(); +                    let has_scrolled = notify_scroll( +                        state, +                        &self.on_scroll, +                        bounds, +                        content_bounds, +                        shell, +                    ); -                if has_scrolled || in_transaction { -                    event::Status::Captured -                } else { -                    event::Status::Ignored -                } -            } -            Event::Touch(event) -                if state.scroll_area_touched_at.is_some() -                    || !mouse_over_y_scrollbar && !mouse_over_x_scrollbar => -            { -                match event { -                    touch::Event::FingerPressed { .. } => { -                        let Some(cursor_position) = cursor.position() else { -                            return event::Status::Ignored; -                        }; +                    let in_transaction = state.last_scrolled.is_some(); -                        state.scroll_area_touched_at = Some(cursor_position); +                    if has_scrolled || in_transaction { +                        shell.capture_event();                      } -                    touch::Event::FingerMoved { .. } => { -                        if let Some(scroll_box_touched_at) = -                            state.scroll_area_touched_at -                        { +                } +                Event::Touch(event) +                    if state.scroll_area_touched_at.is_some() +                        || !mouse_over_y_scrollbar +                            && !mouse_over_x_scrollbar => +                { +                    match event { +                        touch::Event::FingerPressed { .. } => {                              let Some(cursor_position) = cursor.position()                              else { -                                return event::Status::Ignored; +                                return;                              }; -                            let delta = Vector::new( -                                scroll_box_touched_at.x - cursor_position.x, -                                scroll_box_touched_at.y - cursor_position.y, -                            ); - -                            state.scroll( -                                self.direction.align(delta), -                                bounds, -                                content_bounds, -                            ); -                              state.scroll_area_touched_at =                                  Some(cursor_position); - -                            // TODO: bubble up touch movements if not consumed. -                            let _ = notify_scroll( -                                state, -                                &self.on_scroll, -                                bounds, -                                content_bounds, -                                shell, -                            );                          } +                        touch::Event::FingerMoved { .. } => { +                            if let Some(scroll_box_touched_at) = +                                state.scroll_area_touched_at +                            { +                                let Some(cursor_position) = cursor.position() +                                else { +                                    return; +                                }; + +                                let delta = Vector::new( +                                    scroll_box_touched_at.x - cursor_position.x, +                                    scroll_box_touched_at.y - cursor_position.y, +                                ); + +                                state.scroll( +                                    self.direction.align(delta), +                                    bounds, +                                    content_bounds, +                                ); + +                                state.scroll_area_touched_at = +                                    Some(cursor_position); + +                                // TODO: bubble up touch movements if not consumed. +                                let _ = notify_scroll( +                                    state, +                                    &self.on_scroll, +                                    bounds, +                                    content_bounds, +                                    shell, +                                ); +                            } +                        } +                        _ => {}                      } -                    _ => {} -                } -                event::Status::Captured +                    shell.capture_event(); +                } +                Event::Window(window::Event::RedrawRequested(_)) => { +                    let _ = notify_viewport( +                        state, +                        &self.on_scroll, +                        bounds, +                        content_bounds, +                        shell, +                    ); +                } +                _ => {}              } -            Event::Window(window::Event::RedrawRequested(_)) => { -                let _ = notify_viewport( -                    state, -                    &self.on_scroll, -                    bounds, -                    content_bounds, -                    shell, -                ); +        }; + +        update(); -                event::Status::Ignored +        let status = if state.y_scroller_grabbed_at.is_some() +            || state.x_scroller_grabbed_at.is_some() +        { +            Status::Dragged { +                is_horizontal_scrollbar_dragged: state +                    .x_scroller_grabbed_at +                    .is_some(), +                is_vertical_scrollbar_dragged: state +                    .y_scroller_grabbed_at +                    .is_some(), +            } +        } else if cursor_over_scrollable.is_some() { +            Status::Hovered { +                is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar, +                is_vertical_scrollbar_hovered: mouse_over_y_scrollbar,              } -            _ => event::Status::Ignored, +        } else { +            Status::Active +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.last_status = Some(status); +        } + +        if last_offsets != (state.offset_x, state.offset_y) +            || self +                .last_status +                .is_some_and(|last_status| last_status != status) +        { +            shell.request_redraw();          }      } @@ -920,27 +968,8 @@ where              _ => mouse::Cursor::Unavailable,          }; -        let status = if state.y_scroller_grabbed_at.is_some() -            || state.x_scroller_grabbed_at.is_some() -        { -            Status::Dragged { -                is_horizontal_scrollbar_dragged: state -                    .x_scroller_grabbed_at -                    .is_some(), -                is_vertical_scrollbar_dragged: state -                    .y_scroller_grabbed_at -                    .is_some(), -            } -        } else if cursor_over_scrollable.is_some() { -            Status::Hovered { -                is_horizontal_scrollbar_hovered: mouse_over_x_scrollbar, -                is_vertical_scrollbar_hovered: mouse_over_y_scrollbar, -            } -        } else { -            Status::Active -        }; - -        let style = theme.style(&self.class, status); +        let style = theme +            .style(&self.class, self.last_status.unwrap_or(Status::Active));          container::draw_background(renderer, &style.container, layout.bounds()); @@ -1323,7 +1352,7 @@ impl operation::Scrollable for State {      }  } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)]  enum Offset {      Absolute(f32),      Relative(f32), diff --git a/widget/src/shader.rs b/widget/src/shader.rs index fa692336..8ec57482 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -1,23 +1,22 @@  //! A custom shader widget for wgpu applications. -mod event;  mod program; -pub use event::Event;  pub use program::Program; -use crate::core; +use crate::core::event;  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::renderer;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::{self, Widget};  use crate::core::window; -use crate::core::{Clipboard, Element, Length, Rectangle, Shell, Size}; +use crate::core::{Clipboard, Element, Event, Length, Rectangle, Shell, Size};  use crate::renderer::wgpu::primitive;  use std::marker::PhantomData;  pub use crate::graphics::Viewport; +pub use crate::Action;  pub use primitive::{Primitive, Storage};  /// A widget which can render custom shaders with Iced's `wgpu` backend. @@ -87,7 +86,7 @@ where          layout::atomic(limits, self.width, self.height)      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: crate::core::Event, @@ -97,40 +96,34 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let bounds = layout.bounds(); -        let custom_shader_event = match event { -            core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)), -            core::Event::Keyboard(keyboard_event) => { -                Some(Event::Keyboard(keyboard_event)) -            } -            core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), -            core::Event::Window(window::Event::RedrawRequested(instant)) => { -                Some(Event::RedrawRequested(instant)) -            } -            core::Event::Window(_) => None, -        }; - -        if let Some(custom_shader_event) = custom_shader_event { -            let state = tree.state.downcast_mut::<P::State>(); +        let state = tree.state.downcast_mut::<P::State>(); -            let (event_status, message) = self.program.update( -                state, -                custom_shader_event, -                bounds, -                cursor, -                shell, -            ); +        if let Some(action) = self.program.update(state, event, bounds, cursor) +        { +            let (message, redraw_request, event_status) = action.into_inner();              if let Some(message) = message {                  shell.publish(message);              } -            return event_status; -        } +            if let Some(redraw_request) = redraw_request { +                match redraw_request { +                    window::RedrawRequest::NextFrame => { +                        shell.request_redraw(); +                    } +                    window::RedrawRequest::At(at) => { +                        shell.request_redraw_at(at); +                    } +                } +            } -        event::Status::Ignored +            if event_status == event::Status::Captured { +                shell.capture_event(); +            } +        }      }      fn mouse_interaction( @@ -194,9 +187,8 @@ where          event: Event,          bounds: Rectangle,          cursor: mouse::Cursor, -        shell: &mut Shell<'_, Message>, -    ) -> (event::Status, Option<Message>) { -        T::update(self, state, event, bounds, cursor, shell) +    ) -> Option<Action<Message>> { +        T::update(self, state, event, bounds, cursor)      }      fn draw( diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs deleted file mode 100644 index 005c8725..00000000 --- a/widget/src/shader/event.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Handle events of a custom shader widget. -use crate::core::keyboard; -use crate::core::mouse; -use crate::core::time::Instant; -use crate::core::touch; - -pub use crate::core::event::Status; - -/// A [`Shader`] event. -/// -/// [`Shader`]: crate::Shader -#[derive(Debug, Clone, PartialEq)] -pub enum Event { -    /// A mouse event. -    Mouse(mouse::Event), - -    /// A touch event. -    Touch(touch::Event), - -    /// A keyboard event. -    Keyboard(keyboard::Event), - -    /// A window requested a redraw. -    RedrawRequested(Instant), -} diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 902c7c3b..0fc110af 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -1,8 +1,7 @@ -use crate::core::event;  use crate::core::mouse; -use crate::core::{Rectangle, Shell}; +use crate::core::Rectangle;  use crate::renderer::wgpu::Primitive; -use crate::shader; +use crate::shader::{self, Action};  /// The state and logic of a [`Shader`] widget.  /// @@ -18,10 +17,10 @@ pub trait Program<Message> {      type Primitive: Primitive + 'static;      /// Update the internal [`State`] of the [`Program`]. This can be used to reflect state changes -    /// based on mouse & other events. You can use the [`Shell`] to publish messages, request a -    /// redraw for the window, etc. +    /// based on mouse & other events. You can return an [`Action`] to publish a message, request a +    /// redraw, or capture the event.      /// -    /// By default, this method does and returns nothing. +    /// By default, this method returns `None`.      ///      /// [`State`]: Self::State      fn update( @@ -30,9 +29,8 @@ pub trait Program<Message> {          _event: shader::Event,          _bounds: Rectangle,          _cursor: mouse::Cursor, -        _shell: &mut Shell<'_, Message>, -    ) -> (event::Status, Option<Message>) { -        (event::Status::Ignored, None) +    ) -> Option<Action<Message>> { +        None      }      /// Draws the [`Primitive`]. diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 31aa0e0c..84630f9e 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -29,7 +29,6 @@  //! }  //! ```  use crate::core::border::{self, Border}; -use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::keyboard::key::{self, Key};  use crate::core::layout; @@ -37,9 +36,10 @@ use crate::core::mouse;  use crate::core::renderer;  use crate::core::touch;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{ -    self, Background, Clipboard, Color, Element, Layout, Length, Pixels, Point, -    Rectangle, Shell, Size, Theme, Widget, +    self, Background, Clipboard, Color, Element, Event, Layout, Length, Pixels, +    Point, Rectangle, Shell, Size, Theme, Widget,  };  use std::ops::RangeInclusive; @@ -95,6 +95,7 @@ where      width: Length,      height: f32,      class: Theme::Class<'a>, +    status: Option<Status>,  }  impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> @@ -141,6 +142,7 @@ where              width: Length::Fill,              height: Self::DEFAULT_HEIGHT,              class: Theme::default(), +            status: None,          }      } @@ -240,7 +242,7 @@ where          layout::atomic(limits, self.width, self.height)      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -250,188 +252,202 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let state = tree.state.downcast_mut::<State>(); -        let is_dragging = state.is_dragging; -        let current_value = self.value; +        let mut update = || { +            let current_value = self.value; -        let locate = |cursor_position: Point| -> Option<T> { -            let bounds = layout.bounds(); -            let new_value = if cursor_position.x <= bounds.x { -                Some(*self.range.start()) -            } else if cursor_position.x >= bounds.x + bounds.width { -                Some(*self.range.end()) -            } else { -                let step = if state.keyboard_modifiers.shift() { -                    self.shift_step.unwrap_or(self.step) +            let locate = |cursor_position: Point| -> Option<T> { +                let bounds = layout.bounds(); + +                let new_value = if cursor_position.x <= bounds.x { +                    Some(*self.range.start()) +                } else if cursor_position.x >= bounds.x + bounds.width { +                    Some(*self.range.end())                  } else { -                    self.step -                } -                .into(); +                    let step = if state.keyboard_modifiers.shift() { +                        self.shift_step.unwrap_or(self.step) +                    } else { +                        self.step +                    } +                    .into(); + +                    let start = (*self.range.start()).into(); +                    let end = (*self.range.end()).into(); -                let start = (*self.range.start()).into(); -                let end = (*self.range.end()).into(); +                    let percent = f64::from(cursor_position.x - bounds.x) +                        / f64::from(bounds.width); -                let percent = f64::from(cursor_position.x - bounds.x) -                    / f64::from(bounds.width); +                    let steps = (percent * (end - start) / step).round(); +                    let value = steps * step + start; -                let steps = (percent * (end - start) / step).round(); -                let value = steps * step + start; +                    T::from_f64(value.min(end)) +                }; -                T::from_f64(value.min(end)) +                new_value              }; -            new_value -        }; +            let increment = |value: T| -> Option<T> { +                let step = if state.keyboard_modifiers.shift() { +                    self.shift_step.unwrap_or(self.step) +                } else { +                    self.step +                } +                .into(); -        let increment = |value: T| -> Option<T> { -            let step = if state.keyboard_modifiers.shift() { -                self.shift_step.unwrap_or(self.step) -            } else { -                self.step -            } -            .into(); +                let steps = (value.into() / step).round(); +                let new_value = step * (steps + 1.0); -            let steps = (value.into() / step).round(); -            let new_value = step * (steps + 1.0); +                if new_value > (*self.range.end()).into() { +                    return Some(*self.range.end()); +                } -            if new_value > (*self.range.end()).into() { -                return Some(*self.range.end()); -            } +                T::from_f64(new_value) +            }; -            T::from_f64(new_value) -        }; +            let decrement = |value: T| -> Option<T> { +                let step = if state.keyboard_modifiers.shift() { +                    self.shift_step.unwrap_or(self.step) +                } else { +                    self.step +                } +                .into(); -        let decrement = |value: T| -> Option<T> { -            let step = if state.keyboard_modifiers.shift() { -                self.shift_step.unwrap_or(self.step) -            } else { -                self.step -            } -            .into(); +                let steps = (value.into() / step).round(); +                let new_value = step * (steps - 1.0); -            let steps = (value.into() / step).round(); -            let new_value = step * (steps - 1.0); +                if new_value < (*self.range.start()).into() { +                    return Some(*self.range.start()); +                } -            if new_value < (*self.range.start()).into() { -                return Some(*self.range.start()); -            } +                T::from_f64(new_value) +            }; -            T::from_f64(new_value) -        }; +            let change = |new_value: T| { +                if (self.value.into() - new_value.into()).abs() > f64::EPSILON { +                    shell.publish((self.on_change)(new_value)); -        let change = |new_value: T| { -            if (self.value.into() - new_value.into()).abs() > f64::EPSILON { -                shell.publish((self.on_change)(new_value)); +                    self.value = new_value; +                } +            }; -                self.value = new_value; -            } -        }; +            match &event { +                Event::Mouse(mouse::Event::ButtonPressed( +                    mouse::Button::Left, +                )) +                | Event::Touch(touch::Event::FingerPressed { .. }) => { +                    if let Some(cursor_position) = +                        cursor.position_over(layout.bounds()) +                    { +                        if state.keyboard_modifiers.command() { +                            let _ = self.default.map(change); +                            state.is_dragging = false; +                        } else { +                            let _ = locate(cursor_position).map(change); +                            state.is_dragging = true; +                        } -        match event { -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerPressed { .. }) => { -                if let Some(cursor_position) = -                    cursor.position_over(layout.bounds()) -                { -                    if state.keyboard_modifiers.command() { -                        let _ = self.default.map(change); -                        state.is_dragging = false; -                    } else { -                        let _ = locate(cursor_position).map(change); -                        state.is_dragging = true; +                        shell.capture_event();                      } - -                    return event::Status::Captured;                  } -            } -            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerLifted { .. }) -            | Event::Touch(touch::Event::FingerLost { .. }) => { -                if is_dragging { -                    if let Some(on_release) = self.on_release.clone() { -                        shell.publish(on_release); -                    } -                    state.is_dragging = false; +                Event::Mouse(mouse::Event::ButtonReleased( +                    mouse::Button::Left, +                )) +                | Event::Touch(touch::Event::FingerLifted { .. }) +                | Event::Touch(touch::Event::FingerLost { .. }) => { +                    if state.is_dragging { +                        if let Some(on_release) = self.on_release.clone() { +                            shell.publish(on_release); +                        } +                        state.is_dragging = false; -                    return event::Status::Captured; +                        shell.capture_event(); +                    }                  } -            } -            Event::Mouse(mouse::Event::CursorMoved { .. }) -            | Event::Touch(touch::Event::FingerMoved { .. }) => { -                if is_dragging { -                    let _ = cursor.position().and_then(locate).map(change); +                Event::Mouse(mouse::Event::CursorMoved { .. }) +                | Event::Touch(touch::Event::FingerMoved { .. }) => { +                    if state.is_dragging { +                        let _ = cursor.position().and_then(locate).map(change); -                    return event::Status::Captured; -                } -            } -            Event::Mouse(mouse::Event::WheelScrolled { delta }) -                if state.keyboard_modifiers.control() => -            { -                if cursor.is_over(layout.bounds()) { -                    let delta = match delta { -                        mouse::ScrollDelta::Lines { x: _, y } => y, -                        mouse::ScrollDelta::Pixels { x: _, y } => y, -                    }; - -                    if delta < 0.0 { -                        let _ = decrement(current_value).map(change); -                    } else { -                        let _ = increment(current_value).map(change); +                        shell.capture_event();                      } - -                    return event::Status::Captured;                  } -            } -            Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { -                if cursor.is_over(layout.bounds()) { -                    match key { -                        Key::Named(key::Named::ArrowUp) => { -                            let _ = increment(current_value).map(change); -                        } -                        Key::Named(key::Named::ArrowDown) => { +                Event::Mouse(mouse::Event::WheelScrolled { delta }) +                    if state.keyboard_modifiers.control() => +                { +                    if cursor.is_over(layout.bounds()) { +                        let delta = match delta { +                            mouse::ScrollDelta::Lines { x: _, y } => y, +                            mouse::ScrollDelta::Pixels { x: _, y } => y, +                        }; + +                        if *delta < 0.0 {                              let _ = decrement(current_value).map(change); +                        } else { +                            let _ = increment(current_value).map(change);                          } -                        _ => (), + +                        shell.capture_event();                      } +                } +                Event::Keyboard(keyboard::Event::KeyPressed { +                    key, .. +                }) => { +                    if cursor.is_over(layout.bounds()) { +                        match key { +                            Key::Named(key::Named::ArrowUp) => { +                                let _ = increment(current_value).map(change); +                            } +                            Key::Named(key::Named::ArrowDown) => { +                                let _ = decrement(current_value).map(change); +                            } +                            _ => (), +                        } -                    return event::Status::Captured; +                        shell.capture_event(); +                    }                  } +                Event::Keyboard(keyboard::Event::ModifiersChanged( +                    modifiers, +                )) => { +                    state.keyboard_modifiers = *modifiers; +                } +                _ => {}              } -            Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { -                state.keyboard_modifiers = modifiers; -            } -            _ => {} -        } +        }; -        event::Status::Ignored +        update(); + +        let current_status = if state.is_dragging { +            Status::Dragged +        } else if cursor.is_over(layout.bounds()) { +            Status::Hovered +        } else { +            Status::Active +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.status = Some(current_status); +        } else if self.status.is_some_and(|status| status != current_status) { +            shell.request_redraw(); +        }      }      fn draw(          &self, -        tree: &Tree, +        _tree: &Tree,          renderer: &mut Renderer,          theme: &Theme,          _style: &renderer::Style,          layout: Layout<'_>, -        cursor: mouse::Cursor, +        _cursor: mouse::Cursor,          _viewport: &Rectangle,      ) { -        let state = tree.state.downcast_ref::<State>();          let bounds = layout.bounds(); -        let is_mouse_over = cursor.is_over(bounds); -        let style = theme.style( -            &self.class, -            if state.is_dragging { -                Status::Dragged -            } else if is_mouse_over { -                Status::Hovered -            } else { -                Status::Active -            }, -        ); +        let style = +            theme.style(&self.class, self.status.unwrap_or(Status::Active));          let (handle_width, handle_height, handle_border_radius) =              match style.handle.shape { diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 6a44c328..52fd5031 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -1,12 +1,12 @@  //! Display content on top of other content. -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay;  use crate::core::renderer;  use crate::core::widget::{Operation, Tree};  use crate::core::{ -    Clipboard, Element, Layout, Length, Rectangle, Shell, Size, Vector, Widget, +    Clipboard, Element, Event, Layout, Length, Rectangle, Shell, Size, Vector, +    Widget,  };  /// A container that displays children on top of each other. @@ -204,7 +204,7 @@ where          });      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -214,40 +214,41 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { +    ) {          let is_over = cursor.is_over(layout.bounds()); -        self.children +        for ((child, state), layout) in self +            .children              .iter_mut()              .rev()              .zip(tree.children.iter_mut().rev())              .zip(layout.children().rev()) -            .map(|((child, state), layout)| { -                let status = child.as_widget_mut().on_event( -                    state, -                    event.clone(), -                    layout, -                    cursor, -                    renderer, -                    clipboard, -                    shell, -                    viewport, +        { +            child.as_widget_mut().update( +                state, +                event.clone(), +                layout, +                cursor, +                renderer, +                clipboard, +                shell, +                viewport, +            ); + +            if is_over && cursor != mouse::Cursor::Unavailable { +                let interaction = child.as_widget().mouse_interaction( +                    state, layout, cursor, viewport, renderer,                  ); -                if is_over && cursor != mouse::Cursor::Unavailable { -                    let interaction = child.as_widget().mouse_interaction( -                        state, layout, cursor, viewport, renderer, -                    ); - -                    if interaction != mouse::Interaction::None { -                        cursor = mouse::Cursor::Unavailable; -                    } +                if interaction != mouse::Interaction::None { +                    cursor = mouse::Cursor::Unavailable;                  } +            } -                status -            }) -            .find(|&status| status == event::Status::Captured) -            .unwrap_or(event::Status::Ignored) +            if shell.is_event_captured() { +                return; +            } +        }      }      fn mouse_interaction( diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index 3d241375..7ef2707b 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -1,5 +1,4 @@  use crate::core::alignment; -use crate::core::event;  use crate::core::layout;  use crate::core::mouse;  use crate::core::renderer; @@ -355,7 +354,7 @@ where          );      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -365,7 +364,7 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Link>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          match event {              Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {                  if let Some(position) = cursor.position_in(layout.bounds()) { @@ -374,9 +373,16 @@ where                          .downcast_mut::<State<Link, Renderer::Paragraph>>();                      if let Some(span) = state.paragraph.hit_span(position) { -                        state.span_pressed = Some(span); - -                        return event::Status::Captured; +                        if self +                            .spans +                            .as_ref() +                            .as_ref() +                            .get(span) +                            .is_some_and(|span| span.link.is_some()) +                        { +                            state.span_pressed = Some(span); +                            shell.capture_event(); +                        }                      }                  }              } @@ -409,8 +415,6 @@ where              }              _ => {}          } - -        event::Status::Ignored      }      fn mouse_interaction( diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a298252a..b6e291e4 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -33,7 +33,6 @@  //! ```  use crate::core::alignment;  use crate::core::clipboard::{self, Clipboard}; -use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::keyboard::key;  use crate::core::layout::{self, Layout}; @@ -47,7 +46,7 @@ use crate::core::widget::operation;  use crate::core::widget::{self, Widget};  use crate::core::window;  use crate::core::{ -    Background, Border, Color, Element, Length, Padding, Pixels, Point, +    Background, Border, Color, Element, Event, Length, Padding, Pixels, Point,      Rectangle, Shell, Size, SmolStr, Theme, Vector,  }; @@ -120,6 +119,7 @@ pub struct TextEditor<          &Highlighter::Highlight,          &Theme,      ) -> highlighter::Format<Renderer::Font>, +    last_status: Option<Status>,  }  impl<'a, Message, Theme, Renderer> @@ -147,6 +147,7 @@ where              highlighter_format: |_highlight, _theme| {                  highlighter::Format::default()              }, +            last_status: None,          }      }  } @@ -270,6 +271,7 @@ where              on_edit: self.on_edit,              highlighter_settings: settings,              highlighter_format: to_format, +            last_status: self.last_status,          }      } @@ -596,7 +598,7 @@ where          }      } -    fn on_event( +    fn update(          &mut self,          tree: &mut widget::Tree,          event: Event, @@ -606,12 +608,16 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let Some(on_edit) = self.on_edit.as_ref() else { -            return event::Status::Ignored; +            return;          };          let state = tree.state.downcast_mut::<State<Highlighter>>(); +        let is_redraw = matches!( +            event, +            Event::Window(window::Event::RedrawRequested(_now)), +        );          match event {              Event::Window(window::Event::Unfocused) => { @@ -624,7 +630,7 @@ where                      focus.is_window_focused = true;                      focus.updated_at = Instant::now(); -                    shell.request_redraw(window::RedrawRequest::NextFrame); +                    shell.request_redraw();                  }              }              Event::Window(window::Event::RedrawRequested(now)) => { @@ -637,168 +643,193 @@ where                                  - (now - focus.updated_at).as_millis()                                      % Focus::CURSOR_BLINK_INTERVAL_MILLIS; -                        shell.request_redraw(window::RedrawRequest::At( +                        shell.request_redraw_at(                              now + Duration::from_millis(                                  millis_until_redraw as u64,                              ), -                        )); +                        );                      }                  }              }              _ => {}          } -        let Some(update) = Update::from_event( +        if let Some(update) = Update::from_event(              event,              state,              layout.bounds(),              self.padding,              cursor,              self.key_binding.as_deref(), -        ) else { -            return event::Status::Ignored; -        }; - -        match update { -            Update::Click(click) => { -                let action = match click.kind() { -                    mouse::click::Kind::Single => { -                        Action::Click(click.position()) -                    } -                    mouse::click::Kind::Double => Action::SelectWord, -                    mouse::click::Kind::Triple => Action::SelectLine, -                }; - -                state.focus = Some(Focus::now()); -                state.last_click = Some(click); -                state.drag_click = Some(click.kind()); +        ) { +            match update { +                Update::Click(click) => { +                    let action = match click.kind() { +                        mouse::click::Kind::Single => { +                            Action::Click(click.position()) +                        } +                        mouse::click::Kind::Double => Action::SelectWord, +                        mouse::click::Kind::Triple => Action::SelectLine, +                    }; -                shell.publish(on_edit(action)); -            } -            Update::Drag(position) => { -                shell.publish(on_edit(Action::Drag(position))); -            } -            Update::Release => { -                state.drag_click = None; -            } -            Update::Scroll(lines) => { -                let bounds = self.content.0.borrow().editor.bounds(); +                    state.focus = Some(Focus::now()); +                    state.last_click = Some(click); +                    state.drag_click = Some(click.kind()); -                if bounds.height >= i32::MAX as f32 { -                    return event::Status::Ignored; +                    shell.publish(on_edit(action)); +                    shell.capture_event(); +                } +                Update::Drag(position) => { +                    shell.publish(on_edit(Action::Drag(position)));                  } +                Update::Release => { +                    state.drag_click = None; +                } +                Update::Scroll(lines) => { +                    let bounds = self.content.0.borrow().editor.bounds(); -                let lines = lines + state.partial_scroll; -                state.partial_scroll = lines.fract(); +                    if bounds.height >= i32::MAX as f32 { +                        return; +                    } -                shell.publish(on_edit(Action::Scroll { -                    lines: lines as i32, -                })); -            } -            Update::Binding(binding) => { -                fn apply_binding< -                    H: text::Highlighter, -                    R: text::Renderer, -                    Message, -                >( -                    binding: Binding<Message>, -                    content: &Content<R>, -                    state: &mut State<H>, -                    on_edit: &dyn Fn(Action) -> Message, -                    clipboard: &mut dyn Clipboard, -                    shell: &mut Shell<'_, Message>, -                ) { -                    let mut publish = |action| shell.publish(on_edit(action)); - -                    match binding { -                        Binding::Unfocus => { -                            state.focus = None; -                            state.drag_click = None; -                        } -                        Binding::Copy => { -                            if let Some(selection) = content.selection() { -                                clipboard.write( -                                    clipboard::Kind::Standard, -                                    selection, -                                ); -                            } -                        } -                        Binding::Cut => { -                            if let Some(selection) = content.selection() { -                                clipboard.write( -                                    clipboard::Kind::Standard, -                                    selection, -                                ); +                    let lines = lines + state.partial_scroll; +                    state.partial_scroll = lines.fract(); +                    shell.publish(on_edit(Action::Scroll { +                        lines: lines as i32, +                    })); +                    shell.capture_event(); +                } +                Update::Binding(binding) => { +                    fn apply_binding< +                        H: text::Highlighter, +                        R: text::Renderer, +                        Message, +                    >( +                        binding: Binding<Message>, +                        content: &Content<R>, +                        state: &mut State<H>, +                        on_edit: &dyn Fn(Action) -> Message, +                        clipboard: &mut dyn Clipboard, +                        shell: &mut Shell<'_, Message>, +                    ) { +                        let mut publish = +                            |action| shell.publish(on_edit(action)); + +                        match binding { +                            Binding::Unfocus => { +                                state.focus = None; +                                state.drag_click = None; +                            } +                            Binding::Copy => { +                                if let Some(selection) = content.selection() { +                                    clipboard.write( +                                        clipboard::Kind::Standard, +                                        selection, +                                    ); +                                } +                            } +                            Binding::Cut => { +                                if let Some(selection) = content.selection() { +                                    clipboard.write( +                                        clipboard::Kind::Standard, +                                        selection, +                                    ); + +                                    publish(Action::Edit(Edit::Delete)); +                                } +                            } +                            Binding::Paste => { +                                if let Some(contents) = +                                    clipboard.read(clipboard::Kind::Standard) +                                { +                                    publish(Action::Edit(Edit::Paste( +                                        Arc::new(contents), +                                    ))); +                                } +                            } +                            Binding::Move(motion) => { +                                publish(Action::Move(motion)); +                            } +                            Binding::Select(motion) => { +                                publish(Action::Select(motion)); +                            } +                            Binding::SelectWord => { +                                publish(Action::SelectWord); +                            } +                            Binding::SelectLine => { +                                publish(Action::SelectLine); +                            } +                            Binding::SelectAll => { +                                publish(Action::SelectAll); +                            } +                            Binding::Insert(c) => { +                                publish(Action::Edit(Edit::Insert(c))); +                            } +                            Binding::Enter => { +                                publish(Action::Edit(Edit::Enter)); +                            } +                            Binding::Backspace => { +                                publish(Action::Edit(Edit::Backspace)); +                            } +                            Binding::Delete => {                                  publish(Action::Edit(Edit::Delete));                              } -                        } -                        Binding::Paste => { -                            if let Some(contents) = -                                clipboard.read(clipboard::Kind::Standard) -                            { -                                publish(Action::Edit(Edit::Paste(Arc::new( -                                    contents, -                                )))); +                            Binding::Sequence(sequence) => { +                                for binding in sequence { +                                    apply_binding( +                                        binding, content, state, on_edit, +                                        clipboard, shell, +                                    ); +                                }                              } -                        } -                        Binding::Move(motion) => { -                            publish(Action::Move(motion)); -                        } -                        Binding::Select(motion) => { -                            publish(Action::Select(motion)); -                        } -                        Binding::SelectWord => { -                            publish(Action::SelectWord); -                        } -                        Binding::SelectLine => { -                            publish(Action::SelectLine); -                        } -                        Binding::SelectAll => { -                            publish(Action::SelectAll); -                        } -                        Binding::Insert(c) => { -                            publish(Action::Edit(Edit::Insert(c))); -                        } -                        Binding::Enter => { -                            publish(Action::Edit(Edit::Enter)); -                        } -                        Binding::Backspace => { -                            publish(Action::Edit(Edit::Backspace)); -                        } -                        Binding::Delete => { -                            publish(Action::Edit(Edit::Delete)); -                        } -                        Binding::Sequence(sequence) => { -                            for binding in sequence { -                                apply_binding( -                                    binding, content, state, on_edit, -                                    clipboard, shell, -                                ); +                            Binding::Custom(message) => { +                                shell.publish(message);                              }                          } -                        Binding::Custom(message) => { -                            shell.publish(message); -                        }                      } -                } -                apply_binding( -                    binding, -                    self.content, -                    state, -                    on_edit, -                    clipboard, -                    shell, -                ); +                    apply_binding( +                        binding, +                        self.content, +                        state, +                        on_edit, +                        clipboard, +                        shell, +                    ); + +                    if let Some(focus) = &mut state.focus { +                        focus.updated_at = Instant::now(); +                    } -                if let Some(focus) = &mut state.focus { -                    focus.updated_at = Instant::now(); +                    shell.capture_event();                  }              }          } -        event::Status::Captured +        let status = { +            let is_disabled = self.on_edit.is_none(); +            let is_hovered = cursor.is_over(layout.bounds()); + +            if is_disabled { +                Status::Disabled +            } else if state.focus.is_some() { +                Status::Focused { is_hovered } +            } else if is_hovered { +                Status::Hovered +            } else { +                Status::Active +            } +        }; + +        if is_redraw { +            self.last_status = Some(status); +        } else if self +            .last_status +            .is_some_and(|last_status| status != last_status) +        { +            shell.request_redraw(); +        }      }      fn draw( @@ -808,7 +839,7 @@ where          theme: &Theme,          _defaults: &renderer::Style,          layout: Layout<'_>, -        cursor: mouse::Cursor, +        _cursor: mouse::Cursor,          _viewport: &Rectangle,      ) {          let bounds = layout.bounds(); @@ -824,20 +855,8 @@ where              |highlight| (self.highlighter_format)(highlight, theme),          ); -        let is_disabled = self.on_edit.is_none(); -        let is_mouse_over = cursor.is_over(bounds); - -        let status = if is_disabled { -            Status::Disabled -        } else if state.focus.is_some() { -            Status::Focused -        } else if is_mouse_over { -            Status::Hovered -        } else { -            Status::Active -        }; - -        let style = theme.style(&self.class, status); +        let style = theme +            .style(&self.class, self.last_status.unwrap_or(Status::Active));          renderer.fill_quad(              renderer::Quad { @@ -1036,7 +1055,7 @@ impl<Message> Binding<Message> {              status,          } = event; -        if status != Status::Focused { +        if !matches!(status, Status::Focused { .. }) {              return None;          } @@ -1176,7 +1195,9 @@ impl<Message> Update<Message> {                  ..              }) => {                  let status = if state.focus.is_some() { -                    Status::Focused +                    Status::Focused { +                        is_hovered: cursor.is_over(bounds), +                    }                  } else {                      Status::Active                  }; @@ -1222,7 +1243,10 @@ pub enum Status {      /// The [`TextEditor`] is being hovered.      Hovered,      /// The [`TextEditor`] is focused. -    Focused, +    Focused { +        /// Whether the [`TextEditor`] is hovered, while focused. +        is_hovered: bool, +    },      /// The [`TextEditor`] cannot be interacted with.      Disabled,  } @@ -1297,7 +1321,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {              },              ..active          }, -        Status::Focused => Style { +        Status::Focused { .. } => Style {              border: Border {                  color: palette.primary.strong.color,                  ..active.border diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ff413779..51e61cc0 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -42,7 +42,6 @@ use editor::Editor;  use crate::core::alignment;  use crate::core::clipboard::{self, Clipboard}; -use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::keyboard::key;  use crate::core::layout; @@ -57,8 +56,8 @@ use crate::core::widget::operation::{self, Operation};  use crate::core::widget::tree::{self, Tree};  use crate::core::window;  use crate::core::{ -    Background, Border, Color, Element, Layout, Length, Padding, Pixels, Point, -    Rectangle, Shell, Size, Theme, Vector, Widget, +    Background, Border, Color, Element, Event, Layout, Length, Padding, Pixels, +    Point, Rectangle, Shell, Size, Theme, Vector, Widget,  };  use crate::runtime::task::{self, Task};  use crate::runtime::Action; @@ -120,6 +119,7 @@ pub struct TextInput<      on_submit: Option<Message>,      icon: Option<Icon<Renderer::Font>>,      class: Theme::Class<'a>, +    last_status: Option<Status>,  }  /// The default [`Padding`] of a [`TextInput`]. @@ -150,6 +150,7 @@ where              on_submit: None,              icon: None,              class: Theme::default(), +            last_status: None,          }      } @@ -400,7 +401,7 @@ where          renderer: &mut Renderer,          theme: &Theme,          layout: Layout<'_>, -        cursor: mouse::Cursor, +        _cursor: mouse::Cursor,          value: Option<&Value>,          viewport: &Rectangle,      ) { @@ -416,19 +417,8 @@ where          let mut children_layout = layout.children();          let text_bounds = children_layout.next().unwrap().bounds(); -        let is_mouse_over = cursor.is_over(bounds); - -        let status = if is_disabled { -            Status::Disabled -        } else if state.is_focused() { -            Status::Focused -        } else if is_mouse_over { -            Status::Hovered -        } else { -            Status::Active -        }; - -        let style = theme.style(&self.class, status); +        let style = theme +            .style(&self.class, self.last_status.unwrap_or(Status::Disabled));          renderer.fill_quad(              renderer::Quad { @@ -637,7 +627,7 @@ where          operation.text_input(state, self.id.as_ref().map(|id| &id.0));      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -647,7 +637,7 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let update_cache = |state, value| {              replace_paragraph(                  renderer, @@ -660,22 +650,21 @@ where              );          }; -        match event { +        match &event {              Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))              | Event::Touch(touch::Event::FingerPressed { .. }) => {                  let state = state::<Renderer>(tree); +                let cursor_before = state.cursor;                  let click_position = cursor.position_over(layout.bounds());                  state.is_focused = if click_position.is_some() { -                    state.is_focused.or_else(|| { -                        let now = Instant::now(); - -                        Some(Focus { -                            updated_at: now, -                            now, -                            is_window_focused: true, -                        }) +                    let now = Instant::now(); + +                    Some(Focus { +                        updated_at: now, +                        now, +                        is_window_focused: true,                      })                  } else {                      None @@ -760,7 +749,11 @@ where                      state.last_click = Some(click); -                    return event::Status::Captured; +                    if cursor_before != state.cursor { +                        shell.request_redraw(); +                    } + +                    shell.capture_event();                  }              }              Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -801,11 +794,21 @@ where                      )                      .unwrap_or(0); +                    let selection_before = state.cursor.selection(&value); +                      state                          .cursor                          .select_range(state.cursor.start(&value), position); -                    return event::Status::Captured; +                    if let Some(focus) = &mut state.is_focused { +                        focus.updated_at = Instant::now(); +                    } + +                    if selection_before != state.cursor.selection(&value) { +                        shell.request_redraw(); +                    } + +                    shell.capture_event();                  }              }              Event::Keyboard(keyboard::Event::KeyPressed { @@ -815,7 +818,6 @@ where                  if let Some(focus) = &mut state.is_focused {                      let modifiers = state.keyboard_modifiers; -                    focus.updated_at = Instant::now();                      match key.as_ref() {                          keyboard::Key::Character("c") @@ -831,14 +833,15 @@ where                                  );                              } -                            return event::Status::Captured; +                            shell.capture_event(); +                            return;                          }                          keyboard::Key::Character("x")                              if state.keyboard_modifiers.command()                                  && !self.is_secure =>                          {                              let Some(on_input) = &self.on_input else { -                                return event::Status::Ignored; +                                return;                              };                              if let Some((start, end)) = @@ -856,17 +859,18 @@ where                              let message = (on_input)(editor.contents());                              shell.publish(message); +                            shell.capture_event(); +                            focus.updated_at = Instant::now();                              update_cache(state, &self.value); - -                            return event::Status::Captured; +                            return;                          }                          keyboard::Key::Character("v")                              if state.keyboard_modifiers.command()                                  && !state.keyboard_modifiers.alt() =>                          {                              let Some(on_input) = &self.on_input else { -                                return event::Status::Ignored; +                                return;                              };                              let content = match state.is_pasting.take() { @@ -885,7 +889,6 @@ where                              let mut editor =                                  Editor::new(&mut self.value, &mut state.cursor); -                              editor.paste(content.clone());                              let message = if let Some(paste) = &self.on_paste { @@ -894,26 +897,35 @@ where                                  (on_input)(editor.contents())                              };                              shell.publish(message); +                            shell.capture_event();                              state.is_pasting = Some(content); - +                            focus.updated_at = Instant::now();                              update_cache(state, &self.value); - -                            return event::Status::Captured; +                            return;                          }                          keyboard::Key::Character("a")                              if state.keyboard_modifiers.command() =>                          { +                            let cursor_before = state.cursor; +                              state.cursor.select_all(&self.value); -                            return event::Status::Captured; +                            if cursor_before != state.cursor { +                                focus.updated_at = Instant::now(); + +                                shell.request_redraw(); +                            } + +                            shell.capture_event(); +                            return;                          }                          _ => {}                      }                      if let Some(text) = text {                          let Some(on_input) = &self.on_input else { -                            return event::Status::Ignored; +                            return;                          };                          state.is_pasting = None; @@ -928,12 +940,11 @@ where                              let message = (on_input)(editor.contents());                              shell.publish(message); +                            shell.capture_event();                              focus.updated_at = Instant::now(); -                              update_cache(state, &self.value); - -                            return event::Status::Captured; +                            return;                          }                      } @@ -941,11 +952,12 @@ where                          keyboard::Key::Named(key::Named::Enter) => {                              if let Some(on_submit) = self.on_submit.clone() {                                  shell.publish(on_submit); +                                shell.capture_event();                              }                          }                          keyboard::Key::Named(key::Named::Backspace) => {                              let Some(on_input) = &self.on_input else { -                                return event::Status::Ignored; +                                return;                              };                              if modifiers.jump() @@ -968,12 +980,14 @@ where                              let message = (on_input)(editor.contents());                              shell.publish(message); +                            shell.capture_event(); +                            focus.updated_at = Instant::now();                              update_cache(state, &self.value);                          }                          keyboard::Key::Named(key::Named::Delete) => {                              let Some(on_input) = &self.on_input else { -                                return event::Status::Ignored; +                                return;                              };                              if modifiers.jump() @@ -999,10 +1013,14 @@ where                              let message = (on_input)(editor.contents());                              shell.publish(message); +                            shell.capture_event(); +                            focus.updated_at = Instant::now();                              update_cache(state, &self.value);                          }                          keyboard::Key::Named(key::Named::Home) => { +                            let cursor_before = state.cursor; +                              if modifiers.shift() {                                  state.cursor.select_range(                                      state.cursor.start(&self.value), @@ -1011,8 +1029,18 @@ where                              } else {                                  state.cursor.move_to(0);                              } + +                            if cursor_before != state.cursor { +                                focus.updated_at = Instant::now(); + +                                shell.request_redraw(); +                            } + +                            shell.capture_event();                          }                          keyboard::Key::Named(key::Named::End) => { +                            let cursor_before = state.cursor; +                              if modifiers.shift() {                                  state.cursor.select_range(                                      state.cursor.start(&self.value), @@ -1021,10 +1049,20 @@ where                              } else {                                  state.cursor.move_to(self.value.len());                              } + +                            if cursor_before != state.cursor { +                                focus.updated_at = Instant::now(); + +                                shell.request_redraw(); +                            } + +                            shell.capture_event();                          }                          keyboard::Key::Named(key::Named::ArrowLeft)                              if modifiers.macos_command() =>                          { +                            let cursor_before = state.cursor; +                              if modifiers.shift() {                                  state.cursor.select_range(                                      state.cursor.start(&self.value), @@ -1033,10 +1071,20 @@ where                              } else {                                  state.cursor.move_to(0);                              } + +                            if cursor_before != state.cursor { +                                focus.updated_at = Instant::now(); + +                                shell.request_redraw(); +                            } + +                            shell.capture_event();                          }                          keyboard::Key::Named(key::Named::ArrowRight)                              if modifiers.macos_command() =>                          { +                            let cursor_before = state.cursor; +                              if modifiers.shift() {                                  state.cursor.select_range(                                      state.cursor.start(&self.value), @@ -1045,8 +1093,18 @@ where                              } else {                                  state.cursor.move_to(self.value.len());                              } + +                            if cursor_before != state.cursor { +                                focus.updated_at = Instant::now(); + +                                shell.request_redraw(); +                            } + +                            shell.capture_event();                          }                          keyboard::Key::Named(key::Named::ArrowLeft) => { +                            let cursor_before = state.cursor; +                              if modifiers.jump() && !self.is_secure {                                  if modifiers.shift() {                                      state @@ -1062,8 +1120,18 @@ where                              } else {                                  state.cursor.move_left(&self.value);                              } + +                            if cursor_before != state.cursor { +                                focus.updated_at = Instant::now(); + +                                shell.request_redraw(); +                            } + +                            shell.capture_event();                          }                          keyboard::Key::Named(key::Named::ArrowRight) => { +                            let cursor_before = state.cursor; +                              if modifiers.jump() && !self.is_secure {                                  if modifiers.shift() {                                      state @@ -1079,6 +1147,14 @@ where                              } else {                                  state.cursor.move_right(&self.value);                              } + +                            if cursor_before != state.cursor { +                                focus.updated_at = Instant::now(); + +                                shell.request_redraw(); +                            } + +                            shell.capture_event();                          }                          keyboard::Key::Named(key::Named::Escape) => {                              state.is_focused = None; @@ -1087,39 +1163,22 @@ where                              state.keyboard_modifiers =                                  keyboard::Modifiers::default(); -                        } -                        keyboard::Key::Named( -                            key::Named::Tab -                            | key::Named::ArrowUp -                            | key::Named::ArrowDown, -                        ) => { -                            return event::Status::Ignored; + +                            shell.capture_event();                          }                          _ => {}                      } - -                    return event::Status::Captured;                  }              }              Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => {                  let state = state::<Renderer>(tree);                  if state.is_focused.is_some() { -                    match key.as_ref() { -                        keyboard::Key::Character("v") => { -                            state.is_pasting = None; -                        } -                        keyboard::Key::Named( -                            key::Named::Tab -                            | key::Named::ArrowUp -                            | key::Named::ArrowDown, -                        ) => { -                            return event::Status::Ignored; -                        } -                        _ => {} -                    } +                    if let keyboard::Key::Character("v") = key.as_ref() { +                        state.is_pasting = None; -                    return event::Status::Captured; +                        shell.capture_event(); +                    }                  }                  state.is_pasting = None; @@ -1127,7 +1186,7 @@ where              Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => {                  let state = state::<Renderer>(tree); -                state.keyboard_modifiers = modifiers; +                state.keyboard_modifiers = *modifiers;              }              Event::Window(window::Event::Unfocused) => {                  let state = state::<Renderer>(tree); @@ -1143,32 +1202,59 @@ where                      focus.is_window_focused = true;                      focus.updated_at = Instant::now(); -                    shell.request_redraw(window::RedrawRequest::NextFrame); +                    shell.request_redraw();                  }              }              Event::Window(window::Event::RedrawRequested(now)) => {                  let state = state::<Renderer>(tree);                  if let Some(focus) = &mut state.is_focused { -                    if focus.is_window_focused { -                        focus.now = now; +                    if focus.is_window_focused +                        && matches!( +                            state.cursor.state(&self.value), +                            cursor::State::Index(_) +                        ) +                    { +                        focus.now = *now;                          let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS -                            - (now - focus.updated_at).as_millis() +                            - (*now - focus.updated_at).as_millis()                                  % CURSOR_BLINK_INTERVAL_MILLIS; -                        shell.request_redraw(window::RedrawRequest::At( -                            now + Duration::from_millis( +                        shell.request_redraw_at( +                            *now + Duration::from_millis(                                  millis_until_redraw as u64,                              ), -                        )); +                        );                      }                  }              }              _ => {}          } -        event::Status::Ignored +        let state = state::<Renderer>(tree); +        let is_disabled = self.on_input.is_none(); + +        let status = if is_disabled { +            Status::Disabled +        } else if state.is_focused() { +            Status::Focused { +                is_hovered: cursor.is_over(layout.bounds()), +            } +        } else if cursor.is_over(layout.bounds()) { +            Status::Hovered +        } else { +            Status::Active +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.last_status = Some(status); +        } else if self +            .last_status +            .is_some_and(|last_status| status != last_status) +        { +            shell.request_redraw(); +        }      }      fn draw( @@ -1535,7 +1621,10 @@ pub enum Status {      /// The [`TextInput`] is being hovered.      Hovered,      /// The [`TextInput`] is focused. -    Focused, +    Focused { +        /// Whether the [`TextInput`] is hovered, while focused. +        is_hovered: bool, +    },      /// The [`TextInput`] cannot be interacted with.      Disabled,  } @@ -1612,7 +1701,7 @@ pub fn default(theme: &Theme, status: Status) -> Style {              },              ..active          }, -        Status::Focused => Style { +        Status::Focused { .. } => Style {              border: Border {                  color: palette.primary.strong.color,                  ..active.border diff --git a/widget/src/text_input/cursor.rs b/widget/src/text_input/cursor.rs index f682b17d..a326fc8f 100644 --- a/widget/src/text_input/cursor.rs +++ b/widget/src/text_input/cursor.rs @@ -2,13 +2,13 @@  use crate::text_input::Value;  /// The cursor of a text input. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)]  pub struct Cursor {      state: State,  }  /// The state of a [`Cursor`]. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)]  pub enum State {      /// Cursor without a selection      Index(usize), diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 499a9fe8..82160f24 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -1,5 +1,4 @@  use crate::container; -use crate::core::event::{self, Event};  use crate::core::layout;  use crate::core::mouse;  use crate::core::overlay; @@ -7,8 +6,8 @@ use crate::core::renderer;  use crate::core::widget::tree::{self, Tree};  use crate::core::widget::Operation;  use crate::core::{ -    Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, -    Shell, Size, Vector, Widget, +    Background, Clipboard, Color, Element, Event, Layout, Length, Point, +    Rectangle, Shell, Size, Vector, Widget,  };  use std::marker::PhantomData; @@ -111,7 +110,7 @@ where              .operate(tree, layout, renderer, operation);      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -121,10 +120,10 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { -        self.content.as_widget_mut().on_event( +    ) { +        self.content.as_widget_mut().update(              tree, event, layout, cursor, renderer, clipboard, shell, viewport, -        ) +        );      }      fn mouse_interaction( @@ -219,7 +218,7 @@ where                  );              } -            fn on_event( +            fn update(                  &mut self,                  event: Event,                  layout: Layout<'_>, @@ -227,9 +226,9 @@ where                  renderer: &Renderer,                  clipboard: &mut dyn Clipboard,                  shell: &mut Shell<'_, Message>, -            ) -> event::Status { +            ) {                  self.content -                    .on_event(event, layout, cursor, renderer, clipboard, shell) +                    .update(event, layout, cursor, renderer, clipboard, shell);              }              fn operate( diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index fdd2e68c..5dfa0c0e 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -31,7 +31,6 @@  //! }  //! ```  use crate::core::alignment; -use crate::core::event;  use crate::core::layout;  use crate::core::mouse;  use crate::core::renderer; @@ -39,6 +38,7 @@ use crate::core::text;  use crate::core::touch;  use crate::core::widget;  use crate::core::widget::tree::{self, Tree}; +use crate::core::window;  use crate::core::{      Border, Clipboard, Color, Element, Event, Layout, Length, Pixels,      Rectangle, Shell, Size, Theme, Widget, @@ -99,6 +99,7 @@ pub struct Toggler<      spacing: f32,      font: Option<Renderer::Font>,      class: Theme::Class<'a>, +    last_status: Option<Status>,  }  impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer> @@ -132,6 +133,7 @@ where              spacing: Self::DEFAULT_SIZE / 2.0,              font: None,              class: Theme::default(), +            last_status: None,          }      } @@ -304,7 +306,7 @@ where          )      } -    fn on_event( +    fn update(          &mut self,          _state: &mut Tree,          event: Event, @@ -314,9 +316,9 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let Some(on_toggle) = &self.on_toggle else { -            return event::Status::Ignored; +            return;          };          match event { @@ -326,13 +328,31 @@ where                  if mouse_over {                      shell.publish(on_toggle(!self.is_toggled)); - -                    event::Status::Captured -                } else { -                    event::Status::Ignored +                    shell.capture_event();                  }              } -            _ => event::Status::Ignored, +            _ => {} +        } + +        let current_status = if self.on_toggle.is_none() { +            Status::Disabled +        } else if cursor.is_over(layout.bounds()) { +            Status::Hovered { +                is_toggled: self.is_toggled, +            } +        } else { +            Status::Active { +                is_toggled: self.is_toggled, +            } +        }; + +        if let Event::Window(window::Event::RedrawRequested(_now)) = event { +            self.last_status = Some(current_status); +        } else if self +            .last_status +            .is_some_and(|status| status != current_status) +        { +            shell.request_redraw();          }      } @@ -362,7 +382,7 @@ where          theme: &Theme,          style: &renderer::Style,          layout: Layout<'_>, -        cursor: mouse::Cursor, +        _cursor: mouse::Cursor,          viewport: &Rectangle,      ) {          /// Makes sure that the border radius of the toggler looks good at every size. @@ -391,21 +411,8 @@ where          }          let bounds = toggler_layout.bounds(); -        let is_mouse_over = cursor.is_over(layout.bounds()); - -        let status = if self.on_toggle.is_none() { -            Status::Disabled -        } else if is_mouse_over { -            Status::Hovered { -                is_toggled: self.is_toggled, -            } -        } else { -            Status::Active { -                is_toggled: self.is_toggled, -            } -        }; - -        let style = theme.style(&self.class, status); +        let style = theme +            .style(&self.class, self.last_status.unwrap_or(Status::Disabled));          let border_radius = bounds.height / BORDER_RADIUS_RATIO;          let space = SPACE_RATIO * bounds.height; diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index e98f4da7..e66f5e4a 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -22,7 +22,6 @@  //! }  //! ```  use crate::container; -use crate::core::event::{self, Event};  use crate::core::layout::{self, Layout};  use crate::core::mouse;  use crate::core::overlay; @@ -30,8 +29,8 @@ use crate::core::renderer;  use crate::core::text;  use crate::core::widget::{self, Widget};  use crate::core::{ -    Clipboard, Element, Length, Padding, Pixels, Point, Rectangle, Shell, Size, -    Vector, +    Clipboard, Element, Event, Length, Padding, Pixels, Point, Rectangle, +    Shell, Size, Vector,  };  /// An element to display a widget over another. @@ -190,7 +189,7 @@ where              .layout(&mut tree.children[0], renderer, limits)      } -    fn on_event( +    fn update(          &mut self,          tree: &mut widget::Tree,          event: Event, @@ -200,7 +199,7 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          viewport: &Rectangle, -    ) -> event::Status { +    ) {          let state = tree.state.downcast_mut::<State>();          let was_idle = *state == State::Idle; @@ -216,7 +215,7 @@ where              shell.invalidate_layout();          } -        self.content.as_widget_mut().on_event( +        self.content.as_widget_mut().update(              &mut tree.children[0],              event,              layout, @@ -225,7 +224,7 @@ where              clipboard,              shell,              viewport, -        ) +        );      }      fn mouse_interaction( diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index 18633474..ec72a455 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -35,7 +35,6 @@ pub use crate::slider::{  };  use crate::core::border::Border; -use crate::core::event::{self, Event};  use crate::core::keyboard;  use crate::core::keyboard::key::{self, Key};  use crate::core::layout::{self, Layout}; @@ -44,8 +43,8 @@ use crate::core::renderer;  use crate::core::touch;  use crate::core::widget::tree::{self, Tree};  use crate::core::{ -    self, Clipboard, Element, Length, Pixels, Point, Rectangle, Shell, Size, -    Widget, +    self, Clipboard, Element, Event, Length, Pixels, Point, Rectangle, Shell, +    Size, Widget,  };  /// An vertical bar and a handle that selects a single value from a range of @@ -244,7 +243,7 @@ where          layout::atomic(limits, self.width, self.height)      } -    fn on_event( +    fn update(          &mut self,          tree: &mut Tree,          event: Event, @@ -254,7 +253,7 @@ where          _clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,          _viewport: &Rectangle, -    ) -> event::Status { +    ) {          let state = tree.state.downcast_mut::<State>();          let is_dragging = state.is_dragging;          let current_value = self.value; @@ -350,7 +349,7 @@ where                          state.is_dragging = true;                      } -                    return event::Status::Captured; +                    shell.capture_event();                  }              }              Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -362,7 +361,7 @@ where                      }                      state.is_dragging = false; -                    return event::Status::Captured; +                    shell.capture_event();                  }              }              Event::Mouse(mouse::Event::CursorMoved { .. }) @@ -370,7 +369,7 @@ where                  if is_dragging {                      let _ = cursor.position().and_then(locate).map(change); -                    return event::Status::Captured; +                    shell.capture_event();                  }              }              Event::Mouse(mouse::Event::WheelScrolled { delta }) @@ -388,7 +387,7 @@ where                          let _ = increment(current_value).map(change);                      } -                    return event::Status::Captured; +                    shell.capture_event();                  }              }              Event::Keyboard(keyboard::Event::KeyPressed { key, .. }) => { @@ -403,7 +402,7 @@ where                          _ => (),                      } -                    return event::Status::Captured; +                    shell.capture_event();                  }              }              Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { @@ -411,8 +410,6 @@ where              }              _ => {}          } - -        event::Status::Ignored      }      fn draw( | 
