//! Build and reuse custom widgets using The Elm Architecture. use iced_native::event; use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::overlay; use iced_native::renderer; use iced_native::widget::tree::{self, Tree}; use iced_native::{ Clipboard, Element, Length, Point, Rectangle, Shell, Size, Widget, }; use ouroboros::self_referencing; use std::cell::{Ref, RefCell}; 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 internal state of this [`Component`]. type State: Default; /// 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, state: &mut Self::State, event: Self::Event, ) -> Option; /// Produces the widgets of the [`Component`], which may trigger an [`Event`](Component::Event) /// on user interaction. fn view(&self, state: &Self::State) -> 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> where C: Component + 'a, C::State: 'static, Message: 'a, Renderer: iced_native::Renderer + 'a, { Element::new(Instance { state: RefCell::new(Some( StateBuilder { component: Box::new(component), message: PhantomData, state: PhantomData, element_builder: |_| None, } .build(), )), }) } struct Instance<'a, Message, Renderer, Event, S> { state: RefCell>>, } #[self_referencing] struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> { component: Box + 'a>, message: PhantomData, state: PhantomData, #[borrows(component)] #[covariant] element: Option>, } impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S> where S: Default, { fn rebuild_element(&self, state: &S) { let heads = self.state.borrow_mut().take().unwrap().into_heads(); *self.state.borrow_mut() = Some( StateBuilder { component: heads.component, message: PhantomData, state: PhantomData, element_builder: |component| Some(component.view(state)), } .build(), ); } fn with_element( &self, f: impl FnOnce(&Element<'_, Event, Renderer>) -> T, ) -> T { self.with_element_mut(|element| f(element)) } fn with_element_mut( &self, f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T, ) -> T { self.state .borrow_mut() .as_mut() .unwrap() .with_element_mut(|element| f(element.as_mut().unwrap())) } } impl<'a, Message, Renderer, Event, S> Widget for Instance<'a, Message, Renderer, Event, S> where S: 'static + Default, Renderer: iced_native::Renderer, { fn tag(&self) -> tree::Tag { struct Tag(T); tree::Tag::of::>() } fn state(&self) -> tree::State { tree::State::new(S::default()) } fn children(&self) -> Vec { self.rebuild_element(&S::default()); self.with_element(|element| vec![Tree::new(element)]) } fn diff(&self, tree: &mut Tree) { self.rebuild_element(tree.state.downcast_ref()); self.with_element(|element| { tree.diff_children(std::slice::from_ref(&element)) }) } fn width(&self) -> Length { self.with_element(|element| element.as_widget().width()) } fn height(&self) -> Length { self.with_element(|element| element.as_widget().height()) } fn layout( &self, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { self.with_element(|element| { element.as_widget().layout(renderer, limits) }) } fn on_event( &mut self, tree: &mut Tree, event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); let event_status = self.with_element_mut(|element| { element.as_widget_mut().on_event( &mut tree.children[0], event, layout, cursor_position, renderer, clipboard, &mut local_shell, ) }); local_shell.revalidate_layout(|| shell.invalidate_layout()); if !local_messages.is_empty() { let mut heads = self.state.take().unwrap().into_heads(); for message in local_messages.into_iter().filter_map(|message| { heads .component .update(tree.state.downcast_mut::(), message) }) { shell.publish(message); } self.state = RefCell::new(Some( StateBuilder { component: heads.component, message: PhantomData, state: PhantomData, element_builder: |state| { Some(state.view(tree.state.downcast_ref::())) }, } .build(), )); self.with_element(|element| { tree.diff_children(std::slice::from_ref(&element)) }); shell.invalidate_layout(); } event_status } fn draw( &self, tree: &Tree, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, ) { self.with_element(|element| { element.as_widget().draw( &tree.children[0], renderer, theme, style, layout, cursor_position, viewport, ); }); } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.with_element(|element| { element.as_widget().mouse_interaction( &tree.children[0], layout, cursor_position, viewport, renderer, ) }) } fn overlay<'b>( &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { let overlay = OverlayBuilder { instance: self, instance_ref_builder: |instance| instance.state.borrow(), tree, types: PhantomData, overlay_builder: |instance, tree| { instance .as_ref() .unwrap() .borrow_element() .as_ref() .unwrap() .as_widget() .overlay(&mut tree.children[0], layout, renderer) }, } .build(); let has_overlay = overlay.with_overlay(|overlay| { overlay.as_ref().map(overlay::Element::position) }); has_overlay.map(|position| { overlay::Element::new( position, Box::new(OverlayInstance { overlay: Some(overlay), }), ) }) } } #[self_referencing] struct Overlay<'a, 'b, Message, Renderer, Event, S> { instance: &'a Instance<'b, Message, Renderer, Event, S>, tree: &'a mut Tree, types: PhantomData<(Message, Event, S)>, #[borrows(instance)] #[covariant] instance_ref: Ref<'this, Option>>, #[borrows(instance_ref, mut tree)] #[covariant] overlay: Option>, } struct OverlayInstance<'a, 'b, Message, Renderer, Event, S> { overlay: Option>, } impl<'a, 'b, Message, Renderer, Event, S> OverlayInstance<'a, 'b, Message, Renderer, Event, S> { fn with_overlay_maybe( &self, f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T, ) -> Option { self.overlay .as_ref() .unwrap() .borrow_overlay() .as_ref() .map(f) } fn with_overlay_mut_maybe( &mut self, f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T, ) -> Option { self.overlay .as_mut() .unwrap() .with_overlay_mut(|overlay| overlay.as_mut().map(f)) } } impl<'a, 'b, Message, Renderer, Event, S> overlay::Overlay for OverlayInstance<'a, 'b, Message, Renderer, Event, S> where Renderer: iced_native::Renderer, S: 'static + Default, { fn layout( &self, renderer: &Renderer, bounds: Size, position: Point, ) -> layout::Node { self.with_overlay_maybe(|overlay| { let vector = position - overlay.position(); overlay.layout(renderer, bounds).translate(vector) }) .unwrap_or_default() } fn draw( &self, renderer: &mut Renderer, theme: &Renderer::Theme, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, ) { let _ = self.with_overlay_maybe(|overlay| { overlay.draw(renderer, theme, style, layout, cursor_position); }); } fn mouse_interaction( &self, layout: Layout<'_>, cursor_position: Point, viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { self.with_overlay_maybe(|overlay| { overlay.mouse_interaction( layout, cursor_position, viewport, renderer, ) }) .unwrap_or_default() } fn on_event( &mut self, event: iced_native::Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> iced_native::event::Status { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); let event_status = self .with_overlay_mut_maybe(|overlay| { overlay.on_event( event, layout, cursor_position, renderer, clipboard, &mut local_shell, ) }) .unwrap_or(iced_native::event::Status::Ignored); local_shell.revalidate_layout(|| shell.invalidate_layout()); if !local_messages.is_empty() { let overlay = self.overlay.take().unwrap().into_heads(); let mut heads = overlay.instance.state.take().unwrap().into_heads(); for message in local_messages.into_iter().filter_map(|message| { heads .component .update(overlay.tree.state.downcast_mut::(), message) }) { shell.publish(message); } *overlay.instance.state.borrow_mut() = Some( StateBuilder { component: heads.component, message: PhantomData, state: PhantomData, element_builder: |state| { Some(state.view(overlay.tree.state.downcast_ref::())) }, } .build(), ); overlay.instance.with_element(|element| { overlay.tree.diff_children(std::slice::from_ref(&element)) }); self.overlay = Some( OverlayBuilder { instance: overlay.instance, instance_ref_builder: |instance| instance.state.borrow(), tree: overlay.tree, types: PhantomData, overlay_builder: |_, _| None, } .build(), ); shell.invalidate_layout(); } event_status } }