summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
Diffstat (limited to 'widget')
-rw-r--r--widget/src/action.rs89
-rw-r--r--widget/src/button.rs63
-rw-r--r--widget/src/canvas.rs71
-rw-r--r--widget/src/canvas/event.rs21
-rw-r--r--widget/src/canvas/program.rs15
-rw-r--r--widget/src/checkbox.rs58
-rw-r--r--widget/src/column.rs37
-rw-r--r--widget/src/combo_box.rs42
-rw-r--r--widget/src/container.rs11
-rw-r--r--widget/src/helpers.rs56
-rw-r--r--widget/src/image/viewer.rs32
-rw-r--r--widget/src/keyed/column.rs37
-rw-r--r--widget/src/lazy.rs24
-rw-r--r--widget/src/lazy/component.rs68
-rw-r--r--widget/src/lazy/responsive.rs60
-rw-r--r--widget/src/lib.rs2
-rw-r--r--widget/src/mouse_area.rs158
-rw-r--r--widget/src/overlay/menu.rs60
-rw-r--r--widget/src/pane_grid.rs197
-rw-r--r--widget/src/pane_grid/content.rs47
-rw-r--r--widget/src/pane_grid/title_bar.rs40
-rw-r--r--widget/src/pick_list.rs83
-rw-r--r--widget/src/radio.rs53
-rw-r--r--widget/src/row.rs43
-rw-r--r--widget/src/scrollable.rs625
-rw-r--r--widget/src/shader.rs58
-rw-r--r--widget/src/shader/event.rs25
-rw-r--r--widget/src/shader/program.rs16
-rw-r--r--widget/src/slider.rs294
-rw-r--r--widget/src/stack.rs55
-rw-r--r--widget/src/text/rich.rs20
-rw-r--r--widget/src/text_editor.rs332
-rw-r--r--widget/src/text_input.rs251
-rw-r--r--widget/src/text_input/cursor.rs4
-rw-r--r--widget/src/themer.rs19
-rw-r--r--widget/src/toggler.rs57
-rw-r--r--widget/src/tooltip.rs13
-rw-r--r--widget/src/vertical_slider.rs21
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 @@
//! ```
//! ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
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(