Draft reactive-rendering feature for button
This commit is contained in:
parent
42a2cb6d4f
commit
5c33ce18ed
6 changed files with 162 additions and 92 deletions
26
Cargo.toml
26
Cargo.toml
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
iced = { path = "../..", features = ["debug", "multi-window"] }
|
iced = { path = "../..", features = ["debug"] }
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,10 @@ where
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => _debug.toggle(),
|
} => {
|
||||||
|
_debug.toggle();
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue