Draft reactive-rendering feature for button

This commit is contained in:
Héctor Ramón Jiménez 2024-10-22 00:13:42 +02:00
parent 42a2cb6d4f
commit 5c33ce18ed
No known key found for this signature in database
GPG key ID: 4C07CEC81AFA161F
6 changed files with 162 additions and 92 deletions

View file

@ -22,20 +22,20 @@ all-features = true
maintenance = { status = "actively-developed" } maintenance = { status = "actively-developed" }
[features] [features]
default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"] default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme", "reactive-rendering"]
# Enable the `wgpu` GPU-accelerated renderer backend # Enables the `wgpu` GPU-accelerated renderer backend
wgpu = ["iced_renderer/wgpu", "iced_widget/wgpu"] 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"] tiny-skia = ["iced_renderer/tiny-skia"]
# Enables the `Image` widget # Enables the `image` widget
image = ["image-without-codecs", "image/default"] 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"] image-without-codecs = ["iced_widget/image", "dep:image"]
# Enables the `Svg` widget # Enables the `svg` widget
svg = ["iced_widget/svg"] svg = ["iced_widget/svg"]
# Enables the `Canvas` widget # Enables the `canvas` widget
canvas = ["iced_widget/canvas"] canvas = ["iced_widget/canvas"]
# Enables the `QRCode` widget # Enables the `qr_code` widget
qr_code = ["iced_widget/qr_code"] qr_code = ["iced_widget/qr_code"]
# Enables the `markdown` widget # Enables the `markdown` widget
markdown = ["iced_widget/markdown"] markdown = ["iced_widget/markdown"]
@ -55,18 +55,18 @@ system = ["iced_winit/system"]
web-colors = ["iced_renderer/web-colors"] web-colors = ["iced_renderer/web-colors"]
# Enables the WebGL backend, replacing WebGPU # Enables the WebGL backend, replacing WebGPU
webgl = ["iced_renderer/webgl"] webgl = ["iced_renderer/webgl"]
# Enables the syntax `highlighter` module # Enables syntax highligthing
highlighter = ["iced_highlighter", "iced_widget/highlighter"] highlighter = ["iced_highlighter", "iced_widget/highlighter"]
# Enables experimental multi-window support.
multi-window = ["iced_winit/multi-window"]
# Enables the advanced module # Enables the advanced module
advanced = ["iced_core/advanced", "iced_widget/advanced"] 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"] 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"] auto-detect-theme = ["iced_core/auto-detect-theme"]
# Enables strict assertions for debugging purposes at the expense of performance # Enables strict assertions for debugging purposes at the expense of performance
strict-assertions = ["iced_renderer/strict-assertions"] strict-assertions = ["iced_renderer/strict-assertions"]
# Redraws only when widgets react to some runtime event
reactive-rendering = ["iced_winit/reactive-rendering"]
[dependencies] [dependencies]
iced_core.workspace = true iced_core.workspace = true

View file

@ -6,4 +6,4 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["debug", "multi-window"] } iced = { path = "../..", features = ["debug"] }

View file

@ -26,6 +26,7 @@ use crate::core::theme::palette;
use crate::core::touch; use crate::core::touch;
use crate::core::widget::tree::{self, Tree}; use crate::core::widget::tree::{self, Tree};
use crate::core::widget::Operation; use crate::core::widget::Operation;
use crate::core::window;
use crate::core::{ use crate::core::{
Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle, Background, Clipboard, Color, Element, Layout, Length, Padding, Rectangle,
Shadow, Shell, Size, Theme, Vector, Widget, Shadow, Shell, Size, Theme, Vector, Widget,
@ -81,6 +82,7 @@ where
padding: Padding, padding: Padding,
clip: bool, clip: bool,
class: Theme::Class<'a>, class: Theme::Class<'a>,
status: Option<Status>,
} }
enum OnPress<'a, Message> { enum OnPress<'a, Message> {
@ -117,6 +119,7 @@ where
padding: DEFAULT_PADDING, padding: DEFAULT_PADDING,
clip: false, clip: false,
class: Theme::default(), class: Theme::default(),
status: None,
} }
} }
@ -294,49 +297,85 @@ where
return event::Status::Captured; return event::Status::Captured;
} }
match event { let mut update = || {
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) match event {
| Event::Touch(touch::Event::FingerPressed { .. }) => { Event::Mouse(mouse::Event::ButtonPressed(
if self.on_press.is_some() { mouse::Button::Left,
let bounds = layout.bounds(); ))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
if cursor.is_over(bounds) { if self.on_press.is_some() {
let state = tree.state.downcast_mut::<State>();
state.is_pressed = true;
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::<State>();
if state.is_pressed {
state.is_pressed = false;
let bounds = layout.bounds(); let bounds = layout.bounds();
if cursor.is_over(bounds) { if cursor.is_over(bounds) {
shell.publish(on_press); let state = tree.state.downcast_mut::<State>();
}
return event::Status::Captured; state.is_pressed = true;
return event::Status::Captured;
}
} }
} }
} Event::Mouse(mouse::Event::ButtonReleased(
Event::Touch(touch::Event::FingerLost { .. }) => { mouse::Button::Left,
let state = tree.state.downcast_mut::<State>(); ))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
if let Some(on_press) =
self.on_press.as_ref().map(OnPress::get)
{
let state = tree.state.downcast_mut::<State>();
state.is_pressed = false; if state.is_pressed {
state.is_pressed = false;
let bounds = layout.bounds();
if cursor.is_over(bounds) {
shell.publish(on_press);
}
return event::Status::Captured;
}
}
}
Event::Touch(touch::Event::FingerLost { .. }) => {
let state = tree.state.downcast_mut::<State>();
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::<State>();
if state.is_pressed {
Status::Pressed
} else {
Status::Hovered
}
} else {
Status::Active
};
if let Event::Window(window::Event::RedrawRequested(_now)) = event {
self.status = Some(current_status);
} else {
match self.status {
Some(status) if status != current_status => {
shell.request_redraw(window::RedrawRequest::NextFrame);
}
_ => {}
} }
_ => {}
} }
event::Status::Ignored update_status
} }
fn draw( fn draw(
@ -351,23 +390,8 @@ where
) { ) {
let bounds = layout.bounds(); let bounds = layout.bounds();
let content_layout = layout.children().next().unwrap(); let content_layout = layout.children().next().unwrap();
let is_mouse_over = cursor.is_over(bounds); let style =
theme.style(&self.class, self.status.unwrap_or(Status::Disabled));
let status = if self.on_press.is_none() {
Status::Disabled
} else if is_mouse_over {
let state = tree.state.downcast_ref::<State>();
if state.is_pressed {
Status::Pressed
} else {
Status::Hovered
}
} else {
Status::Active
};
let style = theme.style(&self.class, status);
if style.background.is_some() if style.background.is_some()
|| style.border.width > 0.0 || style.border.width > 0.0

View file

@ -22,7 +22,7 @@ x11 = ["winit/x11"]
wayland = ["winit/wayland"] wayland = ["winit/wayland"]
wayland-dlopen = ["winit/wayland-dlopen"] wayland-dlopen = ["winit/wayland-dlopen"]
wayland-csd-adwaita = ["winit/wayland-csd-adwaita"] wayland-csd-adwaita = ["winit/wayland-csd-adwaita"]
multi-window = ["iced_runtime/multi-window"] reactive-rendering = []
[dependencies] [dependencies]
iced_futures.workspace = true iced_futures.workspace = true

View file

@ -691,6 +691,7 @@ async fn run_instance<P, C>(
let mut ui_caches = FxHashMap::default(); let mut ui_caches = FxHashMap::default();
let mut user_interfaces = ManuallyDrop::new(FxHashMap::default()); let mut user_interfaces = ManuallyDrop::new(FxHashMap::default());
let mut clipboard = Clipboard::unconnected(); let mut clipboard = Clipboard::unconnected();
let mut redraw_queue = Vec::new();
debug.startup_finished(); debug.startup_finished();
@ -758,14 +759,30 @@ async fn run_instance<P, C>(
} }
Event::EventLoopAwakened(event) => { Event::EventLoopAwakened(event) => {
match event { match event {
event::Event::NewEvents( event::Event::NewEvents(event::StartCause::Init) => {
event::StartCause::Init
| event::StartCause::ResumeTimeReached { .. },
) => {
for (_id, window) in window_manager.iter_mut() { for (_id, window) in window_manager.iter_mut() {
window.raw.request_redraw(); 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::Event::PlatformSpecific(
event::PlatformSpecific::MacOS( event::PlatformSpecific::MacOS(
event::MacOS::ReceivedUrl(url), event::MacOS::ReceivedUrl(url),
@ -857,23 +874,19 @@ async fn run_instance<P, C>(
status: core::event::Status::Ignored, status: core::event::Status::Ignored,
}); });
let _ = control_sender.start_send(Control::ChangeFlow( if let user_interface::State::Updated {
match ui_state { redraw_request: Some(redraw_request),
user_interface::State::Updated { } = ui_state
redraw_request: Some(redraw_request), {
} => match redraw_request { match redraw_request {
window::RedrawRequest::NextFrame => { window::RedrawRequest::NextFrame => {
window.raw.request_redraw(); window.raw.request_redraw();
}
ControlFlow::Wait window::RedrawRequest::At(at) => {
} redraw_queue.push((at, id));
window::RedrawRequest::At(at) => { }
ControlFlow::WaitUntil(at) }
} }
},
_ => ControlFlow::Wait,
},
));
let physical_size = window.state.physical_size(); let physical_size = window.state.physical_size();
@ -1065,13 +1078,25 @@ async fn run_instance<P, C>(
&mut messages, &mut messages,
); );
#[cfg(not(feature = "reactive-rendering"))]
window.raw.request_redraw(); window.raw.request_redraw();
if !uis_stale { match ui_state {
uis_stale = matches!( #[cfg(feature = "reactive-rendering")]
ui_state, user_interface::State::Updated {
user_interface::State::Outdated 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 for (event, status) in window_events
@ -1139,6 +1164,24 @@ async fn run_instance<P, C>(
actions = 0; 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),
));
}
} }
_ => {} _ => {}
} }

View file

@ -190,7 +190,10 @@ where
.. ..
}, },
.. ..
} => _debug.toggle(), } => {
_debug.toggle();
window.request_redraw();
}
_ => {} _ => {}
} }
} }