summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/src/program/state.rs38
-rw-r--r--runtime/src/window.rs10
-rw-r--r--runtime/src/window/action.rs9
-rw-r--r--runtime/src/window/screenshot.rs92
4 files changed, 149 insertions, 0 deletions
diff --git a/runtime/src/program/state.rs b/runtime/src/program/state.rs
index d83e3f54..35df6078 100644
--- a/runtime/src/program/state.rs
+++ b/runtime/src/program/state.rs
@@ -1,6 +1,7 @@
use crate::core::event::{self, Event};
use crate::core::mouse;
use crate::core::renderer;
+use crate::core::widget::operation::{self, Operation};
use crate::core::{Clipboard, Size};
use crate::user_interface::{self, UserInterface};
use crate::{Command, Debug, Program};
@@ -173,6 +174,43 @@ where
(uncaptured_events, command)
}
+
+ /// Applies [`widget::Operation`]s to the [`State`]
+ pub fn operate(
+ &mut self,
+ renderer: &mut P::Renderer,
+ operations: impl Iterator<Item = Box<dyn Operation<P::Message>>>,
+ bounds: Size,
+ debug: &mut Debug,
+ ) {
+ let mut user_interface = build_user_interface(
+ &mut self.program,
+ self.cache.take().unwrap(),
+ renderer,
+ bounds,
+ debug,
+ );
+
+ for operation in operations {
+ let mut current_operation = Some(operation);
+
+ while let Some(mut operation) = current_operation.take() {
+ user_interface.operate(renderer, operation.as_mut());
+
+ match operation.finish() {
+ operation::Outcome::None => {}
+ operation::Outcome::Some(message) => {
+ self.queued_messages.push(message)
+ }
+ operation::Outcome::Chain(next) => {
+ current_operation = Some(next);
+ }
+ };
+ }
+ }
+
+ self.cache = Some(user_interface.into_cache());
+ }
}
fn build_user_interface<'a, P: Program>(
diff --git a/runtime/src/window.rs b/runtime/src/window.rs
index 9356581a..5219fbfd 100644
--- a/runtime/src/window.rs
+++ b/runtime/src/window.rs
@@ -1,7 +1,10 @@
//! 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;
@@ -123,3 +126,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 d0137895..b6964e36 100644
--- a/runtime/src/window/action.rs
+++ b/runtime/src/window/action.rs
@@ -1,6 +1,7 @@
use crate::core::window::{Icon, Level, Mode, UserAttention};
use crate::core::Size;
use crate::futures::MaybeSend;
+use crate::window::Screenshot;
use std::fmt;
@@ -87,6 +88,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> {
@@ -117,6 +120,11 @@ impl<T> Action<T> {
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))
+ }))
+ }
}
}
}
@@ -152,6 +160,7 @@ impl<T> fmt::Debug for Action<T> {
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,
+}