From 90c20ac46b72b6d8f735f7efd283b9d1dfecfb9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 11 Jan 2022 10:47:56 +0700 Subject: [PATCH 01/10] Draft `Responsive` widget --- examples/pane_grid/src/main.rs | 47 +++++--- glow/src/lib.rs | 2 +- glow/src/widget.rs | 3 + glow/src/widget/responsive.rs | 6 + native/src/lib.rs | 2 +- native/src/widget.rs | 3 + native/src/widget/responsive.rs | 188 ++++++++++++++++++++++++++++++++ src/widget.rs | 9 +- wgpu/src/lib.rs | 2 +- wgpu/src/widget.rs | 3 + wgpu/src/widget/responsive.rs | 6 + 11 files changed, 248 insertions(+), 23 deletions(-) create mode 100644 glow/src/widget/responsive.rs create mode 100644 native/src/widget/responsive.rs create mode 100644 wgpu/src/widget/responsive.rs diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 8225e9e726..59e5f3f9ca 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,7 +1,13 @@ +use iced::alignment::{self, Alignment}; +use iced::button::{self, Button}; +use iced::executor; +use iced::keyboard; +use iced::pane_grid::{self, PaneGrid}; +use iced::responsive::{self, Responsive}; +use iced::scrollable::{self, Scrollable}; use iced::{ - alignment, button, executor, keyboard, pane_grid, scrollable, Alignment, - Application, Button, Color, Column, Command, Container, Element, Length, - PaneGrid, Row, Scrollable, Settings, Subscription, Text, + Application, Color, Column, Command, Container, Element, Length, Row, + Settings, Size, Subscription, Text, }; use iced_native::{event, subscription, Event}; @@ -154,17 +160,24 @@ impl Application for Example { let pane_grid = PaneGrid::new(&mut self.panes, |id, pane| { let is_focused = focus == Some(id); - let text = if pane.is_pinned { "Unpin" } else { "Pin" }; - let pin_button = - Button::new(&mut pane.pin_button, Text::new(text).size(14)) - .on_press(Message::TogglePin(id)) - .style(style::Button::Pin) - .padding(3); + let Pane { + responsive, + pin_button, + is_pinned, + content, + .. + } = pane; + + let text = if *is_pinned { "Unpin" } else { "Pin" }; + let pin_button = Button::new(pin_button, Text::new(text).size(14)) + .on_press(Message::TogglePin(id)) + .style(style::Button::Pin) + .padding(3); let title = Row::with_children(vec![ pin_button.into(), Text::new("Pane").into(), - Text::new(pane.content.id.to_string()) + Text::new(content.id.to_string()) .color(if is_focused { PANE_ID_COLOR_FOCUSED } else { @@ -175,7 +188,7 @@ impl Application for Example { .spacing(5); let title_bar = pane_grid::TitleBar::new(title) - .controls(pane.controls.view(id, total_panes, pane.is_pinned)) + .controls(pane.controls.view(id, total_panes, *is_pinned)) .padding(10) .style(if is_focused { style::TitleBar::Focused @@ -183,11 +196,9 @@ impl Application for Example { style::TitleBar::Active }); - pane_grid::Content::new(pane.content.view( - id, - total_panes, - pane.is_pinned, - )) + pane_grid::Content::new(Responsive::new(responsive, move |size| { + content.view(id, total_panes, *is_pinned, size) + })) .title_bar(title_bar) .style(if is_focused { style::Pane::Focused @@ -242,6 +253,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { } struct Pane { + pub responsive: responsive::State, pub is_pinned: bool, pub pin_button: button::State, pub content: Content, @@ -263,6 +275,7 @@ struct Controls { impl Pane { fn new(id: usize) -> Self { Self { + responsive: responsive::State::new(), is_pinned: false, pin_button: button::State::new(), content: Content::new(id), @@ -286,6 +299,7 @@ impl Content { pane: pane_grid::Pane, total_panes: usize, is_pinned: bool, + size: Size, ) -> Element { let Content { scroll, @@ -338,6 +352,7 @@ impl Content { .width(Length::Fill) .spacing(10) .align_items(Alignment::Center) + .push(Text::new(format!("{}x{}", size.width, size.height)).size(18)) .push(controls); Container::new(content) diff --git a/glow/src/lib.rs b/glow/src/lib.rs index 362933d4ff..7fde122cc0 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -7,7 +7,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(rust_2018_idioms)] diff --git a/glow/src/widget.rs b/glow/src/widget.rs index ee2810f96a..85ef2f02ce 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -16,6 +16,7 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; +pub mod responsive; pub mod rule; pub mod scrollable; pub mod slider; @@ -38,6 +39,8 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] +pub use responsive::Responsive; +#[doc(no_inline)] pub use rule::Rule; #[doc(no_inline)] pub use scrollable::Scrollable; diff --git a/glow/src/widget/responsive.rs b/glow/src/widget/responsive.rs new file mode 100644 index 0000000000..1a64ac6284 --- /dev/null +++ b/glow/src/widget/responsive.rs @@ -0,0 +1,6 @@ +use crate::Renderer; + +pub use iced_native::widget::responsive::State; + +pub type Responsive<'a, Message> = + iced_native::widget::Responsive<'a, Message, Renderer>; diff --git a/native/src/lib.rs b/native/src/lib.rs index 4753bba2c6..4211995ff6 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -31,7 +31,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/native/src/widget.rs b/native/src/widget.rs index caa3f0bc15..d1619bf3f6 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -20,6 +20,7 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; +pub mod responsive; pub mod row; pub mod rule; pub mod scrollable; @@ -50,6 +51,8 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] +pub use responsive::Responsive; +#[doc(no_inline)] pub use row::Row; #[doc(no_inline)] pub use rule::Rule; diff --git a/native/src/widget/responsive.rs b/native/src/widget/responsive.rs new file mode 100644 index 0000000000..0cb85d451f --- /dev/null +++ b/native/src/widget/responsive.rs @@ -0,0 +1,188 @@ +use crate::event::{self, Event}; +use crate::layout::{self, Layout}; +use crate::renderer; +use crate::{ + Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, +}; + +use std::cell::RefCell; +use std::hash::Hasher as _; + +#[derive(Debug, Clone, Default)] +pub struct State { + last_size: Option, + last_layout: layout::Node, + last_layout_hash: u64, +} + +impl State { + pub fn new() -> State { + State::default() + } +} + +#[allow(missing_debug_implementations)] +pub struct Responsive<'a, Message, Renderer>( + RefCell>, +); + +impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> { + pub fn new( + state: &'a mut State, + view: impl FnOnce(Size) -> Element<'a, Message, Renderer> + 'a, + ) -> Self { + Self(RefCell::new(Internal { + state, + content: Content::Pending(Some(Box::new(view))), + })) + } +} + +impl<'a, Message, Renderer> Widget + for Responsive<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Fill + } + + fn hash_layout(&self, _hasher: &mut Hasher) {} + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let size = limits.max(); + + self.0.borrow_mut().state.last_size = Some(size); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + use std::ops::DerefMut; + + let mut internal = self.0.borrow_mut(); + + let Internal { content, state } = internal.deref_mut(); + + let content = content.resolve(state, renderer); + + let content_layout = Layout::with_offset( + layout.position() - Point::ORIGIN, + &state.last_layout, + ); + + content.on_event( + event, + content_layout, + cursor_position, + renderer, + clipboard, + shell, + ) + } + + fn draw( + &self, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + use std::ops::DerefMut; + + let mut internal = self.0.borrow_mut(); + + let Internal { content, state } = internal.deref_mut(); + + let content = content.resolve(state, renderer); + + let content_layout = Layout::with_offset( + layout.position() - Point::ORIGIN, + &state.last_layout, + ); + + content.draw(renderer, style, content_layout, cursor_position, viewport) + } +} + +struct Internal<'a, Message, Renderer> { + state: &'a mut State, + content: Content<'a, Message, Renderer>, +} + +enum Content<'a, Message, Renderer> { + Pending( + Option Element<'a, Message, Renderer> + 'a>>, + ), + Ready(Element<'a, Message, Renderer>), +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ + fn resolve( + &mut self, + state: &mut State, + renderer: &Renderer, + ) -> &mut Element<'a, Message, Renderer> { + match self { + Content::Ready(element) => element, + Content::Pending(view) => { + let element = + view.take().unwrap()(state.last_size.unwrap_or(Size::ZERO)); + + let new_layout_hash = { + let mut hasher = Hasher::default(); + element.hash_layout(&mut hasher); + + hasher.finish() + }; + + if new_layout_hash != state.last_layout_hash { + state.last_layout = element.layout( + renderer, + &layout::Limits::new( + Size::ZERO, + state.last_size.unwrap_or(Size::ZERO), + ), + ); + + state.last_layout_hash = new_layout_hash; + } + + *self = Content::Ready(element); + + self.resolve(state, renderer) + } + } + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: crate::Renderer + 'a, + Message: 'a, +{ + fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { + Self::new(responsive) + } +} diff --git a/src/widget.rs b/src/widget.rs index 0f0b0325f7..c2790e4a3f 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,8 +17,8 @@ mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, - Space, Text, + responsive, rule, scrollable, slider, text_input, toggler, tooltip, + Column, Row, Space, Text, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] @@ -54,8 +54,9 @@ mod platform { pub use { button::Button, checkbox::Checkbox, container::Container, image::Image, pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, - radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, - svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip, + radio::Radio, responsive::Responsive, rule::Rule, + scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, toggler::Toggler, tooltip::Tooltip, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index fb03854b12..69763e66ad 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -23,7 +23,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 99ae0ac21c..ac8653d4cb 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -16,6 +16,7 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; +pub mod responsive; pub mod rule; pub mod scrollable; pub mod slider; @@ -38,6 +39,8 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] +pub use responsive::Responsive; +#[doc(no_inline)] pub use rule::Rule; #[doc(no_inline)] pub use scrollable::Scrollable; diff --git a/wgpu/src/widget/responsive.rs b/wgpu/src/widget/responsive.rs new file mode 100644 index 0000000000..1a64ac6284 --- /dev/null +++ b/wgpu/src/widget/responsive.rs @@ -0,0 +1,6 @@ +use crate::Renderer; + +pub use iced_native::widget::responsive::State; + +pub type Responsive<'a, Message> = + iced_native::widget::Responsive<'a, Message, Renderer>; From 6ab4611a6eec9c4bb4ca1ff1bb580bb7edf49add Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 11 Jan 2022 13:47:43 +0700 Subject: [PATCH 02/10] Invalidate widget tree from `Responsive` widget ... by introducing a new `invalidate_widgets` method to `Shell` --- glutin/src/application.rs | 14 +++++++--- lazy/src/component.rs | 4 +-- native/src/lib.rs | 4 +-- native/src/program/state.rs | 12 ++++----- native/src/shell.rs | 20 ++++++++++++++- native/src/user_interface.rs | 45 ++++++++++++++++++++++++--------- native/src/widget/responsive.rs | 4 +++ winit/src/application.rs | 15 +++++++---- 8 files changed, 85 insertions(+), 33 deletions(-) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index a1165de379..437c17ee3d 100644 --- a/glutin/src/application.rs +++ b/glutin/src/application.rs @@ -9,7 +9,8 @@ use iced_winit::application; use iced_winit::conversion; use iced_winit::futures; use iced_winit::futures::channel::mpsc; -use iced_winit::{Cache, Clipboard, Debug, Proxy, Settings}; +use iced_winit::user_interface; +use iced_winit::{Clipboard, Debug, Proxy, Settings}; use glutin::window::Window; use std::mem::ManuallyDrop; @@ -177,7 +178,7 @@ async fn run_instance( let mut user_interface = ManuallyDrop::new(application::build_user_interface( &mut application, - Cache::default(), + user_interface::Cache::default(), &mut renderer, state.logical_size(), &mut debug, @@ -198,7 +199,7 @@ async fn run_instance( debug.event_processing_started(); - let statuses = user_interface.update( + let (interface_state, statuses) = user_interface.update( &events, state.cursor_position(), &mut renderer, @@ -212,7 +213,12 @@ async fn run_instance( runtime.broadcast(event); } - if !messages.is_empty() { + if !messages.is_empty() + || matches!( + interface_state, + user_interface::State::Outdated + ) + { let cache = ManuallyDrop::into_inner(user_interface).into_cache(); diff --git a/lazy/src/component.rs b/lazy/src/component.rs index fd3f6ff7b6..48ee1d8742 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -149,7 +149,7 @@ where ) }); - local_shell.with_invalid_layout(|| shell.invalidate_layout()); + local_shell.revalidate_layout(|| shell.invalidate_layout()); if !local_messages.is_empty() { let mut component = self @@ -375,7 +375,7 @@ where }) .unwrap_or_else(|| iced_native::event::Status::Ignored); - local_shell.with_invalid_layout(|| shell.invalidate_layout()); + local_shell.revalidate_layout(|| shell.invalidate_layout()); if !local_messages.is_empty() { let mut component = diff --git a/native/src/lib.rs b/native/src/lib.rs index 4211995ff6..c527a69a24 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -50,6 +50,7 @@ pub mod subscription; pub mod svg; pub mod text; pub mod touch; +pub mod user_interface; pub mod widget; pub mod window; @@ -57,7 +58,6 @@ mod element; mod hasher; mod runtime; mod shell; -mod user_interface; // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. @@ -91,5 +91,5 @@ pub use renderer::Renderer; pub use runtime::Runtime; pub use shell::Shell; pub use subscription::Subscription; -pub use user_interface::{Cache, UserInterface}; +pub use user_interface::UserInterface; pub use widget::Widget; diff --git a/native/src/program/state.rs b/native/src/program/state.rs index 26c0eb21ac..cb87a628ef 100644 --- a/native/src/program/state.rs +++ b/native/src/program/state.rs @@ -1,8 +1,6 @@ use crate::mouse; -use crate::{ - Cache, Clipboard, Command, Debug, Event, Point, Program, Size, - UserInterface, -}; +use crate::user_interface::{self, UserInterface}; +use crate::{Clipboard, Command, Debug, Event, Point, Program, Size}; /// The execution state of a [`Program`]. It leverages caching, event /// processing, and rendering primitive storage. @@ -12,7 +10,7 @@ where P: Program + 'static, { program: P, - cache: Option, + cache: Option, queued_events: Vec, queued_messages: Vec, mouse_interaction: mouse::Interaction, @@ -32,7 +30,7 @@ where ) -> Self { let user_interface = build_user_interface( &mut program, - Cache::default(), + user_interface::Cache::default(), renderer, bounds, debug, @@ -161,7 +159,7 @@ where fn build_user_interface<'a, P: Program>( program: &'a mut P, - cache: Cache, + cache: user_interface::Cache, renderer: &mut P::Renderer, size: Size, debug: &mut Debug, diff --git a/native/src/shell.rs b/native/src/shell.rs index e916f52dbe..4a0aa9c66b 100644 --- a/native/src/shell.rs +++ b/native/src/shell.rs @@ -8,6 +8,7 @@ pub struct Shell<'a, Message> { messages: &'a mut Vec, is_layout_invalid: bool, + are_widgets_invalid: bool, } impl<'a, Message> Shell<'a, Message> { @@ -16,12 +17,13 @@ impl<'a, Message> Shell<'a, Message> { Self { messages, is_layout_invalid: false, + are_widgets_invalid: false, } } /// Triggers the given function if the layout is invalid, cleaning it in the /// process. - pub fn with_invalid_layout(&mut self, f: impl FnOnce()) { + pub fn revalidate_layout(&mut self, f: impl FnOnce()) { if self.is_layout_invalid { self.is_layout_invalid = false; @@ -41,6 +43,13 @@ impl<'a, Message> Shell<'a, Message> { self.is_layout_invalid = true; } + /// Invalidates the current application widgets. + /// + /// The shell will rebuild and relayout the widget tree. + pub fn invalidate_widgets(&mut self) { + self.are_widgets_invalid = true; + } + /// Merges the current [`Shell`] with another one by applying the given /// function to the messages of the latter. /// @@ -50,5 +59,14 @@ impl<'a, Message> Shell<'a, Message> { self.is_layout_invalid = self.is_layout_invalid || other.is_layout_invalid; + + self.are_widgets_invalid = + self.are_widgets_invalid || other.are_widgets_invalid; + } + + /// Returns whether the widgets of the current application have been + /// invalidated. + pub fn are_widgets_invalid(&self) -> bool { + self.are_widgets_invalid } } diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 40f7a20420..1c78b75437 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -42,7 +42,8 @@ where /// is naive way to set up our application loop: /// /// ```no_run - /// use iced_native::{UserInterface, Cache, Size}; + /// use iced_native::Size; + /// use iced_native::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -61,7 +62,7 @@ where /// # } /// // Initialization /// let mut counter = Counter::new(); - /// let mut cache = Cache::new(); + /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); /// @@ -136,7 +137,8 @@ where /// completing [the previous example](#example): /// /// ```no_run - /// use iced_native::{clipboard, UserInterface, Cache, Size, Point}; + /// use iced_native::{clipboard, Size, Point}; + /// use iced_native::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -155,7 +157,7 @@ where /// # pub fn update(&mut self, message: ()) {} /// # } /// let mut counter = Counter::new(); - /// let mut cache = Cache::new(); + /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); @@ -176,7 +178,7 @@ where /// ); /// /// // Update the user interface - /// let event_statuses = user_interface.update( + /// let (state, event_statuses) = user_interface.update( /// &events, /// cursor_position, /// &mut renderer, @@ -199,7 +201,9 @@ where renderer: &mut Renderer, clipboard: &mut dyn Clipboard, messages: &mut Vec, - ) -> Vec { + ) -> (State, Vec) { + let mut state = State::Updated; + let (base_cursor, overlay_statuses) = if let Some(mut overlay) = self.root.overlay(Layout::new(&self.base.layout)) { @@ -227,7 +231,7 @@ where &mut shell, ); - shell.with_invalid_layout(|| { + shell.revalidate_layout(|| { layer = Self::overlay_layer( None, bounds, @@ -236,6 +240,10 @@ where ); }); + if shell.are_widgets_invalid() { + state = State::Outdated; + } + event_status }) .collect(); @@ -255,7 +263,7 @@ where (cursor_position, vec![event::Status::Ignored; events.len()]) }; - events + let event_statuses = events .iter() .cloned() .zip(overlay_statuses.into_iter()) @@ -271,7 +279,7 @@ where &mut shell, ); - shell.with_invalid_layout(|| { + shell.revalidate_layout(|| { let hash = { let hasher = &mut crate::Hasher::default(); self.root.hash_layout(hasher); @@ -288,9 +296,15 @@ where self.overlay = None; }); + if shell.are_widgets_invalid() { + state = State::Outdated; + } + event_status.merge(overlay_status) }) - .collect() + .collect(); + + (state, event_statuses) } /// Draws the [`UserInterface`] with the provided [`Renderer`]. @@ -306,7 +320,8 @@ where /// [completing the last example](#example-1): /// /// ```no_run - /// use iced_native::{clipboard, UserInterface, Cache, Size, Point}; + /// use iced_native::{clipboard, Size, Point}; + /// use iced_native::user_interface::{self, UserInterface}; /// use iced_wgpu::Renderer; /// /// # mod iced_wgpu { @@ -325,7 +340,7 @@ where /// # pub fn update(&mut self, message: ()) {} /// # } /// let mut counter = Counter::new(); - /// let mut cache = Cache::new(); + /// let mut cache = user_interface::Cache::new(); /// let mut renderer = Renderer::new(); /// let mut window_size = Size::new(1024.0, 768.0); /// let mut cursor_position = Point::default(); @@ -548,3 +563,9 @@ impl Default for Cache { Cache::new() } } + +#[derive(Debug, Clone, Copy)] +pub enum State { + Outdated, + Updated, +} diff --git a/native/src/widget/responsive.rs b/native/src/widget/responsive.rs index 0cb85d451f..becaa98050 100644 --- a/native/src/widget/responsive.rs +++ b/native/src/widget/responsive.rs @@ -80,6 +80,10 @@ where let Internal { content, state } = internal.deref_mut(); + if state.last_size != Some(state.last_layout.size()) { + shell.invalidate_widgets(); + } + let content = content.resolve(state, renderer); let content_layout = Layout::with_offset( diff --git a/winit/src/application.rs b/winit/src/application.rs index 32423e80c2..e9212b5e37 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -15,7 +15,7 @@ use iced_futures::futures; use iced_futures::futures::channel::mpsc; use iced_graphics::window; use iced_native::program::Program; -use iced_native::{Cache, UserInterface}; +use iced_native::user_interface::{self, UserInterface}; use std::mem::ManuallyDrop; @@ -250,7 +250,7 @@ async fn run_instance( let mut user_interface = ManuallyDrop::new(build_user_interface( &mut application, - Cache::default(), + user_interface::Cache::default(), &mut renderer, state.logical_size(), &mut debug, @@ -271,7 +271,7 @@ async fn run_instance( debug.event_processing_started(); - let statuses = user_interface.update( + let (interface_state, statuses) = user_interface.update( &events, state.cursor_position(), &mut renderer, @@ -285,7 +285,12 @@ async fn run_instance( runtime.broadcast(event); } - if !messages.is_empty() { + if !messages.is_empty() + || matches!( + interface_state, + user_interface::State::Outdated, + ) + { let cache = ManuallyDrop::into_inner(user_interface).into_cache(); @@ -471,7 +476,7 @@ pub fn requests_exit( /// [`struct@Debug`] information accordingly. pub fn build_user_interface<'a, A: Application>( application: &'a mut A, - cache: Cache, + cache: user_interface::Cache, renderer: &mut A::Renderer, size: Size, debug: &mut Debug, From 810e086728e938d1d12758c7b486c1e371127349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 11 Jan 2022 14:12:28 +0700 Subject: [PATCH 03/10] Introduce `Renderer` argument to `mouse_interaction` and `on_event` --- graphics/src/widget/canvas.rs | 1 + lazy/src/component.rs | 23 ++++++++++-- native/src/element.rs | 37 +++++++++++++----- native/src/overlay.rs | 1 + native/src/overlay/element.rs | 18 +++++++-- native/src/overlay/menu.rs | 10 ++++- native/src/user_interface.rs | 48 +++++++++++++----------- native/src/widget.rs | 2 + native/src/widget/button.rs | 5 ++- native/src/widget/checkbox.rs | 1 + native/src/widget/column.rs | 7 +++- native/src/widget/container.rs | 6 ++- native/src/widget/image/viewer.rs | 1 + native/src/widget/pane_grid.rs | 11 +++++- native/src/widget/pane_grid/content.rs | 11 ++++-- native/src/widget/pane_grid/title_bar.rs | 14 +++++-- native/src/widget/pick_list.rs | 2 + native/src/widget/radio.rs | 1 + native/src/widget/row.rs | 7 +++- native/src/widget/scrollable.rs | 5 ++- native/src/widget/slider.rs | 1 + native/src/widget/text_input.rs | 1 + native/src/widget/toggler.rs | 1 + native/src/widget/tooltip.rs | 9 ++++- 24 files changed, 165 insertions(+), 58 deletions(-) diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 97846d658d..f9722f33ca 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -193,6 +193,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let cursor = Cursor::from_window_position(cursor_position); diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 48ee1d8742..836c3f01eb 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -214,15 +214,22 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { self.with_element(|element| { - element.mouse_interaction(layout, cursor_position, viewport) + element.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) }) } fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { let has_overlay = self .state @@ -236,7 +243,9 @@ where CacheBuilder { element, message: PhantomData, - overlay_builder: |element| element.overlay(layout), + overlay_builder: |element| { + element.overlay(layout, renderer) + }, } .build(), ); @@ -331,9 +340,15 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { self.with_overlay_maybe(|overlay| { - overlay.mouse_interaction(layout, cursor_position, viewport) + overlay.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) }) .unwrap_or_default() } @@ -397,7 +412,7 @@ where element: state.view(), message: PhantomData, overlay_builder: |element| { - element.overlay(layout) + element.overlay(layout, renderer) }, } .build(), diff --git a/native/src/element.rs b/native/src/element.rs index 7e806b08f0..6afa3f6236 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -259,9 +259,14 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { - self.widget - .mouse_interaction(layout, cursor_position, viewport) + self.widget.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) } /// Computes the _layout_ hash of the [`Element`]. @@ -273,8 +278,9 @@ where pub fn overlay<'b>( &'b mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { - self.widget.overlay(layout) + self.widget.overlay(layout, renderer) } } @@ -363,9 +369,14 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { - self.widget - .mouse_interaction(layout, cursor_position, viewport) + self.widget.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -375,11 +386,12 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { let mapper = &self.mapper; self.widget - .overlay(layout) + .overlay(layout, renderer) .map(move |overlay| overlay.map(mapper)) } } @@ -482,10 +494,14 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { - self.element - .widget - .mouse_interaction(layout, cursor_position, viewport) + self.element.widget.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -495,7 +511,8 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { - self.element.overlay(layout) + self.element.overlay(layout, renderer) } } diff --git a/native/src/overlay.rs b/native/src/overlay.rs index e66d421a57..d4b641af1c 100644 --- a/native/src/overlay.rs +++ b/native/src/overlay.rs @@ -84,6 +84,7 @@ where _layout: Layout<'_>, _cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { mouse::Interaction::Idle } diff --git a/native/src/overlay/element.rs b/native/src/overlay/element.rs index 70cb417e6c..e76216008b 100644 --- a/native/src/overlay/element.rs +++ b/native/src/overlay/element.rs @@ -80,9 +80,14 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { - self.overlay - .mouse_interaction(layout, cursor_position, viewport) + self.overlay.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) } /// Draws the [`Element`] and its children using the given [`Layout`]. @@ -160,9 +165,14 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { - self.content - .mouse_interaction(layout, cursor_position, viewport) + self.content.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) } fn draw( diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index c0282aaede..2deef5512f 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -239,9 +239,14 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { - self.container - .mouse_interaction(layout, cursor_position, viewport) + self.container.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) } fn draw( @@ -392,6 +397,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { let is_mouse_over = layout.bounds().contains(cursor_position); diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index 1c78b75437..b6bed5ed34 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -205,7 +205,7 @@ where let mut state = State::Updated; let (base_cursor, overlay_statuses) = if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout)) + self.root.overlay(Layout::new(&self.base.layout), renderer) { let bounds = self.bounds; @@ -391,7 +391,7 @@ where let viewport = Rectangle::with_size(self.bounds); if let Some(mut overlay) = - self.root.overlay(Layout::new(&self.base.layout)) + self.root.overlay(Layout::new(&self.base.layout), renderer) { let layer = Self::overlay_layer( self.overlay.take(), @@ -432,6 +432,7 @@ where Layout::new(&self.base.layout), cursor_position, &viewport, + renderer, ); let Self { @@ -449,30 +450,33 @@ where overlay .as_ref() .and_then(|layer| { - root.overlay(Layout::new(&base.layout)).map(|overlay| { - let overlay_interaction = overlay.mouse_interaction( - Layout::new(&layer.layout), - cursor_position, - &viewport, - ); - - let overlay_bounds = layer.layout.bounds(); - - renderer.with_layer(overlay_bounds, |renderer| { - overlay.draw( - renderer, - &renderer::Style::default(), + root.overlay(Layout::new(&base.layout), renderer).map( + |overlay| { + let overlay_interaction = overlay.mouse_interaction( Layout::new(&layer.layout), cursor_position, + &viewport, + renderer, ); - }); - if overlay_bounds.contains(cursor_position) { - overlay_interaction - } else { - base_interaction - } - }) + let overlay_bounds = layer.layout.bounds(); + + renderer.with_layer(overlay_bounds, |renderer| { + overlay.draw( + renderer, + &renderer::Style::default(), + Layout::new(&layer.layout), + cursor_position, + ); + }); + + if overlay_bounds.contains(cursor_position) { + overlay_interaction + } else { + base_interaction + } + }, + ) }) .unwrap_or(base_interaction) } diff --git a/native/src/widget.rs b/native/src/widget.rs index d1619bf3f6..30405346c5 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -179,6 +179,7 @@ where _layout: Layout<'_>, _cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { mouse::Interaction::Idle } @@ -187,6 +188,7 @@ where fn overlay( &mut self, _layout: Layout<'_>, + _renderer: &Renderer, ) -> Option> { None } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 686289e4bf..b4a3adc31c 100644 --- a/native/src/widget/button.rs +++ b/native/src/widget/button.rs @@ -253,6 +253,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { let is_mouse_over = layout.bounds().contains(cursor_position); let is_disabled = self.on_press.is_none(); @@ -343,8 +344,10 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { - self.content.overlay(layout.children().next().unwrap()) + self.content + .overlay(layout.children().next().unwrap(), renderer) } } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index 95856928e7..a314140c20 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -195,6 +195,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { if layout.bounds().contains(cursor_position) { mouse::Interaction::Pointer diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 4c43c8c80b..66ed5e231f 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -169,6 +169,7 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { self.children .iter() @@ -178,6 +179,7 @@ where layout, cursor_position, viewport, + renderer, ) }) .max() @@ -217,11 +219,14 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| child.widget.overlay(layout)) + .filter_map(|(child, layout)| { + child.widget.overlay(layout, renderer) + }) .next() } } diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 0c291788b0..4444732a62 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -184,11 +184,13 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { self.content.widget.mouse_interaction( layout.children().next().unwrap(), cursor_position, viewport, + renderer, ) } @@ -235,8 +237,10 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { - self.content.overlay(layout.children().next().unwrap()) + self.content + .overlay(layout.children().next().unwrap(), renderer) } } diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs index 645ce852d8..486d5fa5d6 100644 --- a/native/src/widget/image/viewer.rs +++ b/native/src/widget/image/viewer.rs @@ -286,6 +286,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index b3d8b819e9..5f293fa6d1 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -472,6 +472,7 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { if self.state.picked_pane().is_some() { return mouse::Interaction::Grab; @@ -511,7 +512,12 @@ where .iter() .zip(layout.children()) .map(|((_pane, content), layout)| { - content.mouse_interaction(layout, cursor_position, viewport) + content.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) }) .max() .unwrap_or_default() @@ -678,11 +684,12 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { self.elements .iter_mut() .zip(layout.children()) - .filter_map(|((_, pane), layout)| pane.overlay(layout)) + .filter_map(|((_, pane), layout)| pane.overlay(layout, renderer)) .next() } } diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 533827b764..6d12aa2d5c 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -205,6 +205,7 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { let (body_layout, title_bar_interaction) = if let Some(title_bar) = &self.title_bar { @@ -222,6 +223,7 @@ where title_bar_layout, cursor_position, viewport, + renderer, ); (children.next().unwrap(), mouse_interaction) @@ -230,7 +232,7 @@ where }; self.body - .mouse_interaction(body_layout, cursor_position, viewport) + .mouse_interaction(body_layout, cursor_position, viewport, renderer) .max(title_bar_interaction) } @@ -245,17 +247,18 @@ where pub(crate) fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { if let Some(title_bar) = self.title_bar.as_mut() { let mut children = layout.children(); let title_bar_layout = children.next()?; - match title_bar.overlay(title_bar_layout) { + match title_bar.overlay(title_bar_layout, renderer) { Some(overlay) => Some(overlay), - None => self.body.overlay(children.next()?), + None => self.body.overlay(children.next()?, renderer), } } else { - self.body.overlay(layout) + self.body.overlay(layout, renderer) } } } diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs index 353e1ce9b8..ea9ebfc4bb 100644 --- a/native/src/widget/pane_grid/title_bar.rs +++ b/native/src/widget/pane_grid/title_bar.rs @@ -258,6 +258,7 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { let mut children = layout.children(); let padded = children.next().unwrap(); @@ -269,13 +270,19 @@ where title_layout, cursor_position, viewport, + renderer, ); if let Some(controls) = &self.controls { let controls_layout = children.next().unwrap(); controls - .mouse_interaction(controls_layout, cursor_position, viewport) + .mouse_interaction( + controls_layout, + cursor_position, + viewport, + renderer, + ) .max(title_interaction) } else { title_interaction @@ -285,6 +292,7 @@ where pub(crate) fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { let mut children = layout.children(); let padded = children.next()?; @@ -296,11 +304,11 @@ where content, controls, .. } = self; - content.overlay(title_layout).or_else(move || { + content.overlay(title_layout, renderer).or_else(move || { controls.as_mut().and_then(|controls| { let controls_layout = children.next()?; - controls.overlay(controls_layout) + controls.overlay(controls_layout, renderer) }) }) } diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 1f79e07a30..a200fb1330 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -331,6 +331,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); @@ -415,6 +416,7 @@ where fn overlay( &mut self, layout: Layout<'_>, + _renderer: &Renderer, ) -> Option> { if *self.is_open { let bounds = layout.bounds(); diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 1dbe7cf5b4..c6cc46edc3 100644 --- a/native/src/widget/radio.rs +++ b/native/src/widget/radio.rs @@ -209,6 +209,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { if layout.bounds().contains(cursor_position) { mouse::Interaction::Pointer diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index a0174f79f6..c423d31f99 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -168,6 +168,7 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { self.children .iter() @@ -177,6 +178,7 @@ where layout, cursor_position, viewport, + renderer, ) }) .max() @@ -216,11 +218,14 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { self.children .iter_mut() .zip(layout.children()) - .filter_map(|(child, layout)| child.widget.overlay(layout)) + .filter_map(|(child, layout)| { + child.widget.overlay(layout, renderer) + }) .next() } } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 3ef737c0cb..4b005c6b20 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -427,6 +427,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); @@ -457,6 +458,7 @@ where y: bounds.y + offset as f32, ..bounds }, + renderer, ) } } @@ -581,11 +583,12 @@ where fn overlay( &mut self, layout: Layout<'_>, + renderer: &Renderer, ) -> Option> { let Self { content, state, .. } = self; content - .overlay(layout.children().next().unwrap()) + .overlay(layout.children().next().unwrap(), renderer) .map(|overlay| { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index 65632a5ce1..917bfc14e3 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -361,6 +361,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { let bounds = layout.bounds(); let is_mouse_over = bounds.contains(cursor_position); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index f06b6aa6cf..03adeb12b8 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -764,6 +764,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { if layout.bounds().contains(cursor_position) { mouse::Interaction::Text diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 7eca58d2b7..002e0a4ffd 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -196,6 +196,7 @@ where layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { if layout.bounds().contains(cursor_position) { mouse::Interaction::Pointer diff --git a/native/src/widget/tooltip.rs b/native/src/widget/tooltip.rs index 79a57824f5..f66308648d 100644 --- a/native/src/widget/tooltip.rs +++ b/native/src/widget/tooltip.rs @@ -147,9 +147,14 @@ where layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, + renderer: &Renderer, ) -> mouse::Interaction { - self.content - .mouse_interaction(layout, cursor_position, viewport) + self.content.mouse_interaction( + layout, + cursor_position, + viewport, + renderer, + ) } fn draw( From 2e255b7b91264cf1e53e89dfdefd95270957b2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 11 Jan 2022 14:21:38 +0700 Subject: [PATCH 04/10] Implement `Widget::mouse_interaction` for `Responsive` --- native/src/widget/responsive.rs | 62 ++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/native/src/widget/responsive.rs b/native/src/widget/responsive.rs index becaa98050..fd9e490a3c 100644 --- a/native/src/widget/responsive.rs +++ b/native/src/widget/responsive.rs @@ -1,5 +1,6 @@ use crate::event::{self, Event}; use crate::layout::{self, Layout}; +use crate::mouse; use crate::renderer; use crate::{ Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, @@ -74,22 +75,13 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { - use std::ops::DerefMut; - let mut internal = self.0.borrow_mut(); - let Internal { content, state } = internal.deref_mut(); - - if state.last_size != Some(state.last_layout.size()) { + if internal.state.last_size != Some(internal.state.last_layout.size()) { shell.invalidate_widgets(); } - let content = content.resolve(state, renderer); - - let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &state.last_layout, - ); + let (content, content_layout) = internal.content(layout, renderer); content.on_event( event, @@ -109,20 +101,28 @@ where cursor_position: Point, viewport: &Rectangle, ) { - use std::ops::DerefMut; - let mut internal = self.0.borrow_mut(); + let (content, content_layout) = internal.content(layout, renderer); - let Internal { content, state } = internal.deref_mut(); - - let content = content.resolve(state, renderer); + content.draw(renderer, style, content_layout, cursor_position, viewport) + } - let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &state.last_layout, - ); + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let mut internal = self.0.borrow_mut(); + let (content, content_layout) = internal.content(layout, renderer); - content.draw(renderer, style, content_layout, cursor_position, viewport) + content.mouse_interaction( + content_layout, + cursor_position, + viewport, + renderer, + ) } } @@ -131,6 +131,26 @@ struct Internal<'a, Message, Renderer> { content: Content<'a, Message, Renderer>, } +impl<'a, Message, Renderer> Internal<'a, Message, Renderer> +where + Renderer: crate::Renderer, +{ + fn content( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> (&mut Element<'a, Message, Renderer>, Layout<'_>) { + let content = self.content.resolve(&mut self.state, renderer); + + let content_layout = Layout::with_offset( + layout.position() - Point::ORIGIN, + &self.state.last_layout, + ); + + (content, content_layout) + } +} + enum Content<'a, Message, Renderer> { Pending( Option Element<'a, Message, Renderer> + 'a>>, From 5a03cac7e75ccb8ca87a97def723694be0471742 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 11 Jan 2022 14:55:48 +0700 Subject: [PATCH 05/10] Remove superfluous generic type in `iced_lazy::Cache` --- lazy/src/component.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lazy/src/component.rs b/lazy/src/component.rs index 836c3f01eb..ca0d115c55 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -24,11 +24,11 @@ where state: RefCell::new(Some( StateBuilder { component: Box::new(component), + message: PhantomData, cache_builder: |state| { Some( CacheBuilder { element: state.view(), - message: PhantomData, overlay_builder: |_| None, } .build(), @@ -55,20 +55,20 @@ struct Instance<'a, Message, Renderer, Event> { #[self_referencing] struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> { component: Box + 'a>, + message: PhantomData, #[borrows(mut component)] #[covariant] - cache: Option>, + cache: Option>, } #[self_referencing] -struct Cache<'a, Message, Renderer: 'a, Event: 'a> { - element: Element<'a, Event, Renderer>, - message: PhantomData, +struct Cache<'a, Message: 'a, Renderer: 'a> { + element: Element<'a, Message, Renderer>, #[borrows(mut element)] #[covariant] - overlay: Option>, + overlay: Option>, } impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { @@ -94,7 +94,6 @@ impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { *cache = Some( CacheBuilder { element, - message: PhantomData, overlay_builder: |_| None, } .build(), @@ -170,11 +169,11 @@ where *self.state.borrow_mut() = Some( StateBuilder { component, + message: PhantomData, cache_builder: |state| { Some( CacheBuilder { element: state.view(), - message: PhantomData, overlay_builder: |_| None, } .build(), @@ -242,7 +241,6 @@ where *cache = Some( CacheBuilder { element, - message: PhantomData, overlay_builder: |element| { element.overlay(layout, renderer) }, @@ -406,11 +404,11 @@ where self.instance.state = RefCell::new(Some( StateBuilder { component, + message: PhantomData, cache_builder: |state| { Some( CacheBuilder { element: state.view(), - message: PhantomData, overlay_builder: |element| { element.overlay(layout, renderer) }, From 870d651f35c4dad12c805951fca70213816983de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jan 2022 11:15:05 +0700 Subject: [PATCH 06/10] Implement `Widget::overlay` for `Responsive` widget --- examples/pane_grid/Cargo.toml | 1 + examples/pane_grid/src/main.rs | 2 +- glow/src/widget.rs | 3 - glow/src/widget/responsive.rs | 6 - lazy/src/cache.rs | 13 + lazy/src/component.rs | 11 +- lazy/src/lib.rs | 6 + lazy/src/responsive.rs | 413 ++++++++++++++++++++++++++++++++ native/src/widget.rs | 3 - native/src/widget/responsive.rs | 212 ---------------- src/widget.rs | 9 +- wgpu/src/widget.rs | 3 - wgpu/src/widget/responsive.rs | 6 - 13 files changed, 440 insertions(+), 248 deletions(-) delete mode 100644 glow/src/widget/responsive.rs create mode 100644 lazy/src/cache.rs create mode 100644 lazy/src/responsive.rs delete mode 100644 native/src/widget/responsive.rs delete mode 100644 wgpu/src/widget/responsive.rs diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index e489f2108d..03e6cd4ab2 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -8,3 +8,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["debug"] } iced_native = { path = "../../native" } +iced_lazy = { path = "../../lazy" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 59e5f3f9ca..9330ddb4e7 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -3,12 +3,12 @@ use iced::button::{self, Button}; use iced::executor; use iced::keyboard; use iced::pane_grid::{self, PaneGrid}; -use iced::responsive::{self, Responsive}; use iced::scrollable::{self, Scrollable}; use iced::{ Application, Color, Column, Command, Container, Element, Length, Row, Settings, Size, Subscription, Text, }; +use iced_lazy::responsive::{self, Responsive}; use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { diff --git a/glow/src/widget.rs b/glow/src/widget.rs index 85ef2f02ce..ee2810f96a 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -16,7 +16,6 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; -pub mod responsive; pub mod rule; pub mod scrollable; pub mod slider; @@ -39,8 +38,6 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] -pub use responsive::Responsive; -#[doc(no_inline)] pub use rule::Rule; #[doc(no_inline)] pub use scrollable::Scrollable; diff --git a/glow/src/widget/responsive.rs b/glow/src/widget/responsive.rs deleted file mode 100644 index 1a64ac6284..0000000000 --- a/glow/src/widget/responsive.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::Renderer; - -pub use iced_native::widget::responsive::State; - -pub type Responsive<'a, Message> = - iced_native::widget::Responsive<'a, Message, Renderer>; diff --git a/lazy/src/cache.rs b/lazy/src/cache.rs new file mode 100644 index 0000000000..229b79128f --- /dev/null +++ b/lazy/src/cache.rs @@ -0,0 +1,13 @@ +use iced_native::overlay; +use iced_native::Element; + +use ouroboros::self_referencing; + +#[self_referencing(pub_extras)] +pub struct Cache<'a, Message: 'a, Renderer: 'a> { + pub element: Element<'a, Message, Renderer>, + + #[borrows(mut element)] + #[covariant] + pub overlay: Option>, +} diff --git a/lazy/src/component.rs b/lazy/src/component.rs index ca0d115c55..ae8d6bbe3f 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -1,3 +1,5 @@ +use crate::{Cache, CacheBuilder}; + use iced_native::event; use iced_native::layout::{self, Layout}; use iced_native::mouse; @@ -62,15 +64,6 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> { cache: Option>, } -#[self_referencing] -struct Cache<'a, Message: 'a, Renderer: 'a> { - element: Element<'a, Message, Renderer>, - - #[borrows(mut element)] - #[covariant] - overlay: Option>, -} - impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { fn with_element( &self, diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index 42e5f5871b..05fce76570 100644 --- a/lazy/src/lib.rs +++ b/lazy/src/lib.rs @@ -1,3 +1,9 @@ pub mod component; +pub mod responsive; pub use component::Component; +pub use responsive::Responsive; + +mod cache; + +use cache::{Cache, CacheBuilder}; diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs new file mode 100644 index 0000000000..2afad2e555 --- /dev/null +++ b/lazy/src/responsive.rs @@ -0,0 +1,413 @@ +use crate::{Cache, CacheBuilder}; + +use iced_native::event::{self, Event}; +use iced_native::layout::{self, Layout}; +use iced_native::mouse; +use iced_native::overlay; +use iced_native::renderer; +use iced_native::{ + Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, +}; + +use std::cell::RefCell; +use std::hash::{Hash, Hasher as _}; +use std::ops::Deref; + +#[derive(Debug, Clone, Default)] +pub struct State { + last_size: Option, + last_layout: layout::Node, + last_layout_hash: u64, +} + +impl State { + pub fn new() -> State { + State::default() + } + + pub fn layout(&self, parent: Layout<'_>) -> Layout<'_> { + Layout::with_offset( + parent.position() - Point::ORIGIN, + &self.last_layout, + ) + } +} + +#[allow(missing_debug_implementations)] +pub struct Responsive<'a, Message, Renderer>( + RefCell>, +); + +impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> { + pub fn new( + state: &'a mut State, + view: impl FnOnce(Size) -> Element<'a, Message, Renderer> + 'a, + ) -> Self { + Self(RefCell::new(Internal { + state, + content: Content::Pending(Some(Box::new(view))), + })) + } +} + +impl<'a, Message, Renderer> Widget + for Responsive<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn width(&self) -> Length { + Length::Fill + } + + fn height(&self) -> Length { + Length::Fill + } + + fn hash_layout(&self, _hasher: &mut Hasher) {} + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let size = limits.max(); + + self.0.borrow_mut().state.last_size = Some(size); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let mut internal = self.0.borrow_mut(); + + if internal.state.last_size != Some(internal.state.last_layout.size()) { + shell.invalidate_widgets(); + } + + internal.resolve(renderer, |state, renderer, content| { + content.on_event( + event, + state.layout(layout), + cursor_position, + renderer, + clipboard, + shell, + ) + }) + } + + fn draw( + &self, + renderer: &mut Renderer, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + ) { + let mut internal = self.0.borrow_mut(); + + internal.resolve(renderer, |state, renderer, content| { + content.draw( + renderer, + style, + state.layout(layout), + cursor_position, + viewport, + ) + }) + } + + fn mouse_interaction( + &self, + layout: Layout<'_>, + cursor_position: Point, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let mut internal = self.0.borrow_mut(); + + internal.resolve(renderer, |state, renderer, content| { + content.mouse_interaction( + state.layout(layout), + cursor_position, + viewport, + renderer, + ) + }) + } + + fn overlay( + &mut self, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + let has_overlay = { + use std::ops::DerefMut; + + let mut internal = self.0.borrow_mut(); + + let _ = + internal.resolve(renderer, |_state, _renderer, _content| {}); + + let Internal { content, state } = internal.deref_mut(); + + let content_layout = state.layout(layout); + + match content { + Content::Pending(_) => false, + Content::Ready(cache) => { + *cache = Some( + CacheBuilder { + element: cache.take().unwrap().into_heads().element, + overlay_builder: |element| { + element.overlay(content_layout, renderer) + }, + } + .build(), + ); + + cache.as_ref().unwrap().borrow_overlay().is_some() + } + } + }; + + has_overlay.then(|| { + overlay::Element::new( + layout.position(), + Box::new(Overlay { instance: self }), + ) + }) + } +} + +struct Internal<'a, Message, Renderer> { + state: &'a mut State, + content: Content<'a, Message, Renderer>, +} + +impl<'a, Message, Renderer> Internal<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn resolve( + &mut self, + renderer: R, + f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, + ) -> T + where + R: Deref, + { + self.content.resolve(&mut self.state, renderer, f) + } +} + +enum Content<'a, Message, Renderer> { + Pending( + Option Element<'a, Message, Renderer> + 'a>>, + ), + Ready(Option>), +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn resolve( + &mut self, + state: &mut State, + renderer: R, + f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, + ) -> T + where + R: Deref, + { + match self { + Content::Ready(cache) => { + let mut heads = cache.take().unwrap().into_heads(); + + let result = f(state, renderer, &mut heads.element); + + *cache = Some( + CacheBuilder { + element: heads.element, + overlay_builder: |_| None, + } + .build(), + ); + + result + } + Content::Pending(view) => { + let element = + view.take().unwrap()(state.last_size.unwrap_or(Size::ZERO)); + + let new_layout_hash = { + let mut hasher = Hasher::default(); + element.hash_layout(&mut hasher); + + hasher.finish() + }; + + if new_layout_hash != state.last_layout_hash { + state.last_layout = element.layout( + renderer.deref(), + &layout::Limits::new( + Size::ZERO, + state.last_size.unwrap_or(Size::ZERO), + ), + ); + + state.last_layout_hash = new_layout_hash; + } + + *self = Content::Ready(Some( + CacheBuilder { + element, + overlay_builder: |_| None, + } + .build(), + )); + + self.resolve(state, renderer, f) + } + } + } +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: iced_native::Renderer + 'a, + Message: 'a, +{ + fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { + Self::new(responsive) + } +} + +struct Overlay<'a, 'b, Message, Renderer> { + instance: &'b mut Responsive<'a, Message, Renderer>, +} + +impl<'a, 'b, Message, Renderer> Overlay<'a, 'b, Message, Renderer> { + fn with_overlay_maybe( + &self, + f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + let internal = self.instance.0.borrow(); + + match &internal.content { + Content::Pending(_) => None, + Content::Ready(cache) => { + cache.as_ref().unwrap().borrow_overlay().as_ref().map(f) + } + } + } + + fn with_overlay_mut_maybe( + &self, + f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option { + let mut internal = self.instance.0.borrow_mut(); + + match &mut internal.content { + Content::Pending(_) => None, + Content::Ready(cache) => cache + .as_mut() + .unwrap() + .with_overlay_mut(|overlay| overlay.as_mut().map(f)), + } + } +} + +impl<'a, 'b, Message, Renderer> overlay::Overlay + for Overlay<'a, 'b, Message, Renderer> +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, + style: &renderer::Style, + layout: Layout<'_>, + cursor_position: Point, + ) { + self.with_overlay_maybe(|overlay| { + overlay.draw(renderer, 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 hash_layout(&self, state: &mut Hasher, position: Point) { + struct Marker; + std::any::TypeId::of::().hash(state); + + (position.x as u32).hash(state); + (position.y as u32).hash(state); + + self.with_overlay_maybe(|overlay| { + overlay.hash_layout(state); + }); + } + + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> iced_native::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) + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 30405346c5..acd2e58015 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -20,7 +20,6 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; -pub mod responsive; pub mod row; pub mod rule; pub mod scrollable; @@ -51,8 +50,6 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] -pub use responsive::Responsive; -#[doc(no_inline)] pub use row::Row; #[doc(no_inline)] pub use rule::Rule; diff --git a/native/src/widget/responsive.rs b/native/src/widget/responsive.rs deleted file mode 100644 index fd9e490a3c..0000000000 --- a/native/src/widget/responsive.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::event::{self, Event}; -use crate::layout::{self, Layout}; -use crate::mouse; -use crate::renderer; -use crate::{ - Clipboard, Element, Hasher, Length, Point, Rectangle, Shell, Size, Widget, -}; - -use std::cell::RefCell; -use std::hash::Hasher as _; - -#[derive(Debug, Clone, Default)] -pub struct State { - last_size: Option, - last_layout: layout::Node, - last_layout_hash: u64, -} - -impl State { - pub fn new() -> State { - State::default() - } -} - -#[allow(missing_debug_implementations)] -pub struct Responsive<'a, Message, Renderer>( - RefCell>, -); - -impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> { - pub fn new( - state: &'a mut State, - view: impl FnOnce(Size) -> Element<'a, Message, Renderer> + 'a, - ) -> Self { - Self(RefCell::new(Internal { - state, - content: Content::Pending(Some(Box::new(view))), - })) - } -} - -impl<'a, Message, Renderer> Widget - for Responsive<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Fill - } - - fn hash_layout(&self, _hasher: &mut Hasher) {} - - fn layout( - &self, - _renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - let size = limits.max(); - - self.0.borrow_mut().state.last_size = Some(size); - - layout::Node::new(size) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - renderer: &Renderer, - clipboard: &mut dyn Clipboard, - shell: &mut Shell<'_, Message>, - ) -> event::Status { - let mut internal = self.0.borrow_mut(); - - if internal.state.last_size != Some(internal.state.last_layout.size()) { - shell.invalidate_widgets(); - } - - let (content, content_layout) = internal.content(layout, renderer); - - content.on_event( - event, - content_layout, - cursor_position, - renderer, - clipboard, - shell, - ) - } - - fn draw( - &self, - renderer: &mut Renderer, - style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - ) { - let mut internal = self.0.borrow_mut(); - let (content, content_layout) = internal.content(layout, renderer); - - content.draw(renderer, style, content_layout, cursor_position, viewport) - } - - fn mouse_interaction( - &self, - layout: Layout<'_>, - cursor_position: Point, - viewport: &Rectangle, - renderer: &Renderer, - ) -> mouse::Interaction { - let mut internal = self.0.borrow_mut(); - let (content, content_layout) = internal.content(layout, renderer); - - content.mouse_interaction( - content_layout, - cursor_position, - viewport, - renderer, - ) - } -} - -struct Internal<'a, Message, Renderer> { - state: &'a mut State, - content: Content<'a, Message, Renderer>, -} - -impl<'a, Message, Renderer> Internal<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn content( - &mut self, - layout: Layout<'_>, - renderer: &Renderer, - ) -> (&mut Element<'a, Message, Renderer>, Layout<'_>) { - let content = self.content.resolve(&mut self.state, renderer); - - let content_layout = Layout::with_offset( - layout.position() - Point::ORIGIN, - &self.state.last_layout, - ); - - (content, content_layout) - } -} - -enum Content<'a, Message, Renderer> { - Pending( - Option Element<'a, Message, Renderer> + 'a>>, - ), - Ready(Element<'a, Message, Renderer>), -} - -impl<'a, Message, Renderer> Content<'a, Message, Renderer> -where - Renderer: crate::Renderer, -{ - fn resolve( - &mut self, - state: &mut State, - renderer: &Renderer, - ) -> &mut Element<'a, Message, Renderer> { - match self { - Content::Ready(element) => element, - Content::Pending(view) => { - let element = - view.take().unwrap()(state.last_size.unwrap_or(Size::ZERO)); - - let new_layout_hash = { - let mut hasher = Hasher::default(); - element.hash_layout(&mut hasher); - - hasher.finish() - }; - - if new_layout_hash != state.last_layout_hash { - state.last_layout = element.layout( - renderer, - &layout::Limits::new( - Size::ZERO, - state.last_size.unwrap_or(Size::ZERO), - ), - ); - - state.last_layout_hash = new_layout_hash; - } - - *self = Content::Ready(element); - - self.resolve(state, renderer) - } - } - } -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: crate::Renderer + 'a, - Message: 'a, -{ - fn from(responsive: Responsive<'a, Message, Renderer>) -> Self { - Self::new(responsive) - } -} diff --git a/src/widget.rs b/src/widget.rs index c2790e4a3f..0f0b0325f7 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -17,8 +17,8 @@ mod platform { pub use crate::renderer::widget::{ button, checkbox, container, pane_grid, pick_list, progress_bar, radio, - responsive, rule, scrollable, slider, text_input, toggler, tooltip, - Column, Row, Space, Text, + rule, scrollable, slider, text_input, toggler, tooltip, Column, Row, + Space, Text, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] @@ -54,9 +54,8 @@ mod platform { pub use { button::Button, checkbox::Checkbox, container::Container, image::Image, pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, - radio::Radio, responsive::Responsive, rule::Rule, - scrollable::Scrollable, slider::Slider, svg::Svg, - text_input::TextInput, toggler::Toggler, tooltip::Tooltip, + radio::Radio, rule::Rule, scrollable::Scrollable, slider::Slider, + svg::Svg, text_input::TextInput, toggler::Toggler, tooltip::Tooltip, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index ac8653d4cb..99ae0ac21c 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -16,7 +16,6 @@ pub mod pane_grid; pub mod pick_list; pub mod progress_bar; pub mod radio; -pub mod responsive; pub mod rule; pub mod scrollable; pub mod slider; @@ -39,8 +38,6 @@ pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; #[doc(no_inline)] -pub use responsive::Responsive; -#[doc(no_inline)] pub use rule::Rule; #[doc(no_inline)] pub use scrollable::Scrollable; diff --git a/wgpu/src/widget/responsive.rs b/wgpu/src/widget/responsive.rs deleted file mode 100644 index 1a64ac6284..0000000000 --- a/wgpu/src/widget/responsive.rs +++ /dev/null @@ -1,6 +0,0 @@ -use crate::Renderer; - -pub use iced_native::widget::responsive::State; - -pub type Responsive<'a, Message> = - iced_native::widget::Responsive<'a, Message, Renderer>; From c85caff7a5b1ef78529c8d2a4f3f3cdd98301693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jan 2022 11:19:51 +0700 Subject: [PATCH 07/10] Increase text size of pane dimensions in `pane_grid` example --- examples/pane_grid/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 9330ddb4e7..2962ca259e 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -352,7 +352,7 @@ impl Content { .width(Length::Fill) .spacing(10) .align_items(Alignment::Center) - .push(Text::new(format!("{}x{}", size.width, size.height)).size(18)) + .push(Text::new(format!("{}x{}", size.width, size.height)).size(24)) .push(controls); Container::new(content) From 7ef0259a2cb7116496f438266a00b73d482eff8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jan 2022 11:22:57 +0700 Subject: [PATCH 08/10] Write missing docs for `user_interface` module --- glow/src/lib.rs | 2 +- native/src/lib.rs | 2 +- native/src/user_interface.rs | 6 ++++++ wgpu/src/lib.rs | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/glow/src/lib.rs b/glow/src/lib.rs index 7fde122cc0..362933d4ff 100644 --- a/glow/src/lib.rs +++ b/glow/src/lib.rs @@ -7,7 +7,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(rust_2018_idioms)] diff --git a/native/src/lib.rs b/native/src/lib.rs index c527a69a24..a5526e6d49 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -31,7 +31,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs index b6bed5ed34..4b1194be64 100644 --- a/native/src/user_interface.rs +++ b/native/src/user_interface.rs @@ -1,3 +1,4 @@ +//! Implement your own event loop to drive a user interface. use crate::event::{self, Event}; use crate::layout; use crate::mouse; @@ -568,8 +569,13 @@ impl Default for Cache { } } +/// The resulting state after updating a [`UserInterface`]. #[derive(Debug, Clone, Copy)] pub enum State { + /// The [`UserInterface`] is outdated and needs to be rebuilt. Outdated, + + /// The [`UserInterface`] is up-to-date and can be reused without + /// rebuilding. Updated, } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 69763e66ad..fb03854b12 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -23,7 +23,7 @@ #![doc( html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg" )] -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] From 241e123c9b49bb5f8c6bf223eef666c94042dd8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jan 2022 11:39:54 +0700 Subject: [PATCH 09/10] Write documentation for `component` in `iced_lazy` --- lazy/src/component.rs | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/lazy/src/component.rs b/lazy/src/component.rs index ae8d6bbe3f..2b7290451f 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -1,3 +1,4 @@ +//! Build and reuse custom widgets using The Elm Architecture. use crate::{Cache, CacheBuilder}; use iced_native::event; @@ -14,6 +15,35 @@ use std::cell::RefCell; use std::hash::Hash; use std::marker::PhantomData; +/// A reusable, custom widget that uses The Elm Architecture. +/// +/// A [`Component`] allows you to implement custom widgets as if they were +/// `iced` applications with encapsulated state. +/// +/// In other words, a [`Component`] allows you to turn `iced` applications into +/// custom widgets and embed them without cumbersome wiring. +/// +/// A [`Component`] produces widgets that may fire an [`Event`](Component::Event) +/// and update the internal state of the [`Component`]. +/// +/// Additionally, a [`Component`] is capable of producing a `Message` to notify +/// the parent application of any relevant interactions. +pub trait Component { + /// The type of event this [`Component`] handles internally. + type Event; + + /// Processes an [`Event`](Component::Event) and updates the [`Component`] state accordingly. + /// + /// It can produce a `Message` for the parent application. + fn update(&mut self, event: Self::Event) -> Option; + + /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event) + /// on user interaction. + fn view(&mut self) -> Element; +} + +/// Turns an implementor of [`Component`] into an [`Element`] that can be +/// embedded in any application. pub fn view<'a, C, Message, Renderer>( component: C, ) -> Element<'a, Message, Renderer> @@ -42,14 +72,6 @@ where }) } -pub trait Component { - type Event; - - fn update(&mut self, event: Self::Event) -> Option; - - fn view(&mut self) -> Element; -} - struct Instance<'a, Message, Renderer, Event> { state: RefCell>>, } From f6c436aec1acb674078bf7e878b9e49f28e947a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Jan 2022 11:48:49 +0700 Subject: [PATCH 10/10] Write docs for `responsive` in `iced_lazy` --- lazy/src/responsive.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lazy/src/responsive.rs b/lazy/src/responsive.rs index 2afad2e555..da7bb40832 100644 --- a/lazy/src/responsive.rs +++ b/lazy/src/responsive.rs @@ -1,3 +1,4 @@ +//! Build responsive widgets. use crate::{Cache, CacheBuilder}; use iced_native::event::{self, Event}; @@ -13,6 +14,7 @@ use std::cell::RefCell; use std::hash::{Hash, Hasher as _}; use std::ops::Deref; +/// The state of a [`Responsive`] widget. #[derive(Debug, Clone, Default)] pub struct State { last_size: Option, @@ -25,7 +27,7 @@ impl State { State::default() } - pub fn layout(&self, parent: Layout<'_>) -> Layout<'_> { + fn layout(&self, parent: Layout<'_>) -> Layout<'_> { Layout::with_offset( parent.position() - Point::ORIGIN, &self.last_layout, @@ -33,12 +35,22 @@ impl State { } } +/// A widget that is aware of its dimensions. +/// +/// A [`Responsive`] widget will always try to fill all the available space of +/// its parent. #[allow(missing_debug_implementations)] pub struct Responsive<'a, Message, Renderer>( RefCell>, ); impl<'a, Message, Renderer> Responsive<'a, Message, Renderer> { + /// Creates a new [`Responsive`] widget with the given [`State`] and a + /// closure that produces its contents. + /// + /// The `view` closure will be provided with the current [`Size`] of + /// the [`Responsive`] widget and, therefore, can be used to build the + /// contents of the widget in a responsive way. pub fn new( state: &'a mut State, view: impl FnOnce(Size) -> Element<'a, Message, Renderer> + 'a,