diff options
author | 2024-12-06 04:06:41 +0100 | |
---|---|---|
committer | 2024-12-10 04:51:08 +0100 | |
commit | 1aeb317f2dbfb63215e6226073e67878ffa6503b (patch) | |
tree | d883266d796360a1e4d883dc82a4fb284f724790 /core/src | |
parent | 8e3636d769d96ab5ba49a9647b72c59ae2226dd0 (diff) | |
download | iced-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.rs | 34 | ||||
-rw-r--r-- | core/src/window.rs | 2 | ||||
-rw-r--r-- | core/src/window/screenshot.rs | 108 |
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, +} |