Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reactive Rendering #2662

Merged
merged 28 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
5c33ce1
Draft `reactive-rendering` feature for `button`
hecrj Oct 21, 2024
97bcca0
Remove `TODO` about reactive rendering in `iced_winit`
hecrj Oct 21, 2024
3ba7c71
Implement `reactive-rendering` for `slider`
hecrj Oct 21, 2024
5249039
Implement `reactive-rendering` for `text_input`
hecrj Oct 22, 2024
0691e61
Fix `WindowManager::is_idle` in `iced_winit`
hecrj Oct 22, 2024
f6c322f
Implement `reactive-rendering` for `checkbox`
hecrj Oct 22, 2024
0c77702
Implement `reactive-rendering` for `radio`
hecrj Oct 22, 2024
46017c6
Implement `reactive-rendering` for `toggler`
hecrj Oct 22, 2024
7908b6e
Request a redraw when a window is resized
hecrj Oct 23, 2024
fdf046d
Implement `reactive-rendering` for `pick_list`
hecrj Oct 23, 2024
908af3f
Implement `reactive-rendering` for `menu`
hecrj Oct 23, 2024
7fbc195
Implement `reactive-rendering` for `scrollable`
hecrj Oct 23, 2024
752403d
Split `Shell::request_redraw` into two different methods
hecrj Oct 25, 2024
dcc184b
Replace `event::Status` in `Widget::on_event` with `Shell::capture_ev…
hecrj Oct 25, 2024
f02bfc3
Rename `Widget::on_event` to `update`
hecrj Oct 25, 2024
a84b328
Implement `reactive-rendering` for `combo_box`
hecrj Oct 25, 2024
920596e
Implement `reactive-rendering` for `canvas`
hecrj Oct 28, 2024
4e47450
Implement `reactive-rendering` for `pane_grid`
hecrj Oct 29, 2024
c6af79a
Fix deferred layout on resize after drawing
hecrj Oct 29, 2024
14ec330
Replace `reactive-rendering` feature with `unconditional-rendering`
hecrj Nov 4, 2024
6fc1676
Unify `shader::Program` API with `canvas::Program`
hecrj Nov 4, 2024
d5a886d
Fix `hover` widget not redrawing when hovered
hecrj Nov 4, 2024
3482ffe
Implement `reactive-rendering` for `text_editor`
hecrj Nov 4, 2024
fec7522
Fix `text_editor` capturing mouse release events
hecrj Nov 4, 2024
03bffe3
Fix `pick_list` not requesting a redraw when open
hecrj Nov 4, 2024
e5f1e31
Rename `Overlay::on_event` to `update`
hecrj Nov 5, 2024
9511bfb
Fix event capturing order in `pane_grid`
hecrj Nov 6, 2024
28ec6df
Fix cross-axis compression in `layout::flex`
hecrj Nov 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,19 @@ maintenance = { status = "actively-developed" }

[features]
default = ["wgpu", "tiny-skia", "fira-sans", "auto-detect-theme"]
# Enable the `wgpu` GPU-accelerated renderer backend
# 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"]
Expand All @@ -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 on every runtime event, and not only when a widget requests it
unconditional-rendering = ["iced_winit/unconditional-rendering"]

[dependencies]
iced_core.workspace = true
Expand Down
21 changes: 9 additions & 12 deletions core/src/element.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
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;
Expand Down Expand Up @@ -309,7 +308,7 @@ where
self.widget.operate(tree, layout, renderer, operation);
}

fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
Expand All @@ -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.update(
tree,
event,
layout,
Expand All @@ -335,8 +334,6 @@ where
);

shell.merge(local_shell, &self.mapper);

status
}

fn draw(
Expand Down Expand Up @@ -447,7 +444,7 @@ where
.operate(state, layout, renderer, operation);
}

fn on_event(
fn update(
&mut self,
state: &mut Tree,
event: Event,
Expand All @@ -457,10 +454,10 @@ where
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
self.element.widget.on_event(
) {
self.element.widget.update(
state, event, layout, cursor, renderer, clipboard, shell, viewport,
)
);
}

fn draw(
Expand Down
53 changes: 52 additions & 1 deletion core/src/layout/flex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -90,6 +91,10 @@ where
let mut nodes: Vec<Node> = 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();
Expand Down Expand Up @@ -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;
}
}
}

Expand All @@ -135,17 +175,26 @@ 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();

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 {
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 3 additions & 5 deletions core/src/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Message, Theme, Renderer>
Expand Down Expand Up @@ -57,16 +56,15 @@ where
/// * a [`Clipboard`], if available
///
/// By default, it does nothing.
fn on_event(
fn update(
&mut self,
_event: Event,
_layout: Layout<'_>,
_cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
event::Status::Ignored
) {
}

/// Returns the current [`mouse::Interaction`] of the [`Overlay`].
Expand Down
17 changes: 7 additions & 10 deletions core/src/overlay/element.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -50,17 +49,17 @@ where
}

/// Processes a runtime [`Event`].
pub fn on_event(
pub fn update(
&mut self,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
) {
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`].
Expand Down Expand Up @@ -149,19 +148,19 @@ where
self.content.operate(layout, renderer, operation);
}

fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
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.update(
event,
layout,
cursor,
Expand All @@ -171,8 +170,6 @@ where
);

shell.merge(local_shell, self.mapper);

event_status
}

fn mouse_interaction(
Expand Down
29 changes: 12 additions & 17 deletions core/src/overlay/group.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::event;
use crate::layout;
use crate::mouse;
use crate::overlay;
Expand Down Expand Up @@ -73,29 +72,25 @@ where
)
}

fn on_event(
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
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.update(
event.clone(),
layout,
cursor,
renderer,
clipboard,
shell,
);
}
}

fn draw(
Expand Down
Loading
Loading