summaryrefslogtreecommitdiffstats
path: root/core/src
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-12-06 04:06:41 +0100
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-12-10 04:51:08 +0100
commit1aeb317f2dbfb63215e6226073e67878ffa6503b (patch)
treed883266d796360a1e4d883dc82a4fb284f724790 /core/src
parent8e3636d769d96ab5ba49a9647b72c59ae2226dd0 (diff)
downloadiced-1aeb317f2dbfb63215e6226073e67878ffa6503b.tar.gz
iced-1aeb317f2dbfb63215e6226073e67878ffa6503b.tar.bz2
iced-1aeb317f2dbfb63215e6226073e67878ffa6503b.zip
Add image and hash snapshot-based testing to `iced_test`
Diffstat (limited to 'core/src')
-rw-r--r--core/src/theme.rs34
-rw-r--r--core/src/window.rs2
-rw-r--r--core/src/window/screenshot.rs108
3 files changed, 144 insertions, 0 deletions
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/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,
+}