summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/element.rs25
-rw-r--r--core/src/keyboard/key.rs6
-rw-r--r--core/src/layout/flex.rs53
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/mouse/click.rs2
-rw-r--r--core/src/overlay.rs8
-rw-r--r--core/src/overlay/element.rs27
-rw-r--r--core/src/overlay/group.rs39
-rw-r--r--core/src/renderer.rs19
-rw-r--r--core/src/settings.rs48
-rw-r--r--core/src/shell.rs47
-rw-r--r--core/src/text.rs4
-rw-r--r--core/src/theme.rs34
-rw-r--r--core/src/theme/palette.rs64
-rw-r--r--core/src/widget.rs8
-rw-r--r--core/src/widget/operation.rs203
-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.rs16
-rw-r--r--core/src/window.rs2
-rw-r--r--core/src/window/event.rs4
-rw-r--r--core/src/window/screenshot.rs108
-rw-r--r--core/src/window/settings.rs1
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 {