Skip to content

Commit

Permalink
Implement reactive-rendering for canvas
Browse files Browse the repository at this point in the history
  • Loading branch information
hecrj committed Oct 28, 2024
1 parent ab2d29b commit dfdf2b5
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 142 deletions.
78 changes: 39 additions & 39 deletions examples/bezier_tool/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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,
}
}

Expand Down
52 changes: 33 additions & 19 deletions examples/game_of_life/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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);
Expand All @@ -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) => {
Expand All @@ -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 {
Expand All @@ -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, .. }
Expand Down Expand Up @@ -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,
}
}

Expand Down
33 changes: 16 additions & 17 deletions examples/multitouch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
33 changes: 14 additions & 19 deletions examples/sierpinski_triangle/src/main.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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(
Expand Down
89 changes: 89 additions & 0 deletions widget/src/action.rs
Original file line number Diff line number Diff line change
@@ -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,
)
}
}
Loading

0 comments on commit dfdf2b5

Please sign in to comment.