diff options
Diffstat (limited to '')
| -rw-r--r-- | runtime/src/window.rs | 135 | ||||
| -rw-r--r-- | runtime/src/window/action.rs (renamed from native/src/window/action.rs) | 73 | ||||
| -rw-r--r-- | runtime/src/window/screenshot.rs | 92 | 
3 files changed, 273 insertions, 27 deletions
diff --git a/runtime/src/window.rs b/runtime/src/window.rs new file mode 100644 index 00000000..5219fbfd --- /dev/null +++ b/runtime/src/window.rs @@ -0,0 +1,135 @@ +//! 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, Level, Mode, UserAttention}; +use crate::core::Size; +use crate::futures::subscription::{self, Subscription}; + +/// Subscribes to the frames of the window of the running application. +/// +/// The resulting [`Subscription`] will produce items at a rate equal to the +/// refresh rate of the window. Note that this rate may be variable, as it is +/// normally managed by the graphics driver and/or the OS. +/// +/// In any case, this [`Subscription`] is useful to smoothly draw application-driven +/// animations without missing any frames. +pub fn frames() -> Subscription<Instant> { +    subscription::raw_events(|event, _status| match event { +        iced_core::Event::Window(Event::RedrawRequested(at)) => Some(at), +        _ => None, +    }) +} + +/// Closes the current window and exits the application. +pub fn close<Message>() -> Command<Message> { +    Command::single(command::Action::Window(Action::Close)) +} + +/// Begins dragging the window while the left mouse button is held. +pub fn drag<Message>() -> Command<Message> { +    Command::single(command::Action::Window(Action::Drag)) +} + +/// Resizes the window to the given logical dimensions. +pub fn resize<Message>(new_size: Size<u32>) -> Command<Message> { +    Command::single(command::Action::Window(Action::Resize(new_size))) +} + +/// Fetches the current window size in logical dimensions. +pub fn fetch_size<Message>( +    f: impl FnOnce(Size<u32>) -> Message + 'static, +) -> Command<Message> { +    Command::single(command::Action::Window(Action::FetchSize(Box::new(f)))) +} + +/// Maximizes the window. +pub fn maximize<Message>(maximized: bool) -> Command<Message> { +    Command::single(command::Action::Window(Action::Maximize(maximized))) +} + +/// Minimes the window. +pub fn minimize<Message>(minimized: bool) -> Command<Message> { +    Command::single(command::Action::Window(Action::Minimize(minimized))) +} + +/// Moves a window to the given logical coordinates. +pub fn move_to<Message>(x: i32, y: i32) -> Command<Message> { +    Command::single(command::Action::Window(Action::Move { x, y })) +} + +/// Changes the [`Mode`] of the window. +pub fn change_mode<Message>(mode: Mode) -> Command<Message> { +    Command::single(command::Action::Window(Action::ChangeMode(mode))) +} + +/// Fetches the current [`Mode`] of the window. +pub fn fetch_mode<Message>( +    f: impl FnOnce(Mode) -> Message + 'static, +) -> Command<Message> { +    Command::single(command::Action::Window(Action::FetchMode(Box::new(f)))) +} + +/// Toggles the window to maximized or back. +pub fn toggle_maximize<Message>() -> Command<Message> { +    Command::single(command::Action::Window(Action::ToggleMaximize)) +} + +/// Toggles the window decorations. +pub fn toggle_decorations<Message>() -> Command<Message> { +    Command::single(command::Action::Window(Action::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. +pub fn request_user_attention<Message>( +    user_attention: Option<UserAttention>, +) -> Command<Message> { +    Command::single(command::Action::Window(Action::RequestUserAttention( +        user_attention, +    ))) +} + +/// Brings the window to the front and sets input focus. Has no effect if the window is +/// already in focus, minimized, or not visible. +/// +/// This [`Command`] 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. +pub fn gain_focus<Message>() -> Command<Message> { +    Command::single(command::Action::Window(Action::GainFocus)) +} + +/// 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. +pub fn fetch_id<Message>( +    f: impl FnOnce(u64) -> Message + 'static, +) -> Command<Message> { +    Command::single(command::Action::Window(Action::FetchId(Box::new(f)))) +} + +/// Changes the [`Icon`] of the window. +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/native/src/window/action.rs b/runtime/src/window/action.rs index 5751bf97..cebec4ae 100644 --- a/native/src/window/action.rs +++ b/runtime/src/window/action.rs @@ -1,13 +1,15 @@ -use crate::window::{Mode, UserAttention, Settings}; +use crate::core::window::{Icon, Level, Mode, UserAttention, Settings}; +use crate::core::Size; +use crate::futures::MaybeSend; +use crate::window::Screenshot; -use iced_futures::MaybeSend;  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 @@ -19,13 +21,10 @@ pub enum Action<T> {          settings: Settings,      },      /// Resize the window. -    Resize { -        /// The new logical width of the window -        width: u32, -        /// The new logical height of the window -        height: u32, -    }, -    /// Sets the window to maximized or back +    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), @@ -75,14 +74,27 @@ pub enum Action<T> {      ///      /// - **Web / Wayland:** Unsupported.      GainFocus, -    /// Change whether or not the window will always be on top of other windows. +    /// 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:** Unsupported. -    ChangeAlwaysOnTop(bool), -    /// Fetch an identifier unique to the window. -    FetchId(Box<dyn FnOnce(u64) -> T + 'static>), +    /// - **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> { @@ -95,10 +107,11 @@ impl<T> Action<T> {          T: 'static,      {          match self { -            Self::Spawn { settings } => Action::Spawn { settings },              Self::Close => Action::Close,              Self::Drag => Action::Drag, -            Self::Resize { width, height } => Action::Resize { width, height }, +            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 }, @@ -110,10 +123,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)) +                })) +            }          }      }  } @@ -126,10 +143,8 @@ impl<T> fmt::Debug for Action<T> {              Self::Spawn { settings } => {                  write!(f, "Action::Spawn {{ settings: {:?} }}", settings)              } -            Self::Resize { width, height } => write!( -                f, -                "Action::Resize {{ widget: {width}, height: {height} }}" -            ), +            Self::Resize(size) => write!(f, "Action::Resize({size:?})"), +            Self::FetchSize(_) => write!(f, "Action::FetchSize"),              Self::Maximize(maximized) => {                  write!(f, "Action::Maximize({maximized})")              } @@ -147,10 +162,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, +}  | 
