summaryrefslogtreecommitdiffstats
path: root/native/src/user_interface.rs
diff options
context:
space:
mode:
Diffstat (limited to 'native/src/user_interface.rs')
-rw-r--r--native/src/user_interface.rs592
1 files changed, 0 insertions, 592 deletions
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
deleted file mode 100644
index 315027fa..00000000
--- a/native/src/user_interface.rs
+++ /dev/null
@@ -1,592 +0,0 @@
-//! Implement your own event loop to drive a user interface.
-use crate::core::event::{self, Event};
-use crate::core::layout;
-use crate::core::mouse;
-use crate::core::renderer;
-use crate::core::widget;
-use crate::core::window;
-use crate::core::{Clipboard, Point, Rectangle, Size, Vector};
-use crate::core::{Element, Layout, Shell};
-
-/// A set of interactive graphical elements with a specific [`Layout`].
-///
-/// It can be updated and drawn.
-///
-/// Iced tries to avoid dictating how to write your event loop. You are in
-/// charge of using this type in your system in any way you want.
-///
-/// # Example
-/// The [`integration_opengl`] & [`integration_wgpu`] examples use a
-/// [`UserInterface`] to integrate Iced in an existing graphical application.
-///
-/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_opengl
-/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.8/examples/integration_wgpu
-#[allow(missing_debug_implementations)]
-pub struct UserInterface<'a, Message, Renderer> {
- root: Element<'a, Message, Renderer>,
- base: layout::Node,
- state: widget::Tree,
- overlay: Option<layout::Node>,
- bounds: Size,
-}
-
-impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
-where
- Renderer: iced_core::Renderer,
-{
- /// Builds a user interface for an [`Element`].
- ///
- /// It is able to avoid expensive computations when using a [`Cache`]
- /// obtained from a previous instance of a [`UserInterface`].
- ///
- /// # Example
- /// Imagine we want to build a [`UserInterface`] for
- /// [the counter example that we previously wrote](index.html#usage). Here
- /// is naive way to set up our application loop:
- ///
- /// ```no_run
- /// # mod iced_wgpu {
- /// # pub use iced_native::core::renderer::Null as Renderer;
- /// # }
- /// #
- /// # pub struct Counter;
- /// #
- /// # impl Counter {
- /// # pub fn new() -> Self { Counter }
- /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
- /// # pub fn update(&mut self, _: ()) {}
- /// # }
- /// use iced_native::core::Size;
- /// use iced_native::user_interface::{self, UserInterface};
- /// use iced_wgpu::Renderer;
- ///
- /// // Initialization
- /// let mut counter = Counter::new();
- /// let mut cache = user_interface::Cache::new();
- /// let mut renderer = Renderer::new();
- /// let mut window_size = Size::new(1024.0, 768.0);
- ///
- /// // Application loop
- /// loop {
- /// // Process system events here...
- ///
- /// // Build the user interface
- /// let user_interface = UserInterface::build(
- /// counter.view(),
- /// window_size,
- /// cache,
- /// &mut renderer,
- /// );
- ///
- /// // Update and draw the user interface here...
- /// // ...
- ///
- /// // Obtain the cache for the next iteration
- /// cache = user_interface.into_cache();
- /// }
- /// ```
- pub fn build<E: Into<Element<'a, Message, Renderer>>>(
- root: E,
- bounds: Size,
- cache: Cache,
- renderer: &mut Renderer,
- ) -> Self {
- let root = root.into();
-
- let Cache { mut state } = cache;
- state.diff(root.as_widget());
-
- let base =
- renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));
-
- UserInterface {
- root,
- base,
- state,
- overlay: None,
- bounds,
- }
- }
-
- /// Updates the [`UserInterface`] by processing each provided [`Event`].
- ///
- /// It returns __messages__ that may have been produced as a result of user
- /// interactions. You should feed these to your __update logic__.
- ///
- /// # Example
- /// Let's allow our [counter](index.html#usage) to change state by
- /// completing [the previous example](#example):
- ///
- /// ```no_run
- /// # mod iced_wgpu {
- /// # pub use iced_native::core::renderer::Null as Renderer;
- /// # }
- /// #
- /// # pub struct Counter;
- /// #
- /// # impl Counter {
- /// # pub fn new() -> Self { Counter }
- /// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
- /// # pub fn update(&mut self, _: ()) {}
- /// # }
- /// use iced_native::core::{clipboard, Size, Point};
- /// use iced_native::user_interface::{self, UserInterface};
- /// use iced_wgpu::Renderer;
- ///
- /// let mut counter = Counter::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();
- /// let mut clipboard = clipboard::Null;
- ///
- /// // Initialize our event storage
- /// let mut events = Vec::new();
- /// let mut messages = Vec::new();
- ///
- /// loop {
- /// // Obtain system events...
- ///
- /// let mut user_interface = UserInterface::build(
- /// counter.view(),
- /// window_size,
- /// cache,
- /// &mut renderer,
- /// );
- ///
- /// // Update the user interface
- /// let (state, event_statuses) = user_interface.update(
- /// &events,
- /// cursor_position,
- /// &mut renderer,
- /// &mut clipboard,
- /// &mut messages
- /// );
- ///
- /// cache = user_interface.into_cache();
- ///
- /// // Process the produced messages
- /// for message in messages.drain(..) {
- /// counter.update(message);
- /// }
- /// }
- /// ```
- pub fn update(
- &mut self,
- events: &[Event],
- cursor_position: Point,
- renderer: &mut Renderer,
- clipboard: &mut dyn Clipboard,
- messages: &mut Vec<Message>,
- ) -> (State, Vec<event::Status>) {
- use std::mem::ManuallyDrop;
-
- let mut outdated = false;
- let mut redraw_request = None;
-
- let mut manual_overlay =
- ManuallyDrop::new(self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ));
-
- let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
- let bounds = self.bounds;
-
- let mut overlay = manual_overlay.as_mut().unwrap();
- let mut layout = overlay.layout(renderer, bounds, Vector::ZERO);
- let mut event_statuses = Vec::new();
-
- for event in events.iter().cloned() {
- let mut shell = Shell::new(messages);
-
- let event_status = overlay.on_event(
- event,
- Layout::new(&layout),
- cursor_position,
- renderer,
- clipboard,
- &mut shell,
- );
-
- event_statuses.push(event_status);
-
- match (redraw_request, shell.redraw_request()) {
- (None, Some(at)) => {
- redraw_request = Some(at);
- }
- (Some(current), Some(new)) if new < current => {
- redraw_request = Some(new);
- }
- _ => {}
- }
-
- if shell.is_layout_invalid() {
- let _ = ManuallyDrop::into_inner(manual_overlay);
-
- self.base = renderer.layout(
- &self.root,
- &layout::Limits::new(Size::ZERO, self.bounds),
- );
-
- manual_overlay =
- ManuallyDrop::new(self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ));
-
- if manual_overlay.is_none() {
- break;
- }
-
- overlay = manual_overlay.as_mut().unwrap();
-
- shell.revalidate_layout(|| {
- layout = overlay.layout(renderer, bounds, Vector::ZERO);
- });
- }
-
- if shell.are_widgets_invalid() {
- outdated = true;
- }
- }
-
- let base_cursor = manual_overlay
- .as_ref()
- .filter(|overlay| {
- overlay.is_over(Layout::new(&layout), cursor_position)
- })
- .map(|_| {
- // TODO: Type-safe cursor availability
- Point::new(-1.0, -1.0)
- })
- .unwrap_or(cursor_position);
-
- self.overlay = Some(layout);
-
- (base_cursor, event_statuses)
- } else {
- (cursor_position, vec![event::Status::Ignored; events.len()])
- };
-
- let _ = ManuallyDrop::into_inner(manual_overlay);
-
- let event_statuses = events
- .iter()
- .cloned()
- .zip(overlay_statuses.into_iter())
- .map(|(event, overlay_status)| {
- if matches!(overlay_status, event::Status::Captured) {
- return overlay_status;
- }
-
- let mut shell = Shell::new(messages);
-
- let event_status = self.root.as_widget_mut().on_event(
- &mut self.state,
- event,
- Layout::new(&self.base),
- base_cursor,
- renderer,
- clipboard,
- &mut shell,
- );
-
- if matches!(event_status, event::Status::Captured) {
- self.overlay = None;
- }
-
- match (redraw_request, shell.redraw_request()) {
- (None, Some(at)) => {
- redraw_request = Some(at);
- }
- (Some(current), Some(new)) if new < current => {
- redraw_request = Some(new);
- }
- _ => {}
- }
-
- shell.revalidate_layout(|| {
- self.base = renderer.layout(
- &self.root,
- &layout::Limits::new(Size::ZERO, self.bounds),
- );
-
- self.overlay = None;
- });
-
- if shell.are_widgets_invalid() {
- outdated = true;
- }
-
- event_status.merge(overlay_status)
- })
- .collect();
-
- (
- if outdated {
- State::Outdated
- } else {
- State::Updated { redraw_request }
- },
- event_statuses,
- )
- }
-
- /// Draws the [`UserInterface`] with the provided [`Renderer`].
- ///
- /// It returns the current [`mouse::Interaction`]. You should update the
- /// icon of the mouse cursor accordingly in your system.
- ///
- /// [`Renderer`]: crate::Renderer
- ///
- /// # Example
- /// We can finally draw our [counter](index.html#usage) by
- /// [completing the last example](#example-1):
- ///
- /// ```no_run
- /// # mod iced_wgpu {
- /// # pub use iced_native::core::renderer::Null as Renderer;
- /// # pub type Theme = ();
- /// # }
- /// #
- /// # pub struct Counter;
- /// #
- /// # impl Counter {
- /// # pub fn new() -> Self { Counter }
- /// # pub fn view(&self) -> Element<(), Renderer> { unimplemented!() }
- /// # pub fn update(&mut self, _: ()) {}
- /// # }
- /// use iced_native::core::clipboard;
- /// use iced_native::core::renderer;
- /// use iced_native::core::{Element, Size, Point};
- /// use iced_native::user_interface::{self, UserInterface};
- /// use iced_wgpu::{Renderer, Theme};
- ///
- /// let mut counter = Counter::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();
- /// let mut clipboard = clipboard::Null;
- /// let mut events = Vec::new();
- /// let mut messages = Vec::new();
- /// let mut theme = Theme::default();
- ///
- /// loop {
- /// // Obtain system events...
- ///
- /// let mut user_interface = UserInterface::build(
- /// counter.view(),
- /// window_size,
- /// cache,
- /// &mut renderer,
- /// );
- ///
- /// // Update the user interface
- /// let event_statuses = user_interface.update(
- /// &events,
- /// cursor_position,
- /// &mut renderer,
- /// &mut clipboard,
- /// &mut messages
- /// );
- ///
- /// // Draw the user interface
- /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position);
- ///
- /// cache = user_interface.into_cache();
- ///
- /// for message in messages.drain(..) {
- /// counter.update(message);
- /// }
- ///
- /// // Update mouse cursor icon...
- /// // Flush rendering operations...
- /// }
- /// ```
- pub fn draw(
- &mut self,
- renderer: &mut Renderer,
- theme: &Renderer::Theme,
- style: &renderer::Style,
- cursor_position: Point,
- ) -> mouse::Interaction {
- // TODO: Move to shell level (?)
- renderer.clear();
-
- let viewport = Rectangle::with_size(self.bounds);
-
- let base_cursor = if let Some(overlay) = self
- .root
- .as_widget_mut()
- .overlay(&mut self.state, Layout::new(&self.base), renderer)
- {
- let overlay_layout = self.overlay.take().unwrap_or_else(|| {
- overlay.layout(renderer, self.bounds, Vector::ZERO)
- });
-
- let new_cursor_position = if overlay
- .is_over(Layout::new(&overlay_layout), cursor_position)
- {
- Point::new(-1.0, -1.0)
- } else {
- cursor_position
- };
-
- self.overlay = Some(overlay_layout);
-
- new_cursor_position
- } else {
- cursor_position
- };
-
- self.root.as_widget().draw(
- &self.state,
- renderer,
- theme,
- style,
- Layout::new(&self.base),
- base_cursor,
- &viewport,
- );
-
- let base_interaction = self.root.as_widget().mouse_interaction(
- &self.state,
- Layout::new(&self.base),
- cursor_position,
- &viewport,
- renderer,
- );
-
- let Self {
- overlay,
- root,
- base,
- ..
- } = self;
-
- // TODO: Currently, we need to call Widget::overlay twice to
- // implement the painter's algorithm properly.
- //
- // Once we have a proper persistent widget tree, we should be able to
- // avoid this additional call.
- overlay
- .as_ref()
- .and_then(|layout| {
- root.as_widget_mut()
- .overlay(&mut self.state, Layout::new(base), renderer)
- .map(|overlay| {
- let overlay_interaction = overlay.mouse_interaction(
- Layout::new(layout),
- cursor_position,
- &viewport,
- renderer,
- );
-
- let overlay_bounds = layout.bounds();
-
- renderer.with_layer(overlay_bounds, |renderer| {
- overlay.draw(
- renderer,
- theme,
- style,
- Layout::new(layout),
- cursor_position,
- );
- });
-
- if overlay.is_over(Layout::new(layout), cursor_position)
- {
- overlay_interaction
- } else {
- base_interaction
- }
- })
- })
- .unwrap_or(base_interaction)
- }
-
- /// Applies a [`widget::Operation`] to the [`UserInterface`].
- pub fn operate(
- &mut self,
- renderer: &Renderer,
- operation: &mut dyn widget::Operation<Message>,
- ) {
- self.root.as_widget().operate(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- operation,
- );
-
- if let Some(mut overlay) = self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ) {
- if self.overlay.is_none() {
- self.overlay =
- Some(overlay.layout(renderer, self.bounds, Vector::ZERO));
- }
-
- overlay.operate(
- Layout::new(self.overlay.as_ref().unwrap()),
- renderer,
- operation,
- );
- }
- }
-
- /// Relayouts and returns a new [`UserInterface`] using the provided
- /// bounds.
- pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
- Self::build(self.root, bounds, Cache { state: self.state }, renderer)
- }
-
- /// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
- /// process.
- pub fn into_cache(self) -> Cache {
- Cache { state: self.state }
- }
-}
-
-/// Reusable data of a specific [`UserInterface`].
-#[derive(Debug)]
-pub struct Cache {
- state: widget::Tree,
-}
-
-impl Cache {
- /// Creates an empty [`Cache`].
- ///
- /// You should use this to initialize a [`Cache`] before building your first
- /// [`UserInterface`].
- pub fn new() -> Cache {
- Cache {
- state: widget::Tree::empty(),
- }
- }
-}
-
-impl Default for Cache {
- fn default() -> 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 {
- /// The [`Instant`] when a redraw should be performed.
- redraw_request: Option<window::RedrawRequest>,
- },
-}