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.rs329
1 files changed, 237 insertions, 92 deletions
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index b71b9003..7a64ac59 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -1,7 +1,7 @@
-use crate::{
- input::{mouse, Touch},
- layout, Clipboard, Element, Event, Layout, Point, Size,
-};
+use crate::event::{self, Event};
+use crate::layout;
+use crate::overlay;
+use crate::{Clipboard, Element, Layout, Point, Rectangle, Size};
use std::hash::Hasher;
@@ -12,14 +12,17 @@ use std::hash::Hasher;
/// 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.
///
-/// [`Layout`]: struct.Layout.html
+/// # Example
+/// The [`integration` example] uses a [`UserInterface`] to integrate Iced in
+/// an existing graphical application.
+///
+/// [`integration` example]: https://github.com/hecrj/iced/tree/0.2/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
- hash: u64,
root: Element<'a, Message, Renderer>,
- layout: layout::Node,
+ base: Layer,
+ overlay: Option<Layer>,
bounds: Size,
- cursor_position: Point,
}
impl<'a, Message, Renderer> UserInterface<'a, Message, Renderer>
@@ -31,10 +34,6 @@ where
/// It is able to avoid expensive computations when using a [`Cache`]
/// obtained from a previous instance of a [`UserInterface`].
///
- /// [`Element`]: struct.Element.html
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
- ///
/// # Example
/// Imagine we want to build a [`UserInterface`] for
/// [the counter example that we previously wrote](index.html#usage). Here
@@ -91,25 +90,37 @@ where
) -> Self {
let root = root.into();
- let hash = {
- let hasher = &mut crate::Hasher::default();
- root.hash_layout(hasher);
+ let (base, overlay) = {
+ let hash = {
+ let hasher = &mut crate::Hasher::default();
+ root.hash_layout(hasher);
- hasher.finish()
- };
+ hasher.finish()
+ };
- let layout = if hash == cache.hash && bounds == cache.bounds {
- cache.layout
- } else {
- renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds))
+ let layout_is_cached =
+ hash == cache.base.hash && bounds == cache.bounds;
+
+ let (layout, overlay) = if layout_is_cached {
+ (cache.base.layout, cache.overlay)
+ } else {
+ (
+ renderer.layout(
+ &root,
+ &layout::Limits::new(Size::ZERO, bounds),
+ ),
+ None,
+ )
+ };
+
+ (Layer { layout, hash }, overlay)
};
UserInterface {
- hash,
root,
- layout,
+ base,
+ overlay,
bounds,
- cursor_position: cache.cursor_position,
}
}
@@ -118,15 +129,12 @@ where
/// It returns __messages__ that may have been produced as a result of user
/// interactions. You should feed these to your __update logic__.
///
- /// [`UserInterface`]: struct.UserInterface.html
- /// [`Event`]: enum.Event.html
- ///
/// # Example
/// Let's allow our [counter](index.html#usage) to change state by
/// completing [the previous example](#example):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size};
+ /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -148,12 +156,14 @@ where
/// let mut cache = Cache::new();
/// let mut renderer = Renderer::new();
/// let mut window_size = Size::new(1024.0, 768.0);
+ /// let mut cursor_position = Point::default();
///
/// // Initialize our event storage
/// let mut events = Vec::new();
+ /// let mut messages = Vec::new();
///
/// loop {
- /// // Process system events...
+ /// // Obtain system events...
///
/// let mut user_interface = UserInterface::build(
/// counter.view(),
@@ -163,61 +173,103 @@ where
/// );
///
/// // Update the user interface
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// let event_statuses = user_interface.update(
+ /// &events,
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// &mut messages
+ /// );
///
/// cache = user_interface.into_cache();
///
/// // Process the produced messages
- /// for message in messages {
+ /// for message in messages.drain(..) {
/// counter.update(message);
/// }
/// }
/// ```
pub fn update(
&mut self,
- events: impl IntoIterator<Item = Event>,
+ events: &[Event],
+ cursor_position: Point,
clipboard: Option<&dyn Clipboard>,
renderer: &Renderer,
- ) -> Vec<Message> {
- let mut messages = Vec::new();
-
- for event in events {
- match event {
- Event::Mouse(mouse::Event::CursorMoved { position })
- | Event::Touch(Touch { position, .. }) => {
- self.cursor_position = position;
- }
- _ => {}
- }
-
- self.root.widget.on_event(
- event,
- Layout::new(&self.layout),
- self.cursor_position,
- &mut messages,
+ messages: &mut Vec<Message>,
+ ) -> Vec<event::Status> {
+ let (base_cursor, overlay_statuses) = if let Some(mut overlay) =
+ self.root.overlay(Layout::new(&self.base.layout))
+ {
+ let layer = Self::overlay_layer(
+ self.overlay.take(),
+ self.bounds,
+ &mut overlay,
renderer,
- clipboard,
);
- }
- messages
+ let event_statuses = events
+ .iter()
+ .cloned()
+ .map(|event| {
+ overlay.on_event(
+ event,
+ Layout::new(&layer.layout),
+ cursor_position,
+ messages,
+ renderer,
+ clipboard,
+ )
+ })
+ .collect();
+
+ let base_cursor = if layer.layout.bounds().contains(cursor_position)
+ {
+ // TODO: Type-safe cursor availability
+ Point::new(-1.0, -1.0)
+ } else {
+ cursor_position
+ };
+
+ self.overlay = Some(layer);
+
+ (base_cursor, event_statuses)
+ } else {
+ (cursor_position, vec![event::Status::Ignored; events.len()])
+ };
+
+ events
+ .iter()
+ .cloned()
+ .zip(overlay_statuses.into_iter())
+ .map(|(event, overlay_status)| {
+ let event_status = self.root.widget.on_event(
+ event,
+ Layout::new(&self.base.layout),
+ base_cursor,
+ messages,
+ renderer,
+ clipboard,
+ );
+
+ event_status.merge(overlay_status)
+ })
+ .collect()
}
/// Draws the [`UserInterface`] with the provided [`Renderer`].
///
- /// It returns the current state of the [`MouseCursor`]. You should update
- /// the icon of the mouse cursor accordingly in your system.
+ /// It returns the some [`Renderer::Output`]. You should update the icon of
+ /// the mouse cursor accordingly in your system.
///
- /// [`UserInterface`]: struct.UserInterface.html
- /// [`Renderer`]: trait.Renderer.html
- /// [`MouseCursor`]: enum.MouseCursor.html
+ /// [`Renderer`]: crate::Renderer
+ /// [`Renderer::Output`]: crate::Renderer::Output
///
/// # Example
/// We can finally draw our [counter](index.html#usage) by
/// [completing the last example](#example-1):
///
/// ```no_run
- /// use iced_native::{UserInterface, Cache, Size};
+ /// use iced_native::{UserInterface, Cache, Size, Point};
/// use iced_wgpu::Renderer;
///
/// # mod iced_wgpu {
@@ -239,10 +291,12 @@ where
/// let mut cache = 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 events = Vec::new();
+ /// let mut messages = Vec::new();
///
/// loop {
- /// // Process system events...
+ /// // Obtain system events...
///
/// let mut user_interface = UserInterface::build(
/// counter.view(),
@@ -251,14 +305,21 @@ where
/// &mut renderer,
/// );
///
- /// let messages = user_interface.update(events.drain(..), None, &renderer);
+ /// // Update the user interface
+ /// let event_statuses = user_interface.update(
+ /// &events,
+ /// cursor_position,
+ /// None,
+ /// &renderer,
+ /// &mut messages
+ /// );
///
/// // Draw the user interface
- /// let mouse_cursor = user_interface.draw(&mut renderer);
+ /// let mouse_cursor = user_interface.draw(&mut renderer, cursor_position);
///
/// cache = user_interface.into_cache();
///
- /// for message in messages {
+ /// for message in messages.drain(..) {
/// counter.update(message);
/// }
///
@@ -266,39 +327,132 @@ where
/// // Flush rendering operations...
/// }
/// ```
- pub fn draw(&self, renderer: &mut Renderer) -> Renderer::Output {
- self.root.widget.draw(
+ pub fn draw(
+ &mut self,
+ renderer: &mut Renderer,
+ cursor_position: Point,
+ ) -> Renderer::Output {
+ let viewport = Rectangle::with_size(self.bounds);
+
+ let overlay = if let Some(mut overlay) =
+ self.root.overlay(Layout::new(&self.base.layout))
+ {
+ let layer = Self::overlay_layer(
+ self.overlay.take(),
+ self.bounds,
+ &mut overlay,
+ renderer,
+ );
+
+ let overlay_bounds = layer.layout.bounds();
+
+ let overlay_primitives = overlay.draw(
+ renderer,
+ &Renderer::Defaults::default(),
+ Layout::new(&layer.layout),
+ cursor_position,
+ );
+
+ self.overlay = Some(layer);
+
+ Some((overlay_primitives, overlay_bounds))
+ } else {
+ None
+ };
+
+ if let Some((overlay_primitives, overlay_bounds)) = overlay {
+ let base_cursor = if overlay_bounds.contains(cursor_position) {
+ Point::new(-1.0, -1.0)
+ } else {
+ cursor_position
+ };
+
+ let base_primitives = self.root.widget.draw(
+ renderer,
+ &Renderer::Defaults::default(),
+ Layout::new(&self.base.layout),
+ base_cursor,
+ &viewport,
+ );
+
+ renderer.overlay(
+ base_primitives,
+ overlay_primitives,
+ overlay_bounds,
+ )
+ } else {
+ self.root.widget.draw(
+ renderer,
+ &Renderer::Defaults::default(),
+ Layout::new(&self.base.layout),
+ cursor_position,
+ &viewport,
+ )
+ }
+ }
+
+ /// 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 {
+ base: self.base,
+ overlay: self.overlay,
+ bounds: self.bounds,
+ },
renderer,
- &Renderer::Defaults::default(),
- Layout::new(&self.layout),
- self.cursor_position,
)
}
/// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
/// process.
- ///
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
pub fn into_cache(self) -> Cache {
Cache {
- hash: self.hash,
- layout: self.layout,
+ base: self.base,
+ overlay: self.overlay,
bounds: self.bounds,
- cursor_position: self.cursor_position,
+ }
+ }
+
+ fn overlay_layer(
+ cache: Option<Layer>,
+ bounds: Size,
+ overlay: &mut overlay::Element<'_, Message, Renderer>,
+ renderer: &Renderer,
+ ) -> Layer {
+ let new_hash = {
+ let hasher = &mut crate::Hasher::default();
+ overlay.hash_layout(hasher);
+
+ hasher.finish()
+ };
+
+ let layout = match cache {
+ Some(Layer { hash, layout }) if new_hash == hash => layout,
+ _ => overlay.layout(renderer, bounds),
+ };
+
+ Layer {
+ layout,
+ hash: new_hash,
}
}
}
+#[derive(Debug, Clone)]
+struct Layer {
+ layout: layout::Node,
+ hash: u64,
+}
+
/// Reusable data of a specific [`UserInterface`].
-///
-/// [`UserInterface`]: struct.UserInterface.html
#[derive(Debug, Clone)]
pub struct Cache {
- hash: u64,
- layout: layout::Node,
+ base: Layer,
+ overlay: Option<Layer>,
bounds: Size,
- cursor_position: Point,
}
impl Cache {
@@ -306,15 +460,14 @@ impl Cache {
///
/// You should use this to initialize a [`Cache`] before building your first
/// [`UserInterface`].
- ///
- /// [`Cache`]: struct.Cache.html
- /// [`UserInterface`]: struct.UserInterface.html
pub fn new() -> Cache {
Cache {
- hash: 0,
- layout: layout::Node::new(Size::new(0.0, 0.0)),
+ base: Layer {
+ layout: layout::Node::new(Size::new(0.0, 0.0)),
+ hash: 0,
+ },
+ overlay: None,
bounds: Size::ZERO,
- cursor_position: Point::new(-1.0, -1.0),
}
}
}
@@ -324,11 +477,3 @@ impl Default for Cache {
Cache::new()
}
}
-
-impl PartialEq for Cache {
- fn eq(&self, other: &Cache) -> bool {
- self.hash == other.hash && self.cursor_position == other.cursor_position
- }
-}
-
-impl Eq for Cache {}