summaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
authorLibravatar Héctor <hector@hecrj.dev>2024-12-17 17:28:46 +0100
committerLibravatar GitHub <noreply@github.com>2024-12-17 17:28:46 +0100
commitf2c9b6b2ffc50d67d9789e77cb55eeb2a0ebe470 (patch)
tree4941905adf134468acc079610bb6f25d7461d543 /core/src
parenta687a837653a576cb0599f7bc8ecd9c6054213a9 (diff)
parente5545aaa579f428e45853d125ac86155d8395104 (diff)
downloadiced-f2c9b6b2ffc50d67d9789e77cb55eeb2a0ebe470.tar.gz
iced-f2c9b6b2ffc50d67d9789e77cb55eeb2a0ebe470.tar.bz2
iced-f2c9b6b2ffc50d67d9789e77cb55eeb2a0ebe470.zip
Merge pull request #2698 from iced-rs/feature/test-crate
Headless Mode Testing
Diffstat (limited to 'core/src')
-rw-r--r--core/src/keyboard/key.rs6
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/renderer.rs19
-rw-r--r--core/src/settings.rs48
-rw-r--r--core/src/theme.rs34
-rw-r--r--core/src/widget/operation.rs199
-rw-r--r--core/src/widget/operation/focusable.rs35
-rw-r--r--core/src/widget/operation/scrollable.rs6
-rw-r--r--core/src/widget/operation/text_input.rs28
-rw-r--r--core/src/widget/text.rs10
-rw-r--r--core/src/window.rs2
-rw-r--r--core/src/window/screenshot.rs108
-rw-r--r--core/src/window/settings.rs1
13 files changed, 442 insertions, 56 deletions
diff --git a/core/src/keyboard/key.rs b/core/src/keyboard/key.rs
index 69a91902..47169d9a 100644
--- a/core/src/keyboard/key.rs
+++ b/core/src/keyboard/key.rs
@@ -32,6 +32,12 @@ impl Key {
}
}
+impl From<Named> for Key {
+ fn from(named: Named) -> Self {
+ Self::Named(named)
+ }
+}
+
/// A named key.
///
/// This is mostly the `NamedKey` type found in [`winit`].
diff --git a/core/src/lib.rs b/core/src/lib.rs
index df599f45..645f7a90 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -40,6 +40,7 @@ mod pixels;
mod point;
mod rectangle;
mod rotation;
+mod settings;
mod shadow;
mod shell;
mod size;
@@ -67,6 +68,7 @@ pub use point::Point;
pub use rectangle::Rectangle;
pub use renderer::Renderer;
pub use rotation::Rotation;
+pub use settings::Settings;
pub use shadow::Shadow;
pub use shell::Shell;
pub use size::Size;
diff --git a/core/src/renderer.rs b/core/src/renderer.rs
index 6684517f..68e070e8 100644
--- a/core/src/renderer.rs
+++ b/core/src/renderer.rs
@@ -3,7 +3,8 @@
mod null;
use crate::{
- Background, Border, Color, Rectangle, Shadow, Size, Transformation, Vector,
+ Background, Border, Color, Font, Pixels, Rectangle, Shadow, Size,
+ Transformation, Vector,
};
/// A component that can be used by widgets to draw themselves on a screen.
@@ -100,3 +101,19 @@ impl Default for Style {
}
}
}
+
+/// A headless renderer is a renderer that can render offscreen without
+/// a window nor a compositor.
+pub trait Headless {
+ /// Creates a new [`Headless`] renderer;
+ fn new(default_font: Font, default_text_size: Pixels) -> Self;
+
+ /// Draws offscreen into a screenshot, returning a collection of
+ /// bytes representing the rendered pixels in RGBA order.
+ fn screenshot(
+ &mut self,
+ size: Size<u32>,
+ scale_factor: f32,
+ background_color: Color,
+ ) -> Vec<u8>;
+}
diff --git a/core/src/settings.rs b/core/src/settings.rs
new file mode 100644
index 00000000..3189c8d1
--- /dev/null
+++ b/core/src/settings.rs
@@ -0,0 +1,48 @@
+//! Configure your application.
+use crate::{Font, Pixels};
+
+use std::borrow::Cow;
+
+/// The settings of an iced program.
+#[derive(Debug, Clone)]
+pub struct Settings {
+ /// The identifier of the application.
+ ///
+ /// If provided, this identifier may be used to identify the application or
+ /// communicate with it through the windowing system.
+ pub id: Option<String>,
+
+ /// The fonts to load on boot.
+ pub fonts: Vec<Cow<'static, [u8]>>,
+
+ /// The default [`Font`] to be used.
+ ///
+ /// By default, it uses [`Family::SansSerif`](crate::font::Family::SansSerif).
+ pub default_font: Font,
+
+ /// The text size that will be used by default.
+ ///
+ /// The default value is `16.0`.
+ pub default_text_size: Pixels,
+
+ /// If set to true, the renderer will try to perform antialiasing for some
+ /// primitives.
+ ///
+ /// Enabling it can produce a smoother result in some widgets, like the
+ /// `canvas` widget, at a performance cost.
+ ///
+ /// By default, it is disabled.
+ pub antialiasing: bool,
+}
+
+impl Default for Settings {
+ fn default() -> Self {
+ Self {
+ id: None,
+ fonts: Vec::new(),
+ default_font: Font::default(),
+ default_text_size: Pixels(16.0),
+ antialiasing: false,
+ }
+ }
+}
diff --git a/core/src/theme.rs b/core/src/theme.rs
index 6b2c04da..23480cec 100644
--- a/core/src/theme.rs
+++ b/core/src/theme.rs
@@ -3,6 +3,8 @@ pub mod palette;
pub use palette::Palette;
+use crate::Color;
+
use std::fmt;
use std::sync::Arc;
@@ -246,3 +248,35 @@ impl fmt::Display for Custom {
write!(f, "{}", self.name)
}
}
+
+/// The base style of a [`Theme`].
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Style {
+ /// The background [`Color`] of the application.
+ pub background_color: Color,
+
+ /// The default text [`Color`] of the application.
+ pub text_color: Color,
+}
+
+/// The default blank style of a [`Theme`].
+pub trait Base {
+ /// Returns the default base [`Style`] of a [`Theme`].
+ fn base(&self) -> Style;
+}
+
+impl Base for Theme {
+ fn base(&self) -> Style {
+ default(self)
+ }
+}
+
+/// The default [`Style`] of a built-in [`Theme`].
+pub fn default(theme: &Theme) -> Style {
+ let palette = theme.extended_palette();
+
+ Style {
+ background_color: palette.background.base.color,
+ text_color: palette.background.base.text,
+ }
+}
diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs
index 6bdb27f6..8fc627bf 100644
--- a/core/src/widget/operation.rs
+++ b/core/src/widget/operation.rs
@@ -30,24 +30,45 @@ pub trait Operation<T = ()>: Send {
);
/// Operates on a widget that can be focused.
- fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
+ fn focusable(
+ &mut self,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ _state: &mut dyn Focusable,
+ ) {
+ }
/// Operates on a widget that can be scrolled.
fn scrollable(
&mut self,
- _state: &mut dyn Scrollable,
_id: Option<&Id>,
_bounds: Rectangle,
_content_bounds: Rectangle,
_translation: Vector,
+ _state: &mut dyn Scrollable,
) {
}
/// Operates on a widget that has text input.
- fn text_input(&mut self, _state: &mut dyn TextInput, _id: Option<&Id>) {}
+ fn text_input(
+ &mut self,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ _state: &mut dyn TextInput,
+ ) {
+ }
+
+ /// Operates on a widget that contains some text.
+ fn text(&mut self, _id: Option<&Id>, _bounds: Rectangle, _text: &str) {}
/// Operates on a custom widget with some state.
- fn custom(&mut self, _state: &mut dyn Any, _id: Option<&Id>) {}
+ fn custom(
+ &mut self,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ _state: &mut dyn Any,
+ ) {
+ }
/// Finishes the [`Operation`] and returns its [`Outcome`].
fn finish(&self) -> Outcome<T> {
@@ -68,33 +89,52 @@ where
self.as_mut().container(id, bounds, operate_on_children);
}
- fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
- self.as_mut().focusable(state, id);
+ fn focusable(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
+ self.as_mut().focusable(id, bounds, state);
}
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: Vector,
+ state: &mut dyn Scrollable,
) {
self.as_mut().scrollable(
- state,
id,
bounds,
content_bounds,
translation,
+ state,
);
}
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
- self.as_mut().text_input(state, id);
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
+ self.as_mut().text_input(id, bounds, state);
+ }
+
+ fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) {
+ self.as_mut().text(id, bounds, text);
}
- fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
- self.as_mut().custom(state, id);
+ fn custom(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Any,
+ ) {
+ self.as_mut().custom(id, bounds, state);
}
fn finish(&self) -> Outcome<O> {
@@ -150,33 +190,52 @@ where
});
}
- fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
- self.operation.focusable(state, id);
+ fn focusable(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
+ self.operation.focusable(id, bounds, state);
}
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: Vector,
+ state: &mut dyn Scrollable,
) {
self.operation.scrollable(
- state,
id,
bounds,
content_bounds,
translation,
+ state,
);
}
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
- self.operation.text_input(state, id);
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
+ self.operation.text_input(id, bounds, state);
}
- fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
- self.operation.custom(state, id);
+ fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) {
+ self.operation.text(id, bounds, text);
+ }
+
+ fn custom(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Any,
+ ) {
+ self.operation.custom(id, bounds, state);
}
fn finish(&self) -> Outcome<O> {
@@ -234,39 +293,55 @@ where
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: Vector,
+ state: &mut dyn Scrollable,
) {
self.operation.scrollable(
- state,
id,
bounds,
content_bounds,
translation,
+ state,
);
}
fn focusable(
&mut self,
- state: &mut dyn Focusable,
id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Focusable,
) {
- self.operation.focusable(state, id);
+ self.operation.focusable(id, bounds, state);
}
fn text_input(
&mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
state: &mut dyn TextInput,
+ ) {
+ self.operation.text_input(id, bounds, state);
+ }
+
+ fn text(
+ &mut self,
id: Option<&Id>,
+ bounds: Rectangle,
+ text: &str,
) {
- self.operation.text_input(state, id);
+ self.operation.text(id, bounds, text);
}
- fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
- self.operation.custom(state, id);
+ fn custom(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Any,
+ ) {
+ self.operation.custom(id, bounds, state);
}
}
@@ -275,33 +350,52 @@ where
MapRef { operation }.container(id, bounds, operate_on_children);
}
- fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
- self.operation.focusable(state, id);
+ fn focusable(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
+ self.operation.focusable(id, bounds, state);
}
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: Vector,
+ state: &mut dyn Scrollable,
) {
self.operation.scrollable(
- state,
id,
bounds,
content_bounds,
translation,
+ state,
);
}
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
- self.operation.text_input(state, id);
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
+ self.operation.text_input(id, bounds, state);
+ }
+
+ fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) {
+ self.operation.text(id, bounds, text);
}
- fn custom(&mut self, state: &mut dyn Any, id: Option<&Id>) {
- self.operation.custom(state, id);
+ fn custom(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Any,
+ ) {
+ self.operation.custom(id, bounds, state);
}
fn finish(&self) -> Outcome<B> {
@@ -361,33 +455,52 @@ where
});
}
- fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
- self.operation.focusable(state, id);
+ fn focusable(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
+ self.operation.focusable(id, bounds, state);
}
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: crate::Vector,
+ state: &mut dyn Scrollable,
) {
self.operation.scrollable(
- state,
id,
bounds,
content_bounds,
translation,
+ state,
);
}
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
- self.operation.text_input(state, id);
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
+ self.operation.text_input(id, bounds, state);
}
- fn custom(&mut self, state: &mut dyn std::any::Any, id: Option<&Id>) {
- self.operation.custom(state, id);
+ fn text(&mut self, id: Option<&Id>, bounds: Rectangle, text: &str) {
+ self.operation.text(id, bounds, text);
+ }
+
+ fn custom(
+ &mut self,
+ id: Option<&Id>,
+ bounds: Rectangle,
+ state: &mut dyn Any,
+ ) {
+ self.operation.custom(id, bounds, state);
}
fn finish(&self) -> Outcome<B> {
diff --git a/core/src/widget/operation/focusable.rs b/core/src/widget/operation/focusable.rs
index 867c682e..8f66e575 100644
--- a/core/src/widget/operation/focusable.rs
+++ b/core/src/widget/operation/focusable.rs
@@ -32,7 +32,12 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
}
impl<T> Operation<T> for Focus {
- fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ fn focusable(
+ &mut self,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
match id {
Some(id) if id == &self.target => {
state.focus();
@@ -64,7 +69,12 @@ pub fn count() -> impl Operation<Count> {
}
impl Operation<Count> for CountFocusable {
- fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
+ fn focusable(
+ &mut self,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
if state.is_focused() {
self.count.focused = Some(self.count.total);
}
@@ -104,7 +114,12 @@ where
}
impl<T> Operation<T> for FocusPrevious {
- fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
+ fn focusable(
+ &mut self,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
if self.count.total == 0 {
return;
}
@@ -147,7 +162,12 @@ where
}
impl<T> Operation<T> for FocusNext {
- fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
+ fn focusable(
+ &mut self,
+ _id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
match self.count.focused {
None if self.current == 0 => state.focus(),
Some(focused) if focused == self.current => state.unfocus(),
@@ -179,7 +199,12 @@ pub fn find_focused() -> impl Operation<Id> {
}
impl Operation<Id> for FindFocused {
- fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
+ fn focusable(
+ &mut self,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn Focusable,
+ ) {
if state.is_focused() && id.is_some() {
self.focused = id.cloned();
}
diff --git a/core/src/widget/operation/scrollable.rs b/core/src/widget/operation/scrollable.rs
index c2fecf56..7c78c087 100644
--- a/core/src/widget/operation/scrollable.rs
+++ b/core/src/widget/operation/scrollable.rs
@@ -39,11 +39,11 @@ pub fn snap_to<T>(target: Id, offset: RelativeOffset) -> impl Operation<T> {
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
_bounds: Rectangle,
_content_bounds: Rectangle,
_translation: Vector,
+ state: &mut dyn Scrollable,
) {
if Some(&self.target) == id {
state.snap_to(self.offset);
@@ -74,11 +74,11 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
_bounds: Rectangle,
_content_bounds: Rectangle,
_translation: Vector,
+ state: &mut dyn Scrollable,
) {
if Some(&self.target) == id {
state.scroll_to(self.offset);
@@ -109,11 +109,11 @@ pub fn scroll_by<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
fn scrollable(
&mut self,
- state: &mut dyn Scrollable,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
_translation: Vector,
+ state: &mut dyn Scrollable,
) {
if Some(&self.target) == id {
state.scroll_by(self.offset, bounds, content_bounds);
diff --git a/core/src/widget/operation/text_input.rs b/core/src/widget/operation/text_input.rs
index 41731d4c..a46f1a2d 100644
--- a/core/src/widget/operation/text_input.rs
+++ b/core/src/widget/operation/text_input.rs
@@ -23,7 +23,12 @@ pub fn move_cursor_to_front<T>(target: Id) -> impl Operation<T> {
}
impl<T> Operation<T> for MoveCursor {
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
match id {
Some(id) if id == &self.target => {
state.move_cursor_to_front();
@@ -53,7 +58,12 @@ pub fn move_cursor_to_end<T>(target: Id) -> impl Operation<T> {
}
impl<T> Operation<T> for MoveCursor {
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
match id {
Some(id) if id == &self.target => {
state.move_cursor_to_end();
@@ -84,7 +94,12 @@ pub fn move_cursor_to<T>(target: Id, position: usize) -> impl Operation<T> {
}
impl<T> Operation<T> for MoveCursor {
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
match id {
Some(id) if id == &self.target => {
state.move_cursor_to(self.position);
@@ -113,7 +128,12 @@ pub fn select_all<T>(target: Id) -> impl Operation<T> {
}
impl<T> Operation<T> for MoveCursor {
- fn text_input(&mut self, state: &mut dyn TextInput, id: Option<&Id>) {
+ fn text_input(
+ &mut self,
+ id: Option<&Id>,
+ _bounds: Rectangle,
+ state: &mut dyn TextInput,
+ ) {
match id {
Some(id) if id == &self.target => {
state.select_all();
diff --git a/core/src/widget/text.rs b/core/src/widget/text.rs
index d3d1cffd..c7ec3c8b 100644
--- a/core/src/widget/text.rs
+++ b/core/src/widget/text.rs
@@ -267,6 +267,16 @@ where
draw(renderer, defaults, layout, state.0.raw(), style, viewport);
}
+
+ fn operate(
+ &self,
+ _state: &mut Tree,
+ layout: Layout<'_>,
+ _renderer: &Renderer,
+ operation: &mut dyn super::Operation,
+ ) {
+ operation.text(None, layout.bounds(), &self.fragment);
+ }
}
/// Produces the [`layout::Node`] of a [`Text`] widget.
diff --git a/core/src/window.rs b/core/src/window.rs
index 448ffc45..a3389998 100644
--- a/core/src/window.rs
+++ b/core/src/window.rs
@@ -1,5 +1,6 @@
//! Build window-based GUI applications.
pub mod icon;
+pub mod screenshot;
pub mod settings;
mod event;
@@ -17,5 +18,6 @@ pub use level::Level;
pub use mode::Mode;
pub use position::Position;
pub use redraw_request::RedrawRequest;
+pub use screenshot::Screenshot;
pub use settings::Settings;
pub use user_attention::UserAttention;
diff --git a/core/src/window/screenshot.rs b/core/src/window/screenshot.rs
new file mode 100644
index 00000000..424168bb
--- /dev/null
+++ b/core/src/window/screenshot.rs
@@ -0,0 +1,108 @@
+//! Take screenshots of a window.
+use crate::{Rectangle, Size};
+
+use bytes::Bytes;
+use std::fmt::{Debug, Formatter};
+
+/// 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: Bytes,
+ /// The size of the [`Screenshot`] in physical pixels.
+ pub size: Size<u32>,
+ /// The scale factor of the [`Screenshot`]. This can be useful when converting between widget
+ /// bounds (which are in logical pixels) to crop screenshots.
+ pub scale_factor: f64,
+}
+
+impl Debug for Screenshot {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(
+ f,
+ "Screenshot: {{ \n bytes: {}\n scale: {}\n size: {:?} }}",
+ self.bytes.len(),
+ self.scale_factor,
+ self.size
+ )
+ }
+}
+
+impl Screenshot {
+ /// Creates a new [`Screenshot`].
+ pub fn new(
+ bytes: impl Into<Bytes>,
+ size: Size<u32>,
+ scale_factor: f64,
+ ) -> Self {
+ Self {
+ bytes: bytes.into(),
+ size,
+ scale_factor,
+ }
+ }
+
+ /// 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: Bytes::from(chopped),
+ size: Size::new(region.width, region.height),
+ scale_factor: self.scale_factor,
+ })
+ }
+}
+
+impl AsRef<[u8]> for Screenshot {
+ fn as_ref(&self) -> &[u8] {
+ &self.bytes
+ }
+}
+
+impl From<Screenshot> for Bytes {
+ fn from(screenshot: Screenshot) -> Self {
+ screenshot.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,
+}
diff --git a/core/src/window/settings.rs b/core/src/window/settings.rs
index fbbf86ab..c1f36a6c 100644
--- a/core/src/window/settings.rs
+++ b/core/src/window/settings.rs
@@ -28,6 +28,7 @@ use crate::window::{Icon, Level, Position};
use crate::Size;
pub use platform::PlatformSpecific;
+
/// The window settings of an application.
#[derive(Debug, Clone)]
pub struct Settings {