summaryrefslogtreecommitdiffstats
path: root/runtime/src/window
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/src/window')
-rw-r--r--runtime/src/window/action.rs175
-rw-r--r--runtime/src/window/screenshot.rs92
2 files changed, 267 insertions, 0 deletions
diff --git a/runtime/src/window/action.rs b/runtime/src/window/action.rs
new file mode 100644
index 00000000..cebec4ae
--- /dev/null
+++ b/runtime/src/window/action.rs
@@ -0,0 +1,175 @@
+use crate::core::window::{Icon, Level, Mode, UserAttention, Settings};
+use crate::core::Size;
+use crate::futures::MaybeSend;
+use crate::window::Screenshot;
+
+use std::fmt;
+
+/// An operation to be performed on some window.
+pub enum Action<T> {
+ /// Close the current window and exits the application.
+ Close,
+ /// 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
+ /// button was pressed immediately before this function is called.
+ Drag,
+ /// Spawns a new window with the provided [`window::Settings`].
+ Spawn {
+ /// The settings of the [`Window`].
+ settings: Settings,
+ },
+ /// Resize the window.
+ Resize(Size<u32>),
+ /// Fetch the current size of the window.
+ FetchSize(Box<dyn FnOnce(Size<u32>) -> T + 'static>),
+ /// Set the window to maximized or back
+ Maximize(bool),
+ /// Set the window to minimized or back
+ Minimize(bool),
+ /// Move the window.
+ ///
+ /// Unsupported on Wayland.
+ Move {
+ /// The new logical x location of the window
+ x: i32,
+ /// The new logical y location of the window
+ y: i32,
+ },
+ /// Change the [`Mode`] of the window.
+ ChangeMode(Mode),
+ /// Fetch the current [`Mode`] of the window.
+ FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>),
+ /// Toggle the window to maximized or back
+ ToggleMaximize,
+ /// Toggle whether window has decorations.
+ ///
+ /// ## Platform-specific
+ /// - **X11:** Not implemented.
+ /// - **Web:** Unsupported.
+ ToggleDecorations,
+ /// Request user attention to the window, this has no effect if the application
+ /// is already focused. How requesting for user attention manifests is platform dependent,
+ /// see [`UserAttention`] for details.
+ ///
+ /// Providing `None` will unset the request for user attention. Unsetting the request for
+ /// user attention might not be done automatically by the WM when the window receives input.
+ ///
+ /// ## Platform-specific
+ ///
+ /// - **iOS / Android / Web:** Unsupported.
+ /// - **macOS:** `None` has no effect.
+ /// - **X11:** Requests for user attention must be manually cleared.
+ /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect.
+ RequestUserAttention(Option<UserAttention>),
+ /// Bring the window to the front and sets input focus. Has no effect if the window is
+ /// already in focus, minimized, or not visible.
+ ///
+ /// This method steals input focus from other applications. Do not use this method unless
+ /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive
+ /// user experience.
+ ///
+ /// ## Platform-specific
+ ///
+ /// - **Web / Wayland:** Unsupported.
+ GainFocus,
+ /// Change the window [`Level`].
+ ChangeLevel(Level),
+ /// Fetch an identifier unique to the window.
+ FetchId(Box<dyn FnOnce(u64) -> T + 'static>),
+ /// Change the window [`Icon`].
+ ///
+ /// On Windows and X11, this is typically the small icon in the top-left
+ /// corner of the titlebar.
+ ///
+ /// ## Platform-specific
+ ///
+ /// - **Web / Wayland / macOS:** Unsupported.
+ ///
+ /// - **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but it's
+ /// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
+ ///
+ /// - **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> {
+ /// Maps the output of a window [`Action`] using the provided closure.
+ pub fn map<A>(
+ self,
+ f: impl Fn(T) -> A + 'static + MaybeSend + Sync,
+ ) -> Action<A>
+ where
+ T: 'static,
+ {
+ match self {
+ Self::Close => Action::Close,
+ Self::Drag => Action::Drag,
+ Self::Spawn { settings } => Action::Spawn { settings },
+ Self::Resize(size) => Action::Resize(size),
+ Self::FetchSize(o) => Action::FetchSize(Box::new(move |s| f(o(s)))),
+ Self::Maximize(maximized) => Action::Maximize(maximized),
+ Self::Minimize(minimized) => Action::Minimize(minimized),
+ Self::Move { x, y } => Action::Move { x, y },
+ Self::ChangeMode(mode) => Action::ChangeMode(mode),
+ Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))),
+ Self::ToggleMaximize => Action::ToggleMaximize,
+ Self::ToggleDecorations => Action::ToggleDecorations,
+ Self::RequestUserAttention(attention_type) => {
+ Action::RequestUserAttention(attention_type)
+ }
+ Self::GainFocus => Action::GainFocus,
+ 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))
+ }))
+ }
+ }
+ }
+}
+
+impl<T> fmt::Debug for Action<T> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Self::Close => write!(f, "Action::Close"),
+ Self::Drag => write!(f, "Action::Drag"),
+ Self::Spawn { settings } => {
+ write!(f, "Action::Spawn {{ settings: {:?} }}", settings)
+ }
+ Self::Resize(size) => write!(f, "Action::Resize({size:?})"),
+ Self::FetchSize(_) => write!(f, "Action::FetchSize"),
+ Self::Maximize(maximized) => {
+ write!(f, "Action::Maximize({maximized})")
+ }
+ Self::Minimize(minimized) => {
+ write!(f, "Action::Minimize({minimized}")
+ }
+ Self::Move { x, y } => {
+ write!(f, "Action::Move {{ x: {x}, y: {y} }}")
+ }
+ Self::ChangeMode(mode) => write!(f, "Action::SetMode({mode:?})"),
+ Self::FetchMode(_) => write!(f, "Action::FetchMode"),
+ Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"),
+ Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"),
+ Self::RequestUserAttention(_) => {
+ write!(f, "Action::RequestUserAttention")
+ }
+ Self::GainFocus => write!(f, "Action::GainFocus"),
+ 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,
+}