summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-10-28 16:58:00 +0100
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-11-05 23:52:58 +0100
commit920596ed6f44acf8d87d2135c1b8967bab23d5b9 (patch)
treeca3f673c01bf9abba79f6b5f172df189788d50de
parenta84b328dcc3e2f941f9595a2f8c3b1d061442722 (diff)
downloadiced-920596ed6f44acf8d87d2135c1b8967bab23d5b9.tar.gz
iced-920596ed6f44acf8d87d2135c1b8967bab23d5b9.tar.bz2
iced-920596ed6f44acf8d87d2135c1b8967bab23d5b9.zip
Implement `reactive-rendering` for `canvas`
-rw-r--r--examples/bezier_tool/src/main.rs78
-rw-r--r--examples/game_of_life/src/main.rs52
-rw-r--r--examples/multitouch/src/main.rs33
-rw-r--r--examples/sierpinski_triangle/src/main.rs33
-rw-r--r--widget/src/action.rs89
-rw-r--r--widget/src/canvas.rs63
-rw-r--r--widget/src/canvas/event.rs21
-rw-r--r--widget/src/canvas/program.rs15
-rw-r--r--widget/src/lib.rs2
9 files changed, 244 insertions, 142 deletions
diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs
index 949bfad7..5e4da0c2 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<Curve>) {
- let Some(cursor_position) = cursor.position_in(bounds) else {
- return (event::Status::Ignored, None);
- };
+ ) -> Option<canvas::Action<Curve>> {
+ 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 9dcebecc..7a7224d5 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<Message>) {
+ ) -> Option<canvas::Action<Message>> {
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 d5e5dffa..5f4a5c90 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<Message> for Multitouch {
fn update(
&self,
_state: &mut Self::State,
- event: event::Event,
+ event: Event,
_bounds: Rectangle,
_cursor: mouse::Cursor,
- ) -> (event::Status, Option<Message>) {
- match event {
- event::Event::Touch(touch_event) => match touch_event {
+ ) -> Option<canvas::Action<Message>> {
+ 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 99e7900a..d4d483f5 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<Message> for SierpinskiGraph {
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
- ) -> (event::Status, Option<Message>) {
- let Some(cursor_position) = cursor.position_in(bounds) else {
- return (event::Status::Ignored, None);
- };
+ ) -> Option<canvas::Action<Message>> {
+ 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 00000000..1dd3a787
--- /dev/null
+++ b/widget/src/action.rs
@@ -0,0 +1,89 @@
+use crate::core::event;
+use crate::core::time::Instant;
+use crate::core::window;
+
+/// A runtime action that can be performed by some widgets.
+#[derive(Debug, Clone)]
+pub struct Action<Message> {
+ message_to_publish: Option<Message>,
+ redraw_request: Option<window::RedrawRequest>,
+ event_status: event::Status,
+}
+
+impl<Message> Action<Message> {
+ fn new() -> Self {
+ Self {
+ message_to_publish: None,
+ redraw_request: None,
+ event_status: event::Status::Ignored,
+ }
+ }
+
+ /// Creates a new "capturing" [`Action`]. A capturing [`Action`]
+ /// will make other widgets consider it final and prevent further
+ /// processing.
+ ///
+ /// Prevents "event bubbling".
+ pub fn capture() -> Self {
+ Self {
+ event_status: event::Status::Captured,
+ ..Self::new()
+ }
+ }
+
+ /// Creates a new [`Action`] that publishes the given `Message` for
+ /// the application to handle.
+ ///
+ /// Publishing a `Message` always produces a redraw.
+ pub fn publish(message: Message) -> Self {
+ Self {
+ message_to_publish: Some(message),
+ ..Self::new()
+ }
+ }
+
+ /// Creates a new [`Action`] that requests a redraw to happen as
+ /// soon as possible; without publishing any `Message`.
+ pub fn request_redraw() -> Self {
+ Self {
+ redraw_request: Some(window::RedrawRequest::NextFrame),
+ ..Self::new()
+ }
+ }
+
+ /// Creates a new [`Action`] that requests a redraw to happen at
+ /// the given [`Instant`]; without publishing any `Message`.
+ ///
+ /// This can be useful to efficiently animate content, like a
+ /// blinking caret on a text input.
+ pub fn request_redraw_at(at: Instant) -> Self {
+ Self {
+ redraw_request: Some(window::RedrawRequest::At(at)),
+ ..Self::new()
+ }
+ }
+
+ /// Marks the [`Action`] as "capturing". See [`Self::capture`].
+ pub fn and_capture(mut self) -> Self {
+ self.event_status = event::Status::Captured;
+ self
+ }
+
+ /// Converts the [`Action`] into its internal parts.
+ ///
+ /// This method is meant to be used by runtimes, libraries, or internal
+ /// widget implementations.
+ pub fn into_inner(
+ self,
+ ) -> (
+ Option<Message>,
+ Option<window::RedrawRequest>,
+ event::Status,
+ ) {
+ (
+ self.message_to_publish,
+ self.redraw_request,
+ self.event_status,
+ )
+ }
+}
diff --git a/widget/src/canvas.rs b/widget/src/canvas.rs
index 63a25064..23cc3f2b 100644
--- a/widget/src/canvas.rs
+++ b/widget/src/canvas.rs
@@ -48,24 +48,24 @@
//! canvas(Circle { radius: 50.0 }).into()
//! }
//! ```
-pub mod event;
-
mod program;
-pub use event::Event;
pub use program::Program;
+pub use crate::core::event::Event;
pub use crate::graphics::cache::Group;
pub use crate::graphics::geometry::{
fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
LineJoin, Path, Stroke, Style, Text,
};
+pub use crate::Action;
-use crate::core;
+use crate::core::event;
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
+use crate::core::window;
use crate::core::{
Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,
};
@@ -148,6 +148,7 @@ where
message_: PhantomData<Message>,
theme_: PhantomData<Theme>,
renderer_: PhantomData<Renderer>,
+ last_mouse_interaction: Option<mouse::Interaction>,
}
impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer>
@@ -166,6 +167,7 @@ where
message_: PhantomData,
theme_: PhantomData,
renderer_: PhantomData,
+ last_mouse_interaction: None,
}
}
@@ -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::<P::State>();
+ let state = tree.state.downcast_mut::<P::State>();
+ let is_redraw_request = matches!(
+ event,
+ Event::Window(window::Event::RedrawRequested(_now)),
+ );
- let (event_status, message) =
- self.program.update(state, canvas_event, bounds, cursor);
+ if let Some(action) = self.program.update(state, event, bounds, cursor)
+ {
+ let (message, redraw_request, event_status) = action.into_inner();
if let Some(message) = message {
shell.publish(message);
}
+ 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 a8eb47f7..00000000
--- a/widget/src/canvas/event.rs
+++ /dev/null
@@ -1,21 +0,0 @@
-//! Handle events of a canvas.
-use crate::core::keyboard;
-use crate::core::mouse;
-use crate::core::touch;
-
-pub use crate::core::event::Status;
-
-/// A [`Canvas`] event.
-///
-/// [`Canvas`]: crate::Canvas
-#[derive(Debug, Clone, PartialEq)]
-pub enum Event {
- /// A mouse event.
- Mouse(mouse::Event),
-
- /// A touch event.
- Touch(touch::Event),
-
- /// A keyboard event.
- Keyboard(keyboard::Event),
-}
diff --git a/widget/src/canvas/program.rs b/widget/src/canvas/program.rs
index a7ded0f4..c68b2830 100644
--- a/widget/src/canvas/program.rs
+++ b/widget/src/canvas/program.rs
@@ -1,8 +1,8 @@
-use crate::canvas::event::{self, Event};
use crate::canvas::mouse;
-use crate::canvas::Geometry;
+use crate::canvas::{Event, Geometry};
use crate::core::Rectangle;
use crate::graphics::geometry;
+use crate::Action;
/// The state and logic of a [`Canvas`].
///
@@ -22,8 +22,9 @@ where
/// When a [`Program`] is used in a [`Canvas`], the runtime will call this
/// method for each [`Event`].
///
- /// This method can optionally return a `Message` to notify an application
- /// of any meaningful interactions.
+ /// This method can optionally return an [`Action`] to either notify an
+ /// application of any meaningful interactions, capture the event, or
+ /// request a redraw.
///
/// By default, this method does and returns nothing.
///
@@ -34,8 +35,8 @@ where
_event: Event,
_bounds: Rectangle,
_cursor: mouse::Cursor,
- ) -> (event::Status, Option<Message>) {
- (event::Status::Ignored, None)
+ ) -> Option<Action<Message>> {
+ None
}
/// Draws the state of the [`Program`], producing a bunch of [`Geometry`].
@@ -84,7 +85,7 @@ where
event: Event,
bounds: Rectangle,
cursor: mouse::Cursor,
- ) -> (event::Status, Option<Message>) {
+ ) -> Option<Action<Message>> {
T::update(self, state, event, bounds, cursor)
}
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index a68720d6..776a04a0 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -8,6 +8,7 @@ pub use iced_renderer::graphics;
pub use iced_runtime as runtime;
pub use iced_runtime::core;
+mod action;
mod column;
mod mouse_area;
mod row;
@@ -131,4 +132,5 @@ pub use qr_code::QRCode;
pub mod markdown;
pub use crate::core::theme::{self, Theme};
+pub use action::Action;
pub use renderer::Renderer;