summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/src/lib.rs5
-rw-r--r--runtime/src/overlay.rs4
-rw-r--r--runtime/src/overlay/nested.rs353
-rw-r--r--runtime/src/program/state.rs10
-rw-r--r--runtime/src/user_interface.rs152
-rw-r--r--runtime/src/window.rs20
-rw-r--r--runtime/src/window/action.rs35
-rw-r--r--runtime/src/window/screenshot.rs92
8 files changed, 584 insertions, 87 deletions
diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs
index 8a277e47..4bbf9687 100644
--- a/runtime/src/lib.rs
+++ b/runtime/src/lib.rs
@@ -33,7 +33,7 @@
)]
#![deny(
missing_debug_implementations,
- //missing_docs,
+ missing_docs,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,
@@ -42,11 +42,12 @@
clippy::useless_conversion
)]
#![forbid(unsafe_code, rust_2018_idioms)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
+#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod clipboard;
pub mod command;
pub mod font;
pub mod keyboard;
+pub mod overlay;
pub mod program;
pub mod system;
pub mod user_interface;
diff --git a/runtime/src/overlay.rs b/runtime/src/overlay.rs
new file mode 100644
index 00000000..03390980
--- /dev/null
+++ b/runtime/src/overlay.rs
@@ -0,0 +1,4 @@
+//! Overlays for user interfaces.
+mod nested;
+
+pub use nested::Nested;
diff --git a/runtime/src/overlay/nested.rs b/runtime/src/overlay/nested.rs
new file mode 100644
index 00000000..b729f769
--- /dev/null
+++ b/runtime/src/overlay/nested.rs
@@ -0,0 +1,353 @@
+use crate::core::event;
+use crate::core::layout;
+use crate::core::mouse;
+use crate::core::overlay;
+use crate::core::renderer;
+use crate::core::widget;
+use crate::core::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size};
+
+/// An [`Overlay`] container that displays nested overlays
+#[allow(missing_debug_implementations)]
+pub struct Nested<'a, Message, Renderer> {
+ overlay: overlay::Element<'a, Message, Renderer>,
+}
+
+impl<'a, Message, Renderer> Nested<'a, Message, Renderer>
+where
+ Renderer: renderer::Renderer,
+{
+ /// Creates a nested overlay from the provided [`overlay::Element`]
+ pub fn new(element: overlay::Element<'a, Message, Renderer>) -> Self {
+ Self { overlay: element }
+ }
+
+ /// Returns the position of the [`Nested`] overlay.
+ pub fn position(&self) -> Point {
+ self.overlay.position()
+ }
+
+ /// Returns the layout [`Node`] of the [`Nested`] overlay.
+ pub fn layout(
+ &mut self,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ renderer: &Renderer,
+ bounds: Size,
+ position: Point,
+ ) -> layout::Node
+ where
+ Renderer: renderer::Renderer,
+ {
+ let translation = position - Point::ORIGIN;
+
+ let node = element.layout(renderer, bounds, translation);
+
+ if let Some(mut nested) =
+ element.overlay(Layout::new(&node), renderer)
+ {
+ layout::Node::with_children(
+ node.size(),
+ vec![
+ node,
+ recurse(&mut nested, renderer, bounds, position),
+ ],
+ )
+ } else {
+ layout::Node::with_children(node.size(), vec![node])
+ }
+ }
+
+ recurse(&mut self.overlay, renderer, bounds, position)
+ }
+
+ /// Draws the [`Nested`] overlay using the associated `Renderer`.
+ pub fn draw(
+ &mut self,
+ renderer: &mut Renderer,
+ theme: &<Renderer as renderer::Renderer>::Theme,
+ style: &renderer::Style,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ ) {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ renderer: &mut Renderer,
+ theme: &<Renderer as renderer::Renderer>::Theme,
+ style: &renderer::Style,
+ cursor: mouse::Cursor,
+ ) where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ let nested_layout = layouts.next();
+
+ let is_over = cursor
+ .position()
+ .zip(nested_layout)
+ .and_then(|(cursor_position, nested_layout)| {
+ element.overlay(layout, renderer).map(|nested| {
+ nested.is_over(
+ nested_layout.children().next().unwrap(),
+ renderer,
+ cursor_position,
+ )
+ })
+ })
+ .unwrap_or_default();
+
+ renderer.with_layer(layout.bounds(), |renderer| {
+ element.draw(
+ renderer,
+ theme,
+ style,
+ layout,
+ if is_over {
+ mouse::Cursor::Unavailable
+ } else {
+ cursor
+ },
+ );
+ });
+
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(nested_layout)
+ {
+ recurse(
+ &mut nested,
+ nested_layout,
+ renderer,
+ theme,
+ style,
+ cursor,
+ );
+ }
+ }
+ }
+
+ recurse(&mut self.overlay, layout, renderer, theme, style, cursor);
+ }
+
+ /// Applies a [`widget::Operation`] to the [`Nested`] overlay.
+ pub fn operate(
+ &mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation<Message>,
+ ) {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ operation: &mut dyn widget::Operation<Message>,
+ ) where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ element.operate(layout, renderer, operation);
+
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(layouts.next())
+ {
+ recurse(&mut nested, nested_layout, renderer, operation);
+ }
+ }
+ }
+
+ recurse(&mut self.overlay, layout, renderer, operation)
+ }
+
+ /// Processes a runtime [`Event`].
+ pub fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> event::Status {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ event: Event,
+ cursor: mouse::Cursor,
+ renderer: &Renderer,
+ clipboard: &mut dyn Clipboard,
+ shell: &mut Shell<'_, Message>,
+ ) -> (event::Status, bool)
+ where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ let (nested_status, nested_is_over) =
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(layouts.next())
+ {
+ recurse(
+ &mut nested,
+ nested_layout,
+ event.clone(),
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ )
+ } else {
+ (event::Status::Ignored, false)
+ };
+
+ if matches!(nested_status, event::Status::Ignored) {
+ let is_over = nested_is_over
+ || cursor
+ .position()
+ .map(|cursor_position| {
+ element.is_over(
+ layout,
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default();
+
+ (
+ element.on_event(
+ event,
+ layout,
+ if nested_is_over {
+ mouse::Cursor::Unavailable
+ } else {
+ cursor
+ },
+ renderer,
+ clipboard,
+ shell,
+ ),
+ is_over,
+ )
+ } else {
+ (nested_status, nested_is_over)
+ }
+ } else {
+ (event::Status::Ignored, false)
+ }
+ }
+
+ let (status, _) = recurse(
+ &mut self.overlay,
+ layout,
+ event,
+ cursor,
+ renderer,
+ clipboard,
+ shell,
+ );
+
+ status
+ }
+
+ /// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay.
+ pub fn mouse_interaction(
+ &mut self,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> mouse::Interaction {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ cursor: mouse::Cursor,
+ viewport: &Rectangle,
+ renderer: &Renderer,
+ ) -> Option<mouse::Interaction>
+ where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ let layout = layouts.next()?;
+ let cursor_position = cursor.position()?;
+
+ if !element.is_over(layout, renderer, cursor_position) {
+ return None;
+ }
+
+ Some(
+ element
+ .overlay(layout, renderer)
+ .zip(layouts.next())
+ .and_then(|(mut overlay, layout)| {
+ recurse(
+ &mut overlay,
+ layout,
+ cursor,
+ viewport,
+ renderer,
+ )
+ })
+ .unwrap_or_else(|| {
+ element.mouse_interaction(
+ layout, cursor, viewport, renderer,
+ )
+ }),
+ )
+ }
+
+ recurse(&mut self.overlay, layout, cursor, viewport, renderer)
+ .unwrap_or_default()
+ }
+
+ /// Returns true if the cursor is over the [`Nested`] overlay.
+ pub fn is_over(
+ &mut self,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool {
+ fn recurse<Message, Renderer>(
+ element: &mut overlay::Element<'_, Message, Renderer>,
+ layout: Layout<'_>,
+ renderer: &Renderer,
+ cursor_position: Point,
+ ) -> bool
+ where
+ Renderer: renderer::Renderer,
+ {
+ let mut layouts = layout.children();
+
+ if let Some(layout) = layouts.next() {
+ if element.is_over(layout, renderer, cursor_position) {
+ return true;
+ }
+
+ if let Some((mut nested, nested_layout)) =
+ element.overlay(layout, renderer).zip(layouts.next())
+ {
+ recurse(
+ &mut nested,
+ nested_layout,
+ renderer,
+ cursor_position,
+ )
+ } else {
+ false
+ }
+ } else {
+ false
+ }
+ }
+
+ recurse(&mut self.overlay, layout, renderer, cursor_position)
+ }
+}
diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs
index 2fa9934d..d83e3f54 100644
--- a/runtime/src/program/state.rs
+++ b/runtime/src/program/state.rs
@@ -1,7 +1,7 @@
use crate::core::event::{self, Event};
use crate::core::mouse;
use crate::core::renderer;
-use crate::core::{Clipboard, Point, Size};
+use crate::core::{Clipboard, Size};
use crate::user_interface::{self, UserInterface};
use crate::{Command, Debug, Program};
@@ -88,7 +88,7 @@ where
pub fn update(
&mut self,
bounds: Size,
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut P::Renderer,
theme: &<P::Renderer as iced_core::Renderer>::Theme,
style: &renderer::Style,
@@ -108,7 +108,7 @@ where
let (_, event_statuses) = user_interface.update(
&self.queued_events,
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut messages,
@@ -131,7 +131,7 @@ where
let command = if messages.is_empty() {
debug.draw_started();
self.mouse_interaction =
- user_interface.draw(renderer, theme, style, cursor_position);
+ user_interface.draw(renderer, theme, style, cursor);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
@@ -163,7 +163,7 @@ where
debug.draw_started();
self.mouse_interaction =
- user_interface.draw(renderer, theme, style, cursor_position);
+ user_interface.draw(renderer, theme, style, cursor);
debug.draw_finished();
self.cache = Some(user_interface.into_cache());
diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs
index c29de7db..619423fd 100644
--- a/runtime/src/user_interface.rs
+++ b/runtime/src/user_interface.rs
@@ -5,8 +5,8 @@ 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};
+use crate::core::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
+use crate::overlay;
/// A set of interactive graphical elements with a specific [`Layout`].
///
@@ -16,11 +16,10 @@ use crate::core::{Element, Layout, Shell};
/// 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.
+/// The [`integration`] example uses a [`UserInterface`] to integrate Iced in an
+/// existing graphical application.
///
-/// [`integration_opengl`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_opengl
-/// [`integration_wgpu`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration_wgpu
+/// [`integration`]: https://github.com/iced-rs/iced/tree/0.9/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
@@ -129,7 +128,9 @@ where
/// # pub fn view(&self) -> iced_core::Element<(), Renderer> { unimplemented!() }
/// # pub fn update(&mut self, _: ()) {}
/// # }
- /// use iced_runtime::core::{clipboard, Size, Point};
+ /// use iced_runtime::core::clipboard;
+ /// use iced_runtime::core::mouse;
+ /// use iced_runtime::core::Size;
/// use iced_runtime::user_interface::{self, UserInterface};
/// use iced_wgpu::Renderer;
///
@@ -137,7 +138,7 @@ where
/// 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 cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
///
/// // Initialize our event storage
@@ -157,7 +158,7 @@ where
/// // Update the user interface
/// let (state, event_statuses) = user_interface.update(
/// &events,
- /// cursor_position,
+ /// cursor,
/// &mut renderer,
/// &mut clipboard,
/// &mut messages
@@ -174,7 +175,7 @@ where
pub fn update(
&mut self,
events: &[Event],
- cursor_position: Point,
+ cursor: mouse::Cursor,
renderer: &mut Renderer,
clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
@@ -184,18 +185,18 @@ where
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 mut manual_overlay = ManuallyDrop::new(
+ self.root
+ .as_widget_mut()
+ .overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new),
+ );
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 layout = overlay.layout(renderer, bounds, Point::ORIGIN);
let mut event_statuses = Vec::new();
for event in events.iter().cloned() {
@@ -204,7 +205,7 @@ where
let event_status = overlay.on_event(
event,
Layout::new(&layout),
- cursor_position,
+ cursor,
renderer,
clipboard,
&mut shell,
@@ -230,12 +231,16 @@ where
&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,
- ));
+ manual_overlay = ManuallyDrop::new(
+ self.root
+ .as_widget_mut()
+ .overlay(
+ &mut self.state,
+ Layout::new(&self.base),
+ renderer,
+ )
+ .map(overlay::Nested::new),
+ );
if manual_overlay.is_none() {
break;
@@ -244,7 +249,8 @@ where
overlay = manual_overlay.as_mut().unwrap();
shell.revalidate_layout(|| {
- layout = overlay.layout(renderer, bounds, Vector::ZERO);
+ layout =
+ overlay.layout(renderer, bounds, Point::ORIGIN);
});
}
@@ -253,22 +259,29 @@ where
}
}
- 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)
+ let base_cursor = if manual_overlay
+ .as_mut()
+ .and_then(|overlay| {
+ cursor.position().map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(&layout),
+ renderer,
+ cursor_position,
+ )
+ })
})
- .unwrap_or(cursor_position);
+ .unwrap_or_default()
+ {
+ mouse::Cursor::Unavailable
+ } else {
+ cursor
+ };
self.overlay = Some(layout);
(base_cursor, event_statuses)
} else {
- (cursor_position, vec![event::Status::Ignored; events.len()])
+ (cursor, vec![event::Status::Ignored; events.len()])
};
let _ = ManuallyDrop::into_inner(manual_overlay);
@@ -360,8 +373,9 @@ where
/// # pub fn update(&mut self, _: ()) {}
/// # }
/// use iced_runtime::core::clipboard;
+ /// use iced_runtime::core::mouse;
/// use iced_runtime::core::renderer;
- /// use iced_runtime::core::{Element, Size, Point};
+ /// use iced_runtime::core::{Element, Size};
/// use iced_runtime::user_interface::{self, UserInterface};
/// use iced_wgpu::{Renderer, Theme};
///
@@ -369,7 +383,7 @@ where
/// 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 cursor = mouse::Cursor::default();
/// let mut clipboard = clipboard::Null;
/// let mut events = Vec::new();
/// let mut messages = Vec::new();
@@ -388,14 +402,14 @@ where
/// // Update the user interface
/// let event_statuses = user_interface.update(
/// &events,
- /// cursor_position,
+ /// cursor,
/// &mut renderer,
/// &mut clipboard,
/// &mut messages
/// );
///
/// // Draw the user interface
- /// let mouse_cursor = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor_position);
+ /// let mouse_interaction = user_interface.draw(&mut renderer, &theme, &renderer::Style::default(), cursor);
///
/// cache = user_interface.into_cache();
///
@@ -412,35 +426,44 @@ where
renderer: &mut Renderer,
theme: &Renderer::Theme,
style: &renderer::Style,
- cursor_position: Point,
+ cursor: mouse::Cursor,
) -> mouse::Interaction {
// TODO: Move to shell level (?)
renderer.clear();
let viewport = Rectangle::with_size(self.bounds);
- let base_cursor = if let Some(overlay) = self
+ let base_cursor = if let Some(mut overlay) = self
.root
.as_widget_mut()
.overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new)
{
let overlay_layout = self.overlay.take().unwrap_or_else(|| {
- overlay.layout(renderer, self.bounds, Vector::ZERO)
+ overlay.layout(renderer, self.bounds, Point::ORIGIN)
});
- let new_cursor_position = if overlay
- .is_over(Layout::new(&overlay_layout), cursor_position)
+ let cursor = if cursor
+ .position()
+ .map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(&overlay_layout),
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default()
{
- Point::new(-1.0, -1.0)
+ mouse::Cursor::Unavailable
} else {
- cursor_position
+ cursor
};
self.overlay = Some(overlay_layout);
- new_cursor_position
+ cursor
} else {
- cursor_position
+ cursor
};
self.root.as_widget().draw(
@@ -456,7 +479,7 @@ where
let base_interaction = self.root.as_widget().mouse_interaction(
&self.state,
Layout::new(&self.base),
- cursor_position,
+ base_cursor,
&viewport,
renderer,
);
@@ -478,10 +501,11 @@ where
.and_then(|layout| {
root.as_widget_mut()
.overlay(&mut self.state, Layout::new(base), renderer)
- .map(|overlay| {
+ .map(overlay::Nested::new)
+ .map(|mut overlay| {
let overlay_interaction = overlay.mouse_interaction(
Layout::new(layout),
- cursor_position,
+ cursor,
&viewport,
renderer,
);
@@ -494,11 +518,20 @@ where
theme,
style,
Layout::new(layout),
- cursor_position,
+ cursor,
);
});
- if overlay.is_over(Layout::new(layout), cursor_position)
+ if cursor
+ .position()
+ .map(|cursor_position| {
+ overlay.is_over(
+ Layout::new(layout),
+ renderer,
+ cursor_position,
+ )
+ })
+ .unwrap_or_default()
{
overlay_interaction
} else {
@@ -522,14 +555,15 @@ where
operation,
);
- if let Some(mut overlay) = self.root.as_widget_mut().overlay(
- &mut self.state,
- Layout::new(&self.base),
- renderer,
- ) {
+ if let Some(mut overlay) = self
+ .root
+ .as_widget_mut()
+ .overlay(&mut self.state, Layout::new(&self.base), renderer)
+ .map(overlay::Nested::new)
+ {
if self.overlay.is_none() {
self.overlay =
- Some(overlay.layout(renderer, self.bounds, Vector::ZERO));
+ Some(overlay.layout(renderer, self.bounds, Point::ORIGIN));
}
overlay.operate(
diff --git a/runtime/src/window.rs b/runtime/src/window.rs
index 833a1125..e448edef 100644
--- a/runtime/src/window.rs
+++ b/runtime/src/window.rs
@@ -1,11 +1,14 @@
//! Build window-based GUI applications.
mod action;
+pub mod screenshot;
+
pub use action::Action;
+pub use screenshot::Screenshot;
use crate::command::{self, Command};
use crate::core::time::Instant;
-use crate::core::window::{Event, Icon, Mode, UserAttention};
+use crate::core::window::{Event, Icon, Level, Mode, UserAttention};
use crate::futures::subscription::{self, Subscription};
/// Subscribes to the frames of the window of the running application.
@@ -53,7 +56,7 @@ pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> {
Command::single(command::Action::Window(Action::Move { x, y }))
}
-/// Sets the [`Mode`] of the window.
+/// Changes the [`Mode`] of the window.
pub fn change_mode<Message>(mode: Mode) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeMode(mode)))
}
@@ -99,9 +102,9 @@ pub fn gain_focus<Message>() -> Command<Message> {
Command::single(command::Action::Window(Action::GainFocus))
}
-/// Changes whether or not the window will always be on top of other windows.
-pub fn change_always_on_top<Message>(on_top: bool) -> Command<Message> {
- Command::single(command::Action::Window(Action::ChangeAlwaysOnTop(on_top)))
+/// Changes the window [`Level`].
+pub fn change_level<Message>(level: Level) -> Command<Message> {
+ Command::single(command::Action::Window(Action::ChangeLevel(level)))
}
/// Fetches an identifier unique to the window.
@@ -115,3 +118,10 @@ pub fn fetch_id<Message>(
pub fn change_icon<Message>(icon: Icon) -> Command<Message> {
Command::single(command::Action::Window(Action::ChangeIcon(icon)))
}
+
+/// Captures a [`Screenshot`] from the window.
+pub fn screenshot<Message>(
+ f: impl FnOnce(Screenshot) -> Message + Send + 'static,
+) -> Command<Message> {
+ Command::single(command::Action::Window(Action::Screenshot(Box::new(f))))
+}
diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs
index 83b71c75..09be1810 100644
--- a/runtime/src/window/action.rs
+++ b/runtime/src/window/action.rs
@@ -1,13 +1,14 @@
-use crate::core::window::{Icon, Mode, UserAttention};
+use crate::core::window::{Icon, Level, Mode, UserAttention};
use crate::futures::MaybeSend;
+use crate::window::Screenshot;
use std::fmt;
/// An operation to be performed on some window.
pub enum Action<T> {
- /// Closes the current window and exits the application.
+ /// Close the current window and exits the application.
Close,
- /// Moves the window with the left mouse button until the button is
+ /// Move the window with the left mouse button until the button is
/// released.
///
/// There’s no guarantee that this will work unless the left mouse
@@ -20,7 +21,7 @@ pub enum Action<T> {
/// The new logical height of the window
height: u32,
},
- /// Sets the window to maximized or back
+ /// Set the window to maximized or back
Maximize(bool),
/// Set the window to minimized or back
Minimize(bool),
@@ -70,15 +71,11 @@ pub enum Action<T> {
///
/// - **Web / Wayland:** Unsupported.
GainFocus,
- /// Change whether or not the window will always be on top of other windows.
- ///
- /// ## Platform-specific
- ///
- /// - **Web / Wayland:** Unsupported.
- ChangeAlwaysOnTop(bool),
+ /// Change the window [`Level`].
+ ChangeLevel(Level),
/// Fetch an identifier unique to the window.
FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
- /// Changes the window [`Icon`].
+ /// Change the window [`Icon`].
///
/// On Windows and X11, this is typically the small icon in the top-left
/// corner of the titlebar.
@@ -93,6 +90,8 @@ pub enum Action<T> {
/// - **X11:** Has no universal guidelines for icon sizes, so you're at the whims of the WM. That
/// said, it's usually in the same ballpark as on Windows.
ChangeIcon(Icon),
+ /// Screenshot the viewport of the window.
+ Screenshot(Box<dyn FnOnce(Screenshot) -> T + 'static>),
}
impl<T> Action<T> {
@@ -119,11 +118,14 @@ impl<T> Action<T> {
Action::RequestUserAttention(attention_type)
}
Self::GainFocus => Action::GainFocus,
- Self::ChangeAlwaysOnTop(on_top) => {
- Action::ChangeAlwaysOnTop(on_top)
- }
+ Self::ChangeLevel(level) => Action::ChangeLevel(level),
Self::FetchId(o) => Action::FetchId(Box::new(move |s| f(o(s)))),
Self::ChangeIcon(icon) => Action::ChangeIcon(icon),
+ Self::Screenshot(tag) => {
+ Action::Screenshot(Box::new(move |screenshot| {
+ f(tag(screenshot))
+ }))
+ }
}
}
}
@@ -154,13 +156,14 @@ impl<T> fmt::Debug for Action<T> {
write!(f, "Action::RequestUserAttention")
}
Self::GainFocus => write!(f, "Action::GainFocus"),
- Self::ChangeAlwaysOnTop(on_top) => {
- write!(f, "Action::AlwaysOnTop({on_top})")
+ Self::ChangeLevel(level) => {
+ write!(f, "Action::ChangeLevel({level:?})")
}
Self::FetchId(_) => write!(f, "Action::FetchId"),
Self::ChangeIcon(_icon) => {
write!(f, "Action::ChangeIcon(icon)")
}
+ Self::Screenshot(_) => write!(f, "Action::Screenshot"),
}
}
}
diff --git a/runtime/src/window/screenshot.rs b/runtime/src/window/screenshot.rs
new file mode 100644
index 00000000..c84286b6
--- /dev/null
+++ b/runtime/src/window/screenshot.rs
@@ -0,0 +1,92 @@
+//! Take screenshots of a window.
+use crate::core::{Rectangle, Size};
+
+use std::fmt::{Debug, Formatter};
+use std::sync::Arc;
+
+/// Data of a screenshot, captured with `window::screenshot()`.
+///
+/// The `bytes` of this screenshot will always be ordered as `RGBA` in the sRGB color space.
+#[derive(Clone)]
+pub struct Screenshot {
+ /// The bytes of the [`Screenshot`].
+ pub bytes: Arc<Vec<u8>>,
+ /// The size of the [`Screenshot`].
+ pub size: Size<u32>,
+}
+
+impl Debug for Screenshot {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Screenshot: {{ \n bytes: {}\n size: {:?} }}",
+ self.bytes.len(),
+ self.size
+ )
+ }
+}
+
+impl Screenshot {
+ /// Creates a new [`Screenshot`].
+ pub fn new(bytes: Vec<u8>, size: Size<u32>) -> Self {
+ Self {
+ bytes: Arc::new(bytes),
+ size,
+ }
+ }
+
+ /// Crops a [`Screenshot`] to the provided `region`. This will always be relative to the
+ /// top-left corner of the [`Screenshot`].
+ pub fn crop(&self, region: Rectangle<u32>) -> Result<Self, CropError> {
+ if region.width == 0 || region.height == 0 {
+ return Err(CropError::Zero);
+ }
+
+ if region.x + region.width > self.size.width
+ || region.y + region.height > self.size.height
+ {
+ return Err(CropError::OutOfBounds);
+ }
+
+ // Image is always RGBA8 = 4 bytes per pixel
+ const PIXEL_SIZE: usize = 4;
+
+ let bytes_per_row = self.size.width as usize * PIXEL_SIZE;
+ let row_range = region.y as usize..(region.y + region.height) as usize;
+ let column_range = region.x as usize * PIXEL_SIZE
+ ..(region.x + region.width) as usize * PIXEL_SIZE;
+
+ let chopped = self.bytes.chunks(bytes_per_row).enumerate().fold(
+ vec![],
+ |mut acc, (row, bytes)| {
+ if row_range.contains(&row) {
+ acc.extend(&bytes[column_range.clone()]);
+ }
+
+ acc
+ },
+ );
+
+ Ok(Self {
+ bytes: Arc::new(chopped),
+ size: Size::new(region.width, region.height),
+ })
+ }
+}
+
+impl AsRef<[u8]> for Screenshot {
+ fn as_ref(&self) -> &[u8] {
+ &self.bytes
+ }
+}
+
+#[derive(Debug, thiserror::Error)]
+/// Errors that can occur when cropping a [`Screenshot`].
+pub enum CropError {
+ #[error("The cropped region is out of bounds.")]
+ /// The cropped region's size is out of bounds.
+ OutOfBounds,
+ #[error("The cropped region is not visible.")]
+ /// The cropped region's size is zero.
+ Zero,
+}