From 20fd1f2047b2c425398d0b237c6d3031deaaa0c9 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Fri, 7 Oct 2022 09:03:44 -0700 Subject: Render pane grid titlebar after body --- native/src/widget/pane_grid/content.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 98ce2c4b..8d26c3d8 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -115,25 +115,25 @@ where let show_controls = bounds.contains(cursor_position); - title_bar.draw( - &tree.children[1], + self.body.as_widget().draw( + &tree.children[0], renderer, theme, style, - title_bar_layout, + body_layout, cursor_position, viewport, - show_controls, ); - self.body.as_widget().draw( - &tree.children[0], + title_bar.draw( + &tree.children[1], renderer, theme, style, - body_layout, + title_bar_layout, cursor_position, viewport, + show_controls, ); } else { self.body.as_widget().draw( -- cgit From 7ea7dbef578ddbe2a9a50da6aab253ba016f1362 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 6 Oct 2022 20:38:21 +0200 Subject: feat: Add window drag support from winit Exposes access to the winit window's window_drag method as an action. --- native/src/window/action.rs | 8 ++++++++ winit/src/application.rs | 3 +++ winit/src/window.rs | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/native/src/window/action.rs b/native/src/window/action.rs index 73338e22..ce125144 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -5,6 +5,12 @@ use std::fmt; /// An operation to be performed on some window. pub enum Action { + /// Moves the window with the left mouse button until the button is + /// released. + /// + /// There’s no guarantee that this will work unless the left mouse + /// button was pressed immediately before this function is called. + Drag, /// Resize the window. Resize { /// The new logical width of the window @@ -37,6 +43,7 @@ impl Action { T: 'static, { match self { + Self::Drag => Action::Drag, Self::Resize { width, height } => Action::Resize { width, height }, Self::Move { x, y } => Action::Move { x, y }, Self::SetMode(mode) => Action::SetMode(mode), @@ -48,6 +55,7 @@ impl Action { impl fmt::Debug for Action { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + Self::Drag => write!(f, "Action::Drag"), Self::Resize { width, height } => write!( f, "Action::Resize {{ widget: {}, height: {} }}", diff --git a/winit/src/application.rs b/winit/src/application.rs index 0496aea9..75c113bf 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -615,6 +615,9 @@ pub fn run_command( } }, command::Action::Window(action) => match action { + window::Action::Drag => { + let _res = window.drag_window(); + } window::Action::Resize { width, height } => { window.set_inner_size(winit::dpi::LogicalSize { width, diff --git a/winit/src/window.rs b/winit/src/window.rs index 265139f7..61c9c3fe 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -4,6 +4,11 @@ use iced_native::window; pub use window::{Event, Mode}; +/// Begins dragging the window while the left mouse button is held. +pub fn drag() -> Command { + Command::single(command::Action::Window(window::Action::Drag)) +} + /// Resizes the window to the given logical dimensions. pub fn resize(width: u32, height: u32) -> Command { Command::single(command::Action::Window(window::Action::Resize { -- cgit From 8a50836ffc32a6d9157eb18740b3947c4dbd7d1f Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Sun, 9 Oct 2022 16:35:28 +0200 Subject: feat: Add window maximize support --- native/src/window/action.rs | 8 ++++++++ winit/src/application.rs | 6 ++++++ winit/src/window.rs | 10 ++++++++++ 3 files changed, 24 insertions(+) diff --git a/native/src/window/action.rs b/native/src/window/action.rs index ce125144..c49fdf9d 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -18,6 +18,8 @@ pub enum Action { /// The new logical height of the window height: u32, }, + /// Sets the window to maximized or back + Maximize(bool), /// Move the window. /// /// Unsupported on Wayland. @@ -29,6 +31,8 @@ pub enum Action { }, /// Set the [`Mode`] of the window. SetMode(Mode), + /// Sets the window to maximized or back + ToggleMaximize, /// Fetch the current [`Mode`] of the window. FetchMode(Box T + 'static>), } @@ -45,8 +49,10 @@ impl Action { match self { Self::Drag => Action::Drag, Self::Resize { width, height } => Action::Resize { width, height }, + Self::Maximize(bool) => Action::Maximize(bool), Self::Move { x, y } => Action::Move { x, y }, Self::SetMode(mode) => Action::SetMode(mode), + Self::ToggleMaximize => Action::ToggleMaximize, Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), } } @@ -61,10 +67,12 @@ impl fmt::Debug for Action { "Action::Resize {{ widget: {}, height: {} }}", width, height ), + Self::Maximize(value) => write!(f, "Action::Maximize({})", value), Self::Move { x, y } => { write!(f, "Action::Move {{ x: {}, y: {} }}", x, y) } Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode), + Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), Self::FetchMode(_) => write!(f, "Action::FetchMode"), } } diff --git a/winit/src/application.rs b/winit/src/application.rs index 75c113bf..7de5a0ce 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -624,6 +624,9 @@ pub fn run_command( height, }); } + window::Action::Maximize(value) => { + window.set_maximized(value); + } window::Action::Move { x, y } => { window.set_outer_position(winit::dpi::LogicalPosition { x, @@ -637,6 +640,9 @@ pub fn run_command( mode, )); } + window::Action::ToggleMaximize => { + window.set_maximized(!window.is_maximized()) + } window::Action::FetchMode(tag) => { let mode = if window.is_visible().unwrap_or(true) { conversion::mode(window.fullscreen()) diff --git a/winit/src/window.rs b/winit/src/window.rs index 61c9c3fe..48210c33 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -17,6 +17,11 @@ pub fn resize(width: u32, height: u32) -> Command { })) } +/// Sets the window to maximized or back. +pub fn maximize(value: bool) -> Command { + Command::single(command::Action::Window(window::Action::Maximize(value))) +} + /// Moves a window to the given logical coordinates. pub fn move_to(x: i32, y: i32) -> Command { Command::single(command::Action::Window(window::Action::Move { x, y })) @@ -27,6 +32,11 @@ pub fn set_mode(mode: Mode) -> Command { Command::single(command::Action::Window(window::Action::SetMode(mode))) } +/// Sets the window to maximized or back. +pub fn toggle_maximize() -> Command { + Command::single(command::Action::Window(window::Action::ToggleMaximize)) +} + /// Fetches the current [`Mode`] of the window. pub fn fetch_mode( f: impl FnOnce(Mode) -> Message + 'static, -- cgit From ac6e137be3e9d2d2a1d8c1284880096a0e2c2a47 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 11 Oct 2022 15:24:26 +0200 Subject: feat: Add window minimize support --- native/src/window/action.rs | 4 ++++ winit/src/application.rs | 3 +++ winit/src/window.rs | 5 +++++ 3 files changed, 12 insertions(+) diff --git a/native/src/window/action.rs b/native/src/window/action.rs index c49fdf9d..009dcc27 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -20,6 +20,8 @@ pub enum Action { }, /// Sets the window to maximized or back Maximize(bool), + /// Set the window to minimized or back + Minimize(bool), /// Move the window. /// /// Unsupported on Wayland. @@ -50,6 +52,7 @@ impl Action { Self::Drag => Action::Drag, Self::Resize { width, height } => Action::Resize { width, height }, Self::Maximize(bool) => Action::Maximize(bool), + Self::Minimize(bool) => Action::Minimize(bool), Self::Move { x, y } => Action::Move { x, y }, Self::SetMode(mode) => Action::SetMode(mode), Self::ToggleMaximize => Action::ToggleMaximize, @@ -68,6 +71,7 @@ impl fmt::Debug for Action { width, height ), Self::Maximize(value) => write!(f, "Action::Maximize({})", value), + Self::Minimize(value) => write!(f, "Action::Minimize({}", value), Self::Move { x, y } => { write!(f, "Action::Move {{ x: {}, y: {} }}", x, y) } diff --git a/winit/src/application.rs b/winit/src/application.rs index 7de5a0ce..939a50c9 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -627,6 +627,9 @@ pub fn run_command( window::Action::Maximize(value) => { window.set_maximized(value); } + window::Action::Minimize(value) => { + window.set_minimized(value); + } window::Action::Move { x, y } => { window.set_outer_position(winit::dpi::LogicalPosition { x, diff --git a/winit/src/window.rs b/winit/src/window.rs index 48210c33..1e704c5b 100644 --- a/winit/src/window.rs +++ b/winit/src/window.rs @@ -22,6 +22,11 @@ pub fn maximize(value: bool) -> Command { Command::single(command::Action::Window(window::Action::Maximize(value))) } +/// Set the window to minimized or back. +pub fn minimize(value: bool) -> Command { + Command::single(command::Action::Window(window::Action::Minimize(value))) +} + /// Moves a window to the given logical coordinates. pub fn move_to(x: i32, y: i32) -> Command { Command::single(command::Action::Window(window::Action::Move { x, y })) -- cgit From dca99f35e938bebdfdad16eaabd220b5d52c2375 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Mon, 17 Oct 2022 14:38:24 -0700 Subject: Fix pane grid mouse interactions - Use `grabbing` interaction while dragging - Ignore grab interaction when dragging is disabled --- native/src/widget/pane_grid.rs | 3 ++- native/src/widget/pane_grid/content.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index d84fb7a0..8eadac48 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -341,6 +341,7 @@ where cursor_position, viewport, renderer, + self.on_drag.is_some(), ) }) .max() @@ -648,7 +649,7 @@ pub fn mouse_interaction( resize_leeway: Option, ) -> Option { if action.picked_pane().is_some() { - return Some(mouse::Interaction::Grab); + return Some(mouse::Interaction::Grabbing); } let resize_axis = diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 8d26c3d8..c236d820 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -238,6 +238,7 @@ where cursor_position: Point, viewport: &Rectangle, renderer: &Renderer, + drag_enabled: bool, ) -> mouse::Interaction { let (body_layout, title_bar_interaction) = if let Some(title_bar) = &self.title_bar { @@ -247,7 +248,7 @@ where let is_over_pick_area = title_bar .is_over_pick_area(title_bar_layout, cursor_position); - if is_over_pick_area { + if is_over_pick_area && drag_enabled { return mouse::Interaction::Grab; } -- cgit From fb036529a222126da3f119da4d94d17776460421 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Mon, 17 Oct 2022 14:56:06 -0700 Subject: Render picked pane last --- native/src/widget/pane_grid.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8eadac48..3ef578bf 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -757,7 +757,12 @@ pub fn draw( cursor_position }; - for ((id, pane), layout) in elements.zip(layout.children()) { + // Render picked pane last + let mut elements = elements.zip(layout.children()).collect::>(); + elements + .sort_by_key(|((id, _), _)| picked_pane.map(|(id, _)| id) == Some(*id)); + + for ((id, pane), layout) in elements { match picked_pane { Some((dragging, origin)) if id == dragging => { let bounds = layout.bounds(); -- cgit From 470723c232493282c513af47d19b3877066ceb2e Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 19 Oct 2022 13:05:56 -0700 Subject: Eliminate unnecessary allocation --- native/src/widget/pane_grid.rs | 46 +++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3ef578bf..12ccbb41 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -757,32 +757,12 @@ pub fn draw( cursor_position }; - // Render picked pane last - let mut elements = elements.zip(layout.children()).collect::>(); - elements - .sort_by_key(|((id, _), _)| picked_pane.map(|(id, _)| id) == Some(*id)); + let mut render_picked_pane = None; - for ((id, pane), layout) in elements { + for ((id, pane), layout) in elements.zip(layout.children()) { match picked_pane { Some((dragging, origin)) if id == dragging => { - let bounds = layout.bounds(); - - renderer.with_translation( - cursor_position - - Point::new(bounds.x + origin.x, bounds.y + origin.y), - |renderer| { - renderer.with_layer(bounds, |renderer| { - draw_pane( - pane, - renderer, - default_style, - layout, - pane_cursor_position, - viewport, - ); - }); - }, - ); + render_picked_pane = Some((pane, origin, layout.bounds())); } _ => { draw_pane( @@ -797,6 +777,26 @@ pub fn draw( } } + // Render picked pane last + if let Some((pane, origin, bounds)) = render_picked_pane { + renderer.with_translation( + cursor_position + - Point::new(bounds.x + origin.x, bounds.y + origin.y), + |renderer| { + renderer.with_layer(bounds, |renderer| { + draw_pane( + pane, + renderer, + default_style, + layout, + pane_cursor_position, + viewport, + ); + }); + }, + ); + }; + if let Some((axis, split_region, is_picked)) = picked_split { let highlight = if is_picked { theme.picked_split(style) -- cgit From 069371c86b2eef62277462810fb5ac1852623bd9 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 19 Oct 2022 13:21:46 -0700 Subject: Use child layout --- native/src/widget/pane_grid.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 12ccbb41..96cf78ef 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -762,7 +762,7 @@ pub fn draw( for ((id, pane), layout) in elements.zip(layout.children()) { match picked_pane { Some((dragging, origin)) if id == dragging => { - render_picked_pane = Some((pane, origin, layout.bounds())); + render_picked_pane = Some((pane, origin, layout)); } _ => { draw_pane( @@ -778,7 +778,9 @@ pub fn draw( } // Render picked pane last - if let Some((pane, origin, bounds)) = render_picked_pane { + if let Some((pane, origin, layout)) = render_picked_pane { + let bounds = layout.bounds(); + renderer.with_translation( cursor_position - Point::new(bounds.x + origin.x, bounds.y + origin.y), -- cgit From f8c363eeacc6c0875834ea1b241de4540f55ba0c Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Thu, 27 Oct 2022 17:40:18 -0700 Subject: Fix drop down not closing when inside scrollable and user clicks outside the scrollable. This is because the scrollable sets -1.0 on cursor.y for any events where cursor is outside it's bounds. I'm not sure why picklist had this logic to stay open on -1.0 / -1.0, any click outside the overlay should close it. --- native/src/widget/pick_list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index c334804e..896f5b35 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -348,9 +348,9 @@ where let state = state(); let event_status = if state.is_open { - // TODO: Encode cursor availability in the type system - state.is_open = - cursor_position.x < 0.0 || cursor_position.y < 0.0; + // Event wasn't processed by overlay, so cursor was clicked either outside it's + // bounds or on the drop-down, either way we close the overlay. + state.is_open = false; event::Status::Captured } else if layout.bounds().contains(cursor_position) { -- cgit From be9cd3a09f4fb027786c634158ddeaca75a169f0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 28 Oct 2022 14:43:18 -0700 Subject: Use `once_cell` instead of `lazy_static` This seems to be generally considered the preferred, idiomatic solution now. This is in the standard library behind a feature flag (apparently now called `std::sync::LazyLock`). --- examples/todos/Cargo.toml | 2 +- examples/todos/src/main.rs | 6 ++---- examples/websocket/Cargo.toml | 2 +- examples/websocket/src/main.rs | 5 ++--- style/Cargo.toml | 4 ++-- style/src/theme/palette.rs | 11 +++++------ 6 files changed, 13 insertions(+), 17 deletions(-) diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 2326ffc6..d9916057 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -7,9 +7,9 @@ publish = false [dependencies] iced = { path = "../..", features = ["async-std", "debug"] } +once_cell = "1.15.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -lazy_static = "1.4" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index bddc0e71..be48ae8c 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -11,12 +11,10 @@ use iced::window; use iced::{Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -lazy_static! { - static ref INPUT_ID: text_input::Id = text_input::Id::unique(); -} +static INPUT_ID: Lazy = Lazy::new(text_input::Id::unique); pub fn main() -> iced::Result { Todos::run(Settings { diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 3981f699..fcb4dd3f 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced = { path = "../..", features = ["tokio", "debug"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } -lazy_static = "1.4" +once_cell = "1.15.0" [dependencies.async-tungstenite] version = "0.16" diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 3902e04c..ff2929da 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -8,6 +8,7 @@ use iced::widget::{ use iced::{ Application, Color, Command, Element, Length, Settings, Subscription, Theme, }; +use once_cell::sync::Lazy; pub fn main() -> iced::Result { WebSocket::run(Settings::default()) @@ -165,6 +166,4 @@ impl Default for State { } } -lazy_static::lazy_static! { - static ref MESSAGE_LOG: scrollable::Id = scrollable::Id::unique(); -} +static MESSAGE_LOG: Lazy = Lazy::new(scrollable::Id::unique); diff --git a/style/Cargo.toml b/style/Cargo.toml index cf9d328b..4a482a4f 100644 --- a/style/Cargo.toml +++ b/style/Cargo.toml @@ -18,5 +18,5 @@ features = ["palette"] [dependencies.palette] version = "0.6" -[dependencies.lazy_static] -version = "1.4" +[dependencies.once_cell] +version = "1.15" diff --git a/style/src/theme/palette.rs b/style/src/theme/palette.rs index 81aa9cc7..4fb5e4c8 100644 --- a/style/src/theme/palette.rs +++ b/style/src/theme/palette.rs @@ -1,6 +1,6 @@ use iced_core::Color; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use palette::{FromColor, Hsl, Mix, RelativeContrast, Srgb}; #[derive(Debug, Clone, Copy, PartialEq)] @@ -66,11 +66,10 @@ pub struct Extended { pub danger: Danger, } -lazy_static! { - pub static ref EXTENDED_LIGHT: Extended = - Extended::generate(Palette::LIGHT); - pub static ref EXTENDED_DARK: Extended = Extended::generate(Palette::DARK); -} +pub static EXTENDED_LIGHT: Lazy = + Lazy::new(|| Extended::generate(Palette::LIGHT)); +pub static EXTENDED_DARK: Lazy = + Lazy::new(|| Extended::generate(Palette::DARK)); impl Extended { pub fn generate(palette: Palette) -> Self { -- cgit From 58d3374229dc500635a608b91b7761dd776ff79e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 29 Oct 2022 04:36:18 +0200 Subject: Keep keyboard modifiers always in sync in `TextInput` --- native/src/widget/text_input.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index c2d25520..a6de90a5 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -717,9 +717,7 @@ where Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { let state = state(); - if state.is_focused { - state.keyboard_modifiers = modifiers; - } + state.keyboard_modifiers = modifiers; } _ => {} } -- cgit From ac6a3cf8eb77df69cbb6538900786faca778cf05 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 29 Oct 2022 04:50:45 +0200 Subject: Reset `is_pasting` for `TextInput` even when unfocused --- native/src/widget/text_input.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index a6de90a5..e5213cbe 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -712,6 +712,8 @@ where } return event::Status::Captured; + } else { + state.is_pasting = None; } } Event::Keyboard(keyboard::Event::ModifiersChanged(modifiers)) => { -- cgit From 0a12590b6f500394194ccd7588af8c6729426d71 Mon Sep 17 00:00:00 2001 From: Wyatt Herkamp Date: Fri, 7 Oct 2022 09:03:41 -0400 Subject: Update `wgpu` to `0.14` and `wgpu_glyph` to `0.18` --- examples/integration_wgpu/src/main.rs | 3 +++ graphics/Cargo.toml | 2 +- graphics/src/window/compositor.rs | 6 +++--- wgpu/Cargo.toml | 6 +++--- wgpu/src/window/compositor.rs | 10 ++++++---- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 69d46c3e..ed03f77a 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -16,6 +16,7 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; +use crate::wgpu::CompositeAlphaMode; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsCast; #[cfg(target_arch = "wasm32")] @@ -119,6 +120,7 @@ pub fn main() { width: physical_size.width, height: physical_size.height, present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: CompositeAlphaMode::Auto, }, ); @@ -213,6 +215,7 @@ pub fn main() { width: size.width, height: size.height, present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: CompositeAlphaMode::Auto }, ); diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml index 49d4d9c6..18fef54f 100644 --- a/graphics/Cargo.toml +++ b/graphics/Cargo.toml @@ -20,7 +20,7 @@ opengl = [] [dependencies] glam = "0.10" -raw-window-handle = "0.4" +raw-window-handle = "0.5" thiserror = "1.0" [dependencies.bytemuck] diff --git a/graphics/src/window/compositor.rs b/graphics/src/window/compositor.rs index 0c4cadcd..52255666 100644 --- a/graphics/src/window/compositor.rs +++ b/graphics/src/window/compositor.rs @@ -2,7 +2,7 @@ //! surfaces. use crate::{Color, Error, Viewport}; -use raw_window_handle::HasRawWindowHandle; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use thiserror::Error; /// A graphics compositor that can draw to windows. @@ -17,7 +17,7 @@ pub trait Compositor: Sized { type Surface; /// Creates a new [`Compositor`]. - fn new( + fn new( settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error>; @@ -25,7 +25,7 @@ pub trait Compositor: Sized { /// Crates a new [`Surface`] for the given window. /// /// [`Surface`]: Self::Surface - fn create_surface( + fn create_surface( &mut self, window: &W, ) -> Self::Surface; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 586f97d3..92ebdcd0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -28,10 +28,10 @@ spirv = ["wgpu/spirv"] webgl = ["wgpu/webgl"] [dependencies] -wgpu = "0.13" -wgpu_glyph = "0.17" +wgpu = "0.14" +wgpu_glyph = "0.18" glyph_brush = "0.7" -raw-window-handle = "0.4" +raw-window-handle = "0.5.0" log = "0.4" guillotiere = "0.6" futures = "0.3" diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index a36d2a87..c34fa735 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -4,9 +4,10 @@ use futures::stream::{self, StreamExt}; use iced_graphics::compositor; use iced_native::futures; -use raw_window_handle::HasRawWindowHandle; +use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; +use wgpu::CompositeAlphaMode; /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] @@ -27,7 +28,7 @@ impl Compositor { /// Requests a new [`Compositor`] with the given [`Settings`]. /// /// Returns `None` if no compatible graphics adapter could be found. - pub async fn request( + pub async fn request( settings: Settings, compatible_window: Option<&W>, ) -> Option { @@ -123,7 +124,7 @@ impl iced_graphics::window::Compositor for Compositor { type Renderer = Renderer; type Surface = wgpu::Surface; - fn new( + fn new( settings: Self::Settings, compatible_window: Option<&W>, ) -> Result<(Self, Self::Renderer), Error> { @@ -138,7 +139,7 @@ impl iced_graphics::window::Compositor for Compositor { Ok((compositor, Renderer::new(backend))) } - fn create_surface( + fn create_surface( &mut self, window: &W, ) -> wgpu::Surface { @@ -162,6 +163,7 @@ impl iced_graphics::window::Compositor for Compositor { present_mode: self.settings.present_mode, width, height, + alpha_mode: CompositeAlphaMode::Auto, }, ); } -- cgit From bea5882c58edac0bd5300d53b31ee4ac295bda25 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 1 Nov 2022 19:17:08 +0100 Subject: Remove inconsistent `wgpu` imports --- examples/integration_wgpu/src/main.rs | 5 ++--- wgpu/src/window/compositor.rs | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index ed03f77a..70f9a48b 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -16,7 +16,6 @@ use winit::{ event_loop::{ControlFlow, EventLoop}, }; -use crate::wgpu::CompositeAlphaMode; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsCast; #[cfg(target_arch = "wasm32")] @@ -120,7 +119,7 @@ pub fn main() { width: physical_size.width, height: physical_size.height, present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: CompositeAlphaMode::Auto, + alpha_mode: wgpu::CompositeAlphaMode::Auto, }, ); @@ -215,7 +214,7 @@ pub fn main() { width: size.width, height: size.height, present_mode: wgpu::PresentMode::AutoVsync, - alpha_mode: CompositeAlphaMode::Auto + alpha_mode: wgpu::CompositeAlphaMode::Auto }, ); diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index c34fa735..6d0c36f6 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -7,7 +7,6 @@ use iced_native::futures; use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; use std::marker::PhantomData; -use wgpu::CompositeAlphaMode; /// A window graphics backend for iced powered by `wgpu`. #[allow(missing_debug_implementations)] @@ -163,7 +162,7 @@ impl iced_graphics::window::Compositor for Compositor { present_mode: self.settings.present_mode, width, height, - alpha_mode: CompositeAlphaMode::Auto, + alpha_mode: wgpu::CompositeAlphaMode::Auto, }, ); } -- cgit From 8f6af6bbfc7a14e9dc71b20ccb9045597bbccbad Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 1 Nov 2022 19:18:47 +0100 Subject: Fix inconsistent version format for `raw-window-handle` dependency --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 92ebdcd0..55eec73f 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -31,7 +31,7 @@ webgl = ["wgpu/webgl"] wgpu = "0.14" wgpu_glyph = "0.18" glyph_brush = "0.7" -raw-window-handle = "0.5.0" +raw-window-handle = "0.5" log = "0.4" guillotiere = "0.6" futures = "0.3" -- cgit From 743a7192b70bc5184aa47e7851098b75b8a1f288 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 1 Nov 2022 19:55:37 +0100 Subject: Remove patch version from `once_cell` dependency --- examples/todos/Cargo.toml | 2 +- examples/websocket/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index d9916057..7ad4d558 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -7,9 +7,9 @@ publish = false [dependencies] iced = { path = "../..", features = ["async-std", "debug"] } -once_cell = "1.15.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +once_cell = "1.15" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index fcb4dd3f..c25f067b 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced = { path = "../..", features = ["tokio", "debug"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } -once_cell = "1.15.0" +once_cell = "1.15" [dependencies.async-tungstenite] version = "0.16" -- cgit From be1729500198895485ac678c425ca75eb3b40151 Mon Sep 17 00:00:00 2001 From: icedrocket <114203630+icedrocket@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:57:55 +0900 Subject: fix: enable `application` feature required in `iced_glutin` --- glutin/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/glutin/Cargo.toml b/glutin/Cargo.toml index d84f9d70..65b16947 100644 --- a/glutin/Cargo.toml +++ b/glutin/Cargo.toml @@ -29,6 +29,7 @@ path = "../native" [dependencies.iced_winit] version = "0.4" path = "../winit" +features = ["application"] [dependencies.iced_graphics] version = "0.3" -- cgit From 71381418116afc1f9c40c8faf2cb1e53072a1cfa Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Thu, 4 Aug 2022 21:25:36 -0700 Subject: feat: implement `Cached` widget --- lazy/src/cached.rs | 344 +++++++++++++++++++++++++++++++++++++++++++++++++++++ lazy/src/lib.rs | 2 + 2 files changed, 346 insertions(+) create mode 100644 lazy/src/cached.rs diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs new file mode 100644 index 00000000..c046179f --- /dev/null +++ b/lazy/src/cached.rs @@ -0,0 +1,344 @@ +use iced_native::event; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; +use iced_native::widget::tree::{self, Tree}; +use iced_native::{Element, Widget}; + +use ouroboros::self_referencing; +use std::cell::{Ref, RefCell, RefMut}; +use std::hash::{Hash, Hasher as H}; +use std::marker::PhantomData; +use std::rc::Rc; + +#[allow(missing_debug_implementations)] +pub struct Cached<'a, Message, Renderer, Dependency, View> { + dependency: Dependency, + view: Box View + 'a>, + element: RefCell>>>>, +} + +impl<'a, Message, Renderer, Dependency, View> + Cached<'a, Message, Renderer, Dependency, View> +where + Dependency: Hash + 'a, + View: Into>, +{ + pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self { + Self { + dependency, + view: Box::new(view), + element: RefCell::new(None), + } + } + + fn with_element( + &self, + f: impl FnOnce(Ref>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow()) + } + + fn with_element_mut( + &self, + f: impl FnOnce(RefMut>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow_mut()) + } +} + +struct Internal { + element: Rc>>, + hash: u64, +} + +impl<'a, Message, Renderer, Dependency, View> Widget + for Cached<'a, Message, Renderer, Dependency, View> +where + View: Into> + 'static, + Dependency: Hash + 'a, + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let hash = hasher.finish(); + + let element = Rc::new(RefCell::new((self.view)().into())); + + (*self.element.borrow_mut()) = Some(element.clone()); + + tree::State::new(Internal { element, hash }) + } + + fn children(&self) -> Vec { + vec![Tree::new( + self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )] + } + + fn diff(&self, tree: &mut Tree) { + let current = tree.state.downcast_mut::>(); + + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let new_hash = hasher.finish(); + + if current.hash != new_hash { + current.hash = new_hash; + + let element = (self.view)().into(); + current.element = Rc::new(RefCell::new(element)); + } + + (*self.element.borrow_mut()) = Some(current.element.clone()); + tree.diff_children(std::slice::from_ref( + &self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )); + } + + fn width(&self) -> Length { + self.with_element(|element| element.as_widget().width()) + } + + fn height(&self) -> Length { + self.with_element(|element| element.as_widget().height()) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.with_element(|element| { + element.as_widget().layout(renderer, limits) + }) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.with_element_mut(|mut element| { + element.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_element(|element| { + element.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + }) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.with_element(|element| { + element.as_widget().draw( + &tree.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ) + }) + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + let overlay = OverlayBuilder { + cached: self, + tree: &mut tree.children[0], + types: PhantomData, + element_ref_builder: |cached| cached.element.borrow(), + element_builder: |element_ref| { + element_ref.as_ref().unwrap().borrow() + }, + overlay_builder: |element, tree| { + element.as_widget().overlay(tree, layout, renderer) + }, + } + .build(); + + let has_overlay = overlay.with_overlay(|overlay| { + overlay.as_ref().map(overlay::Element::position) + }); + + has_overlay + .map(|position| overlay::Element::new(position, Box::new(overlay))) + } +} + +#[self_referencing] +struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { + cached: &'a Cached<'b, Message, Renderer, Dependency, View>, + tree: &'a mut Tree, + types: PhantomData<(Message, Dependency, View)>, + + #[borrows(cached)] + #[covariant] + element_ref: + Ref<'this, Option>>>>, + + #[borrows(element_ref)] + #[covariant] + element: Ref<'this, Element<'static, Message, Renderer>>, + + #[borrows(element, mut tree)] + #[covariant] + overlay: Option>, +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + Overlay<'a, 'b, Message, Renderer, Dependency, View> +{ + fn with_overlay_maybe( + &self, + f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + self.borrow_overlay().as_ref().map(f) + } + + fn with_overlay_mut_maybe( + &mut self, + f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) + } +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + overlay::Overlay + for Overlay<'a, 'b, Message, Renderer, Dependency, View> +where + Renderer: iced_native::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + self.with_overlay_maybe(|overlay| { + let vector = position - overlay.position(); + + overlay.layout(renderer, bounds).translate(vector) + }) + .unwrap_or_default() + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + let _ = self.with_overlay_maybe(|overlay| { + overlay.draw(renderer, theme, style, layout, cursor_position); + }); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_overlay_maybe(|overlay| { + overlay.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) + }) + .unwrap_or_default() + } + + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.with_overlay_mut_maybe(|overlay| { + overlay.on_event( + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + .unwrap_or_else(|| iced_native::event::Status::Ignored) + } +} + +impl<'a, Message, Renderer, Dependency, View> + From> + for Element<'a, Message, Renderer> +where + View: Into> + 'static, + Renderer: iced_native::Renderer + 'static, + Message: 'static, + Dependency: Hash + 'a, +{ + fn from(cached: Cached<'a, Message, Renderer, Dependency, View>) -> Self { + Self::new(cached) + } +} diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index 3827746c..c01b439b 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -17,9 +17,11 @@ clippy::type_complexity )] #![cfg_attr(docsrs, feature(doc_cfg))] +pub mod cached; pub mod component; pub mod responsive; +pub use cached::Cached; pub use component::Component; pub use responsive::Responsive; -- cgit From 50eb9e34b8ea939c263c1f548ef3f228400d4bda Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Fri, 5 Aug 2022 15:40:55 -0700 Subject: add example --- Cargo.toml | 1 + examples/cached/Cargo.toml | 11 ++++ examples/cached/src/main.rs | 141 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 examples/cached/Cargo.toml create mode 100644 examples/cached/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 9c6a435a..d855e82a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "winit", "examples/arc", "examples/bezier_tool", + "examples/cached", "examples/clock", "examples/color_palette", "examples/component", diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml new file mode 100644 index 00000000..21f59886 --- /dev/null +++ b/examples/cached/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "pure_cached" +version = "0.1.0" +authors = ["Nick Senger "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_native = { path = "../../native" } +iced_lazy = { path = "../../lazy" } diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs new file mode 100644 index 00000000..d7787979 --- /dev/null +++ b/examples/cached/src/main.rs @@ -0,0 +1,141 @@ +use iced::widget::{ + button, column, horizontal_rule, horizontal_space, row, scrollable, text, + text_input, +}; +use iced::{Element, Sandbox}; +use iced::{Length, Settings}; +use iced_lazy::Cached; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +#[derive(Hash)] +enum SortOrder { + Ascending, + Descending, +} + +impl std::fmt::Display for SortOrder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} + +struct App { + options: HashSet, + input: String, + sort_order: SortOrder, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + sort_order: SortOrder::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleSortOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleSortOrder => { + self.sort_order = match self.sort_order { + SortOrder::Ascending => SortOrder::Descending, + SortOrder::Descending => SortOrder::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element { + let options = + Cached::new((&self.sort_order, self.options.len()), || { + let mut options = self.options.iter().collect::>(); + options.sort_by(|a, b| match self.sort_order { + SortOrder::Ascending => { + a.to_lowercase().cmp(&b.to_lowercase()) + } + SortOrder::Descending => { + b.to_lowercase().cmp(&a.to_lowercase()) + } + }); + + options.into_iter().fold( + column![horizontal_rule(1)], + |column, option| { + column + .push(row![ + text(option), + horizontal_space(Length::Fill), + button("Delete").on_press( + Message::DeleteOption(option.to_string(),), + ) + ]) + .push(horizontal_rule(1)) + }, + ) + }); + + scrollable( + column![ + button(text(format!( + "Toggle Sort Order ({})", + self.sort_order + ))) + .on_press(Message::ToggleSortOrder), + options, + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + ] + .spacing(20), + ) + .into() + } +} -- cgit From 459d32b98476b05da5e7548c67c28c147b107736 Mon Sep 17 00:00:00 2001 From: Nick Senger Date: Fri, 5 Aug 2022 16:15:19 -0700 Subject: lint --- lazy/src/cached.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs index c046179f..184ec9e7 100644 --- a/lazy/src/cached.rs +++ b/lazy/src/cached.rs @@ -3,8 +3,8 @@ use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; -use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; use iced_native::widget::tree::{self, Tree}; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; use iced_native::{Element, Widget}; use ouroboros::self_referencing; @@ -325,7 +325,7 @@ where shell, ) }) - .unwrap_or_else(|| iced_native::event::Status::Ignored) + .unwrap_or(iced_native::event::Status::Ignored) } } -- cgit From b5d33b0370ba8430bb8dbede7fef377ac2a67667 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:28:54 +0100 Subject: Diff children only when hash differs in `lazy::Cached` --- lazy/src/cached.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs index 184ec9e7..a2a519c0 100644 --- a/lazy/src/cached.rs +++ b/lazy/src/cached.rs @@ -97,12 +97,14 @@ where let element = (self.view)().into(); current.element = Rc::new(RefCell::new(element)); - } - (*self.element.borrow_mut()) = Some(current.element.clone()); - tree.diff_children(std::slice::from_ref( - &self.element.borrow().as_ref().unwrap().borrow().as_widget(), - )); + (*self.element.borrow_mut()) = Some(current.element.clone()); + tree.diff_children(std::slice::from_ref( + &self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )); + } else { + (*self.element.borrow_mut()) = Some(current.element.clone()); + } } fn width(&self) -> Length { -- cgit From 54f9ab7d5f0680bab95a8bdf95b46fb7d06b6ede Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:30:41 +0100 Subject: Implement `Widget::operate` for `lazy::Cached` --- lazy/src/cached.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs index a2a519c0..931184b5 100644 --- a/lazy/src/cached.rs +++ b/lazy/src/cached.rs @@ -4,8 +4,9 @@ use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; use iced_native::widget::tree::{self, Tree}; +use iced_native::widget::{self, Widget}; +use iced_native::Element; use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; -use iced_native::{Element, Widget}; use ouroboros::self_referencing; use std::cell::{Ref, RefCell, RefMut}; @@ -125,6 +126,21 @@ where }) } + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation, + ) { + self.with_element(|element| { + element.as_widget().operate( + &mut tree.children[0], + layout, + operation, + ); + }); + } + fn on_event( &mut self, tree: &mut Tree, -- cgit From 4f83500bb8f522504a7ec0875a6f134eac733175 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:31:04 +0100 Subject: Rename `pure_cached` example to `cached` --- examples/cached/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml index 21f59886..4d4013e6 100644 --- a/examples/cached/Cargo.toml +++ b/examples/cached/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "pure_cached" +name = "cached" version = "0.1.0" authors = ["Nick Senger "] edition = "2021" -- cgit From 1fb84ae5d3732ed51b35fb5419a5ad014e22ca5b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:32:23 +0100 Subject: Remove `iced_native` dependency from `cached` example --- examples/cached/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml index 4d4013e6..2c7edde2 100644 --- a/examples/cached/Cargo.toml +++ b/examples/cached/Cargo.toml @@ -7,5 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } iced_lazy = { path = "../../lazy" } -- cgit From 0e295be8917a543e3641f8c4657db87fed0ce91b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:32:38 +0100 Subject: Move declaration of `SortOrder` in `cached` example --- examples/cached/src/main.rs | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index d7787979..b900ff36 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -12,25 +12,6 @@ pub fn main() -> iced::Result { App::run(Settings::default()) } -#[derive(Hash)] -enum SortOrder { - Ascending, - Descending, -} - -impl std::fmt::Display for SortOrder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Self::Ascending => "Ascending", - Self::Descending => "Descending", - } - ) - } -} - struct App { options: HashSet, input: String, @@ -139,3 +120,22 @@ impl Sandbox for App { .into() } } + +#[derive(Debug, Hash)] +enum SortOrder { + Ascending, + Descending, +} + +impl std::fmt::Display for SortOrder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} -- cgit From 0478df9fd61c67255b0ea213aa83f56f5698ae7d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:35:05 +0100 Subject: Add `padding` to main `column` in `cached` example --- examples/cached/src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index b900ff36..85a4a4f0 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -115,7 +115,8 @@ impl Sandbox for App { ) .on_submit(Message::AddOption(self.input.clone())), ] - .spacing(20), + .spacing(20) + .padding(20), ) .into() } -- cgit From 1687d11389fa8ddfb8d2d7cda64cc6b5c4aa7f9c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:35:17 +0100 Subject: Increase default `padding` of `TextInput` --- native/src/widget/text_input.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index e5213cbe..54a6aaf8 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -92,7 +92,7 @@ where is_secure: false, font: Default::default(), width: Length::Fill, - padding: Padding::ZERO, + padding: Padding::new(5), size: None, on_change: Box::new(on_change), on_paste: None, -- cgit From adf541d4325df22d577342f870f0f95fa357797a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:40:51 +0100 Subject: Improve layout of `cached` example --- examples/cached/src/main.rs | 59 ++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index 85a4a4f0..39364dc9 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -1,9 +1,8 @@ +use iced::theme; use iced::widget::{ - button, column, horizontal_rule, horizontal_space, row, scrollable, text, - text_input, + button, column, horizontal_space, row, scrollable, text, text_input, }; -use iced::{Element, Sandbox}; -use iced::{Length, Settings}; +use iced::{Element, Length, Sandbox, Settings}; use iced_lazy::Cached; use std::collections::HashSet; @@ -74,7 +73,8 @@ impl Sandbox for App { fn view(&self) -> Element { let options = Cached::new((&self.sort_order, self.options.len()), || { - let mut options = self.options.iter().collect::>(); + let mut options: Vec<_> = self.options.iter().collect(); + options.sort_by(|a, b| match self.sort_order { SortOrder::Ascending => { a.to_lowercase().cmp(&b.to_lowercase()) @@ -84,40 +84,45 @@ impl Sandbox for App { } }); - options.into_iter().fold( - column![horizontal_rule(1)], - |column, option| { - column - .push(row![ + column( + options + .into_iter() + .map(|option| { + row![ text(option), horizontal_space(Length::Fill), - button("Delete").on_press( - Message::DeleteOption(option.to_string(),), - ) - ]) - .push(horizontal_rule(1)) - }, + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), ) + .spacing(10) }); - scrollable( - column![ - button(text(format!( - "Toggle Sort Order ({})", - self.sort_order - ))) - .on_press(Message::ToggleSortOrder), - options, + column![ + scrollable(options).height(Length::Fill), + row![ text_input( "Add a new option", &self.input, Message::InputChanged, ) .on_submit(Message::AddOption(self.input.clone())), + button(text(format!( + "Toggle Sort Order ({})", + self.sort_order + ))) + .on_press(Message::ToggleSortOrder) ] - .spacing(20) - .padding(20), - ) + .spacing(10) + ] + .spacing(20) + .padding(20) .into() } } -- cgit From 1cdc1fcd0669bfea096237c07b32742c1a3f2158 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:46:31 +0100 Subject: Rename `iced_lazy::Cached` to `Lazy` :tada: --- examples/cached/src/main.rs | 59 ++++---- lazy/src/cached.rs | 362 -------------------------------------------- lazy/src/lazy.rs | 362 ++++++++++++++++++++++++++++++++++++++++++++ lazy/src/lib.rs | 17 ++- 4 files changed, 405 insertions(+), 395 deletions(-) delete mode 100644 lazy/src/cached.rs create mode 100644 lazy/src/lazy.rs diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index 39364dc9..7c8b06f0 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -3,7 +3,7 @@ use iced::widget::{ button, column, horizontal_space, row, scrollable, text, text_input, }; use iced::{Element, Length, Sandbox, Settings}; -use iced_lazy::Cached; +use iced_lazy::lazy; use std::collections::HashSet; @@ -71,39 +71,36 @@ impl Sandbox for App { } fn view(&self) -> Element { - let options = - Cached::new((&self.sort_order, self.options.len()), || { - let mut options: Vec<_> = self.options.iter().collect(); + let options = lazy((&self.sort_order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); - options.sort_by(|a, b| match self.sort_order { - SortOrder::Ascending => { - a.to_lowercase().cmp(&b.to_lowercase()) - } - SortOrder::Descending => { - b.to_lowercase().cmp(&a.to_lowercase()) - } - }); - - column( - options - .into_iter() - .map(|option| { - row![ - text(option), - horizontal_space(Length::Fill), - button("Delete") - .on_press(Message::DeleteOption( - option.to_string(), - ),) - .style(theme::Button::Destructive) - ] - .into() - }) - .collect(), - ) - .spacing(10) + options.sort_by(|a, b| match self.sort_order { + SortOrder::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + SortOrder::Descending => { + b.to_lowercase().cmp(&a.to_lowercase()) + } }); + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + column![ scrollable(options).height(Length::Fill), row![ diff --git a/lazy/src/cached.rs b/lazy/src/cached.rs deleted file mode 100644 index 931184b5..00000000 --- a/lazy/src/cached.rs +++ /dev/null @@ -1,362 +0,0 @@ -use iced_native::event; -use iced_native::layout::{self, Layout}; -use iced_native::mouse; -use iced_native::overlay; -use iced_native::renderer; -use iced_native::widget::tree::{self, Tree}; -use iced_native::widget::{self, Widget}; -use iced_native::Element; -use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; - -use ouroboros::self_referencing; -use std::cell::{Ref, RefCell, RefMut}; -use std::hash::{Hash, Hasher as H}; -use std::marker::PhantomData; -use std::rc::Rc; - -#[allow(missing_debug_implementations)] -pub struct Cached<'a, Message, Renderer, Dependency, View> { - dependency: Dependency, - view: Box View + 'a>, - element: RefCell>>>>, -} - -impl<'a, Message, Renderer, Dependency, View> - Cached<'a, Message, Renderer, Dependency, View> -where - Dependency: Hash + 'a, - View: Into>, -{ - pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self { - Self { - dependency, - view: Box::new(view), - element: RefCell::new(None), - } - } - - fn with_element( - &self, - f: impl FnOnce(Ref>) -> T, - ) -> T { - f(self.element.borrow().as_ref().unwrap().borrow()) - } - - fn with_element_mut( - &self, - f: impl FnOnce(RefMut>) -> T, - ) -> T { - f(self.element.borrow().as_ref().unwrap().borrow_mut()) - } -} - -struct Internal { - element: Rc>>, - hash: u64, -} - -impl<'a, Message, Renderer, Dependency, View> Widget - for Cached<'a, Message, Renderer, Dependency, View> -where - View: Into> + 'static, - Dependency: Hash + 'a, - Message: 'static, - Renderer: iced_native::Renderer + 'static, -{ - fn tag(&self) -> tree::Tag { - struct Tag(T); - tree::Tag::of::>() - } - - fn state(&self) -> tree::State { - let mut hasher = Hasher::default(); - self.dependency.hash(&mut hasher); - let hash = hasher.finish(); - - let element = Rc::new(RefCell::new((self.view)().into())); - - (*self.element.borrow_mut()) = Some(element.clone()); - - tree::State::new(Internal { element, hash }) - } - - fn children(&self) -> Vec { - vec![Tree::new( - self.element.borrow().as_ref().unwrap().borrow().as_widget(), - )] - } - - fn diff(&self, tree: &mut Tree) { - let current = tree.state.downcast_mut::>(); - - let mut hasher = Hasher::default(); - self.dependency.hash(&mut hasher); - let new_hash = hasher.finish(); - - if current.hash != new_hash { - current.hash = new_hash; - - let element = (self.view)().into(); - current.element = Rc::new(RefCell::new(element)); - - (*self.element.borrow_mut()) = Some(current.element.clone()); - tree.diff_children(std::slice::from_ref( - &self.element.borrow().as_ref().unwrap().borrow().as_widget(), - )); - } else { - (*self.element.borrow_mut()) = Some(current.element.clone()); - } - } - - fn width(&self) -> Length { - self.with_element(|element| element.as_widget().width()) - } - - fn height(&self) -> Length { - self.with_element(|element| element.as_widget().height()) - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - self.with_element(|element| { - element.as_widget().layout(renderer, limits) - }) - } - - fn operate( - &self, - tree: &mut Tree, - layout: Layout<'_>, - operation: &mut dyn widget::Operation, - ) { - self.with_element(|element| { - element.as_widget().operate( - &mut tree.children[0], - layout, - operation, - ); - }); - } - - fn on_event( - &mut self, - tree: &mut Tree, - event: iced_native::Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.with_element_mut(|mut element| { - element.as_widget_mut().on_event( - &mut tree.children[0], - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - } - - fn mouse_interaction( - &self, - tree: &Tree, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.with_element(|element| { - element.as_widget().mouse_interaction( - &tree.children[0], - layout, - cursor_position, - viewport, - renderer, - ) - }) - } - - fn draw( - &self, - tree: &Tree, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - self.with_element(|element| { - element.as_widget().draw( - &tree.children[0], - renderer, - theme, - style, - layout, - cursor_position, - viewport, - ) - }) - } - - fn overlay<'b>( - &'b self, - tree: &'b mut Tree, - layout: Layout<'_>, - renderer: &Renderer, - ) -> Option> { - let overlay = OverlayBuilder { - cached: self, - tree: &mut tree.children[0], - types: PhantomData, - element_ref_builder: |cached| cached.element.borrow(), - element_builder: |element_ref| { - element_ref.as_ref().unwrap().borrow() - }, - overlay_builder: |element, tree| { - element.as_widget().overlay(tree, layout, renderer) - }, - } - .build(); - - let has_overlay = overlay.with_overlay(|overlay| { - overlay.as_ref().map(overlay::Element::position) - }); - - has_overlay - .map(|position| overlay::Element::new(position, Box::new(overlay))) - } -} - -#[self_referencing] -struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { - cached: &'a Cached<'b, Message, Renderer, Dependency, View>, - tree: &'a mut Tree, - types: PhantomData<(Message, Dependency, View)>, - - #[borrows(cached)] - #[covariant] - element_ref: - Ref<'this, Option>>>>, - - #[borrows(element_ref)] - #[covariant] - element: Ref<'this, Element<'static, Message, Renderer>>, - - #[borrows(element, mut tree)] - #[covariant] - overlay: Option>, -} - -impl<'a, 'b, Message, Renderer, Dependency, View> - Overlay<'a, 'b, Message, Renderer, Dependency, View> -{ - fn with_overlay_maybe( - &self, - f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, - ) -> Option { - self.borrow_overlay().as_ref().map(f) - } - - fn with_overlay_mut_maybe( - &mut self, - f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, - ) -> Option { - self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) - } -} - -impl<'a, 'b, Message, Renderer, Dependency, View> - overlay::Overlay - for Overlay<'a, 'b, Message, Renderer, Dependency, View> -where - Renderer: iced_native::Renderer, -{ - fn layout( - &self, - renderer: &Renderer, - bounds: Size, - position: Point, - ) -> layout::Node { - self.with_overlay_maybe(|overlay| { - let vector = position - overlay.position(); - - overlay.layout(renderer, bounds).translate(vector) - }) - .unwrap_or_default() - } - - fn draw( - &self, - renderer: &mut Renderer, - theme: &Renderer::Theme, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - ) { - let _ = self.with_overlay_maybe(|overlay| { - overlay.draw(renderer, theme, style, layout, cursor_position); - }); - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - self.with_overlay_maybe(|overlay| { - overlay.mouse_interaction( - layout, - cursor_position, - viewport, - renderer, - ) - }) - .unwrap_or_default() - } - - fn on_event( - &mut self, - event: iced_native::Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.with_overlay_mut_maybe(|overlay| { - overlay.on_event( - event, - layout, - cursor_position, - renderer, - clipboard, - shell, - ) - }) - .unwrap_or(iced_native::event::Status::Ignored) - } -} - -impl<'a, Message, Renderer, Dependency, View> - From> - for Element<'a, Message, Renderer> -where - View: Into> + 'static, - Renderer: iced_native::Renderer + 'static, - Message: 'static, - Dependency: Hash + 'a, -{ - fn from(cached: Cached<'a, Message, Renderer, Dependency, View>) -> Self { - Self::new(cached) - } -} diff --git a/lazy/src/lazy.rs b/lazy/src/lazy.rs new file mode 100644 index 00000000..d61cc77e --- /dev/null +++ b/lazy/src/lazy.rs @@ -0,0 +1,362 @@ +use iced_native::event; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::widget::tree::{self, Tree}; +use iced_native::widget::{self, Widget}; +use iced_native::Element; +use iced_native::{Clipboard, Hasher, Length, Point, Rectangle, Shell, Size}; + +use ouroboros::self_referencing; +use std::cell::{Ref, RefCell, RefMut}; +use std::hash::{Hash, Hasher as H}; +use std::marker::PhantomData; +use std::rc::Rc; + +#[allow(missing_debug_implementations)] +pub struct Lazy<'a, Message, Renderer, Dependency, View> { + dependency: Dependency, + view: Box View + 'a>, + element: RefCell>>>>, +} + +impl<'a, Message, Renderer, Dependency, View> + Lazy<'a, Message, Renderer, Dependency, View> +where + Dependency: Hash + 'a, + View: Into>, +{ + pub fn new(dependency: Dependency, view: impl Fn() -> View + 'a) -> Self { + Self { + dependency, + view: Box::new(view), + element: RefCell::new(None), + } + } + + fn with_element( + &self, + f: impl FnOnce(Ref>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow()) + } + + fn with_element_mut( + &self, + f: impl FnOnce(RefMut>) -> T, + ) -> T { + f(self.element.borrow().as_ref().unwrap().borrow_mut()) + } +} + +struct Internal { + element: Rc>>, + hash: u64, +} + +impl<'a, Message, Renderer, Dependency, View> Widget + for Lazy<'a, Message, Renderer, Dependency, View> +where + View: Into> + 'static, + Dependency: Hash + 'a, + Message: 'static, + Renderer: iced_native::Renderer + 'static, +{ + fn tag(&self) -> tree::Tag { + struct Tag(T); + tree::Tag::of::>() + } + + fn state(&self) -> tree::State { + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let hash = hasher.finish(); + + let element = Rc::new(RefCell::new((self.view)().into())); + + (*self.element.borrow_mut()) = Some(element.clone()); + + tree::State::new(Internal { element, hash }) + } + + fn children(&self) -> Vec { + vec![Tree::new( + self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )] + } + + fn diff(&self, tree: &mut Tree) { + let current = tree.state.downcast_mut::>(); + + let mut hasher = Hasher::default(); + self.dependency.hash(&mut hasher); + let new_hash = hasher.finish(); + + if current.hash != new_hash { + current.hash = new_hash; + + let element = (self.view)().into(); + current.element = Rc::new(RefCell::new(element)); + + (*self.element.borrow_mut()) = Some(current.element.clone()); + tree.diff_children(std::slice::from_ref( + &self.element.borrow().as_ref().unwrap().borrow().as_widget(), + )); + } else { + (*self.element.borrow_mut()) = Some(current.element.clone()); + } + } + + fn width(&self) -> Length { + self.with_element(|element| element.as_widget().width()) + } + + fn height(&self) -> Length { + self.with_element(|element| element.as_widget().height()) + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + self.with_element(|element| { + element.as_widget().layout(renderer, limits) + }) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + operation: &mut dyn widget::Operation, + ) { + self.with_element(|element| { + element.as_widget().operate( + &mut tree.children[0], + layout, + operation, + ); + }); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.with_element_mut(|mut element| { + element.as_widget_mut().on_event( + &mut tree.children[0], + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_element(|element| { + element.as_widget().mouse_interaction( + &tree.children[0], + layout, + cursor_position, + viewport, + renderer, + ) + }) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + self.with_element(|element| { + element.as_widget().draw( + &tree.children[0], + renderer, + theme, + style, + layout, + cursor_position, + viewport, + ) + }) + } + + fn overlay<'b>( + &'b self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + let overlay = OverlayBuilder { + cached: self, + tree: &mut tree.children[0], + types: PhantomData, + element_ref_builder: |cached| cached.element.borrow(), + element_builder: |element_ref| { + element_ref.as_ref().unwrap().borrow() + }, + overlay_builder: |element, tree| { + element.as_widget().overlay(tree, layout, renderer) + }, + } + .build(); + + let has_overlay = overlay.with_overlay(|overlay| { + overlay.as_ref().map(overlay::Element::position) + }); + + has_overlay + .map(|position| overlay::Element::new(position, Box::new(overlay))) + } +} + +#[self_referencing] +struct Overlay<'a, 'b, Message, Renderer, Dependency, View> { + cached: &'a Lazy<'b, Message, Renderer, Dependency, View>, + tree: &'a mut Tree, + types: PhantomData<(Message, Dependency, View)>, + + #[borrows(cached)] + #[covariant] + element_ref: + Ref<'this, Option>>>>, + + #[borrows(element_ref)] + #[covariant] + element: Ref<'this, Element<'static, Message, Renderer>>, + + #[borrows(element, mut tree)] + #[covariant] + overlay: Option>, +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + Overlay<'a, 'b, Message, Renderer, Dependency, View> +{ + fn with_overlay_maybe( + &self, + f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + self.borrow_overlay().as_ref().map(f) + } + + fn with_overlay_mut_maybe( + &mut self, + f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + self.with_overlay_mut(|overlay| overlay.as_mut().map(f)) + } +} + +impl<'a, 'b, Message, Renderer, Dependency, View> + overlay::Overlay + for Overlay<'a, 'b, Message, Renderer, Dependency, View> +where + Renderer: iced_native::Renderer, +{ + fn layout( + &self, + renderer: &Renderer, + bounds: Size, + position: Point, + ) -> layout::Node { + self.with_overlay_maybe(|overlay| { + let vector = position - overlay.position(); + + overlay.layout(renderer, bounds).translate(vector) + }) + .unwrap_or_default() + } + + fn draw( + &self, + renderer: &mut Renderer, + theme: &Renderer::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + let _ = self.with_overlay_maybe(|overlay| { + overlay.draw(renderer, theme, style, layout, cursor_position); + }); + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.with_overlay_maybe(|overlay| { + overlay.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) + }) + .unwrap_or_default() + } + + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.with_overlay_mut_maybe(|overlay| { + overlay.on_event( + event, + layout, + cursor_position, + renderer, + clipboard, + shell, + ) + }) + .unwrap_or(iced_native::event::Status::Ignored) + } +} + +impl<'a, Message, Renderer, Dependency, View> + From> + for Element<'a, Message, Renderer> +where + View: Into> + 'static, + Renderer: iced_native::Renderer + 'static, + Message: 'static, + Dependency: Hash + 'a, +{ + fn from(lazy: Lazy<'a, Message, Renderer, Dependency, View>) -> Self { + Self::new(lazy) + } +} diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index c01b439b..f49fe4b6 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -17,17 +17,30 @@ clippy::type_complexity )] #![cfg_attr(docsrs, feature(doc_cfg))] -pub mod cached; +mod lazy; + pub mod component; pub mod responsive; -pub use cached::Cached; pub use component::Component; +pub use lazy::Lazy; pub use responsive::Responsive; mod cache; use iced_native::{Element, Size}; +use std::hash::Hash; + +pub fn lazy<'a, Message, Renderer, Dependency, View>( + dependency: Dependency, + view: impl Fn() -> View + 'a, +) -> Lazy<'a, Message, Renderer, Dependency, View> +where + Dependency: Hash + 'a, + View: Into>, +{ + Lazy::new(dependency, view) +} /// Turns an implementor of [`Component`] into an [`Element`] that can be /// embedded in any application. -- cgit From 6efda2457e7b80e9d3d145ceb9910bfbb5af9994 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:47:43 +0100 Subject: Rename `SortOrder` to `Order` in `cached` example --- examples/cached/src/main.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs index 7c8b06f0..8845b874 100644 --- a/examples/cached/src/main.rs +++ b/examples/cached/src/main.rs @@ -14,7 +14,7 @@ pub fn main() -> iced::Result { struct App { options: HashSet, input: String, - sort_order: SortOrder, + order: Order, } impl Default for App { @@ -25,7 +25,7 @@ impl Default for App { .map(ToString::to_string) .collect(), input: Default::default(), - sort_order: SortOrder::Ascending, + order: Order::Ascending, } } } @@ -33,7 +33,7 @@ impl Default for App { #[derive(Debug, Clone)] enum Message { InputChanged(String), - ToggleSortOrder, + ToggleOrder, DeleteOption(String), AddOption(String), } @@ -54,10 +54,10 @@ impl Sandbox for App { Message::InputChanged(input) => { self.input = input; } - Message::ToggleSortOrder => { - self.sort_order = match self.sort_order { - SortOrder::Ascending => SortOrder::Descending, - SortOrder::Descending => SortOrder::Ascending, + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, } } Message::AddOption(option) => { @@ -71,14 +71,12 @@ impl Sandbox for App { } fn view(&self) -> Element { - let options = lazy((&self.sort_order, self.options.len()), || { + let options = lazy((&self.order, self.options.len()), || { let mut options: Vec<_> = self.options.iter().collect(); - options.sort_by(|a, b| match self.sort_order { - SortOrder::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), - SortOrder::Descending => { - b.to_lowercase().cmp(&a.to_lowercase()) - } + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), }); column( @@ -110,11 +108,8 @@ impl Sandbox for App { Message::InputChanged, ) .on_submit(Message::AddOption(self.input.clone())), - button(text(format!( - "Toggle Sort Order ({})", - self.sort_order - ))) - .on_press(Message::ToggleSortOrder) + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) ] .spacing(10) ] @@ -125,12 +120,12 @@ impl Sandbox for App { } #[derive(Debug, Hash)] -enum SortOrder { +enum Order { Ascending, Descending, } -impl std::fmt::Display for SortOrder { +impl std::fmt::Display for Order { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, -- cgit From 415978b80771fdd065b65e115d1dcc6aaa9d792c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 3 Nov 2022 02:55:22 +0100 Subject: Rename `cached` example to `lazy` --- Cargo.toml | 2 +- examples/lazy/Cargo.toml | 10 ++++ examples/lazy/src/main.rs | 139 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 examples/lazy/Cargo.toml create mode 100644 examples/lazy/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index d855e82a..f9b441a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,6 @@ members = [ "winit", "examples/arc", "examples/bezier_tool", - "examples/cached", "examples/clock", "examples/color_palette", "examples/component", @@ -73,6 +72,7 @@ members = [ "examples/geometry", "examples/integration_opengl", "examples/integration_wgpu", + "examples/lazy", "examples/multitouch", "examples/pane_grid", "examples/pick_list", diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml new file mode 100644 index 00000000..79255c25 --- /dev/null +++ b/examples/lazy/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "lazy" +version = "0.1.0" +authors = ["Nick Senger "] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_lazy = { path = "../../lazy" } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs new file mode 100644 index 00000000..8845b874 --- /dev/null +++ b/examples/lazy/src/main.rs @@ -0,0 +1,139 @@ +use iced::theme; +use iced::widget::{ + button, column, horizontal_space, row, scrollable, text, text_input, +}; +use iced::{Element, Length, Sandbox, Settings}; +use iced_lazy::lazy; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +struct App { + options: HashSet, + input: String, + order: Order, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + order: Order::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element { + let options = lazy((&self.order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); + + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), + }); + + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + + column![ + scrollable(options).height(Length::Fill), + row![ + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) + ] + .spacing(10) + ] + .spacing(20) + .padding(20) + .into() + } +} + +#[derive(Debug, Hash)] +enum Order { + Ascending, + Descending, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} -- cgit