diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/src/element.rs | 25 | ||||
-rw-r--r-- | core/src/keyboard/key.rs | 6 | ||||
-rw-r--r-- | core/src/layout/flex.rs | 53 | ||||
-rw-r--r-- | core/src/lib.rs | 2 | ||||
-rw-r--r-- | core/src/mouse/click.rs | 2 | ||||
-rw-r--r-- | core/src/overlay.rs | 8 | ||||
-rw-r--r-- | core/src/overlay/element.rs | 27 | ||||
-rw-r--r-- | core/src/overlay/group.rs | 39 | ||||
-rw-r--r-- | core/src/renderer.rs | 19 | ||||
-rw-r--r-- | core/src/settings.rs | 48 | ||||
-rw-r--r-- | core/src/shell.rs | 47 | ||||
-rw-r--r-- | core/src/text.rs | 4 | ||||
-rw-r--r-- | core/src/theme.rs | 34 | ||||
-rw-r--r-- | core/src/theme/palette.rs | 64 | ||||
-rw-r--r-- | core/src/widget.rs | 8 | ||||
-rw-r--r-- | core/src/widget/operation.rs | 203 | ||||
-rw-r--r-- | core/src/widget/operation/focusable.rs | 35 | ||||
-rw-r--r-- | core/src/widget/operation/scrollable.rs | 6 | ||||
-rw-r--r-- | core/src/widget/operation/text_input.rs | 28 | ||||
-rw-r--r-- | core/src/widget/text.rs | 16 | ||||
-rw-r--r-- | core/src/window.rs | 2 | ||||
-rw-r--r-- | core/src/window/event.rs | 4 | ||||
-rw-r--r-- | core/src/window/screenshot.rs | 108 | ||||
-rw-r--r-- | core/src/window/settings.rs | 1 |
24 files changed, 654 insertions, 135 deletions
diff --git a/core/src/element.rs b/core/src/element.rs index 6ebb8a15..82ba753b 100644 --- a/core/src/element.rs +++ b/core/src/element.rs @@ -1,4 +1,3 @@ -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::overlay; @@ -6,8 +5,8 @@ use crate::renderer; use crate::widget; use crate::widget::tree::{self, Tree}; use crate::{ - Border, Clipboard, Color, Layout, Length, Rectangle, Shell, Size, Vector, - Widget, + Border, Clipboard, Color, Event, Layout, Length, Rectangle, Shell, Size, + Vector, Widget, }; use std::borrow::Borrow; @@ -309,7 +308,7 @@ where self.widget.operate(tree, layout, renderer, operation); } - fn on_event( + fn update( &mut self, tree: &mut Tree, event: Event, @@ -319,11 +318,11 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, B>, viewport: &Rectangle, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let status = self.widget.on_event( + self.widget.update( tree, event, layout, @@ -335,8 +334,6 @@ where ); shell.merge(local_shell, &self.mapper); - - status } fn draw( @@ -397,8 +394,8 @@ where } } -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Explain<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Explain<'_, Message, Theme, Renderer> where Renderer: crate::Renderer, { @@ -447,7 +444,7 @@ where .operate(state, layout, renderer, operation); } - fn on_event( + fn update( &mut self, state: &mut Tree, event: Event, @@ -457,10 +454,10 @@ where clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, viewport: &Rectangle, - ) -> event::Status { - self.element.widget.on_event( + ) { + self.element.widget.update( state, event, layout, cursor, renderer, clipboard, shell, viewport, - ) + ); } fn draw( 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/layout/flex.rs b/core/src/layout/flex.rs index ac80d393..2cff5bfd 100644 --- a/core/src/layout/flex.rs +++ b/core/src/layout/flex.rs @@ -79,6 +79,7 @@ where let max_cross = axis.cross(limits.max()); let mut fill_main_sum = 0; + let mut some_fill_cross = false; let (mut cross, cross_compress) = match axis { Axis::Vertical if width == Length::Shrink => (0.0, true), Axis::Horizontal if height == Length::Shrink => (0.0, true), @@ -90,6 +91,10 @@ where let mut nodes: Vec<Node> = Vec::with_capacity(items.len()); nodes.resize(items.len(), Node::default()); + // FIRST PASS + // We lay out non-fluid elements in the main axis. + // If we need to compress the cross axis, then we skip any of these elements + // that are also fluid in the cross axis. for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); @@ -121,6 +126,41 @@ where nodes[i] = layout; } else { fill_main_sum += fill_main_factor; + some_fill_cross = some_fill_cross || fill_cross_factor != 0; + } + } + + // SECOND PASS (conditional) + // If we must compress the cross axis and there are fluid elements in the + // cross axis, we lay out any of these elements that are also non-fluid in + // the main axis (i.e. the ones we deliberately skipped in the first pass). + // + // We use the maximum cross length obtained in the first pass as the maximum + // cross limit. + if cross_compress && some_fill_cross { + for (i, (child, tree)) in items.iter().zip(trees.iter_mut()).enumerate() + { + let (fill_main_factor, fill_cross_factor) = { + let size = child.as_widget().size(); + + axis.pack(size.width.fill_factor(), size.height.fill_factor()) + }; + + if fill_main_factor == 0 && fill_cross_factor != 0 { + let (max_width, max_height) = axis.pack(available, cross); + + let child_limits = + Limits::new(Size::ZERO, Size::new(max_width, max_height)); + + let layout = + child.as_widget().layout(tree, renderer, &child_limits); + let size = layout.size(); + + available -= axis.main(size); + cross = cross.max(axis.cross(size)); + + nodes[i] = layout; + } } } @@ -135,6 +175,9 @@ where }, }; + // THIRD PASS + // We only have the elements that are fluid in the main axis left. + // We use the remaining space to evenly allocate space based on fill factors. for (i, (child, tree)) in items.iter().zip(trees).enumerate() { let (fill_main_factor, fill_cross_factor) = { let size = child.as_widget().size(); @@ -142,10 +185,16 @@ where axis.pack(size.width.fill_factor(), size.height.fill_factor()) }; - if fill_main_factor != 0 || (cross_compress && fill_cross_factor != 0) { + if fill_main_factor != 0 { let max_main = remaining * fill_main_factor as f32 / fill_main_sum as f32; + let max_main = if max_main.is_nan() { + f32::INFINITY + } else { + max_main + }; + let min_main = if max_main.is_infinite() { 0.0 } else { @@ -178,6 +227,8 @@ where let pad = axis.pack(padding.left, padding.top); let mut main = pad.0; + // FOURTH PASS + // We align all the laid out nodes in the cross axis, if needed. for (i, node) in nodes.iter_mut().enumerate() { if i > 0 { main += spacing; 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/mouse/click.rs b/core/src/mouse/click.rs index 07a4db5a..0a373878 100644 --- a/core/src/mouse/click.rs +++ b/core/src/mouse/click.rs @@ -82,7 +82,7 @@ impl Click { None }; - self.position == new_position + self.position.distance(new_position) < 6.0 && duration .map(|duration| duration.as_millis() <= 300) .unwrap_or(false) diff --git a/core/src/overlay.rs b/core/src/overlay.rs index f09de831..383663af 100644 --- a/core/src/overlay.rs +++ b/core/src/overlay.rs @@ -5,13 +5,12 @@ mod group; pub use element::Element; pub use group::Group; -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; use crate::widget::Tree; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size, Vector}; /// An interactive component that can be displayed on top of other widgets. pub trait Overlay<Message, Theme, Renderer> @@ -57,7 +56,7 @@ where /// * a [`Clipboard`], if available /// /// By default, it does nothing. - fn on_event( + fn update( &mut self, _event: Event, _layout: Layout<'_>, @@ -65,8 +64,7 @@ where _renderer: &Renderer, _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, - ) -> event::Status { - event::Status::Ignored + ) { } /// Returns the current [`mouse::Interaction`] of the [`Overlay`]. diff --git a/core/src/overlay/element.rs b/core/src/overlay/element.rs index 32e987a3..7a179663 100644 --- a/core/src/overlay/element.rs +++ b/core/src/overlay/element.rs @@ -1,11 +1,10 @@ pub use crate::Overlay; -use crate::event::{self, Event}; use crate::layout; use crate::mouse; use crate::renderer; use crate::widget; -use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size}; +use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size}; /// A generic [`Overlay`]. #[allow(missing_debug_implementations)] @@ -50,7 +49,7 @@ where } /// Processes a runtime [`Event`]. - pub fn on_event( + pub fn update( &mut self, event: Event, layout: Layout<'_>, @@ -58,9 +57,9 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) { self.overlay - .on_event(event, layout, cursor, renderer, clipboard, shell) + .update(event, layout, cursor, renderer, clipboard, shell); } /// Returns the current [`mouse::Interaction`] of the [`Element`]. @@ -131,8 +130,8 @@ impl<'a, A, B, Theme, Renderer> Map<'a, A, B, Theme, Renderer> { } } -impl<'a, A, B, Theme, Renderer> Overlay<B, Theme, Renderer> - for Map<'a, A, B, Theme, Renderer> +impl<A, B, Theme, Renderer> Overlay<B, Theme, Renderer> + for Map<'_, A, B, Theme, Renderer> where Renderer: crate::Renderer, { @@ -149,7 +148,7 @@ where self.content.operate(layout, renderer, operation); } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -157,11 +156,11 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, B>, - ) -> event::Status { + ) { let mut local_messages = Vec::new(); let mut local_shell = Shell::new(&mut local_messages); - let event_status = self.content.on_event( + self.content.update( event, layout, cursor, @@ -171,8 +170,6 @@ where ); shell.merge(local_shell, self.mapper); - - event_status } fn mouse_interaction( @@ -206,11 +203,11 @@ where self.content.is_over(layout, renderer, cursor_position) } - fn overlay<'b>( - &'b mut self, + fn overlay<'a>( + &'a mut self, layout: Layout<'_>, renderer: &Renderer, - ) -> Option<Element<'b, B, Theme, Renderer>> { + ) -> Option<Element<'a, B, Theme, Renderer>> { self.content .overlay(layout, renderer) .map(|overlay| overlay.map(self.mapper)) diff --git a/core/src/overlay/group.rs b/core/src/overlay/group.rs index 6541d311..e07744e3 100644 --- a/core/src/overlay/group.rs +++ b/core/src/overlay/group.rs @@ -1,4 +1,3 @@ -use crate::event; use crate::layout; use crate::mouse; use crate::overlay; @@ -58,8 +57,8 @@ where } } -impl<'a, Message, Theme, Renderer> Overlay<Message, Theme, Renderer> - for Group<'a, Message, Theme, Renderer> +impl<Message, Theme, Renderer> Overlay<Message, Theme, Renderer> + for Group<'_, Message, Theme, Renderer> where Renderer: crate::Renderer, { @@ -73,7 +72,7 @@ where ) } - fn on_event( + fn update( &mut self, event: Event, layout: Layout<'_>, @@ -81,21 +80,17 @@ where renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { - self.children - .iter_mut() - .zip(layout.children()) - .map(|(child, layout)| { - child.on_event( - event.clone(), - layout, - cursor, - renderer, - clipboard, - shell, - ) - }) - .fold(event::Status::Ignored, event::Status::merge) + ) { + for (child, layout) in self.children.iter_mut().zip(layout.children()) { + child.update( + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + ); + } } fn draw( @@ -157,11 +152,11 @@ where }) } - fn overlay<'b>( - &'b mut self, + fn overlay<'a>( + &'a mut self, layout: Layout<'_>, renderer: &Renderer, - ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> { + ) -> Option<overlay::Element<'a, Message, Theme, Renderer>> { let children = self .children .iter_mut() 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/shell.rs b/core/src/shell.rs index 2952ceff..12ebbaa8 100644 --- a/core/src/shell.rs +++ b/core/src/shell.rs @@ -1,3 +1,5 @@ +use crate::event; +use crate::time::Instant; use crate::window; /// A connection to the state of a shell. @@ -9,6 +11,7 @@ use crate::window; #[derive(Debug)] pub struct Shell<'a, Message> { messages: &'a mut Vec<Message>, + event_status: event::Status, redraw_request: Option<window::RedrawRequest>, is_layout_invalid: bool, are_widgets_invalid: bool, @@ -19,6 +22,7 @@ impl<'a, Message> Shell<'a, Message> { pub fn new(messages: &'a mut Vec<Message>) -> Self { Self { messages, + event_status: event::Status::Ignored, redraw_request: None, is_layout_invalid: false, are_widgets_invalid: false, @@ -35,14 +39,37 @@ impl<'a, Message> Shell<'a, Message> { self.messages.push(message); } - /// Requests a new frame to be drawn. - pub fn request_redraw(&mut self, request: window::RedrawRequest) { + /// Marks the current event as captured. Prevents "event bubbling". + /// + /// A widget should capture an event when no ancestor should + /// handle it. + pub fn capture_event(&mut self) { + self.event_status = event::Status::Captured; + } + + /// Returns the current [`event::Status`] of the [`Shell`]. + pub fn event_status(&self) -> event::Status { + self.event_status + } + + /// Returns whether the current event has been captured. + pub fn is_event_captured(&self) -> bool { + self.event_status == event::Status::Captured + } + + /// Requests a new frame to be drawn as soon as possible. + pub fn request_redraw(&mut self) { + self.redraw_request = Some(window::RedrawRequest::NextFrame); + } + + /// Requests a new frame to be drawn at the given [`Instant`]. + pub fn request_redraw_at(&mut self, at: Instant) { match self.redraw_request { None => { - self.redraw_request = Some(request); + self.redraw_request = Some(window::RedrawRequest::At(at)); } - Some(current) if request < current => { - self.redraw_request = Some(request); + Some(window::RedrawRequest::At(current)) if at < current => { + self.redraw_request = Some(window::RedrawRequest::At(at)); } _ => {} } @@ -95,8 +122,12 @@ impl<'a, Message> Shell<'a, Message> { pub fn merge<B>(&mut self, other: Shell<'_, B>, f: impl Fn(B) -> Message) { self.messages.extend(other.messages.drain(..).map(f)); - if let Some(at) = other.redraw_request { - self.request_redraw(at); + if let Some(new) = other.redraw_request { + self.redraw_request = Some( + self.redraw_request + .map(|current| if current < new { current } else { new }) + .unwrap_or(new), + ); } self.is_layout_invalid = @@ -104,5 +135,7 @@ impl<'a, Message> Shell<'a, Message> { self.are_widgets_invalid = self.are_widgets_invalid || other.are_widgets_invalid; + + self.event_status = self.event_status.merge(other.event_status); } } diff --git a/core/src/text.rs b/core/src/text.rs index a9e3dce5..c144fd24 100644 --- a/core/src/text.rs +++ b/core/src/text.rs @@ -446,7 +446,7 @@ impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> { } } -impl<'a, Link, Font: PartialEq> PartialEq for Span<'a, Link, Font> { +impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> { fn eq(&self, other: &Self) -> bool { self.text == other.text && self.size == other.size @@ -474,7 +474,7 @@ impl<'a> IntoFragment<'a> for Fragment<'a> { } } -impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> { +impl<'a> IntoFragment<'a> for &'a Fragment<'_> { fn into_fragment(self) -> Fragment<'a> { Fragment::Borrowed(self) } 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/theme/palette.rs b/core/src/theme/palette.rs index e0ff397a..00c38019 100644 --- a/core/src/theme/palette.rs +++ b/core/src/theme/palette.rs @@ -17,6 +17,8 @@ pub struct Palette { pub primary: Color, /// The success [`Color`] of the [`Palette`]. pub success: Color, + /// The warning [`Color`] of the [`Palette`]. + pub warning: Color, /// The danger [`Color`] of the [`Palette`]. pub danger: Color, } @@ -36,6 +38,11 @@ impl Palette { 0x66 as f32 / 255.0, 0x4F as f32 / 255.0, ), + warning: Color::from_rgb( + 0xFF as f32 / 255.0, + 0xC1 as f32 / 255.0, + 0x4E as f32 / 255.0, + ), danger: Color::from_rgb( 0xC3 as f32 / 255.0, 0x42 as f32 / 255.0, @@ -61,6 +68,11 @@ impl Palette { 0x66 as f32 / 255.0, 0x4F as f32 / 255.0, ), + warning: Color::from_rgb( + 0xFF as f32 / 255.0, + 0xC1 as f32 / 255.0, + 0x4E as f32 / 255.0, + ), danger: Color::from_rgb( 0xC3 as f32 / 255.0, 0x42 as f32 / 255.0, @@ -76,6 +88,7 @@ impl Palette { text: color!(0xf8f8f2), // FOREGROUND primary: color!(0xbd93f9), // PURPLE success: color!(0x50fa7b), // GREEN + warning: color!(0xf1fa8c), // YELLOW danger: color!(0xff5555), // RED }; @@ -87,6 +100,7 @@ impl Palette { text: color!(0xeceff4), // nord6 primary: color!(0x8fbcbb), // nord7 success: color!(0xa3be8c), // nord14 + warning: color!(0xebcb8b), // nord13 danger: color!(0xbf616a), // nord11 }; @@ -98,6 +112,7 @@ impl Palette { text: color!(0x657b83), // base00 primary: color!(0x2aa198), // cyan success: color!(0x859900), // green + warning: color!(0xb58900), // yellow danger: color!(0xdc322f), // red }; @@ -109,6 +124,7 @@ impl Palette { text: color!(0x839496), // base0 primary: color!(0x2aa198), // cyan success: color!(0x859900), // green + warning: color!(0xb58900), // yellow danger: color!(0xdc322f), // red }; @@ -120,6 +136,7 @@ impl Palette { text: color!(0x282828), // light FG0_29 primary: color!(0x458588), // light BLUE_4 success: color!(0x98971a), // light GREEN_2 + warning: color!(0xd79921), // light YELLOW_3 danger: color!(0xcc241d), // light RED_1 }; @@ -131,6 +148,7 @@ impl Palette { text: color!(0xfbf1c7), // dark FG0_29 primary: color!(0x458588), // dark BLUE_4 success: color!(0x98971a), // dark GREEN_2 + warning: color!(0xd79921), // dark YELLOW_3 danger: color!(0xcc241d), // dark RED_1 }; @@ -142,6 +160,7 @@ impl Palette { text: color!(0x4c4f69), // Text primary: color!(0x1e66f5), // Blue success: color!(0x40a02b), // Green + warning: color!(0xdf8e1d), // Yellow danger: color!(0xd20f39), // Red }; @@ -153,6 +172,7 @@ impl Palette { text: color!(0xc6d0f5), // Text primary: color!(0x8caaee), // Blue success: color!(0xa6d189), // Green + warning: color!(0xe5c890), // Yellow danger: color!(0xe78284), // Red }; @@ -164,6 +184,7 @@ impl Palette { text: color!(0xcad3f5), // Text primary: color!(0x8aadf4), // Blue success: color!(0xa6da95), // Green + warning: color!(0xeed49f), // Yellow danger: color!(0xed8796), // Red }; @@ -175,6 +196,7 @@ impl Palette { text: color!(0xcdd6f4), // Text primary: color!(0x89b4fa), // Blue success: color!(0xa6e3a1), // Green + warning: color!(0xf9e2af), // Yellow danger: color!(0xf38ba8), // Red }; @@ -186,6 +208,7 @@ impl Palette { text: color!(0x9aa5ce), // Text primary: color!(0x2ac3de), // Blue success: color!(0x9ece6a), // Green + warning: color!(0xe0af68), // Yellow danger: color!(0xf7768e), // Red }; @@ -197,6 +220,7 @@ impl Palette { text: color!(0x9aa5ce), // Text primary: color!(0x2ac3de), // Blue success: color!(0x9ece6a), // Green + warning: color!(0xe0af68), // Yellow danger: color!(0xf7768e), // Red }; @@ -208,6 +232,7 @@ impl Palette { text: color!(0x565a6e), // Text primary: color!(0x166775), // Blue success: color!(0x485e30), // Green + warning: color!(0x8f5e15), // Yellow danger: color!(0x8c4351), // Red }; @@ -219,6 +244,7 @@ impl Palette { text: color!(0xCD7BA), // Fuji White primary: color!(0x2D4F67), // Wave Blue 2 success: color!(0x76946A), // Autumn Green + warning: color!(0xff9e3b), // Ronin Yellow danger: color!(0xC34043), // Autumn Red }; @@ -230,6 +256,7 @@ impl Palette { text: color!(0xc5c9c5), // Dragon White primary: color!(0x223249), // Wave Blue 1 success: color!(0x8a9a7b), // Dragon Green 2 + warning: color!(0xff9e3b), // Ronin Yellow danger: color!(0xc4746e), // Dragon Red }; @@ -241,6 +268,7 @@ impl Palette { text: color!(0x545464), // Lotus Ink 1 primary: color!(0xc9cbd1), // Lotus Violet 3 success: color!(0x6f894e), // Lotus Green + warning: color!(0xe98a00), // Lotus Orange 2 danger: color!(0xc84053), // Lotus Red }; @@ -252,6 +280,7 @@ impl Palette { text: color!(0xbdbdbd), // Foreground primary: color!(0x80a0ff), // Blue (normal) success: color!(0x8cc85f), // Green (normal) + warning: color!(0xe3c78a), // Yellow (normal) danger: color!(0xff5454), // Red (normal) }; @@ -263,6 +292,7 @@ impl Palette { text: color!(0xbdc1c6), // Foreground primary: color!(0x82aaff), // Blue (normal) success: color!(0xa1cd5e), // Green (normal) + warning: color!(0xe3d18a), // Yellow (normal) danger: color!(0xfc514e), // Red (normal) }; @@ -274,6 +304,7 @@ impl Palette { text: color!(0xd0d0d0), primary: color!(0x00b4ff), success: color!(0x00c15a), + warning: color!(0xbe95ff), // Base 14 danger: color!(0xf62d0f), }; @@ -285,6 +316,7 @@ impl Palette { text: color!(0xfecdb2), primary: color!(0xd1d1e0), success: color!(0xb1b695), + warning: color!(0xf5d76e), // Honey danger: color!(0xe06b75), }; } @@ -300,6 +332,8 @@ pub struct Extended { pub secondary: Secondary, /// The set of success colors. pub success: Success, + /// The set of warning colors. + pub warning: Warning, /// The set of danger colors. pub danger: Danger, /// Whether the palette is dark or not. @@ -410,6 +444,11 @@ impl Extended { palette.background, palette.text, ), + warning: Warning::generate( + palette.warning, + palette.background, + palette.text, + ), danger: Danger::generate( palette.danger, palette.background, @@ -545,6 +584,31 @@ impl Success { } } +/// A set of warning colors. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Warning { + /// The base warning color. + pub base: Pair, + /// A weaker version of the base warning color. + pub weak: Pair, + /// A stronger version of the base warning color. + pub strong: Pair, +} + +impl Warning { + /// Generates a set of [`Warning`] colors from the base, background, and text colors. + pub fn generate(base: Color, background: Color, text: Color) -> Self { + let weak = mix(base, background, 0.4); + let strong = deviate(base, 0.1); + + Self { + base: Pair::new(base, text), + weak: Pair::new(weak, text), + strong: Pair::new(strong, text), + } + } +} + /// A set of danger colors. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Danger { diff --git a/core/src/widget.rs b/core/src/widget.rs index 9cfff83d..2a40f823 100644 --- a/core/src/widget.rs +++ b/core/src/widget.rs @@ -10,12 +10,11 @@ pub use operation::Operation; pub use text::Text; pub use tree::Tree; -use crate::event::{self, Event}; use crate::layout::{self, Layout}; use crate::mouse; use crate::overlay; use crate::renderer; -use crate::{Clipboard, Length, Rectangle, Shell, Size, Vector}; +use crate::{Clipboard, Event, Length, Rectangle, Shell, Size, Vector}; /// A component that displays information and allows interaction. /// @@ -112,7 +111,7 @@ where /// Processes a runtime [`Event`]. /// /// By default, it does nothing. - fn on_event( + fn update( &mut self, _state: &mut Tree, _event: Event, @@ -122,8 +121,7 @@ where _clipboard: &mut dyn Clipboard, _shell: &mut Shell<'_, Message>, _viewport: &Rectangle, - ) -> event::Status { - event::Status::Ignored + ) { } /// Returns the current [`mouse::Interaction`] of the [`Widget`]. diff --git a/core/src/widget/operation.rs b/core/src/widget/operation.rs index 097c3601..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> { @@ -138,7 +178,7 @@ where operation: &'a mut dyn Operation<T>, } - impl<'a, T, O> Operation<O> for BlackBox<'a, T> { + impl<T, O> Operation<O> for BlackBox<'_, T> { fn container( &mut self, id: Option<&Id>, @@ -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> { @@ -218,7 +277,7 @@ where operation: &'a mut dyn Operation<A>, } - impl<'a, A, B> Operation<B> for MapRef<'a, A> { + impl<A, B> Operation<B> for MapRef<'_, A> { fn container( &mut self, id: Option<&Id>, @@ -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 8b02f8c2..c7ec3c8b 100644 --- a/core/src/widget/text.rs +++ b/core/src/widget/text.rs @@ -206,8 +206,8 @@ where #[derive(Debug, Default)] pub struct State<P: Paragraph>(pub paragraph::Plain<P>); -impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> - for Text<'a, Theme, Renderer> +impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> + for Text<'_, Theme, Renderer> where Theme: Catalog, Renderer: text::Renderer, @@ -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. @@ -389,7 +399,7 @@ where } /// The appearance of some text. -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Style { /// The [`Color`] of the 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/event.rs b/core/src/window/event.rs index 4e2751ee..45d29179 100644 --- a/core/src/window/event.rs +++ b/core/src/window/event.rs @@ -9,8 +9,8 @@ pub enum Event { /// A window was opened. Opened { /// The position of the opened window. This is relative to the top-left corner of the desktop - /// the window is on, including virtual desktops. Refers to window's "inner" position, - /// or the client area, in logical pixels. + /// the window is on, including virtual desktops. Refers to window's "outer" position, + /// or the window area, in logical pixels. /// /// **Note**: Not available in Wayland. position: Option<Point>, 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 e87fcc83..9432eaaa 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 { |