From 5c33ce18ed8b12db9a6ba138112804761d26fddb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 00:13:42 +0200 Subject: [PATCH 01/29] Draft `reactive-rendering` feature for `button` --- Cargo.toml | 26 +++---- examples/multi_window/Cargo.toml | 2 +- widget/src/button.rs | 112 +++++++++++++++++++------------ winit/Cargo.toml | 2 +- winit/src/program.rs | 95 +++++++++++++++++++------- winit/src/program/state.rs | 5 +- 6 files changed, 156 insertions(+), 86 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2db66da2e1..bf85ada2b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,20 +22,20 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"] -# Enable the `wgpu` GPU-accelerated renderer backend +default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme", "reactive-rendering"] +# Enables the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] -# Enable the `tiny-skia` software renderer backend +# Enables the `tiny-skia` software renderer backend tiny-skia = ["iced_renderer/tiny-skia"] -# Enables the `Image` widget +# Enables the `image` widget image = ["image-without-codecs", "image/default"] -# Enables the `Image` widget, without any built-in codecs of the `image` crate +# Enables the `image` widget, without any built-in codecs of the `image` crate image-without-codecs = ["iced_widget/image", "dep:image"] -# Enables the `Svg` widget +# Enables the `svg` widget svg = ["iced_widget/svg"] -# Enables the `Canvas` widget +# Enables the `canvas` widget canvas = ["iced_widget/canvas"] -# Enables the `QRCode` widget +# Enables the `qr_code` widget qr_code = ["iced_widget/qr_code"] # Enables the `markdown` widget markdown = ["iced_widget/markdown"] @@ -55,18 +55,18 @@ system = ["iced_winit/system"] web-colors = ["iced_renderer/web-colors"] # Enables the WebGL backend, replacing WebGPU webgl = ["iced_renderer/webgl"] -# Enables the syntax `highlighter` module +# Enables syntax highligthing highlighter = ["iced_highlighter", "iced_widget/highlighter"] -# Enables experimental multi-window support. -multi-window = ["iced_winit/multi-window"] # Enables the advanced module advanced = ["iced_core/advanced", "iced_widget/advanced"] -# Enables embedding Fira Sans as the default font on Wasm builds +# Embeds Fira Sans as the default font on Wasm builds fira-sans = ["iced_renderer/fira-sans"] -# Enables auto-detecting light/dark mode for the built-in theme +# Auto-detects light/dark mode for the built-in theme auto-detect-theme = ["iced_core/auto-detect-theme"] # Enables strict assertions for debugging purposes at the expense of performance strict-assertions = ["iced_renderer/strict-assertions"] +# Redraws only when widgets react to some runtime event +reactive-rendering = ["iced_winit/reactive-rendering"] [dependencies] iced_core.workspace = true diff --git a/examples/multi_window/Cargo.toml b/examples/multi_window/Cargo.toml index 2e222dfbb1..3f89417f00 100644 --- a/examples/multi_window/Cargo.toml +++ b/examples/multi_window/Cargo.toml @@ -6,4 +6,4 @@ edition = "2021" publish = false [dependencies] -iced = { path = "../..", features = ["debug", "multi-window"] } +iced = { path = "../..", features = ["debug"] } diff --git a/widget/src/button.rs b/widget/src/button.rs index a3394a013a..5850cea005 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -26,6 +26,7 @@ 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, @@ -81,6 +82,7 @@ where padding: Padding, clip: bool, class: Theme::Class<'a>, + status: Option, } enum OnPress<'a, Message> { @@ -117,6 +119,7 @@ where padding: DEFAULT_PADDING, clip: false, class: Theme::default(), + status: None, } } @@ -294,49 +297,85 @@ where return event::Status::Captured; } - match event { - Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if self.on_press.is_some() { - let bounds = layout.bounds(); + let mut update = || { + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if self.on_press.is_some() { + let bounds = layout.bounds(); - if cursor.is_over(bounds) { - let state = tree.state.downcast_mut::(); + if cursor.is_over(bounds) { + let state = tree.state.downcast_mut::(); - state.is_pressed = true; + state.is_pressed = true; - return event::Status::Captured; + return event::Status::Captured; + } } } - } - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = self.on_press.as_ref().map(OnPress::get) - { - let state = tree.state.downcast_mut::(); + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + if let Some(on_press) = + self.on_press.as_ref().map(OnPress::get) + { + let state = tree.state.downcast_mut::(); - if state.is_pressed { - state.is_pressed = false; + if state.is_pressed { + state.is_pressed = false; - let bounds = layout.bounds(); + let bounds = layout.bounds(); - if cursor.is_over(bounds) { - shell.publish(on_press); - } + if cursor.is_over(bounds) { + shell.publish(on_press); + } - return event::Status::Captured; + return event::Status::Captured; + } } } + Event::Touch(touch::Event::FingerLost { .. }) => { + let state = tree.state.downcast_mut::(); + + state.is_pressed = false; + } + _ => {} + } + + event::Status::Ignored + }; + + let update_status = update(); + + let current_status = if self.on_press.is_none() { + Status::Disabled + } else if cursor.is_over(layout.bounds()) { + let state = tree.state.downcast_ref::(); + + if state.is_pressed { + Status::Pressed + } else { + Status::Hovered } - Event::Touch(touch::Event::FingerLost { .. }) => { - let state = tree.state.downcast_mut::(); + } else { + Status::Active + }; - state.is_pressed = false; + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + self.status = Some(current_status); + } else { + match self.status { + Some(status) if status != current_status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} } - _ => {} } - event::Status::Ignored + update_status } fn draw( @@ -351,23 +390,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::(); - - 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/winit/Cargo.toml b/winit/Cargo.toml index bd6feb008a..b8f5a723e2 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -22,7 +22,7 @@ x11 = ["winit/x11"] wayland = ["winit/wayland"] wayland-dlopen = ["winit/wayland-dlopen"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] -multi-window = ["iced_runtime/multi-window"] +reactive-rendering = [] [dependencies] iced_futures.workspace = true diff --git a/winit/src/program.rs b/winit/src/program.rs index 8d1eec3af9..2ac7ad0da9 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -691,6 +691,7 @@ async fn run_instance( let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); + let mut redraw_queue = Vec::new(); debug.startup_finished(); @@ -758,14 +759,30 @@ async fn run_instance( } Event::EventLoopAwakened(event) => { match event { - event::Event::NewEvents( - event::StartCause::Init - | event::StartCause::ResumeTimeReached { .. }, - ) => { + event::Event::NewEvents(event::StartCause::Init) => { for (_id, window) in window_manager.iter_mut() { window.raw.request_redraw(); } } + event::Event::NewEvents( + event::StartCause::ResumeTimeReached { .. }, + ) => { + let now = Instant::now(); + + while let Some((target, id)) = + redraw_queue.last().copied() + { + if target > now { + break; + } + + let _ = redraw_queue.pop(); + + if let Some(window) = window_manager.get_mut(id) { + window.raw.request_redraw(); + } + } + } event::Event::PlatformSpecific( event::PlatformSpecific::MacOS( event::MacOS::ReceivedUrl(url), @@ -857,23 +874,19 @@ async fn run_instance( status: core::event::Status::Ignored, }); - let _ = control_sender.start_send(Control::ChangeFlow( - match ui_state { - user_interface::State::Updated { - redraw_request: Some(redraw_request), - } => match redraw_request { - window::RedrawRequest::NextFrame => { - window.raw.request_redraw(); - - ControlFlow::Wait - } - window::RedrawRequest::At(at) => { - ControlFlow::WaitUntil(at) - } - }, - _ => ControlFlow::Wait, - }, - )); + if let user_interface::State::Updated { + redraw_request: Some(redraw_request), + } = ui_state + { + match redraw_request { + window::RedrawRequest::NextFrame => { + window.raw.request_redraw(); + } + window::RedrawRequest::At(at) => { + redraw_queue.push((at, id)); + } + } + } let physical_size = window.state.physical_size(); @@ -1065,13 +1078,25 @@ async fn run_instance( &mut messages, ); + #[cfg(not(feature = "reactive-rendering"))] window.raw.request_redraw(); - if !uis_stale { - uis_stale = matches!( - ui_state, - user_interface::State::Outdated - ); + match ui_state { + #[cfg(feature = "reactive-rendering")] + user_interface::State::Updated { + redraw_request: Some(redraw_request), + } => match redraw_request { + window::RedrawRequest::NextFrame => { + window.raw.request_redraw(); + } + window::RedrawRequest::At(at) => { + redraw_queue.push((at, id)); + } + }, + user_interface::State::Outdated => { + uis_stale = true; + } + user_interface::State::Updated { .. } => {} } for (event, status) in window_events @@ -1139,6 +1164,24 @@ async fn run_instance( actions = 0; } } + + if !redraw_queue.is_empty() { + redraw_queue.sort_by( + |(target_a, _), (target_b, _)| { + target_a.cmp(target_b).reverse() + }, + ); + + let (target, _id) = redraw_queue + .last() + .copied() + .expect("Redraw queue is not empty"); + + let _ = + control_sender.start_send(Control::ChangeFlow( + ControlFlow::WaitUntil(target), + )); + } } _ => {} } diff --git a/winit/src/program/state.rs b/winit/src/program/state.rs index a7fa2788d9..b8a5896051 100644 --- a/winit/src/program/state.rs +++ b/winit/src/program/state.rs @@ -190,7 +190,10 @@ where .. }, .. - } => _debug.toggle(), + } => { + _debug.toggle(); + window.request_redraw(); + } _ => {} } } From 97bcca04002d9d7c4812e178d30fb12358fad72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 00:25:30 +0200 Subject: [PATCH 02/29] Remove `TODO` about reactive rendering in `iced_winit` --- winit/src/program.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/winit/src/program.rs b/winit/src/program.rs index 2ac7ad0da9..f15e5be569 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -824,11 +824,6 @@ async fn run_instance( continue; }; - // TODO: Avoid redrawing all the time by forcing widgets to - // request redraws on state changes - // - // Then, we can use the `interface_state` here to decide if a redraw - // is needed right away, or simply wait until a specific time. let redraw_event = core::Event::Window( window::Event::RedrawRequested(Instant::now()), ); From 3ba7c71e3ffb651fde753bcf63bb604c16d4bcc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 00:45:36 +0200 Subject: [PATCH 03/29] Implement `reactive-rendering` for `slider` --- widget/src/slider.rs | 290 +++++++++++++++++++++++-------------------- winit/src/program.rs | 2 + 2 files changed, 160 insertions(+), 132 deletions(-) diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 31aa0e0cba..25f0d85fad 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -37,6 +37,7 @@ 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, @@ -95,6 +96,7 @@ where width: Length, height: f32, class: Theme::Class<'a>, + status: Option, } impl<'a, T, Message, Theme> Slider<'a, T, Message, Theme> @@ -141,6 +143,7 @@ where width: Length::Fill, height: Self::DEFAULT_HEIGHT, class: Theme::default(), + status: None, } } @@ -253,185 +256,208 @@ where ) -> event::Status { let state = tree.state.downcast_mut::(); - 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 { - 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 { + 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 { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); - let increment = |value: T| -> Option { - 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 { + let step = if state.keyboard_modifiers.shift() { + self.shift_step.unwrap_or(self.step) + } else { + self.step + } + .into(); - let decrement = |value: T| -> Option { - 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; + return event::Status::Captured; } - - 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; + return event::Status::Captured; + } } - } - 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); + return event::Status::Captured; } - - 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); } - _ => (), + + 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) => { + let _ = decrement(current_value).map(change); + } + _ => (), + } - return event::Status::Captured; + return event::Status::Captured; + } } + Event::Keyboard(keyboard::Event::ModifiersChanged( + modifiers, + )) => { + state.keyboard_modifiers = *modifiers; + } + _ => {} } - Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - state.keyboard_modifiers = modifiers; + + event::Status::Ignored + }; + + let update_status = 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 { + match self.status { + Some(status) if status != current_status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} } - _ => {} } - event::Status::Ignored + update_status } 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::(); 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/winit/src/program.rs b/winit/src/program.rs index f15e5be569..579038afb5 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1161,6 +1161,8 @@ async fn run_instance( } if !redraw_queue.is_empty() { + // The queue should be fairly short, so we can + // simply sort all of the time. redraw_queue.sort_by( |(target_a, _), (target_b, _)| { target_a.cmp(target_b).reverse() From 52490397d64f187d55f51dc5883e3ba6c0da57a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 02:50:46 +0200 Subject: [PATCH 04/29] Implement `reactive-rendering` for `text_input` ... and fix the redraw queue logic in `iced_winit`. --- widget/src/text_input.rs | 223 +++++++++++++++++++++------- widget/src/text_input/cursor.rs | 4 +- winit/src/program.rs | 48 +++--- winit/src/program/window_manager.rs | 16 ++ 4 files changed, 202 insertions(+), 89 deletions(-) diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index ff41377946..c18009a2cc 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -120,6 +120,7 @@ pub struct TextInput< on_submit: Option, icon: Option>, class: Theme::Class<'a>, + last_status: Option, } /// The default [`Padding`] of a [`TextInput`]. @@ -150,6 +151,7 @@ where on_submit: None, icon: None, class: Theme::default(), + last_status: None, } } @@ -400,7 +402,7 @@ where renderer: &mut Renderer, theme: &Theme, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, value: Option<&Value>, viewport: &Rectangle, ) { @@ -416,19 +418,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 { @@ -660,22 +651,21 @@ where ); }; - match event { + match &event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let state = state::(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,6 +750,10 @@ where state.last_click = Some(click); + if cursor_before != state.cursor { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + return event::Status::Captured; } } @@ -801,10 +795,20 @@ where ) .unwrap_or(0); + let selection_before = state.cursor.selection(&value); + state .cursor .select_range(state.cursor.start(&value), position); + if let Some(focus) = &mut state.is_focused { + focus.updated_at = Instant::now(); + } + + if selection_before != state.cursor.selection(&value) { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + return event::Status::Captured; } } @@ -815,7 +819,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") @@ -857,6 +860,7 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + focus.updated_at = Instant::now(); update_cache(state, &self.value); return event::Status::Captured; @@ -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 { @@ -896,7 +899,7 @@ where shell.publish(message); state.is_pasting = Some(content); - + focus.updated_at = Instant::now(); update_cache(state, &self.value); return event::Status::Captured; @@ -904,8 +907,18 @@ where keyboard::Key::Character("a") if state.keyboard_modifiers.command() => { + let cursor_before = state.cursor; + state.cursor.select_all(&self.value); + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + return event::Status::Captured; } _ => {} @@ -930,7 +943,6 @@ where shell.publish(message); focus.updated_at = Instant::now(); - update_cache(state, &self.value); return event::Status::Captured; @@ -941,6 +953,8 @@ where keyboard::Key::Named(key::Named::Enter) => { if let Some(on_submit) = self.on_submit.clone() { shell.publish(on_submit); + + return event::Status::Captured; } } keyboard::Key::Named(key::Named::Backspace) => { @@ -969,7 +983,10 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + focus.updated_at = Instant::now(); update_cache(state, &self.value); + + return event::Status::Captured; } keyboard::Key::Named(key::Named::Delete) => { let Some(on_input) = &self.on_input else { @@ -1000,9 +1017,14 @@ where let message = (on_input)(editor.contents()); shell.publish(message); + focus.updated_at = Instant::now(); update_cache(state, &self.value); + + return event::Status::Captured; } 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 +1033,20 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } 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 +1055,22 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } 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 +1079,22 @@ where } else { state.cursor.move_to(0); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } 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 +1103,20 @@ where } else { state.cursor.move_to(self.value.len()); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } keyboard::Key::Named(key::Named::ArrowLeft) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1062,8 +1132,20 @@ where } else { state.cursor.move_left(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } keyboard::Key::Named(key::Named::ArrowRight) => { + let cursor_before = state.cursor; + if modifiers.jump() && !self.is_secure { if modifiers.shift() { state @@ -1079,6 +1161,16 @@ where } else { state.cursor.move_right(&self.value); } + + if cursor_before != state.cursor { + focus.updated_at = Instant::now(); + + shell.request_redraw( + window::RedrawRequest::NextFrame, + ); + } + + return event::Status::Captured; } keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; @@ -1087,39 +1179,22 @@ where state.keyboard_modifiers = keyboard::Modifiers::default(); - } - keyboard::Key::Named( - key::Named::Tab - | key::Named::ArrowUp - | key::Named::ArrowDown, - ) => { - return event::Status::Ignored; + + return event::Status::Captured; } _ => {} } - - return event::Status::Captured; } } Event::Keyboard(keyboard::Event::KeyReleased { key, .. }) => { let state = state::(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; + return event::Status::Captured; + } } state.is_pasting = None; @@ -1127,7 +1202,7 @@ where Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { let state = state::(tree); - state.keyboard_modifiers = modifiers; + state.keyboard_modifiers = *modifiers; } Event::Window(window::Event::Unfocused) => { let state = state::(tree); @@ -1150,15 +1225,20 @@ where let state = state::(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( + *now + Duration::from_millis( millis_until_redraw as u64, ), )); @@ -1168,6 +1248,32 @@ where _ => {} } + let state = state::(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 { + match self.last_status { + Some(last_status) if status != last_status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} + } + } + event::Status::Ignored } @@ -1535,7 +1641,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 +1721,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 f682b17d68..a326fc8f9f 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/winit/src/program.rs b/winit/src/program.rs index 579038afb5..a6729fa04c 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -691,7 +691,6 @@ async fn run_instance( let mut ui_caches = FxHashMap::default(); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut clipboard = Clipboard::unconnected(); - let mut redraw_queue = Vec::new(); debug.startup_finished(); @@ -769,17 +768,12 @@ async fn run_instance( ) => { let now = Instant::now(); - while let Some((target, id)) = - redraw_queue.last().copied() - { - if target > now { - break; - } - - let _ = redraw_queue.pop(); - - if let Some(window) = window_manager.get_mut(id) { - window.raw.request_redraw(); + for (_id, window) in window_manager.iter_mut() { + if let Some(redraw_at) = window.redraw_at { + if redraw_at <= now { + window.raw.request_redraw(); + window.redraw_at = None; + } } } } @@ -878,7 +872,7 @@ async fn run_instance( window.raw.request_redraw(); } window::RedrawRequest::At(at) => { - redraw_queue.push((at, id)); + window.redraw_at = Some(at); } } } @@ -1039,7 +1033,10 @@ async fn run_instance( } } event::Event::AboutToWait => { - if events.is_empty() && messages.is_empty() { + if events.is_empty() + && messages.is_empty() + && window_manager.is_idle() + { continue; } @@ -1085,7 +1082,7 @@ async fn run_instance( window.raw.request_redraw(); } window::RedrawRequest::At(at) => { - redraw_queue.push((at, id)); + window.redraw_at = Some(at); } }, user_interface::State::Outdated => { @@ -1160,24 +1157,15 @@ async fn run_instance( } } - if !redraw_queue.is_empty() { - // The queue should be fairly short, so we can - // simply sort all of the time. - redraw_queue.sort_by( - |(target_a, _), (target_b, _)| { - target_a.cmp(target_b).reverse() - }, - ); - - let (target, _id) = redraw_queue - .last() - .copied() - .expect("Redraw queue is not empty"); - + if let Some(redraw_at) = window_manager.redraw_at() { let _ = control_sender.start_send(Control::ChangeFlow( - ControlFlow::WaitUntil(target), + ControlFlow::WaitUntil(redraw_at), )); + } else { + let _ = control_sender.start_send( + Control::ChangeFlow(ControlFlow::Wait), + ); } } _ => {} diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 3d22e15509..7c00a84b9e 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -1,4 +1,5 @@ use crate::core::mouse; +use crate::core::time::Instant; use crate::core::window::Id; use crate::core::{Point, Size}; use crate::graphics::Compositor; @@ -62,6 +63,7 @@ where surface, renderer, mouse_interaction: mouse::Interaction::None, + redraw_at: None, }, ); @@ -74,6 +76,19 @@ where self.entries.is_empty() } + pub fn is_idle(&self) -> bool { + self.entries + .values() + .any(|window| window.redraw_at.is_some()) + } + + pub fn redraw_at(&self) -> Option { + self.entries + .values() + .filter_map(|window| window.redraw_at) + .min() + } + pub fn first(&self) -> Option<&Window> { self.entries.first_key_value().map(|(_id, window)| window) } @@ -138,6 +153,7 @@ where pub mouse_interaction: mouse::Interaction, pub surface: C::Surface, pub renderer: P::Renderer, + pub redraw_at: Option, } impl Window From 0691e617f31aab92cb5ddc4698f841357f4c14ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 02:53:30 +0200 Subject: [PATCH 05/29] Fix `WindowManager::is_idle` in `iced_winit` --- winit/src/program/window_manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winit/src/program/window_manager.rs b/winit/src/program/window_manager.rs index 7c00a84b9e..10a973fe4e 100644 --- a/winit/src/program/window_manager.rs +++ b/winit/src/program/window_manager.rs @@ -79,7 +79,7 @@ where pub fn is_idle(&self) -> bool { self.entries .values() - .any(|window| window.redraw_at.is_some()) + .all(|window| window.redraw_at.is_none()) } pub fn redraw_at(&self) -> Option { From f6c322f2f9aa5dd0ea151c702b2ba4754d68e550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 12:10:24 +0200 Subject: [PATCH 06/29] Implement `reactive-rendering` for `checkbox` --- widget/src/checkbox.rs | 49 ++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 819f0d9da1..e5dea3cc41 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -40,6 +40,7 @@ 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, @@ -100,6 +101,7 @@ pub struct Checkbox< font: Option, icon: Icon, class: Theme::Class<'a>, + last_status: Option, } impl<'a, Message, Theme, Renderer> Checkbox<'a, Message, Theme, Renderer> @@ -139,6 +141,7 @@ where shaping: text::Shaping::Basic, }, class: Theme::default(), + last_status: None, } } @@ -326,6 +329,31 @@ where _ => {} } + 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 { + match self.last_status { + Some(status) if status != current_status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} + } + } + event::Status::Ignored } @@ -351,24 +379,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(); From 0c7770218706c2b1f3d27dd4ea2bc18f489a5ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 12:18:23 +0200 Subject: [PATCH 07/29] Implement `reactive-rendering` for `radio` --- widget/src/radio.rs | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/widget/src/radio.rs b/widget/src/radio.rs index d2a3bd6a37..714d4fb58c 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -66,6 +66,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::{ Background, Clipboard, Color, Element, Layout, Length, Pixels, Rectangle, Shell, Size, Theme, Widget, @@ -147,6 +148,7 @@ where text_wrapping: text::Wrapping, font: Option, class: Theme::Class<'a>, + last_status: Option, } impl<'a, Message, Theme, Renderer> Radio<'a, Message, Theme, Renderer> @@ -192,6 +194,7 @@ where text_wrapping: text::Wrapping::default(), font: None, class: Theme::default(), + last_status: None, } } @@ -344,6 +347,28 @@ where _ => {} } + 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 { + match self.last_status { + Some(status) if status != current_status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} + } + } + event::Status::Ignored } @@ -369,21 +394,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(); From 46017c6483714a8245c090680e4810a621493f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 22 Oct 2024 12:26:12 +0200 Subject: [PATCH 08/29] Implement `reactive-rendering` for `toggler` --- widget/src/toggler.rs | 49 ++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index fdd2e68c31..13244e34c6 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -39,6 +39,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 +100,7 @@ pub struct Toggler< spacing: f32, font: Option, class: Theme::Class<'a>, + last_status: Option, } impl<'a, Message, Theme, Renderer> Toggler<'a, Message, Theme, Renderer> @@ -132,6 +134,7 @@ where spacing: Self::DEFAULT_SIZE / 2.0, font: None, class: Theme::default(), + last_status: None, } } @@ -319,7 +322,7 @@ where return event::Status::Ignored; }; - match event { + let event_status = match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let mouse_over = cursor.is_over(layout.bounds()); @@ -333,7 +336,32 @@ where } } _ => 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 { + match self.last_status { + Some(status) if status != current_status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} + } } + + event_status } fn mouse_interaction( @@ -362,7 +390,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 +419,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; From 7908b6eba91b91c61f7839b3d52fbee124b55cc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 23 Oct 2024 19:37:28 +0200 Subject: [PATCH 09/29] Request a redraw when a window is resized If we do not request it, macOS does not get any `RedrawRequested` events. Shouldn't `winit` [take care of this]? Probably a bug. [take care of this]: https://docs.rs/winit/0.30.5/winit/event/enum.WindowEvent.html#variant.RedrawRequested --- winit/src/program.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/winit/src/program.rs b/winit/src/program.rs index a6729fa04c..fb30ccd987 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -995,6 +995,13 @@ async fn run_instance( continue; }; + if matches!( + window_event, + winit::event::WindowEvent::Resized(_) + ) { + window.raw.request_redraw(); + } + if matches!( window_event, winit::event::WindowEvent::CloseRequested From fdf046daff5e9196713d2513cd1b654cfc122d80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 23 Oct 2024 19:49:19 +0200 Subject: [PATCH 10/29] Implement `reactive-rendering` for `pick_list` --- widget/src/pick_list.rs | 57 ++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 4f1e9da9cd..ec1c054f60 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -71,6 +71,7 @@ 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, @@ -173,6 +174,7 @@ pub struct PickList< handle: Handle, class: ::Class<'a>, menu_class: ::Class<'a>, + last_status: Option, } impl<'a, T, L, V, Message, Theme, Renderer> @@ -208,6 +210,7 @@ where handle: Handle::default(), class: ::default(), menu_class: ::default_menu(), + last_status: None, } } @@ -436,12 +439,11 @@ where shell: &mut Shell<'_, Message>, _viewport: &Rectangle, ) -> event::Status { - match event { + let state = tree.state.downcast_mut::>(); + + let event_status = match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { - let state = - tree.state.downcast_mut::>(); - 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. @@ -474,9 +476,6 @@ where Event::Mouse(mouse::Event::WheelScrolled { delta: mouse::ScrollDelta::Lines { y, .. }, }) => { - let state = - tree.state.downcast_mut::>(); - if state.keyboard_modifiers.command() && cursor.is_over(layout.bounds()) && !state.is_open @@ -519,15 +518,33 @@ where } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { - let state = - tree.state.downcast_mut::>(); - state.keyboard_modifiers = modifiers; event::Status::Ignored } _ => event::Status::Ignored, + }; + + let status = if state.is_open { + Status::Opened + } 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 { + match self.last_status { + Some(last_status) if last_status != status => { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + _ => {} + } } + + event_status } fn mouse_interaction( @@ -555,7 +572,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 +580,12 @@ where let state = tree.state.downcast_ref::>(); 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 +682,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 From 908af3fed72131d21d7c7ffbc503c9b1e8a65144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 23 Oct 2024 20:04:13 +0200 Subject: [PATCH 11/29] Implement `reactive-rendering` for `menu` --- widget/src/overlay/menu.rs | 43 ++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b641e8f516..e79bd3da8d 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -8,7 +8,8 @@ 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, @@ -334,6 +335,10 @@ where class: &'a ::Class<'b>, } +struct ListState { + is_hovered: Option, +} + impl<'a, 'b, T, Message, Theme, Renderer> Widget for List<'a, 'b, T, Message, Theme, Renderer> where @@ -341,6 +346,14 @@ where Theme: Catalog, Renderer: text::Renderer, { + fn tag(&self) -> tree::Tag { + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + tree::State::new(ListState { is_hovered: None }) + } + fn size(&self) -> Size { Size { width: Length::Fill, @@ -376,7 +389,7 @@ where fn on_event( &mut self, - _state: &mut Tree, + tree: &mut Tree, event: Event, layout: Layout<'_>, cursor: mouse::Cursor, @@ -411,14 +424,20 @@ 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( + window::RedrawRequest::NextFrame, + ); } } @@ -451,6 +470,18 @@ where _ => {} } + let state = tree.state.downcast_mut::(); + + if state.is_hovered.is_some_and(|is_hovered| { + is_hovered != cursor.is_over(layout.bounds()) + }) { + shell.request_redraw(window::RedrawRequest::NextFrame); + } + + if let Event::Window(window::Event::RedrawRequested(_now)) = event { + state.is_hovered = Some(cursor.is_over(layout.bounds())); + } + event::Status::Ignored } From 7fbc195b11f9a858bcc8f56f76907af82c966c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 23 Oct 2024 21:07:45 +0200 Subject: [PATCH 12/29] Implement `reactive-rendering` for `scrollable` --- widget/src/scrollable.rs | 625 +++++++++++++++++++++------------------ 1 file changed, 331 insertions(+), 294 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 528d63c1da..c435054768 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -81,6 +81,7 @@ pub struct Scrollable< content: Element<'a, Message, Theme, Renderer>, on_scroll: Option Message + 'a>>, class: Theme::Class<'a>, + last_status: Option, } impl<'a, Message, Theme, Renderer> Scrollable<'a, Message, Theme, Renderer> @@ -108,6 +109,7 @@ where content: content.into(), on_scroll: None, class: Theme::default(), + last_status: None, } .validate() } @@ -531,6 +533,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,336 +553,388 @@ 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 event::Status::Ignored; + }; - 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; + return event::Status::Captured; + } } + _ => {} } - _ => {} - } - } 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; - }; - - 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); + } 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; + }; - let _ = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); - } + 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, + ); - return event::Status::Captured; - } - _ => {} - } - } + state.y_scroller_grabbed_at = + Some(scroller_grabbed_at); - 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(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; } - - return event::Status::Captured; + _ => {} } - _ => {} } - } 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; - }; - 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) = 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; + }; - state.x_scroller_grabbed_at = Some(scroller_grabbed_at); + 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; } + _ => {} } - _ => {} - } - } + } 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; + }; - 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, + 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); + + let _ = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + + return event::Status::Captured; + } + } + _ => {} } - _ => mouse::Cursor::Unavailable, - }; + } - let translation = - state.translation(self.direction, bounds, content_bounds); + 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, + }; - 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 - }, - ) - }; + let translation = + state.translation(self.direction, bounds, content_bounds); - if matches!( - event, - Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch( - touch::Event::FingerLifted { .. } - | touch::Event::FingerLost { .. } + 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 + }, ) - ) { - state.scroll_area_touched_at = None; - state.x_scroller_grabbed_at = None; - state.y_scroller_grabbed_at = None; + }; - return content_status; - } + 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 let event::Status::Captured = content_status { - return event::Status::Captured; - } + return content_status; + } - if let Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) = - event - { - state.keyboard_modifiers = modifiers; + if let event::Status::Captured = content_status { + return event::Status::Captured; + } - return event::Status::Ignored; - } + if let Event::Keyboard(keyboard::Event::ModifiersChanged( + modifiers, + )) = event + { + state.keyboard_modifiers = modifiers; - match event { - Event::Mouse(mouse::Event::WheelScrolled { delta }) => { - if cursor_over_scrollable.is_none() { - return event::Status::Ignored; - } + return event::Status::Ignored; + } + + match event { + Event::Mouse(mouse::Event::WheelScrolled { delta }) => { + if cursor_over_scrollable.is_none() { + return event::Status::Ignored; + } - 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) - }; - - // TODO: Configurable speed/friction (?) - -movement * 60.0 - } - mouse::ScrollDelta::Pixels { x, y } => -Vector::new(x, y), - }; - - state.scroll( - self.direction.align(delta), - bounds, - content_bounds, - ); - - let has_scrolled = notify_scroll( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); + let is_vertical = match self.direction { + Direction::Vertical(_) => true, + Direction::Horizontal(_) => false, + Direction::Both { .. } => !is_shift_pressed, + }; - let in_transaction = state.last_scrolled.is_some(); + let movement = if is_vertical { + Vector::new(x, y) + } else { + Vector::new(y, x) + }; - 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; - }; + // TODO: Configurable speed/friction (?) + -movement * 60.0 + } + mouse::ScrollDelta::Pixels { x, y } => { + -Vector::new(x, y) + } + }; - state.scroll_area_touched_at = Some(cursor_position); + state.scroll( + self.direction.align(delta), + bounds, + content_bounds, + ); + + let has_scrolled = notify_scroll( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); + + let in_transaction = state.last_scrolled.is_some(); + + if has_scrolled || in_transaction { + event::Status::Captured + } else { + event::Status::Ignored } - 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; }; - 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 event::Status::Ignored; + }; + + 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 } + Event::Window(window::Event::RedrawRequested(_)) => { + let _ = notify_viewport( + state, + &self.on_scroll, + bounds, + content_bounds, + shell, + ); - event::Status::Captured + event::Status::Ignored + } + _ => event::Status::Ignored, } - Event::Window(window::Event::RedrawRequested(_)) => { - let _ = notify_viewport( - state, - &self.on_scroll, - bounds, - content_bounds, - shell, - ); + }; - event::Status::Ignored + let event_status = update(); + + 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(window::RedrawRequest::NextFrame); } + + event_status } fn draw( @@ -920,27 +976,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 +1360,7 @@ impl operation::Scrollable for State { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] enum Offset { Absolute(f32), Relative(f32), From 752403d70c851ece620c4007710062b158e8dec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 25 Oct 2024 15:40:05 +0200 Subject: [PATCH 13/29] Split `Shell::request_redraw` into two different methods --- core/src/shell.rs | 24 +++++++---- examples/loading_spinners/src/circular.rs | 4 +- examples/loading_spinners/src/linear.rs | 4 +- examples/toast/src/main.rs | 14 +------ widget/src/button.rs | 9 +--- widget/src/checkbox.rs | 12 +++--- widget/src/lazy/component.rs | 19 ++++++++- widget/src/overlay/menu.rs | 14 +++---- widget/src/pick_list.rs | 12 +++--- widget/src/radio.rs | 12 +++--- widget/src/scrollable.rs | 2 +- widget/src/slider.rs | 9 +--- widget/src/text_editor.rs | 6 +-- widget/src/text_input.rs | 50 ++++++++--------------- widget/src/toggler.rs | 12 +++--- 15 files changed, 89 insertions(+), 114 deletions(-) diff --git a/core/src/shell.rs b/core/src/shell.rs index 2952ceffae..7a92a2bef9 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,3 +1,4 @@ +use crate::time::Instant; use crate::window; /// A connection to the state of a shell. @@ -35,14 +36,19 @@ impl<'a, Message> Shell<'a, Message> { self.messages.push(message); } - /// Requests a new frame to be drawn. - pub fn request_redraw(&mut self, request: window::RedrawRequest) { + /// Requests a new frame to be drawn as soon as possible. + pub fn request_redraw(&mut self) { + self.redraw_request = Some(window::RedrawRequest::NextFrame); + } + + /// Requests a new frame to be drawn at the given [`Instant`]. + pub fn request_redraw_at(&mut self, at: Instant) { match self.redraw_request { None => { - self.redraw_request = Some(request); + self.redraw_request = Some(window::RedrawRequest::At(at)); } - Some(current) if request < current => { - self.redraw_request = Some(request); + Some(window::RedrawRequest::At(current)) if at < current => { + self.redraw_request = Some(window::RedrawRequest::At(at)); } _ => {} } @@ -95,8 +101,12 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); - if let Some(at) = other.redraw_request { - self.request_redraw(at); + if let Some(new) = other.redraw_request { + self.redraw_request = Some( + self.redraw_request + .map(|current| if current < new { current } else { new }) + .unwrap_or(new), + ); } self.is_layout_invalid = diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 9239f01f41..954a777eed 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -7,7 +7,7 @@ use iced::event; use iced::mouse; use iced::time::Instant; use iced::widget::canvas; -use iced::window::{self, RedrawRequest}; +use iced::window; use iced::{ Background, Color, Element, Event, Length, Radians, Rectangle, Renderer, Size, Vector, @@ -283,7 +283,7 @@ where ); state.cache.clear(); - shell.request_redraw(RedrawRequest::NextFrame); + shell.request_redraw(); } event::Status::Ignored diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 164993c69b..81edde75e4 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -6,7 +6,7 @@ use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; use iced::event; use iced::mouse; use iced::time::Instant; -use iced::window::{self, RedrawRequest}; +use iced::window; use iced::{Background, Color, Element, Event, Length, Rectangle, Size}; use super::easing::{self, Easing}; @@ -192,7 +192,7 @@ where if let Event::Window(window::Event::RedrawRequested(now)) = event { *state = state.timed_transition(self.cycle_duration, now); - shell.request_redraw(RedrawRequest::NextFrame); + shell.request_redraw(); } event::Status::Ignored diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 8f6a836e50..0b46c74e89 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -500,8 +500,6 @@ mod toast { shell: &mut Shell<'_, Message>, ) -> event::Status { if let Event::Window(window::Event::RedrawRequested(now)) = &event { - let mut next_redraw: Option = None; - self.instants.iter_mut().enumerate().for_each( |(index, maybe_instant)| { if let Some(instant) = maybe_instant.as_mut() { @@ -512,22 +510,12 @@ mod toast { if remaining == Duration::ZERO { maybe_instant.take(); shell.publish((self.on_close)(index)); - next_redraw = - Some(window::RedrawRequest::NextFrame); } else { - let redraw_at = - window::RedrawRequest::At(*now + remaining); - next_redraw = next_redraw - .map(|redraw| redraw.min(redraw_at)) - .or(Some(redraw_at)); + shell.request_redraw_at(*now + remaining); } } }, ); - - if let Some(redraw) = next_redraw { - shell.request_redraw(redraw); - } } let viewport = layout.bounds(); diff --git a/widget/src/button.rs b/widget/src/button.rs index 5850cea005..46fd0e171a 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -366,13 +366,8 @@ where if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.status = Some(current_status); - } else { - match self.status { - Some(status) if status != current_status => { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - _ => {} - } + } else if self.status.is_some_and(|status| status != current_status) { + shell.request_redraw(); } update_status diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index e5dea3cc41..6c5d7d6b11 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -345,13 +345,11 @@ where if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.last_status = Some(current_status); - } else { - match self.last_status { - Some(status) if status != current_status => { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - _ => {} - } + } else if self + .last_status + .is_some_and(|status| status != current_status) + { + shell.request_redraw(); } event::Status::Ignored diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index c7bc1264c7..e45c24ac62 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -7,6 +7,7 @@ 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, @@ -342,7 +343,14 @@ where 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() { @@ -620,7 +628,14 @@ where 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() { diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index e79bd3da8d..c1a0a5d880 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -435,9 +435,7 @@ where .publish(on_option_hovered(option.clone())); } - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } } @@ -472,14 +470,12 @@ where let state = tree.state.downcast_mut::(); - if state.is_hovered.is_some_and(|is_hovered| { - is_hovered != cursor.is_over(layout.bounds()) - }) { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - 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(); } event::Status::Ignored diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index ec1c054f60..9c9ba9e933 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -535,13 +535,11 @@ where if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.last_status = Some(status); - } else { - match self.last_status { - Some(last_status) if last_status != status => { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - _ => {} - } + } else if self + .last_status + .is_some_and(|last_status| last_status != status) + { + shell.request_redraw(); } event_status diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 714d4fb58c..ed821532e4 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -360,13 +360,11 @@ where if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.last_status = Some(current_status); - } else { - match self.last_status { - Some(status) if status != current_status => { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - _ => {} - } + } else if self + .last_status + .is_some_and(|last_status| last_status != current_status) + { + shell.request_redraw(); } event::Status::Ignored diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index c435054768..abad6ea69e 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -931,7 +931,7 @@ where .last_status .is_some_and(|last_status| last_status != status) { - shell.request_redraw(window::RedrawRequest::NextFrame); + shell.request_redraw(); } event_status diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 25f0d85fad..dbdb5f07cb 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -432,13 +432,8 @@ where if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.status = Some(current_status); - } else { - match self.status { - Some(status) if status != current_status => { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - _ => {} - } + } else if self.status.is_some_and(|status| status != current_status) { + shell.request_redraw(); } update_status diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index a298252a6c..3bb4549442 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -624,7 +624,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,11 +637,11 @@ 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, ), - )); + ); } } } diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index c18009a2cc..8fa7889f42 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -751,7 +751,7 @@ where state.last_click = Some(click); if cursor_before != state.cursor { - shell.request_redraw(window::RedrawRequest::NextFrame); + shell.request_redraw(); } return event::Status::Captured; @@ -806,7 +806,7 @@ where } if selection_before != state.cursor.selection(&value) { - shell.request_redraw(window::RedrawRequest::NextFrame); + shell.request_redraw(); } return event::Status::Captured; @@ -914,9 +914,7 @@ where if cursor_before != state.cursor { focus.updated_at = Instant::now(); - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } return event::Status::Captured; @@ -1037,9 +1035,7 @@ where if cursor_before != state.cursor { focus.updated_at = Instant::now(); - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } return event::Status::Captured; @@ -1059,9 +1055,7 @@ where if cursor_before != state.cursor { focus.updated_at = Instant::now(); - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } return event::Status::Captured; @@ -1083,9 +1077,7 @@ where if cursor_before != state.cursor { focus.updated_at = Instant::now(); - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } return event::Status::Captured; @@ -1107,9 +1099,7 @@ where if cursor_before != state.cursor { focus.updated_at = Instant::now(); - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } return event::Status::Captured; @@ -1136,9 +1126,7 @@ where if cursor_before != state.cursor { focus.updated_at = Instant::now(); - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } return event::Status::Captured; @@ -1165,9 +1153,7 @@ where if cursor_before != state.cursor { focus.updated_at = Instant::now(); - shell.request_redraw( - window::RedrawRequest::NextFrame, - ); + shell.request_redraw(); } return event::Status::Captured; @@ -1218,7 +1204,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)) => { @@ -1237,11 +1223,11 @@ where - (*now - focus.updated_at).as_millis() % CURSOR_BLINK_INTERVAL_MILLIS; - shell.request_redraw(window::RedrawRequest::At( + shell.request_redraw_at( *now + Duration::from_millis( millis_until_redraw as u64, ), - )); + ); } } } @@ -1265,13 +1251,11 @@ where if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.last_status = Some(status); - } else { - match self.last_status { - Some(last_status) if status != last_status => { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - _ => {} - } + } else if self + .last_status + .is_some_and(|last_status| status != last_status) + { + shell.request_redraw(); } event::Status::Ignored diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 13244e34c6..2553a7e4a4 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -352,13 +352,11 @@ where if let Event::Window(window::Event::RedrawRequested(_now)) = event { self.last_status = Some(current_status); - } else { - match self.last_status { - Some(status) if status != current_status => { - shell.request_redraw(window::RedrawRequest::NextFrame); - } - _ => {} - } + } else if self + .last_status + .is_some_and(|status| status != current_status) + { + shell.request_redraw(); } event_status From dcc184b01b753dbecb500205391f6eaaa21c8683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 25 Oct 2024 19:28:18 +0200 Subject: [PATCH 14/29] Replace `event::Status` in `Widget::on_event` with `Shell::capture_event` --- core/src/element.rs | 15 +-- core/src/overlay.rs | 6 +- core/src/overlay/element.rs | 13 +- core/src/overlay/group.rs | 27 ++-- core/src/shell.rs | 23 ++++ core/src/widget.rs | 6 +- examples/loading_spinners/src/circular.rs | 5 +- examples/loading_spinners/src/linear.rs | 5 +- examples/toast/src/main.rs | 53 ++++---- runtime/src/overlay/nested.rs | 74 +++++----- runtime/src/user_interface.rs | 10 +- widget/src/button.rs | 79 +++++------ widget/src/canvas.rs | 8 +- widget/src/checkbox.rs | 11 +- widget/src/column.rs | 35 +++-- widget/src/combo_box.rs | 24 ++-- widget/src/container.rs | 7 +- widget/src/helpers.rs | 38 ++---- widget/src/image/viewer.rs | 30 ++--- widget/src/keyed/column.rs | 35 +++-- widget/src/lazy.rs | 18 ++- widget/src/lazy/component.rs | 43 +++--- widget/src/lazy/responsive.rs | 31 ++--- widget/src/mouse_area.rs | 156 +++++++++------------- widget/src/overlay/menu.rs | 17 +-- widget/src/pane_grid.rs | 49 ++++--- widget/src/pane_grid/content.rs | 20 +-- widget/src/pane_grid/title_bar.rs | 30 ++--- widget/src/pick_list.rs | 25 ++-- widget/src/radio.rs | 12 +- widget/src/row.rs | 37 +++-- widget/src/scrollable.rs | 62 ++++----- widget/src/shader.rs | 16 +-- widget/src/shader/event.rs | 2 - widget/src/shader/program.rs | 4 +- widget/src/slider.rs | 23 ++-- widget/src/stack.rs | 53 ++++---- widget/src/text/rich.rs | 18 ++- widget/src/text_editor.rs | 13 +- widget/src/text_input.rs | 66 +++++---- widget/src/themer.rs | 16 +-- widget/src/toggler.rs | 18 +-- widget/src/tooltip.rs | 9 +- widget/src/vertical_slider.rs | 19 ++- 44 files changed, 556 insertions(+), 705 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 6ebb8a1576..8276b70cda 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,4 +1,3 @@ -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::overlay; @@ -6,8 +5,8 @@ use crate::renderer; use crate::widget; use crate::widget::tree::{self, Tree}; use crate::{ - Border, Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, - Widget, + Border, Clipboard, Color, Event, Layout, Length, Rectangle, Shell, Size, + Vector, Widget, }; use std::borrow::Borrow; @@ -319,11 +318,11 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, B>, viewport: &Rectangle, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let status = self.widget.on_event( + self.widget.on_event( tree, event, layout, @@ -335,8 +334,6 @@ where ); shell.merge(local_shell, &self.mapper); - - status } fn draw( @@ -457,10 +454,10 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.element.widget.on_event( state, event, layout, cursor, renderer, clipboard, shell, viewport, - ) + ); } fn draw( diff --git a/core/src/overlay.rs b/core/src/overlay.rs index f09de8312b..e063bb940e 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -5,13 +5,12 @@ mod group; pub use element::Element; pub use group::Group; -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; use crate::widget::Tree; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size, Vector}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay @@ -65,8 +64,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, - ) -> event::Status { - event::Status::Ignored + ) { } /// Returns the current [`mouse::Interaction`] of the [`Overlay`]. diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 32e987a30f..4a242213a7 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -1,11 +1,10 @@ pub use crate::Overlay; -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; +use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; /// A generic [`Overlay`]. #[allow(missing_debug_implementations)] @@ -58,9 +57,9 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { self.overlay - .on_event(event, layout, cursor, renderer, clipboard, shell) + .on_event(event, layout, cursor, renderer, clipboard, shell); } /// Returns the current [`mouse::Interaction`] of the [`Element`]. @@ -157,11 +156,11 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, B>, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let event_status = self.content.on_event( + self.content.on_event( event, layout, cursor, @@ -171,8 +170,6 @@ where ); shell.merge(local_shell, self.mapper); - - event_status } fn mouse_interaction( diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 6541d311a0..11ebd579d6 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -1,4 +1,3 @@ -use crate::event; use crate::layout; use crate::mouse; use crate::overlay; @@ -81,21 +80,17 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.children - .iter_mut() - .zip(layout.children()) - .map(|(child, layout)| { - child.on_event( - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) + ) { + for (child, layout) in self.children.iter_mut().zip(layout.children()) { + child.on_event( + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + ); + } } fn draw( diff --git a/core/src/shell.rs b/core/src/shell.rs index 7a92a2bef9..12ebbaa89a 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,3 +1,4 @@ +use crate::event; use crate::time::Instant; use crate::window; @@ -10,6 +11,7 @@ use crate::window; #[derive(Debug)] pub struct Shell<'a, Message> { messages: &'a mut Vec, + event_status: event::Status, redraw_request: Option, is_layout_invalid: bool, are_widgets_invalid: bool, @@ -20,6 +22,7 @@ impl<'a, Message> Shell<'a, Message> { pub fn new(messages: &'a mut Vec) -> Self { Self { messages, + event_status: event::Status::Ignored, redraw_request: None, is_layout_invalid: false, are_widgets_invalid: false, @@ -36,6 +39,24 @@ impl<'a, Message> Shell<'a, Message> { self.messages.push(message); } + /// Marks the current event as captured. Prevents "event bubbling". + /// + /// A widget should capture an event when no ancestor should + /// handle it. + pub fn capture_event(&mut self) { + self.event_status = event::Status::Captured; + } + + /// Returns the current [`event::Status`] of the [`Shell`]. + pub fn event_status(&self) -> event::Status { + self.event_status + } + + /// Returns whether the current event has been captured. + pub fn is_event_captured(&self) -> bool { + self.event_status == event::Status::Captured + } + /// Requests a new frame to be drawn as soon as possible. pub fn request_redraw(&mut self) { self.redraw_request = Some(window::RedrawRequest::NextFrame); @@ -114,5 +135,7 @@ impl<'a, Message> Shell<'a, Message> { self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; + + self.event_status = self.event_status.merge(other.event_status); } } diff --git a/core/src/widget.rs b/core/src/widget.rs index 9cfff83d46..bddf99cc91 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -10,12 +10,11 @@ pub use operation::Operation; pub use text::Text; pub use tree::Tree; -use crate::event::{self, Event}; use crate::layout::{self, Layout}; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Length, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard, Event, Length, Rectangle, Shell, Size, Vector}; /// A component that displays information and allows interaction. /// @@ -122,8 +121,7 @@ where _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { - event::Status::Ignored + ) { } /// Returns the current [`mouse::Interaction`] of the [`Widget`]. diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index 954a777eed..e6b59cae40 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -3,7 +3,6 @@ use iced::advanced::layout; use iced::advanced::renderer; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::event; use iced::mouse; use iced::time::Instant; use iced::widget::canvas; @@ -272,7 +271,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); if let Event::Window(window::Event::RedrawRequested(now)) = event { @@ -285,8 +284,6 @@ where state.cache.clear(); shell.request_redraw(); } - - event::Status::Ignored } fn draw( diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 81edde75e4..3457626142 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -3,7 +3,6 @@ use iced::advanced::layout; use iced::advanced::renderer::{self, Quad}; use iced::advanced::widget::tree::{self, Tree}; use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; -use iced::event; use iced::mouse; use iced::time::Instant; use iced::window; @@ -186,7 +185,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); if let Event::Window(window::Event::RedrawRequested(now)) = event { @@ -194,8 +193,6 @@ where shell.request_redraw(); } - - event::Status::Ignored } fn draw( diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 0b46c74e89..079b96b41f 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -169,7 +169,6 @@ mod toast { use iced::advanced::renderer; use iced::advanced::widget::{self, Operation, Tree}; use iced::advanced::{Clipboard, Shell, Widget}; - use iced::event::{self, Event}; use iced::mouse; use iced::theme; use iced::widget::{ @@ -177,8 +176,8 @@ mod toast { }; use iced::window; use iced::{ - Alignment, Center, Element, Fill, Length, Point, Rectangle, Renderer, - Size, Theme, Vector, + Alignment, Center, Element, Event, Fill, Length, Point, Rectangle, + Renderer, Size, Theme, Vector, }; pub const DEFAULT_TIMEOUT: u64 = 5; @@ -369,7 +368,7 @@ mod toast { clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.content.as_widget_mut().on_event( &mut state.children[0], event, @@ -379,7 +378,7 @@ mod toast { clipboard, shell, viewport, - ) + ); } fn draw( @@ -498,7 +497,7 @@ mod toast { renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { if let Event::Window(window::Event::RedrawRequested(now)) = &event { self.instants.iter_mut().enumerate().for_each( |(index, maybe_instant)| { @@ -520,35 +519,33 @@ mod toast { let viewport = layout.bounds(); - self.toasts + for (((child, state), layout), instant) in self + .toasts .iter_mut() .zip(self.state.iter_mut()) .zip(layout.children()) .zip(self.instants.iter_mut()) - .map(|(((child, state), layout), instant)| { - let mut local_messages = vec![]; - let mut local_shell = Shell::new(&mut local_messages); - - let status = child.as_widget_mut().on_event( - state, - event.clone(), - layout, - cursor, - renderer, - clipboard, - &mut local_shell, - &viewport, - ); + { + let mut local_messages = vec![]; + let mut local_shell = Shell::new(&mut local_messages); - if !local_shell.is_empty() { - instant.take(); - } + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + &mut local_shell, + &viewport, + ); - shell.merge(local_shell, std::convert::identity); + if !local_shell.is_empty() { + instant.take(); + } - status - }) - .fold(event::Status::Ignored, event::Status::merge) + shell.merge(local_shell, std::convert::identity); + } } fn draw( diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index da3e6929e6..45f6b220c9 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -166,7 +166,7 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { fn recurse( element: &mut overlay::Element<'_, Message, Theme, Renderer>, layout: Layout<'_>, @@ -175,31 +175,30 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> (event::Status, bool) + ) -> bool where Renderer: renderer::Renderer, { let mut layouts = layout.children(); if let Some(layout) = layouts.next() { - let (nested_status, nested_is_over) = - if let Some((mut nested, nested_layout)) = - element.overlay(layout, renderer).zip(layouts.next()) - { - recurse( - &mut nested, - nested_layout, - event.clone(), - cursor, - renderer, - clipboard, - shell, - ) - } else { - (event::Status::Ignored, false) - }; + let nested_is_over = if let Some((mut nested, nested_layout)) = + element.overlay(layout, renderer).zip(layouts.next()) + { + recurse( + &mut nested, + nested_layout, + event.clone(), + cursor, + renderer, + clipboard, + shell, + ) + } else { + false + }; - if matches!(nested_status, event::Status::Ignored) { + if shell.event_status() == event::Status::Ignored { let is_over = nested_is_over || cursor .position() @@ -212,30 +211,29 @@ where }) .unwrap_or_default(); - ( - element.on_event( - event, - layout, - if nested_is_over { - mouse::Cursor::Unavailable - } else { - cursor - }, - renderer, - clipboard, - shell, - ), - is_over, - ) + element.on_event( + event, + layout, + if nested_is_over { + mouse::Cursor::Unavailable + } else { + cursor + }, + renderer, + clipboard, + shell, + ); + + is_over } else { - (nested_status, nested_is_over) + nested_is_over } } else { - (event::Status::Ignored, false) + false } } - let (status, _) = recurse( + let _ = recurse( &mut self.overlay, layout, event, @@ -244,8 +242,6 @@ where clipboard, shell, ); - - status } /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay. diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 8dfc97a728..cae17bcccd 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -210,7 +210,7 @@ where for event in events.iter().cloned() { let mut shell = Shell::new(messages); - let event_status = overlay.on_event( + overlay.on_event( event, Layout::new(&layout), cursor, @@ -219,7 +219,7 @@ where &mut shell, ); - event_statuses.push(event_status); + event_statuses.push(shell.event_status()); match (redraw_request, shell.redraw_request()) { (None, Some(at)) => { @@ -308,7 +308,7 @@ where let mut shell = Shell::new(messages); - let event_status = self.root.as_widget_mut().on_event( + self.root.as_widget_mut().on_event( &mut self.state, event, Layout::new(&self.base), @@ -319,7 +319,7 @@ where &viewport, ); - if matches!(event_status, event::Status::Captured) { + if shell.event_status() == event::Status::Captured { self.overlay = None; } @@ -347,7 +347,7 @@ where outdated = true; } - event_status.merge(overlay_status) + shell.event_status().merge(overlay_status) }) .collect(); diff --git a/widget/src/button.rs b/widget/src/button.rs index 46fd0e171a..6be17f5902 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -283,8 +283,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().on_event( &mut tree.children[0], event.clone(), layout.children().next().unwrap(), @@ -293,62 +293,53 @@ where clipboard, shell, viewport, - ) { - return event::Status::Captured; + ); + + if shell.event_status() == event::Status::Captured { + return; } - let mut update = || { - match event { - Event::Mouse(mouse::Event::ButtonPressed( - mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerPressed { .. }) => { - if self.on_press.is_some() { - let bounds = layout.bounds(); + match event { + Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerPressed { .. }) => { + if self.on_press.is_some() { + let bounds = layout.bounds(); - if cursor.is_over(bounds) { - let state = tree.state.downcast_mut::(); + if cursor.is_over(bounds) { + let state = tree.state.downcast_mut::(); - state.is_pressed = true; + state.is_pressed = true; - return event::Status::Captured; - } + shell.capture_event(); } } - Event::Mouse(mouse::Event::ButtonReleased( - mouse::Button::Left, - )) - | Event::Touch(touch::Event::FingerLifted { .. }) => { - if let Some(on_press) = - self.on_press.as_ref().map(OnPress::get) - { - let state = tree.state.downcast_mut::(); - - if state.is_pressed { - state.is_pressed = false; + } + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) => { + if let Some(on_press) = self.on_press.as_ref().map(OnPress::get) + { + let state = tree.state.downcast_mut::(); - let bounds = layout.bounds(); + if state.is_pressed { + state.is_pressed = false; - if cursor.is_over(bounds) { - shell.publish(on_press); - } + let bounds = layout.bounds(); - return event::Status::Captured; + if cursor.is_over(bounds) { + shell.publish(on_press); } - } - } - Event::Touch(touch::Event::FingerLost { .. }) => { - let state = tree.state.downcast_mut::(); - state.is_pressed = false; + shell.capture_event(); + } } - _ => {} } + Event::Touch(touch::Event::FingerLost { .. }) => { + let state = tree.state.downcast_mut::(); - event::Status::Ignored - }; - - let update_status = update(); + state.is_pressed = false; + } + _ => {} + } let current_status = if self.on_press.is_none() { Status::Disabled @@ -369,8 +360,6 @@ where } else if self.status.is_some_and(|status| status != current_status) { shell.request_redraw(); } - - update_status } fn draw( diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 9fbccf824e..a9c65bb67e 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -223,7 +223,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let bounds = layout.bounds(); let canvas_event = match event { @@ -245,10 +245,10 @@ where shell.publish(message); } - return event_status; + if event_status == event::Status::Captured { + shell.capture_event(); + } } - - event::Status::Ignored } fn mouse_interaction( diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 6c5d7d6b11..9b5f3602de 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; @@ -42,8 +41,8 @@ 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. @@ -313,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 { .. }) => { @@ -322,7 +321,7 @@ 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(); } } } @@ -351,8 +350,6 @@ where { shell.request_redraw(); } - - event::Status::Ignored } fn mouse_interaction( diff --git a/widget/src/column.rs b/widget/src/column.rs index 213f68fc1a..3fdc17f8ec 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. @@ -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().on_event( + 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 e300f1d0d9..1122861fb3 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -54,7 +54,7 @@ //! } //! } //! ``` -use crate::core::event::{self, Event}; +use crate::core::event; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -65,7 +65,8 @@ use crate::core::text; use crate::core::time::Instant; use crate::core::widget::{self, Widget}; 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; @@ -519,7 +520,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let menu = tree.state.downcast_mut::>(); 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.on_event( &mut tree.children[0], event.clone(), layout, @@ -549,13 +550,16 @@ where viewport, ); + if local_shell.event_status() == event::Status::Captured { + shell.capture_event(); + } + // 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` @@ -619,7 +623,7 @@ where } } - event_status = event::Status::Captured; + shell.capture_event(); } (key::Named::ArrowUp, _) | (key::Named::Tab, true) => { @@ -656,7 +660,7 @@ where } } - event_status = event::Status::Captured; + shell.capture_event(); } (key::Named::ArrowDown, _) | (key::Named::Tab, false) @@ -703,7 +707,7 @@ where } } - event_status = event::Status::Captured; + shell.capture_event(); } _ => {} } @@ -724,7 +728,7 @@ where published_message_to_shell = true; // Unfocus the input - let _ = self.text_input.on_event( + self.text_input.on_event( &mut tree.children[0], Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, @@ -761,8 +765,6 @@ where } } } - - event_status } fn mouse_interaction( diff --git a/widget/src/container.rs b/widget/src/container.rs index f4993ac935..f96c495c20 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, }; @@ -308,7 +307,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.content.as_widget_mut().on_event( tree, event, @@ -318,7 +317,7 @@ where clipboard, shell, viewport, - ) + ); } fn mouse_interaction( diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 52290a54d5..13d69e1f73 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>, @@ -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().on_event( + 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,12 +523,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 Hover<'a, Message, Theme, Renderer> { base: Element<'a, Message, Theme, Renderer>, @@ -658,7 +650,7 @@ 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(); @@ -680,7 +672,7 @@ where }; } - let top_status = if matches!( + if matches!( event, Event::Mouse( mouse::Event::CursorMoved { .. } @@ -699,13 +691,11 @@ 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( @@ -717,7 +707,7 @@ where clipboard, shell, viewport, - ) + ); } fn mouse_interaction( diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index b1aad22c79..5787200bfc 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. @@ -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.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::(); 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 5852ede19d..1172785a20 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. @@ -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().on_event( + 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 232f254c2c..4a9a6154a1 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; @@ -206,7 +205,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.with_element_mut(|element| { element.as_widget_mut().on_event( &mut tree.children[0], @@ -217,8 +216,8 @@ where clipboard, shell, viewport, - ) - }) + ); + }); } fn mouse_interaction( @@ -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.on_event(event, layout, cursor, renderer, clipboard, shell); + }); } fn is_over( diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index e45c24ac62..062e6f3580 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -1,6 +1,5 @@ //! 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; @@ -322,12 +321,12 @@ 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::>>>(); - let event_status = self.with_element_mut(|element| { + self.with_element_mut(|element| { element.as_widget_mut().on_event( &mut t.borrow_mut().as_mut().unwrap().children[0], event, @@ -337,9 +336,13 @@ 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() { @@ -377,8 +380,6 @@ where shell.invalidate_layout(); } - - event_status } fn operate( @@ -608,22 +609,24 @@ 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.on_event( + event, + layout, + cursor, + renderer, + clipboard, + &mut local_shell, + ); + }); + + if local_shell.is_event_captured() { + shell.capture_event(); + } local_shell.revalidate_layout(|| shell.invalidate_layout()); @@ -673,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 a6c40ab0b9..c17798a668 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; @@ -193,14 +192,14 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); 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, @@ -215,7 +214,7 @@ where clipboard, &mut local_shell, viewport, - ) + ); }, ); @@ -224,8 +223,6 @@ where } shell.merge(local_shell, std::convert::identity); - - status } fn draw( @@ -425,28 +422,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.on_event(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/mouse_area.rs b/widget/src/mouse_area.rs index c5a37ae3f1..50188abd69 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. @@ -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().on_event( &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( 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( } 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 c1a0a5d880..78ee3da6f6 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -1,7 +1,6 @@ //! 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; @@ -11,8 +10,8 @@ use crate::core::touch; 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}; @@ -271,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.state, event, layout, cursor, renderer, clipboard, shell, &bounds, - ) + ); } fn mouse_interaction( @@ -397,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(); } } } @@ -460,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(); } } } @@ -477,8 +476,6 @@ where }) { shell.request_redraw(); } - - event::Status::Ignored } fn mouse_interaction( diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index b4ed4b6484..29b7ac87d2 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}; @@ -88,7 +87,7 @@ use crate::core::touch; use crate::core::widget; use crate::core::widget::tree::{self, Tree}; 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, }; @@ -433,9 +432,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(); @@ -451,7 +448,7 @@ where 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, _)) => { @@ -556,9 +553,9 @@ where } } - event_status = event::Status::Captured; + shell.capture_event(); } else if action.picked_split().is_some() { - event_status = event::Status::Captured; + shell.capture_event(); } *action = state::Action::Idle; @@ -600,7 +597,7 @@ where ratio, })); - event_status = event::Status::Captured; + shell.capture_event(); } } } @@ -611,7 +608,8 @@ where let picked_pane = action.picked_pane().map(|(pane, _)| pane); - self.panes + for (((pane, content), tree), layout) in self + .panes .iter() .copied() .zip(&mut self.contents) @@ -622,22 +620,21 @@ where .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) + { + let is_picked = picked_pane == Some(pane); + + content.on_event( + tree, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + is_picked, + ); + } } fn mouse_interaction( diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index ec0676b188..81a5cc1e45 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}; @@ -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.on_event( &mut tree.children[1], event.clone(), children.next().unwrap(), @@ -272,9 +270,7 @@ where layout }; - let body_status = if is_picked { - event::Status::Ignored - } else { + if !is_picked { self.body.as_widget_mut().on_event( &mut tree.children[0], event, @@ -284,10 +280,8 @@ where clipboard, shell, viewport, - ) - }; - - event_status.merge(body_status) + ); + } } pub(crate) fn mouse_interaction( diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 5002b4f73c..ec1dc3025d 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; @@ -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,8 +445,9 @@ 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 { @@ -463,7 +463,7 @@ where clipboard, shell, viewport, - ) + ); } else { show_title = false; @@ -476,7 +476,7 @@ where clipboard, shell, viewport, - ) + ); } } else { controls.full.as_widget_mut().on_event( @@ -488,13 +488,11 @@ where clipboard, shell, viewport, - ) + ); } - } else { - event::Status::Ignored - }; + } - let title_status = if show_title { + if show_title { self.content.as_widget_mut().on_event( &mut tree.children[0], event, @@ -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 9c9ba9e933..9eb43e7206 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; @@ -73,8 +72,8 @@ 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}; @@ -438,10 +437,10 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::>(); - let event_status = match event { + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { if state.is_open { @@ -453,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); @@ -468,9 +467,7 @@ where shell.publish(on_open.clone()); } - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Mouse(mouse::Event::WheelScrolled { @@ -512,17 +509,13 @@ 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)) => { state.keyboard_modifiers = modifiers; - - event::Status::Ignored } - _ => event::Status::Ignored, + _ => {} }; let status = if state.is_open { @@ -541,8 +534,6 @@ where { shell.request_redraw(); } - - event_status } fn mouse_interaction( diff --git a/widget/src/radio.rs b/widget/src/radio.rs index ed821532e4..70e1c42331 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; @@ -68,8 +67,8 @@ 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. @@ -334,14 +333,13 @@ 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(); } } _ => {} @@ -366,8 +364,6 @@ where { shell.request_redraw(); } - - event::Status::Ignored } fn mouse_interaction( diff --git a/widget/src/row.rs b/widget/src/row.rs index 9c0fa97e11..a4b6aa2a23 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, }; @@ -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().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ); + } } fn mouse_interaction( @@ -503,10 +502,10 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.row.on_event( tree, event, layout, cursor, renderer, clipboard, shell, viewport, - ) + ); } fn mouse_interaction( diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index abad6ea69e..33d4f54531 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; @@ -519,7 +518,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); let bounds = layout.bounds(); let cursor_over_scrollable = cursor.position_over(bounds); @@ -561,7 +560,7 @@ where if let Some(scrollbar) = scrollbars.y { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; + return; }; state.scroll_y_to( @@ -581,7 +580,7 @@ where shell, ); - return event::Status::Captured; + shell.capture_event(); } } _ => {} @@ -593,7 +592,7 @@ where )) | Event::Touch(touch::Event::FingerPressed { .. }) => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; + return; }; if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( @@ -621,7 +620,7 @@ where ); } - return event::Status::Captured; + shell.capture_event(); } _ => {} } @@ -632,7 +631,7 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; + return; }; if let Some(scrollbar) = scrollbars.x { @@ -654,7 +653,7 @@ where ); } - return event::Status::Captured; + shell.capture_event(); } _ => {} } @@ -665,7 +664,7 @@ where )) | Event::Touch(touch::Event::FingerPressed { .. }) => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; + return; }; if let (Some(scroller_grabbed_at), Some(scrollbar)) = ( @@ -692,20 +691,19 @@ where shell, ); - return event::Status::Captured; + shell.capture_event(); } } _ => {} } } - let content_status = if state.last_scrolled.is_some() - && matches!( + if state.last_scrolled.is_none() + || !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 @@ -739,7 +737,7 @@ where x: bounds.x + translation.x, ..bounds }, - ) + ); }; if matches!( @@ -754,11 +752,11 @@ where 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( @@ -767,13 +765,13 @@ where { 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; + return; } let delta = match delta { @@ -827,9 +825,7 @@ where let in_transaction = state.last_scrolled.is_some(); if has_scrolled || in_transaction { - event::Status::Captured - } else { - event::Status::Ignored + shell.capture_event(); } } Event::Touch(event) @@ -841,7 +837,7 @@ where touch::Event::FingerPressed { .. } => { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; + return; }; state.scroll_area_touched_at = @@ -853,7 +849,7 @@ where { let Some(cursor_position) = cursor.position() else { - return event::Status::Ignored; + return; }; let delta = Vector::new( @@ -883,7 +879,7 @@ where _ => {} } - event::Status::Captured + shell.capture_event(); } Event::Window(window::Event::RedrawRequested(_)) => { let _ = notify_viewport( @@ -893,14 +889,12 @@ where content_bounds, shell, ); - - event::Status::Ignored } - _ => event::Status::Ignored, + _ => {} } }; - let event_status = update(); + update(); let status = if state.y_scroller_grabbed_at.is_some() || state.x_scroller_grabbed_at.is_some() @@ -933,8 +927,6 @@ where { shell.request_redraw(); } - - event_status } fn draw( diff --git a/widget/src/shader.rs b/widget/src/shader.rs index fa6923360b..5e4d3915dd 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -97,7 +97,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let bounds = layout.bounds(); let custom_shader_event = match event { @@ -115,22 +115,14 @@ where if let Some(custom_shader_event) = custom_shader_event { let state = tree.state.downcast_mut::(); - let (event_status, message) = self.program.update( + self.program.update( state, custom_shader_event, bounds, cursor, shell, ); - - if let Some(message) = message { - shell.publish(message); - } - - return event_status; } - - event::Status::Ignored } fn mouse_interaction( @@ -195,8 +187,8 @@ where bounds: Rectangle, cursor: mouse::Cursor, shell: &mut Shell<'_, Message>, - ) -> (event::Status, Option) { - T::update(self, state, event, bounds, cursor, shell) + ) { + T::update(self, state, event, bounds, cursor, shell); } fn draw( diff --git a/widget/src/shader/event.rs b/widget/src/shader/event.rs index 005c87256b..2d7c79bba2 100644 --- a/widget/src/shader/event.rs +++ b/widget/src/shader/event.rs @@ -4,8 +4,6 @@ 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 diff --git a/widget/src/shader/program.rs b/widget/src/shader/program.rs index 902c7c3bda..5124a1ccd3 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -1,4 +1,3 @@ -use crate::core::event; use crate::core::mouse; use crate::core::{Rectangle, Shell}; use crate::renderer::wgpu::Primitive; @@ -31,8 +30,7 @@ pub trait Program { _bounds: Rectangle, _cursor: mouse::Cursor, _shell: &mut Shell<'_, Message>, - ) -> (event::Status, Option) { - (event::Status::Ignored, None) + ) { } /// Draws the [`Primitive`]. diff --git a/widget/src/slider.rs b/widget/src/slider.rs index dbdb5f07cb..4a424bd9aa 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; @@ -39,8 +38,8 @@ 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; @@ -253,7 +252,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); let mut update = || { @@ -349,7 +348,7 @@ where state.is_dragging = true; } - return event::Status::Captured; + shell.capture_event(); } } Event::Mouse(mouse::Event::ButtonReleased( @@ -363,7 +362,7 @@ where } state.is_dragging = false; - return event::Status::Captured; + shell.capture_event(); } } Event::Mouse(mouse::Event::CursorMoved { .. }) @@ -371,7 +370,7 @@ where if state.is_dragging { let _ = cursor.position().and_then(locate).map(change); - return event::Status::Captured; + shell.capture_event(); } } Event::Mouse(mouse::Event::WheelScrolled { delta }) @@ -389,7 +388,7 @@ where let _ = increment(current_value).map(change); } - return event::Status::Captured; + shell.capture_event(); } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -406,7 +405,7 @@ where _ => (), } - return event::Status::Captured; + shell.capture_event(); } } Event::Keyboard(keyboard::Event::ModifiersChanged( @@ -416,11 +415,9 @@ where } _ => {} } - - event::Status::Ignored }; - let update_status = update(); + update(); let current_status = if state.is_dragging { Status::Dragged @@ -435,8 +432,6 @@ where } else if self.status.is_some_and(|status| status != current_status) { shell.request_redraw(); } - - update_status } fn draw( diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 6a44c32830..2cb628abbf 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. @@ -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().on_event( + 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 3d2413752a..f778b029cd 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; @@ -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::>(); 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 3bb4549442..292e584ed0 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, }; @@ -606,9 +605,9 @@ 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::>(); @@ -656,7 +655,7 @@ where cursor, self.key_binding.as_deref(), ) else { - return event::Status::Ignored; + return; }; match update { @@ -685,7 +684,7 @@ where let bounds = self.content.0.borrow().editor.bounds(); if bounds.height >= i32::MAX as f32 { - return event::Status::Ignored; + return; } let lines = lines + state.partial_scroll; @@ -798,7 +797,7 @@ where } } - event::Status::Captured + shell.capture_event(); } fn draw( diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 8fa7889f42..87dffb984e 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; @@ -638,7 +637,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let update_cache = |state, value| { replace_paragraph( renderer, @@ -754,7 +753,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } } Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) @@ -809,7 +808,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } } Event::Keyboard(keyboard::Event::KeyPressed { @@ -834,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)) = @@ -859,18 +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() { @@ -897,12 +897,12 @@ 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() => @@ -917,14 +917,15 @@ where shell.request_redraw(); } - return event::Status::Captured; + 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; @@ -939,11 +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; } } @@ -951,13 +952,12 @@ where keyboard::Key::Named(key::Named::Enter) => { if let Some(on_submit) = self.on_submit.clone() { shell.publish(on_submit); - - return event::Status::Captured; + 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() @@ -980,15 +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); - - return event::Status::Captured; } keyboard::Key::Named(key::Named::Delete) => { let Some(on_input) = &self.on_input else { - return event::Status::Ignored; + return; }; if modifiers.jump() @@ -1014,11 +1013,10 @@ 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; } keyboard::Key::Named(key::Named::Home) => { let cursor_before = state.cursor; @@ -1038,7 +1036,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } keyboard::Key::Named(key::Named::End) => { let cursor_before = state.cursor; @@ -1058,7 +1056,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowLeft) if modifiers.macos_command() => @@ -1080,7 +1078,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowRight) if modifiers.macos_command() => @@ -1102,7 +1100,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowLeft) => { let cursor_before = state.cursor; @@ -1129,7 +1127,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } keyboard::Key::Named(key::Named::ArrowRight) => { let cursor_before = state.cursor; @@ -1156,7 +1154,7 @@ where shell.request_redraw(); } - return event::Status::Captured; + shell.capture_event(); } keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; @@ -1166,7 +1164,7 @@ where state.keyboard_modifiers = keyboard::Modifiers::default(); - return event::Status::Captured; + shell.capture_event(); } _ => {} } @@ -1179,7 +1177,7 @@ where if let keyboard::Key::Character("v") = key.as_ref() { state.is_pasting = None; - return event::Status::Captured; + shell.capture_event(); } } @@ -1257,8 +1255,6 @@ where { shell.request_redraw(); } - - event::Status::Ignored } fn draw( diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 499a9fe8e8..649cfbdd92 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; @@ -121,10 +120,10 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { self.content.as_widget_mut().on_event( tree, event, layout, cursor, renderer, clipboard, shell, viewport, - ) + ); } fn mouse_interaction( @@ -227,9 +226,10 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.content - .on_event(event, layout, cursor, renderer, clipboard, shell) + ) { + self.content.on_event( + event, layout, cursor, renderer, clipboard, shell, + ); } fn operate( diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 2553a7e4a4..8461fbb2dd 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; @@ -317,26 +316,23 @@ 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; }; - let event_status = match event { + match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) => { let mouse_over = cursor.is_over(layout.bounds()); 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 @@ -358,8 +354,6 @@ where { shell.request_redraw(); } - - event_status } fn mouse_interaction( diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index e98f4da7ea..91151acc65 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. @@ -200,7 +199,7 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); let was_idle = *state == State::Idle; @@ -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 18633474e2..e7e36d2a2c 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 @@ -254,7 +253,7 @@ where _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { + ) { let state = tree.state.downcast_mut::(); 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( From f02bfc3f68322bea0c56283d76888714be401ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 25 Oct 2024 22:06:06 +0200 Subject: [PATCH 15/29] Rename `Widget::on_event` to `update` --- core/src/element.rs | 8 ++++---- core/src/widget.rs | 2 +- examples/loading_spinners/src/circular.rs | 2 +- examples/loading_spinners/src/linear.rs | 2 +- examples/toast/src/main.rs | 6 +++--- runtime/src/user_interface.rs | 2 +- widget/src/button.rs | 4 ++-- widget/src/canvas.rs | 2 +- widget/src/checkbox.rs | 2 +- widget/src/column.rs | 4 ++-- widget/src/combo_box.rs | 6 +++--- widget/src/container.rs | 4 ++-- widget/src/helpers.rs | 10 +++++----- widget/src/image/viewer.rs | 2 +- widget/src/keyed/column.rs | 4 ++-- widget/src/lazy.rs | 4 ++-- widget/src/lazy/component.rs | 4 ++-- widget/src/lazy/responsive.rs | 4 ++-- widget/src/mouse_area.rs | 4 ++-- widget/src/overlay/menu.rs | 4 ++-- widget/src/pane_grid.rs | 2 +- widget/src/pane_grid/content.rs | 2 +- widget/src/pane_grid/title_bar.rs | 8 ++++---- widget/src/pick_list.rs | 2 +- widget/src/radio.rs | 2 +- widget/src/row.rs | 8 ++++---- widget/src/scrollable.rs | 4 ++-- widget/src/shader.rs | 2 +- widget/src/slider.rs | 2 +- widget/src/stack.rs | 4 ++-- widget/src/text/rich.rs | 2 +- widget/src/text_editor.rs | 2 +- widget/src/text_input.rs | 2 +- widget/src/themer.rs | 4 ++-- widget/src/toggler.rs | 2 +- widget/src/tooltip.rs | 4 ++-- widget/src/vertical_slider.rs | 2 +- 37 files changed, 67 insertions(+), 67 deletions(-) diff --git a/core/src/element.rs b/core/src/element.rs index 8276b70cda..03e56b43e2 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -308,7 +308,7 @@ where self.widget.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -322,7 +322,7 @@ where let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - self.widget.on_event( + self.widget.update( tree, event, layout, @@ -444,7 +444,7 @@ where .operate(state, layout, renderer, operation); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -455,7 +455,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.element.widget.on_event( + self.element.widget.update( state, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/core/src/widget.rs b/core/src/widget.rs index bddf99cc91..2a40f8234a 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -111,7 +111,7 @@ where /// Processes a runtime [`Event`]. /// /// By default, it does nothing. - fn on_event( + fn update( &mut self, _state: &mut Tree, _event: Event, diff --git a/examples/loading_spinners/src/circular.rs b/examples/loading_spinners/src/circular.rs index e6b59cae40..a10d5cec93 100644 --- a/examples/loading_spinners/src/circular.rs +++ b/examples/loading_spinners/src/circular.rs @@ -261,7 +261,7 @@ where layout::atomic(limits, self.size, self.size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs index 3457626142..91c8d523ba 100644 --- a/examples/loading_spinners/src/linear.rs +++ b/examples/loading_spinners/src/linear.rs @@ -175,7 +175,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 079b96b41f..893c52e20b 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -358,7 +358,7 @@ mod toast { }); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -369,7 +369,7 @@ mod toast { shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut state.children[0], event, layout, @@ -529,7 +529,7 @@ mod toast { let mut local_messages = vec![]; let mut local_shell = Shell::new(&mut local_messages); - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, event.clone(), layout, diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index cae17bcccd..6997caf46e 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -308,7 +308,7 @@ where let mut shell = Shell::new(messages); - self.root.as_widget_mut().on_event( + self.root.as_widget_mut().update( &mut self.state, event, Layout::new(&self.base), diff --git a/widget/src/button.rs b/widget/src/button.rs index 6be17f5902..64f2b793f2 100644 --- a/widget/src/button.rs +++ b/widget/src/button.rs @@ -273,7 +273,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -284,7 +284,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], event.clone(), layout.children().next().unwrap(), diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index a9c65bb67e..63a2506433 100644 --- a/widget/src/canvas.rs +++ b/widget/src/canvas.rs @@ -213,7 +213,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: core::Event, diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs index 9b5f3602de..625dee7c16 100644 --- a/widget/src/checkbox.rs +++ b/widget/src/checkbox.rs @@ -302,7 +302,7 @@ where ) } - fn on_event( + fn update( &mut self, _tree: &mut Tree, event: Event, diff --git a/widget/src/column.rs b/widget/src/column.rs index 3fdc17f8ec..a3efab9478 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -257,7 +257,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -274,7 +274,7 @@ where .zip(&mut tree.children) .zip(layout.children()) { - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, event.clone(), layout, diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 1122861fb3..6c5d230956 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -510,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, @@ -539,7 +539,7 @@ where let mut local_shell = Shell::new(&mut local_messages); // Provide it to the widget - self.text_input.on_event( + self.text_input.update( &mut tree.children[0], event.clone(), layout, @@ -728,7 +728,7 @@ where published_message_to_shell = true; // Unfocus the input - self.text_input.on_event( + self.text_input.update( &mut tree.children[0], Event::Mouse(mouse::Event::ButtonPressed( mouse::Button::Left, diff --git a/widget/src/container.rs b/widget/src/container.rs index f96c495c20..b7b2b39e78 100644 --- a/widget/src/container.rs +++ b/widget/src/container.rs @@ -297,7 +297,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -308,7 +308,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( tree, event, layout.children().next().unwrap(), diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 13d69e1f73..e1474d341b 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -438,7 +438,7 @@ where .operate(state, layout, renderer, operation); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -454,7 +454,7 @@ where core::Event::Mouse(mouse::Event::ButtonPressed(_)) ); - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( state, event, layout, cursor, renderer, clipboard, shell, viewport, ); @@ -640,7 +640,7 @@ where } } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -682,7 +682,7 @@ where || 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, @@ -698,7 +698,7 @@ where return; } - self.base.as_widget_mut().on_event( + self.base.as_widget_mut().update( base_tree, event.clone(), base_layout, diff --git a/widget/src/image/viewer.rs b/widget/src/image/viewer.rs index 5787200bfc..20a7955f3a 100644 --- a/widget/src/image/viewer.rs +++ b/widget/src/image/viewer.rs @@ -148,7 +148,7 @@ where layout::Node::new(final_size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index 1172785a20..055e2ea110 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -297,7 +297,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -314,7 +314,7 @@ where .zip(&mut tree.children) .zip(layout.children()) { - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, event.clone(), layout, diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 4a9a6154a1..0b4e3cad26 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -195,7 +195,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -207,7 +207,7 @@ where viewport: &Rectangle, ) { self.with_element_mut(|element| { - element.as_widget_mut().on_event( + element.as_widget_mut().update( &mut tree.children[0], event, layout, diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 062e6f3580..6f661ef68d 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -311,7 +311,7 @@ where }) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: core::Event, @@ -327,7 +327,7 @@ where let t = tree.state.downcast_mut::>>>(); self.with_element_mut(|element| { - element.as_widget_mut().on_event( + element.as_widget_mut().update( &mut t.borrow_mut().as_mut().unwrap().children[0], event, layout, diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index c17798a668..a8abbce8fa 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -182,7 +182,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -205,7 +205,7 @@ where layout, &self.view, |tree, renderer, layout, element| { - element.as_widget_mut().on_event( + element.as_widget_mut().update( tree, event, layout, diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs index 50188abd69..d9215a7b4e 100644 --- a/widget/src/mouse_area.rs +++ b/widget/src/mouse_area.rs @@ -215,7 +215,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -226,7 +226,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], event.clone(), layout, diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index 78ee3da6f6..b0c0bbad3a 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -273,7 +273,7 @@ where ) { let bounds = layout.bounds(); - self.list.on_event( + self.list.update( self.state, event, layout, cursor, renderer, clipboard, shell, &bounds, ); @@ -386,7 +386,7 @@ where layout::Node::new(size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 29b7ac87d2..a966627ac2 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -422,7 +422,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index 81a5cc1e45..e0199f0a83 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -271,7 +271,7 @@ where }; if !is_picked { - self.body.as_widget_mut().on_event( + self.body.as_widget_mut().update( &mut tree.children[0], event, body_layout, diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index ec1dc3025d..618eb4c59f 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -454,7 +454,7 @@ where 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, @@ -467,7 +467,7 @@ where } 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, @@ -479,7 +479,7 @@ where ); } } else { - controls.full.as_widget_mut().on_event( + controls.full.as_widget_mut().update( &mut tree.children[1], event.clone(), controls_layout, @@ -493,7 +493,7 @@ where } if show_title { - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], event, title_layout, diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 9eb43e7206..32f859dabf 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -427,7 +427,7 @@ where layout::Node::new(size) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/widget/src/radio.rs b/widget/src/radio.rs index 70e1c42331..b38ae6b434 100644 --- a/widget/src/radio.rs +++ b/widget/src/radio.rs @@ -323,7 +323,7 @@ where ) } - fn on_event( + fn update( &mut self, _state: &mut Tree, event: Event, diff --git a/widget/src/row.rs b/widget/src/row.rs index a4b6aa2a23..96a4ab9291 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -253,7 +253,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -270,7 +270,7 @@ where .zip(&mut tree.children) .zip(layout.children()) { - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, event.clone(), layout, @@ -492,7 +492,7 @@ where self.row.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -503,7 +503,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.row.on_event( + self.row.update( tree, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 33d4f54531..a6a41d9f17 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -508,7 +508,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -724,7 +724,7 @@ where let translation = state.translation(self.direction, bounds, content_bounds); - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( &mut tree.children[0], event.clone(), content, diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 5e4d3915dd..115a5ed9e9 100644 --- a/widget/src/shader.rs +++ b/widget/src/shader.rs @@ -87,7 +87,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: crate::core::Event, diff --git a/widget/src/slider.rs b/widget/src/slider.rs index 4a424bd9aa..84630f9e54 100644 --- a/widget/src/slider.rs +++ b/widget/src/slider.rs @@ -242,7 +242,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/widget/src/stack.rs b/widget/src/stack.rs index 2cb628abbf..52fd5031c1 100644 --- a/widget/src/stack.rs +++ b/widget/src/stack.rs @@ -204,7 +204,7 @@ where }); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -224,7 +224,7 @@ where .zip(tree.children.iter_mut().rev()) .zip(layout.children().rev()) { - child.as_widget_mut().on_event( + child.as_widget_mut().update( state, event.clone(), layout, diff --git a/widget/src/text/rich.rs b/widget/src/text/rich.rs index f778b029cd..7ef2707b3d 100644 --- a/widget/src/text/rich.rs +++ b/widget/src/text/rich.rs @@ -354,7 +354,7 @@ where ); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 292e584ed0..32e149465d 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -595,7 +595,7 @@ where } } - fn on_event( + fn update( &mut self, tree: &mut widget::Tree, event: Event, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 87dffb984e..51e61cc0d5 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -627,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, diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 649cfbdd92..41d2aeae15 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -110,7 +110,7 @@ where .operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -121,7 +121,7 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) { - self.content.as_widget_mut().on_event( + self.content.as_widget_mut().update( tree, event, layout, cursor, renderer, clipboard, shell, viewport, ); } diff --git a/widget/src/toggler.rs b/widget/src/toggler.rs index 8461fbb2dd..5dfa0c0ee0 100644 --- a/widget/src/toggler.rs +++ b/widget/src/toggler.rs @@ -306,7 +306,7 @@ where ) } - fn on_event( + fn update( &mut self, _state: &mut Tree, event: Event, diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 91151acc65..e66f5e4ac6 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -189,7 +189,7 @@ where .layout(&mut tree.children[0], renderer, limits) } - fn on_event( + fn update( &mut self, tree: &mut widget::Tree, event: Event, @@ -215,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, diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs index e7e36d2a2c..ec72a4558b 100644 --- a/widget/src/vertical_slider.rs +++ b/widget/src/vertical_slider.rs @@ -243,7 +243,7 @@ where layout::atomic(limits, self.width, self.height) } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, From a84b328dcc3e2f941f9595a2f8c3b1d061442722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 25 Oct 2024 22:36:55 +0200 Subject: [PATCH 16/29] Implement `reactive-rendering` for `combo_box` --- widget/src/combo_box.rs | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/widget/src/combo_box.rs b/widget/src/combo_box.rs index 6c5d230956..8b8e895d80 100644 --- a/widget/src/combo_box.rs +++ b/widget/src/combo_box.rs @@ -54,7 +54,6 @@ //! } //! } //! ``` -use crate::core::event; use crate::core::keyboard; use crate::core::keyboard::key; use crate::core::layout::{self, Layout}; @@ -64,6 +63,7 @@ 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, Event, Length, Padding, Rectangle, Shell, Size, Theme, Vector, @@ -550,10 +550,21 @@ where viewport, ); - if local_shell.event_status() == event::Status::Captured { + 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; @@ -580,6 +591,7 @@ where ); }); shell.invalidate_layout(); + shell.request_redraw(); } let is_focused = { @@ -624,8 +636,8 @@ where } shell.capture_event(); + shell.request_redraw(); } - (key::Named::ArrowUp, _) | (key::Named::Tab, true) => { if let Some(index) = &mut menu.hovered_option { if *index == 0 { @@ -661,6 +673,7 @@ where } shell.capture_event(); + shell.request_redraw(); } (key::Named::ArrowDown, _) | (key::Named::Tab, false) @@ -708,6 +721,7 @@ where } shell.capture_event(); + shell.request_redraw(); } _ => {} } From 920596ed6f44acf8d87d2135c1b8967bab23d5b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 28 Oct 2024 16:58:00 +0100 Subject: [PATCH 17/29] Implement `reactive-rendering` for `canvas` --- examples/bezier_tool/src/main.rs | 78 ++++++++++----------- examples/game_of_life/src/main.rs | 52 +++++++++----- examples/multitouch/src/main.rs | 33 +++++---- examples/sierpinski_triangle/src/main.rs | 33 ++++----- widget/src/action.rs | 89 ++++++++++++++++++++++++ widget/src/canvas.rs | 63 +++++++++++------ widget/src/canvas/event.rs | 21 ------ widget/src/canvas/program.rs | 15 ++-- widget/src/lib.rs | 2 + 9 files changed, 244 insertions(+), 142 deletions(-) create mode 100644 widget/src/action.rs delete mode 100644 widget/src/canvas/event.rs diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 949bfad7a0..5e4da0c2cd 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -57,8 +57,9 @@ impl Example { mod bezier { use iced::mouse; - use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{self, Canvas, Frame, Geometry, Path, Stroke}; + use iced::widget::canvas::{ + self, Canvas, Event, Frame, Geometry, Path, Stroke, + }; use iced::{Element, Fill, Point, Rectangle, Renderer, Theme}; #[derive(Default)] @@ -96,48 +97,47 @@ mod bezier { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option) { - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + ) -> Option> { + let cursor_position = cursor.position_in(bounds)?; match event { - Event::Mouse(mouse_event) => { - let message = match mouse_event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - match *state { - None => { - *state = Some(Pending::One { - from: cursor_position, - }); - - None - } - Some(Pending::One { from }) => { - *state = Some(Pending::Two { - from, - to: cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - *state = None; - - Some(Curve { - from, - to, - control: cursor_position, - }) - } - } + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => Some( + match *state { + None => { + *state = Some(Pending::One { + from: cursor_position, + }); + + canvas::Action::request_redraw() } - _ => None, - }; + Some(Pending::One { from }) => { + *state = Some(Pending::Two { + from, + to: cursor_position, + }); - (event::Status::Captured, message) + canvas::Action::request_redraw() + } + Some(Pending::Two { from, to }) => { + *state = None; + + canvas::Action::publish(Curve { + from, + to, + control: cursor_position, + }) + } + } + .and_capture(), + ), + Event::Mouse(mouse::Event::CursorMoved { .. }) + if state.is_some() => + { + Some(canvas::Action::request_redraw()) } - _ => (event::Status::Ignored, None), + _ => None, } } diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 9dcebecc4b..7a7224d520 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -193,8 +193,9 @@ mod grid { use iced::mouse; use iced::touch; use iced::widget::canvas; - use iced::widget::canvas::event::{self, Event}; - use iced::widget::canvas::{Cache, Canvas, Frame, Geometry, Path, Text}; + use iced::widget::canvas::{ + Cache, Canvas, Event, Frame, Geometry, Path, Text, + }; use iced::{ Color, Element, Fill, Point, Rectangle, Renderer, Size, Theme, Vector, }; @@ -383,14 +384,12 @@ mod grid { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option) { + ) -> Option> { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { *interaction = Interaction::None; } - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + let cursor_position = cursor.position_in(bounds)?; let cell = Cell::at(self.project(cursor_position, bounds.size())); let is_populated = self.state.contains(&cell); @@ -413,7 +412,12 @@ mod grid { populate.or(unpopulate) }; - (event::Status::Captured, message) + Some( + message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()) + .and_capture(), + ) } Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(button) => { @@ -438,7 +442,12 @@ mod grid { _ => None, }; - (event::Status::Captured, message) + Some( + message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()) + .and_capture(), + ) } mouse::Event::CursorMoved { .. } => { let message = match *interaction { @@ -454,12 +463,14 @@ mod grid { Interaction::None => None, }; - let event_status = match interaction { - Interaction::None => event::Status::Ignored, - _ => event::Status::Captured, - }; + let action = message + .map(canvas::Action::publish) + .unwrap_or(canvas::Action::request_redraw()); - (event_status, message) + Some(match interaction { + Interaction::None => action, + _ => action.and_capture(), + }) } mouse::Event::WheelScrolled { delta } => match delta { mouse::ScrollDelta::Lines { y, .. } @@ -496,18 +507,21 @@ mod grid { None }; - ( - event::Status::Captured, - Some(Message::Scaled(scaling, translation)), + Some( + canvas::Action::publish(Message::Scaled( + scaling, + translation, + )) + .and_capture(), ) } else { - (event::Status::Captured, None) + Some(canvas::Action::capture()) } } }, - _ => (event::Status::Ignored, None), + _ => None, }, - _ => (event::Status::Ignored, None), + _ => None, } } diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs index d5e5dffa76..5f4a5c90ec 100644 --- a/examples/multitouch/src/main.rs +++ b/examples/multitouch/src/main.rs @@ -3,9 +3,8 @@ //! computers like Microsoft Surface. use iced::mouse; use iced::touch; -use iced::widget::canvas::event; use iced::widget::canvas::stroke::{self, Stroke}; -use iced::widget::canvas::{self, Canvas, Geometry}; +use iced::widget::canvas::{self, Canvas, Event, Geometry}; use iced::{Color, Element, Fill, Point, Rectangle, Renderer, Theme}; use std::collections::HashMap; @@ -56,25 +55,25 @@ impl canvas::Program for Multitouch { fn update( &self, _state: &mut Self::State, - event: event::Event, + event: Event, _bounds: Rectangle, _cursor: mouse::Cursor, - ) -> (event::Status, Option) { - match event { - event::Event::Touch(touch_event) => match touch_event { + ) -> Option> { + let message = match event { + Event::Touch( touch::Event::FingerPressed { id, position } - | touch::Event::FingerMoved { id, position } => ( - event::Status::Captured, - Some(Message::FingerPressed { id, position }), - ), + | touch::Event::FingerMoved { id, position }, + ) => Some(Message::FingerPressed { id, position }), + Event::Touch( touch::Event::FingerLifted { id, .. } - | touch::Event::FingerLost { id, .. } => ( - event::Status::Captured, - Some(Message::FingerLifted { id }), - ), - }, - _ => (event::Status::Ignored, None), - } + | touch::Event::FingerLost { id, .. }, + ) => Some(Message::FingerLifted { id }), + _ => None, + }; + + message + .map(canvas::Action::publish) + .map(canvas::Action::and_capture) } fn draw( diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs index 99e7900a51..d4d483f5d4 100644 --- a/examples/sierpinski_triangle/src/main.rs +++ b/examples/sierpinski_triangle/src/main.rs @@ -1,6 +1,5 @@ use iced::mouse; -use iced::widget::canvas::event::{self, Event}; -use iced::widget::canvas::{self, Canvas, Geometry}; +use iced::widget::canvas::{self, Canvas, Event, Geometry}; use iced::widget::{column, row, slider, text}; use iced::{Center, Color, Fill, Point, Rectangle, Renderer, Size, Theme}; @@ -80,26 +79,22 @@ impl canvas::Program for SierpinskiGraph { event: Event, bounds: Rectangle, cursor: mouse::Cursor, - ) -> (event::Status, Option) { - let Some(cursor_position) = cursor.position_in(bounds) else { - return (event::Status::Ignored, None); - }; + ) -> Option> { + let cursor_position = cursor.position_in(bounds)?; match event { - Event::Mouse(mouse_event) => { - let message = match mouse_event { - iced::mouse::Event::ButtonPressed( - iced::mouse::Button::Left, - ) => Some(Message::PointAdded(cursor_position)), - iced::mouse::Event::ButtonPressed( - iced::mouse::Button::Right, - ) => Some(Message::PointRemoved), - _ => None, - }; - (event::Status::Captured, message) - } - _ => (event::Status::Ignored, None), + Event::Mouse(mouse::Event::ButtonPressed(button)) => match button { + mouse::Button::Left => Some(canvas::Action::publish( + Message::PointAdded(cursor_position), + )), + mouse::Button::Right => { + Some(canvas::Action::publish(Message::PointRemoved)) + } + _ => None, + }, + _ => None, } + .map(canvas::Action::and_capture) } fn draw( diff --git a/widget/src/action.rs b/widget/src/action.rs new file mode 100644 index 0000000000..1dd3a787a5 --- /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_to_publish: Option, + redraw_request: Option, + event_status: event::Status, +} + +impl Action { + 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, + Option, + event::Status, + ) { + ( + self.message_to_publish, + self.redraw_request, + self.event_status, + ) + } +} diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs index 63a2506433..23cc3f2b36 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, theme_: PhantomData, renderer_: PhantomData, + last_mouse_interaction: Option, } impl Canvas @@ -166,6 +167,7 @@ where message_: PhantomData, theme_: PhantomData, renderer_: PhantomData, + last_mouse_interaction: None, } } @@ -216,39 +218,60 @@ where 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, + 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::(); + let state = tree.state.downcast_mut::(); + 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); } + 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(); } } + + 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 a8eb47f7e0..0000000000 --- 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 a7ded0f47c..c68b28308a 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) { - (event::Status::Ignored, None) + ) -> Option> { + 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) { + ) -> Option> { T::update(self, state, event, bounds, cursor) } diff --git a/widget/src/lib.rs b/widget/src/lib.rs index a68720d692..776a04a0fb 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; From 4e47450c336a235fe26090665aca1cc7b4d23384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 29 Oct 2024 15:55:47 +0100 Subject: [PATCH 18/29] Implement `reactive-rendering` for `pane_grid` --- widget/src/pane_grid.rs | 115 ++++++++++++++++++++++---------- widget/src/pane_grid/content.rs | 25 +++++++ 2 files changed, 105 insertions(+), 35 deletions(-) diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index a966627ac2..691b0a9341 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -86,6 +86,7 @@ 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, Event, Layout, Length, Pixels, Point, Rectangle, Shell, Size, Theme, Vector, Widget, @@ -166,6 +167,7 @@ pub struct PaneGrid< on_drag: Option Message + 'a>>, on_resize: Option<(f32, Box Message + 'a>)>, class: ::Class<'a>, + last_mouse_interaction: Option, } impl<'a, Message, Theme, Renderer> PaneGrid<'a, Message, Theme, Renderer> @@ -202,6 +204,7 @@ where on_drag: None, on_resize: None, class: ::default(), + last_mouse_interaction: None, } } @@ -292,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 { + 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)] @@ -600,6 +649,8 @@ where shell.capture_event(); } } + } else if action.picked_pane().is_some() { + shell.request_redraw(); } } } @@ -635,6 +686,31 @@ where is_picked, ); } + + if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { + let interaction = self + .grid_interaction(action, layout, cursor) + .or_else(|| { + self.contents.iter().zip(layout.children()).find_map( + |(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( @@ -647,41 +723,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 e0199f0a83..f1f6702385 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -284,6 +284,31 @@ where } } + pub(crate) fn grid_interaction( + &self, + layout: Layout<'_>, + cursor: mouse::Cursor, + drag_enabled: bool, + ) -> Option { + 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); + } + + None + } + pub(crate) fn mouse_interaction( &self, tree: &Tree, From c6af79a1d06013343f9caf2de80597d627254084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 29 Oct 2024 20:53:29 +0100 Subject: [PATCH 19/29] Fix deferred layout on resize after drawing --- widget/src/button.rs | 7 ++- widget/src/lazy/responsive.rs | 23 +++++---- winit/src/program.rs | 92 +++++++++++++---------------------- 3 files changed, 49 insertions(+), 73 deletions(-) diff --git a/widget/src/button.rs b/widget/src/button.rs index 64f2b793f2..9eac2e4ceb 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; @@ -28,8 +27,8 @@ 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. @@ -295,7 +294,7 @@ where viewport, ); - if shell.event_status() == event::Status::Captured { + if shell.is_event_captured() { return; } diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index a8abbce8fa..f9bd0334e8 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -82,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( diff --git a/winit/src/program.rs b/winit/src/program.rs index fb30ccd987..d7afb96959 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -818,6 +818,39 @@ async fn run_instance( continue; }; + let physical_size = window.state.physical_size(); + + if physical_size.width == 0 || physical_size.height == 0 + { + continue; + } + + if window.viewport_version + != window.state.viewport_version() + { + let logical_size = window.state.logical_size(); + + debug.layout_started(); + let ui = user_interfaces + .remove(&id) + .expect("Remove user interface"); + + let _ = user_interfaces.insert( + id, + ui.relayout(logical_size, &mut window.renderer), + ); + debug.layout_finished(); + + compositor.configure_surface( + &mut window.surface, + physical_size.width, + physical_size.height, + ); + + window.viewport_version = + window.state.viewport_version(); + } + let redraw_event = core::Event::Window( window::Event::RedrawRequested(Instant::now()), ); @@ -877,65 +910,6 @@ async fn run_instance( } } - let physical_size = window.state.physical_size(); - - if physical_size.width == 0 || physical_size.height == 0 - { - continue; - } - - if window.viewport_version - != window.state.viewport_version() - { - let logical_size = window.state.logical_size(); - - debug.layout_started(); - let ui = user_interfaces - .remove(&id) - .expect("Remove user interface"); - - let _ = user_interfaces.insert( - id, - ui.relayout(logical_size, &mut window.renderer), - ); - debug.layout_finished(); - - debug.draw_started(); - let new_mouse_interaction = user_interfaces - .get_mut(&id) - .expect("Get user interface") - .draw( - &mut window.renderer, - window.state.theme(), - &renderer::Style { - text_color: window.state.text_color(), - }, - window.state.cursor(), - ); - debug.draw_finished(); - - if new_mouse_interaction != window.mouse_interaction - { - window.raw.set_cursor( - conversion::mouse_interaction( - new_mouse_interaction, - ), - ); - - window.mouse_interaction = - new_mouse_interaction; - } - - compositor.configure_surface( - &mut window.surface, - physical_size.width, - physical_size.height, - ); - - window.viewport_version = - window.state.viewport_version(); - } - debug.render_started(); match compositor.present( &mut window.renderer, From 14ec3307304fbf40e7f281d2356f40456124dfef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 Nov 2024 18:08:12 +0100 Subject: [PATCH 20/29] Replace `reactive-rendering` feature with `unconditional-rendering` --- Cargo.toml | 6 +++--- winit/Cargo.toml | 2 +- winit/src/program.rs | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bf85ada2b1..fc38a89da0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ all-features = true maintenance = { status = "actively-developed" } [features] -default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme", "reactive-rendering"] +default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"] # Enables the `wgpu` GPU-accelerated renderer backend wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] # Enables the `tiny-skia` software renderer backend @@ -65,8 +65,8 @@ fira-sans = ["iced_renderer/fira-sans"] auto-detect-theme = ["iced_core/auto-detect-theme"] # Enables strict assertions for debugging purposes at the expense of performance strict-assertions = ["iced_renderer/strict-assertions"] -# Redraws only when widgets react to some runtime event -reactive-rendering = ["iced_winit/reactive-rendering"] +# Redraws on every runtime event, and not only when a widget requests it +unconditional-rendering = ["iced_winit/unconditional-rendering"] [dependencies] iced_core.workspace = true diff --git a/winit/Cargo.toml b/winit/Cargo.toml index b8f5a723e2..10a6369b0f 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -22,7 +22,7 @@ x11 = ["winit/x11"] wayland = ["winit/wayland"] wayland-dlopen = ["winit/wayland-dlopen"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] -reactive-rendering = [] +unconditional-rendering = [] [dependencies] iced_futures.workspace = true diff --git a/winit/src/program.rs b/winit/src/program.rs index d7afb96959..130bf22025 100644 --- a/winit/src/program.rs +++ b/winit/src/program.rs @@ -1051,11 +1051,13 @@ async fn run_instance( &mut messages, ); - #[cfg(not(feature = "reactive-rendering"))] + #[cfg(feature = "unconditional-rendering")] window.raw.request_redraw(); match ui_state { - #[cfg(feature = "reactive-rendering")] + #[cfg(not( + feature = "unconditional-rendering" + ))] user_interface::State::Updated { redraw_request: Some(redraw_request), } => match redraw_request { From 6fc16769c4fb07d5e976a9c1762ecd17cf734bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 Nov 2024 18:26:46 +0100 Subject: [PATCH 21/29] Unify `shader::Program` API with `canvas::Program` --- widget/src/shader.rs | 54 ++++++++++++++++++------------------ widget/src/shader/event.rs | 23 --------------- widget/src/shader/program.rs | 14 +++++----- 3 files changed, 34 insertions(+), 57 deletions(-) delete mode 100644 widget/src/shader/event.rs diff --git a/widget/src/shader.rs b/widget/src/shader.rs index 115a5ed9e9..8ec57482c7 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. @@ -100,28 +99,30 @@ where ) { 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)) + let state = tree.state.downcast_mut::(); + + 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); } - core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)), - core::Event::Window(window::Event::RedrawRequested(instant)) => { - Some(Event::RedrawRequested(instant)) + + 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(); } - core::Event::Window(_) => None, - }; - - if let Some(custom_shader_event) = custom_shader_event { - let state = tree.state.downcast_mut::(); - - self.program.update( - state, - custom_shader_event, - bounds, - cursor, - shell, - ); } } @@ -186,9 +187,8 @@ where event: Event, bounds: Rectangle, cursor: mouse::Cursor, - shell: &mut Shell<'_, Message>, - ) { - T::update(self, state, event, bounds, cursor, shell); + ) -> Option> { + 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 2d7c79bba2..0000000000 --- a/widget/src/shader/event.rs +++ /dev/null @@ -1,23 +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; - -/// 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 5124a1ccd3..0fc110afeb 100644 --- a/widget/src/shader/program.rs +++ b/widget/src/shader/program.rs @@ -1,7 +1,7 @@ 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. /// @@ -17,10 +17,10 @@ pub trait Program { 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( @@ -29,8 +29,8 @@ pub trait Program { _event: shader::Event, _bounds: Rectangle, _cursor: mouse::Cursor, - _shell: &mut Shell<'_, Message>, - ) { + ) -> Option> { + None } /// Draws the [`Primitive`]. From d5a886dbcb50964ce8c6132a4bb3504d6396d01b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 Nov 2024 23:05:44 +0100 Subject: [PATCH 22/29] Fix `hover` widget not redrawing when hovered --- widget/src/helpers.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index e1474d341b..33dff647bb 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -534,6 +534,7 @@ where top: Element<'a, Message, Theme, Renderer>, is_top_focused: bool, is_top_overlay_active: bool, + is_hovered: bool, } impl<'a, Message, Theme, Renderer> Widget @@ -655,6 +656,8 @@ where 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(); @@ -670,6 +673,10 @@ 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(); } if matches!( @@ -678,7 +685,7 @@ where mouse::Event::CursorMoved { .. } | mouse::Event::ButtonReleased(_) ) - ) || cursor.is_over(layout.bounds()) + ) || is_hovered || self.is_top_focused || self.is_top_overlay_active { @@ -767,6 +774,7 @@ where top: top.into(), is_top_focused: false, is_top_overlay_active: false, + is_hovered: false, }) } From 3482ffecdcadf036b7d61ab3821c6ee661d0ec56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 Nov 2024 23:21:06 +0100 Subject: [PATCH 23/29] Implement `reactive-rendering` for `text_editor` --- widget/src/text_editor.rs | 317 ++++++++++++++++++++------------------ 1 file changed, 170 insertions(+), 147 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 32e149465d..3bd4c7f974 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -119,6 +119,7 @@ pub struct TextEditor< &Highlighter::Highlight, &Theme, ) -> highlighter::Format, + last_status: Option, } impl<'a, Message, Theme, Renderer> @@ -146,6 +147,7 @@ where highlighter_format: |_highlight, _theme| { highlighter::Format::default() }, + last_status: None, } } } @@ -269,6 +271,7 @@ where on_edit: self.on_edit, highlighter_settings: settings, highlighter_format: to_format, + last_status: self.last_status, } } @@ -611,6 +614,10 @@ where }; let state = tree.state.downcast_mut::>(); + let is_redraw = matches!( + event, + Event::Window(window::Event::RedrawRequested(_now)), + ); match event { Event::Window(window::Event::Unfocused) => { @@ -647,157 +654,180 @@ where _ => {} } - 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; - }; - - 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()); + ) { + shell.capture_event(); + + 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; + 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(); - 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, - content: &Content, - state: &mut State, - 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, + })); + } + Update::Binding(binding) => { + fn apply_binding< + H: text::Highlighter, + R: text::Renderer, + Message, + >( + binding: Binding, + content: &Content, + state: &mut State, + 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, - ); - if let Some(focus) = &mut state.focus { - focus.updated_at = Instant::now(); + apply_binding( + binding, + self.content, + state, + on_edit, + clipboard, + shell, + ); + + if let Some(focus) = &mut state.focus { + focus.updated_at = Instant::now(); + } } } } - shell.capture_event(); + 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( @@ -807,7 +837,7 @@ where theme: &Theme, _defaults: &renderer::Style, layout: Layout<'_>, - cursor: mouse::Cursor, + _cursor: mouse::Cursor, _viewport: &Rectangle, ) { let bounds = layout.bounds(); @@ -823,20 +853,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 { @@ -1035,7 +1053,7 @@ impl Binding { status, } = event; - if status != Status::Focused { + if !matches!(status, Status::Focused { .. }) { return None; } @@ -1175,7 +1193,9 @@ impl Update { .. }) => { let status = if state.focus.is_some() { - Status::Focused + Status::Focused { + is_hovered: cursor.is_over(bounds), + } } else { Status::Active }; @@ -1221,7 +1241,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, } @@ -1296,7 +1319,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 From fec75221f9e7e19f9ad9a00de0fde6f205c2d92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 Nov 2024 23:26:09 +0100 Subject: [PATCH 24/29] Fix `text_editor` capturing mouse release events --- widget/src/text_editor.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 3bd4c7f974..b6e291e42b 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -662,8 +662,6 @@ where cursor, self.key_binding.as_deref(), ) { - shell.capture_event(); - match update { Update::Click(click) => { let action = match click.kind() { @@ -679,6 +677,7 @@ where state.drag_click = Some(click.kind()); shell.publish(on_edit(action)); + shell.capture_event(); } Update::Drag(position) => { shell.publish(on_edit(Action::Drag(position))); @@ -699,6 +698,7 @@ where shell.publish(on_edit(Action::Scroll { lines: lines as i32, })); + shell.capture_event(); } Update::Binding(binding) => { fn apply_binding< @@ -801,6 +801,8 @@ where if let Some(focus) = &mut state.focus { focus.updated_at = Instant::now(); } + + shell.capture_event(); } } } From 03bffe3db61b51d9e28f42c5bfea421b5612c484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 Nov 2024 23:29:37 +0100 Subject: [PATCH 25/29] Fix `pick_list` not requesting a redraw when open --- widget/src/pick_list.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/widget/src/pick_list.rs b/widget/src/pick_list.rs index 32f859dabf..6708e7cd14 100644 --- a/widget/src/pick_list.rs +++ b/widget/src/pick_list.rs @@ -518,12 +518,16 @@ where _ => {} }; - let status = if state.is_open { - Status::Opened - } else if cursor.is_over(layout.bounds()) { - Status::Hovered - } else { - Status::Active + let status = { + let is_hovered = cursor.is_over(layout.bounds()); + + if state.is_open { + Status::Opened { is_hovered } + } else if is_hovered { + Status::Hovered + } else { + Status::Active + } }; if let Event::Window(window::Event::RedrawRequested(_now)) = event { @@ -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 From e5f1e31a5c068fe992cab076661cb6e2d120bdf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 6 Nov 2024 00:02:46 +0100 Subject: [PATCH 26/29] Rename `Overlay::on_event` to `update` --- core/src/overlay.rs | 2 +- core/src/overlay/element.rs | 8 ++++---- core/src/overlay/group.rs | 4 ++-- examples/toast/src/main.rs | 2 +- runtime/src/overlay/nested.rs | 4 ++-- runtime/src/user_interface.rs | 2 +- widget/src/lazy.rs | 4 ++-- widget/src/lazy/component.rs | 4 ++-- widget/src/lazy/responsive.rs | 4 ++-- widget/src/overlay/menu.rs | 2 +- widget/src/pane_grid.rs | 2 +- widget/src/pane_grid/content.rs | 4 ++-- widget/src/pane_grid/title_bar.rs | 2 +- widget/src/themer.rs | 7 +++---- 14 files changed, 25 insertions(+), 26 deletions(-) diff --git a/core/src/overlay.rs b/core/src/overlay.rs index e063bb940e..383663af37 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -56,7 +56,7 @@ where /// * a [`Clipboard`], if available /// /// By default, it does nothing. - fn on_event( + fn update( &mut self, _event: Event, _layout: Layout<'_>, diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 4a242213a7..ed870febcd 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -49,7 +49,7 @@ where } /// Processes a runtime [`Event`]. - pub fn on_event( + pub fn update( &mut self, event: Event, layout: Layout<'_>, @@ -59,7 +59,7 @@ where shell: &mut Shell<'_, Message>, ) { self.overlay - .on_event(event, layout, cursor, renderer, clipboard, shell); + .update(event, layout, cursor, renderer, clipboard, shell); } /// Returns the current [`mouse::Interaction`] of the [`Element`]. @@ -148,7 +148,7 @@ where self.content.operate(layout, renderer, operation); } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -160,7 +160,7 @@ where let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - self.content.on_event( + self.content.update( event, layout, cursor, diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 11ebd579d6..2b37425214 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -72,7 +72,7 @@ where ) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -82,7 +82,7 @@ where shell: &mut Shell<'_, Message>, ) { for (child, layout) in self.children.iter_mut().zip(layout.children()) { - child.on_event( + child.update( event.clone(), layout, cursor, diff --git a/examples/toast/src/main.rs b/examples/toast/src/main.rs index 893c52e20b..8d1e3924a0 100644 --- a/examples/toast/src/main.rs +++ b/examples/toast/src/main.rs @@ -489,7 +489,7 @@ mod toast { .translate(Vector::new(self.position.x, self.position.y)) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs index 45f6b220c9..342ad70c69 100644 --- a/runtime/src/overlay/nested.rs +++ b/runtime/src/overlay/nested.rs @@ -158,7 +158,7 @@ where } /// Processes a runtime [`Event`]. - pub fn on_event( + pub fn update( &mut self, event: Event, layout: Layout<'_>, @@ -211,7 +211,7 @@ where }) .unwrap_or_default(); - element.on_event( + element.update( event, layout, if nested_is_over { diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index 6997caf46e..b2826f7114 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -210,7 +210,7 @@ where for event in events.iter().cloned() { let mut shell = Shell::new(messages); - overlay.on_event( + overlay.update( event, Layout::new(&layout), cursor, diff --git a/widget/src/lazy.rs b/widget/src/lazy.rs index 0b4e3cad26..07b90c93f1 100644 --- a/widget/src/lazy.rs +++ b/widget/src/lazy.rs @@ -386,7 +386,7 @@ where .unwrap_or_default() } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -396,7 +396,7 @@ where shell: &mut Shell<'_, Message>, ) { let _ = self.with_overlay_mut_maybe(|overlay| { - overlay.on_event(event, layout, cursor, renderer, clipboard, shell); + overlay.update(event, layout, cursor, renderer, clipboard, shell); }); } diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs index 6f661ef68d..1758b96366 100644 --- a/widget/src/lazy/component.rs +++ b/widget/src/lazy/component.rs @@ -601,7 +601,7 @@ where .unwrap_or_default() } - fn on_event( + fn update( &mut self, event: core::Event, layout: Layout<'_>, @@ -614,7 +614,7 @@ where let mut local_shell = Shell::new(&mut local_messages); let _ = self.with_overlay_mut_maybe(|overlay| { - overlay.on_event( + overlay.update( event, layout, cursor, diff --git a/widget/src/lazy/responsive.rs b/widget/src/lazy/responsive.rs index f9bd0334e8..2aef1fa3b6 100644 --- a/widget/src/lazy/responsive.rs +++ b/widget/src/lazy/responsive.rs @@ -417,7 +417,7 @@ where .unwrap_or_default() } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -429,7 +429,7 @@ where let mut is_layout_invalid = false; let _ = self.with_overlay_mut_maybe(|overlay| { - overlay.on_event(event, layout, cursor, renderer, clipboard, shell); + overlay.update(event, layout, cursor, renderer, clipboard, shell); is_layout_invalid = shell.is_layout_invalid(); }); diff --git a/widget/src/overlay/menu.rs b/widget/src/overlay/menu.rs index b0c0bbad3a..7907ef0190 100644 --- a/widget/src/overlay/menu.rs +++ b/widget/src/overlay/menu.rs @@ -262,7 +262,7 @@ where }) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 691b0a9341..9b87a2d36f 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -674,7 +674,7 @@ where { let is_picked = picked_pane == Some(pane); - content.on_event( + content.update( tree, event.clone(), layout, diff --git a/widget/src/pane_grid/content.rs b/widget/src/pane_grid/content.rs index f1f6702385..fa9f7a9f26 100644 --- a/widget/src/pane_grid/content.rs +++ b/widget/src/pane_grid/content.rs @@ -239,7 +239,7 @@ where ); } - pub(crate) fn on_event( + pub(crate) fn update( &mut self, tree: &mut Tree, event: Event, @@ -254,7 +254,7 @@ where let body_layout = if let Some(title_bar) = &mut self.title_bar { let mut children = layout.children(); - title_bar.on_event( + title_bar.update( &mut tree.children[1], event.clone(), children.next().unwrap(), diff --git a/widget/src/pane_grid/title_bar.rs b/widget/src/pane_grid/title_bar.rs index 618eb4c59f..3f4a651e1b 100644 --- a/widget/src/pane_grid/title_bar.rs +++ b/widget/src/pane_grid/title_bar.rs @@ -427,7 +427,7 @@ where } } - pub(crate) fn on_event( + pub(crate) fn update( &mut self, tree: &mut Tree, event: Event, diff --git a/widget/src/themer.rs b/widget/src/themer.rs index 41d2aeae15..82160f2407 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -218,7 +218,7 @@ where ); } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -227,9 +227,8 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) { - self.content.on_event( - event, layout, cursor, renderer, clipboard, shell, - ); + self.content + .update(event, layout, cursor, renderer, clipboard, shell); } fn operate( From 9511bfb971e8bb7e10f95f7d40d5e23c9197e5d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 6 Nov 2024 22:03:00 +0100 Subject: [PATCH 27/29] Fix event capturing order in `pane_grid` --- widget/src/pane_grid.rs | 79 +++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 9b87a2d36f..7b2956f39f 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -491,6 +491,36 @@ 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 { .. }) => { @@ -601,10 +631,6 @@ where } } } - - shell.capture_event(); - } else if action.picked_split().is_some() { - shell.capture_event(); } *action = state::Action::Idle; @@ -657,49 +683,26 @@ where _ => {} } - 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, - ); - } - if shell.redraw_request() != Some(window::RedrawRequest::NextFrame) { let interaction = self .grid_interaction(action, layout, cursor) .or_else(|| { - self.contents.iter().zip(layout.children()).find_map( - |(content, layout)| { + 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); From 28ec6df8f0ebf96966bee61caf5a325695314b7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 8 Nov 2024 18:07:11 +0100 Subject: [PATCH 28/29] Fix cross-axis compression in `layout::flex` --- core/src/layout/flex.rs | 53 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/core/src/layout/flex.rs b/core/src/layout/flex.rs index ac80d3934a..2cff5bfd8a 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -79,6 +79,7 @@ where let max_cross = axis.cross(limits.max()); let mut fill_main_sum = 0; + let mut some_fill_cross = false; let (mut cross, cross_compress) = match axis { Axis::Vertical if width == Length::Shrink => (0.0, true), Axis::Horizontal if height == Length::Shrink => (0.0, true), @@ -90,6 +91,10 @@ where let mut nodes: Vec = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); + // FIRST PASS + // We lay out non-fluid elements in the main axis. + // If we need to compress the cross axis, then we skip any of these elements + // that are also fluid in the cross axis. for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); @@ -121,6 +126,41 @@ where nodes[i] = layout; } else { fill_main_sum += fill_main_factor; + some_fill_cross = some_fill_cross || fill_cross_factor != 0; + } + } + + // SECOND PASS (conditional) + // If we must compress the cross axis and there are fluid elements in the + // cross axis, we lay out any of these elements that are also non-fluid in + // the main axis (i.e. the ones we deliberately skipped in the first pass). + // + // We use the maximum cross length obtained in the first pass as the maximum + // cross limit. + if cross_compress && some_fill_cross { + for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() + { + let (fill_main_factor, fill_cross_factor) = { + let size = child.as_widget().size(); + + axis.pack(size.width.fill_factor(), size.height.fill_factor()) + }; + + if fill_main_factor == 0 && fill_cross_factor != 0 { + let (max_width, max_height) = axis.pack(available, cross); + + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); + + let layout = + child.as_widget().layout(tree, renderer, &child_limits); + let size = layout.size(); + + available -= axis.main(size); + cross = cross.max(axis.cross(size)); + + nodes[i] = layout; + } } } @@ -135,6 +175,9 @@ where }, }; + // THIRD PASS + // We only have the elements that are fluid in the main axis left. + // We use the remaining space to evenly allocate space based on fill factors. for (i, (child, tree)) in items.iter().zip(trees).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); @@ -142,10 +185,16 @@ where axis.pack(size.width.fill_factor(), size.height.fill_factor()) }; - if fill_main_factor != 0 || (cross_compress && fill_cross_factor != 0) { + if fill_main_factor != 0 { let max_main = remaining * fill_main_factor as f32 / fill_main_sum as f32; + let max_main = if max_main.is_nan() { + f32::INFINITY + } else { + max_main + }; + let min_main = if max_main.is_infinite() { 0.0 } else { @@ -178,6 +227,8 @@ where let pad = axis.pack(padding.left, padding.top); let mut main = pad.0; + // FOURTH PASS + // We align all the laid out nodes in the cross axis, if needed. for (i, node) in nodes.iter_mut().enumerate() { if i > 0 { main += spacing; From ed2e223fe0f62540947945ea0aa56d0daf3e3f76 Mon Sep 17 00:00:00 2001 From: edwloef Date: Mon, 11 Nov 2024 13:04:37 +0100 Subject: [PATCH 29/29] Fix docs of `Scrollable::with_direction` and `Scrollable::direction` --- widget/src/scrollable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index 528d63c1da..24ff1c164f 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -95,7 +95,7 @@ where Self::with_direction(content, Direction::default()) } - /// Creates a new vertical [`Scrollable`]. + /// Creates a new [`Scrollable`] with the given [`Direction`]. pub fn with_direction( content: impl Into>, direction: impl Into, @@ -136,7 +136,7 @@ where self } - /// Creates a new [`Scrollable`] with the given [`Direction`]. + /// Sets the [`Direction`] of the [`Scrollable`]. pub fn direction(mut self, direction: impl Into) -> Self { self.direction = direction.into(); self.validate()