diff options
34 files changed, 765 insertions, 131 deletions
diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index e489f210..03e6cd4a 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 8225e9e7..2962ca25 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,14 @@ +use iced::alignment::{self, Alignment}; +use iced::button::{self, Button}; +use iced::executor; +use iced::keyboard; +use iced::pane_grid::{self, PaneGrid}; +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_lazy::responsive::{self, Responsive}; use iced_native::{event, subscription, Event}; pub fn main() -> iced::Result { @@ -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<Message> { } 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<Message> { 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(24)) .push(controls); Container::new(content) diff --git a/glutin/src/application.rs b/glutin/src/application.rs index a1165de3..437c17ee 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<A, E, C>( 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<A, E, C>( 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<A, E, C>( 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/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 97846d65..f9722f33 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<B>, ) -> mouse::Interaction { let bounds = layout.bounds(); let cursor = Cursor::from_window_position(cursor_position); diff --git a/lazy/src/cache.rs b/lazy/src/cache.rs new file mode 100644 index 00000000..229b7912 --- /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<overlay::Element<'this, Message, Renderer>>, +} diff --git a/lazy/src/component.rs b/lazy/src/component.rs index fd3f6ff7..2b729045 100644 --- a/lazy/src/component.rs +++ b/lazy/src/component.rs @@ -1,3 +1,6 @@ +//! Build and reuse custom widgets using The Elm Architecture. +use crate::{Cache, CacheBuilder}; + use iced_native::event; use iced_native::layout::{self, Layout}; use iced_native::mouse; @@ -12,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<Message, Renderer> { + /// 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<Message>; + + /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event) + /// on user interaction. + fn view(&mut self) -> Element<Self::Event, Renderer>; +} + +/// 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> @@ -24,11 +56,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(), @@ -40,14 +72,6 @@ where }) } -pub trait Component<Message, Renderer> { - type Event; - - fn update(&mut self, event: Self::Event) -> Option<Message>; - - fn view(&mut self) -> Element<Self::Event, Renderer>; -} - struct Instance<'a, Message, Renderer, Event> { state: RefCell<Option<State<'a, Message, Renderer, Event>>>, } @@ -55,20 +79,11 @@ struct Instance<'a, Message, Renderer, Event> { #[self_referencing] struct State<'a, Message: 'a, Renderer: 'a, Event: 'a> { component: Box<dyn Component<Message, Renderer, Event = Event> + 'a>, - - #[borrows(mut component)] - #[covariant] - cache: Option<Cache<'this, Message, Renderer, Event>>, -} - -#[self_referencing] -struct Cache<'a, Message, Renderer: 'a, Event: 'a> { - element: Element<'a, Event, Renderer>, message: PhantomData<Message>, - #[borrows(mut element)] + #[borrows(mut component)] #[covariant] - overlay: Option<overlay::Element<'this, Event, Renderer>>, + cache: Option<Cache<'this, Event, Renderer>>, } impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { @@ -94,7 +109,6 @@ impl<'a, Message, Renderer, Event> Instance<'a, Message, Renderer, Event> { *cache = Some( CacheBuilder { element, - message: PhantomData, overlay_builder: |_| None, } .build(), @@ -149,7 +163,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 @@ -170,11 +184,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(), @@ -214,15 +228,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<overlay::Element<'_, Message, Renderer>> { let has_overlay = self .state @@ -235,8 +256,9 @@ where *cache = Some( CacheBuilder { element, - message: PhantomData, - overlay_builder: |element| element.overlay(layout), + overlay_builder: |element| { + element.overlay(layout, renderer) + }, } .build(), ); @@ -331,9 +353,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() } @@ -375,7 +403,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 = @@ -391,13 +419,13 @@ 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) + element.overlay(layout, renderer) }, } .build(), diff --git a/lazy/src/lib.rs b/lazy/src/lib.rs index 42e5f587..05fce765 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 00000000..da7bb408 --- /dev/null +++ b/lazy/src/responsive.rs @@ -0,0 +1,425 @@ +//! Build responsive widgets. +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; + +/// The state of a [`Responsive`] widget. +#[derive(Debug, Clone, Default)] +pub struct State { + last_size: Option<Size>, + last_layout: layout::Node, + last_layout_hash: u64, +} + +impl State { + pub fn new() -> State { + State::default() + } + + fn layout(&self, parent: Layout<'_>) -> Layout<'_> { + Layout::with_offset( + parent.position() - Point::ORIGIN, + &self.last_layout, + ) + } +} + +/// 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<Internal<'a, Message, Renderer>>, +); + +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, + ) -> Self { + Self(RefCell::new(Internal { + state, + content: Content::Pending(Some(Box::new(view))), + })) + } +} + +impl<'a, Message, Renderer> Widget<Message, Renderer> + 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<overlay::Element<'_, Message, Renderer>> { + 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<R, T>( + &mut self, + renderer: R, + f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, + ) -> T + where + R: Deref<Target = Renderer>, + { + self.content.resolve(&mut self.state, renderer, f) + } +} + +enum Content<'a, Message, Renderer> { + Pending( + Option<Box<dyn FnOnce(Size) -> Element<'a, Message, Renderer> + 'a>>, + ), + Ready(Option<Cache<'a, Message, Renderer>>), +} + +impl<'a, Message, Renderer> Content<'a, Message, Renderer> +where + Renderer: iced_native::Renderer, +{ + fn resolve<R, T>( + &mut self, + state: &mut State, + renderer: R, + f: impl FnOnce(&State, R, &mut Element<'a, Message, Renderer>) -> T, + ) -> T + where + R: Deref<Target = Renderer>, + { + 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<Responsive<'a, Message, Renderer>> + 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<T>( + &self, + f: impl FnOnce(&overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option<T> { + 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<T>( + &self, + f: impl FnOnce(&mut overlay::Element<'_, Message, Renderer>) -> T, + ) -> Option<T> { + 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<Message, Renderer> + 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::<Marker>().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/element.rs b/native/src/element.rs index 7e806b08..6afa3f62 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<overlay::Element<'b, Message, Renderer>> { - 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<overlay::Element<'_, B, Renderer>> { 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<overlay::Element<'_, Message, Renderer>> { - self.element.overlay(layout) + self.element.overlay(layout, renderer) } } diff --git a/native/src/lib.rs b/native/src/lib.rs index 4753bba2..a5526e6d 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/overlay.rs b/native/src/overlay.rs index e66d421a..d4b641af 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 70cb417e..e7621600 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 c0282aae..2deef551 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/program/state.rs b/native/src/program/state.rs index 26c0eb21..cb87a628 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>, + cache: Option<user_interface::Cache>, queued_events: Vec<Event>, queued_messages: Vec<P::Message>, 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 e916f52d..4a0aa9c6 100644 --- a/native/src/shell.rs +++ b/native/src/shell.rs @@ -8,6 +8,7 @@ pub struct Shell<'a, Message> { messages: &'a mut Vec<Message>, 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 40f7a204..4b1194be 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; @@ -42,7 +43,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 +63,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 +138,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 +158,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 +179,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,9 +202,11 @@ where renderer: &mut Renderer, clipboard: &mut dyn Clipboard, messages: &mut Vec<Message>, - ) -> Vec<event::Status> { + ) -> (State, Vec<event::Status>) { + 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; @@ -227,7 +232,7 @@ where &mut shell, ); - shell.with_invalid_layout(|| { + shell.revalidate_layout(|| { layer = Self::overlay_layer( None, bounds, @@ -236,6 +241,10 @@ where ); }); + if shell.are_widgets_invalid() { + state = State::Outdated; + } + event_status }) .collect(); @@ -255,7 +264,7 @@ where (cursor_position, vec![event::Status::Ignored; events.len()]) }; - events + let event_statuses = events .iter() .cloned() .zip(overlay_statuses.into_iter()) @@ -271,7 +280,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 +297,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 +321,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 +341,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(); @@ -376,7 +392,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(), @@ -417,6 +433,7 @@ where Layout::new(&self.base.layout), cursor_position, &viewport, + renderer, ); let Self { @@ -434,30 +451,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) } @@ -548,3 +568,14 @@ impl Default for Cache { Cache::new() } } + +/// 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/native/src/widget.rs b/native/src/widget.rs index caa3f0bc..acd2e580 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -176,6 +176,7 @@ where _layout: Layout<'_>, _cursor_position: Point, _viewport: &Rectangle, + _renderer: &Renderer, ) -> mouse::Interaction { mouse::Interaction::Idle } @@ -184,6 +185,7 @@ where fn overlay( &mut self, _layout: Layout<'_>, + _renderer: &Renderer, ) -> Option<overlay::Element<'_, Message, Renderer>> { None } diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs index 686289e4..b4a3adc3 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<overlay::Element<'_, Message, Renderer>> { - 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 95856928..a314140c 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 4c43c8c8..66ed5e23 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<overlay::Element<'_, Message, Renderer>> { 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 0c291788..4444732a 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<overlay::Element<'_, Message, Renderer>> { - 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 645ce852..486d5fa5 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 b3d8b819..5f293fa6 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<overlay::Element<'_, Message, Renderer>> { 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 533827b7..6d12aa2d 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<overlay::Element<'_, Message, Renderer>> { 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 353e1ce9..ea9ebfc4 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<overlay::Element<'_, Message, Renderer>> { 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 1f79e07a..a200fb13 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<overlay::Element<'_, Message, Renderer>> { if *self.is_open { let bounds = layout.bounds(); diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs index 1dbe7cf5..c6cc46ed 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 a0174f79..c423d31f 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<overlay::Element<'_, Message, Renderer>> { 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 3ef737c0..4b005c6b 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<overlay::Element<'_, Message, Renderer>> { 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 65632a5c..917bfc14 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 f06b6aa6..03adeb12 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 7eca58d2..002e0a4f 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 79a57824..f6630864 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( diff --git a/winit/src/application.rs b/winit/src/application.rs index 32423e80..e9212b5e 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<A, E, C>( 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<A, E, C>( 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<A, E, C>( 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, |