Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2025-03-04 19:11:37 +01:00
commit 8bd5de72ea
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
371 changed files with 33138 additions and 12950 deletions

View file

@ -21,9 +21,9 @@ advanced = []
bitflags.workspace = true
bytes.workspace = true
glam.workspace = true
lilt.workspace = true
log.workspace = true
num-traits.workspace = true
once_cell.workspace = true
palette.workspace = true
rustc-hash.workspace = true
smol_str.workspace = true

View file

@ -13,15 +13,3 @@ This crate is meant to be a starting point for an Iced runtime.
</p>
[documentation]: https://docs.rs/iced_core
## Installation
Add `iced_core` as a dependency in your `Cargo.toml`:
```toml
iced_core = "0.9"
```
__Iced moves fast and the `master` branch can contain breaking changes!__ If
you want to learn about a specific release, check out [the release list].
[the release list]: https://github.com/iced-rs/iced/releases

View file

@ -46,6 +46,16 @@ pub enum Horizontal {
Right,
}
impl From<Alignment> for Horizontal {
fn from(alignment: Alignment) -> Self {
match alignment {
Alignment::Start => Self::Left,
Alignment::Center => Self::Center,
Alignment::End => Self::Right,
}
}
}
/// The vertical [`Alignment`] of some resource.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Vertical {
@ -58,3 +68,13 @@ pub enum Vertical {
/// Align bottom
Bottom,
}
impl From<Alignment> for Vertical {
fn from(alignment: Alignment) -> Self {
match alignment {
Alignment::Start => Self::Top,
Alignment::Center => Self::Center,
Alignment::End => Self::Bottom,
}
}
}

View file

@ -1,6 +1,7 @@
use crate::{Point, Rectangle, Vector};
use std::f32::consts::{FRAC_PI_2, PI};
use std::fmt::Display;
use std::ops::{Add, AddAssign, Div, Mul, RangeInclusive, Rem, Sub, SubAssign};
/// Degrees
@ -237,3 +238,9 @@ impl PartialOrd<f32> for Radians {
self.0.partial_cmp(other)
}
}
impl Display for Radians {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} rad", self.0)
}
}

148
core/src/animation.rs Normal file
View file

@ -0,0 +1,148 @@
//! Animate your applications.
use crate::time::{Duration, Instant};
pub use lilt::{Easing, FloatRepresentable as Float, Interpolable};
/// The animation of some particular state.
///
/// It tracks state changes and allows projecting interpolated values
/// through time.
#[derive(Debug, Clone)]
pub struct Animation<T>
where
T: Clone + Copy + PartialEq + Float,
{
raw: lilt::Animated<T, Instant>,
duration: Duration, // TODO: Expose duration getter in `lilt`
}
impl<T> Animation<T>
where
T: Clone + Copy + PartialEq + Float,
{
/// Creates a new [`Animation`] with the given initial state.
pub fn new(state: T) -> Self {
Self {
raw: lilt::Animated::new(state),
duration: Duration::from_millis(100),
}
}
/// Sets the [`Easing`] function of the [`Animation`].
///
/// See the [Easing Functions Cheat Sheet](https://easings.net) for
/// details!
pub fn easing(mut self, easing: Easing) -> Self {
self.raw = self.raw.easing(easing);
self
}
/// Sets the duration of the [`Animation`] to 100ms.
pub fn very_quick(self) -> Self {
self.duration(Duration::from_millis(100))
}
/// Sets the duration of the [`Animation`] to 200ms.
pub fn quick(self) -> Self {
self.duration(Duration::from_millis(200))
}
/// Sets the duration of the [`Animation`] to 400ms.
pub fn slow(self) -> Self {
self.duration(Duration::from_millis(400))
}
/// Sets the duration of the [`Animation`] to 500ms.
pub fn very_slow(self) -> Self {
self.duration(Duration::from_millis(500))
}
/// Sets the duration of the [`Animation`] to the given value.
pub fn duration(mut self, duration: Duration) -> Self {
self.raw = self.raw.duration(duration.as_secs_f32() * 1_000.0);
self.duration = duration;
self
}
/// Sets a delay for the [`Animation`].
pub fn delay(mut self, duration: Duration) -> Self {
self.raw = self.raw.delay(duration.as_secs_f64() as f32 * 1000.0);
self
}
/// Makes the [`Animation`] repeat a given amount of times.
///
/// Providing 1 repetition plays the animation twice in total.
pub fn repeat(mut self, repetitions: u32) -> Self {
self.raw = self.raw.repeat(repetitions);
self
}
/// Makes the [`Animation`] repeat forever.
pub fn repeat_forever(mut self) -> Self {
self.raw = self.raw.repeat_forever();
self
}
/// Makes the [`Animation`] automatically reverse when repeating.
pub fn auto_reverse(mut self) -> Self {
self.raw = self.raw.auto_reverse();
self
}
/// Transitions the [`Animation`] from its current state to the given new state.
pub fn go(mut self, new_state: T) -> Self {
self.go_mut(new_state);
self
}
/// Transitions the [`Animation`] from its current state to the given new state, by reference.
pub fn go_mut(&mut self, new_state: T) {
self.raw.transition(new_state, Instant::now());
}
/// Returns true if the [`Animation`] is currently in progress.
///
/// An [`Animation`] is in progress when it is transitioning to a different state.
pub fn is_animating(&self, at: Instant) -> bool {
self.raw.in_progress(at)
}
/// Projects the [`Animation`] into an interpolated value at the given [`Instant`]; using the
/// closure provided to calculate the different keyframes of interpolated values.
///
/// If the [`Animation`] state is a `bool`, you can use the simpler [`interpolate`] method.
///
/// [`interpolate`]: Animation::interpolate
pub fn interpolate_with<I>(&self, f: impl Fn(T) -> I, at: Instant) -> I
where
I: Interpolable,
{
self.raw.animate(f, at)
}
/// Retuns the current state of the [`Animation`].
pub fn value(&self) -> T {
self.raw.value
}
}
impl Animation<bool> {
/// Projects the [`Animation`] into an interpolated value at the given [`Instant`]; using the
/// `start` and `end` values as the origin and destination keyframes.
pub fn interpolate<I>(&self, start: I, end: I, at: Instant) -> I
where
I: Interpolable + Clone,
{
self.raw.animate_bool(start, end, at)
}
/// Returns the remaining [`Duration`] of the [`Animation`].
pub fn remaining(&self, at: Instant) -> Duration {
Duration::from_secs_f32(self.interpolate(
self.duration.as_secs_f32(),
0.0,
at,
))
}
}

View file

@ -1,5 +1,5 @@
use crate::gradient::{self, Gradient};
use crate::Color;
use crate::gradient::{self, Gradient};
/// The background of some element.
#[derive(Debug, Clone, Copy, PartialEq)]

View file

@ -10,40 +10,64 @@ pub struct Border {
/// The width of the border.
pub width: f32,
/// The radius of the border.
/// The [`Radius`] of the border.
pub radius: Radius,
}
impl Border {
/// Creates a new default rounded [`Border`] with the given [`Radius`].
///
/// ```
/// # use iced_core::Border;
/// #
/// assert_eq!(Border::rounded(10), Border::default().with_radius(10));
/// ```
pub fn rounded(radius: impl Into<Radius>) -> Self {
Self::default().with_radius(radius)
}
/// Creates a new [`Border`] with the given [`Radius`].
///
/// ```
/// # use iced_core::border::{self, Border};
/// #
/// assert_eq!(border::rounded(10), Border::default().rounded(10));
/// ```
pub fn rounded(radius: impl Into<Radius>) -> Border {
Border::default().rounded(radius)
}
/// Updates the [`Color`] of the [`Border`].
pub fn with_color(self, color: impl Into<Color>) -> Self {
/// Creates a new [`Border`] with the given [`Color`].
///
/// ```
/// # use iced_core::border::{self, Border};
/// # use iced_core::Color;
/// #
/// assert_eq!(border::color(Color::BLACK), Border::default().color(Color::BLACK));
/// ```
pub fn color(color: impl Into<Color>) -> Border {
Border::default().color(color)
}
/// Creates a new [`Border`] with the given `width`.
///
/// ```
/// # use iced_core::border::{self, Border};
/// # use iced_core::Color;
/// #
/// assert_eq!(border::width(10), Border::default().width(10));
/// ```
pub fn width(width: impl Into<Pixels>) -> Border {
Border::default().width(width)
}
impl Border {
/// Sets the [`Color`] of the [`Border`].
pub fn color(self, color: impl Into<Color>) -> Self {
Self {
color: color.into(),
..self
}
}
/// Updates the [`Radius`] of the [`Border`].
pub fn with_radius(self, radius: impl Into<Radius>) -> Self {
/// Sets the [`Radius`] of the [`Border`].
pub fn rounded(self, radius: impl Into<Radius>) -> Self {
Self {
radius: radius.into(),
..self
}
}
/// Updates the width of the [`Border`].
pub fn with_width(self, width: impl Into<Pixels>) -> Self {
/// Sets the width of the [`Border`].
pub fn width(self, width: impl Into<Pixels>) -> Self {
Self {
width: width.into().0,
..self
@ -54,11 +78,160 @@ impl Border {
/// The border radii for the corners of a graphics primitive in the order:
/// top-left, top-right, bottom-right, bottom-left.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Radius([f32; 4]);
pub struct Radius {
/// Top left radius
pub top_left: f32,
/// Top right radius
pub top_right: f32,
/// Bottom right radius
pub bottom_right: f32,
/// Bottom left radius
pub bottom_left: f32,
}
/// Creates a new [`Radius`] with the same value for each corner.
pub fn radius(value: impl Into<Pixels>) -> Radius {
Radius::new(value)
}
/// Creates a new [`Radius`] with the given top left value.
pub fn top_left(value: impl Into<Pixels>) -> Radius {
Radius::default().top_left(value)
}
/// Creates a new [`Radius`] with the given top right value.
pub fn top_right(value: impl Into<Pixels>) -> Radius {
Radius::default().top_right(value)
}
/// Creates a new [`Radius`] with the given bottom right value.
pub fn bottom_right(value: impl Into<Pixels>) -> Radius {
Radius::default().bottom_right(value)
}
/// Creates a new [`Radius`] with the given bottom left value.
pub fn bottom_left(value: impl Into<Pixels>) -> Radius {
Radius::default().bottom_left(value)
}
/// Creates a new [`Radius`] with the given value as top left and top right.
pub fn top(value: impl Into<Pixels>) -> Radius {
Radius::default().top(value)
}
/// Creates a new [`Radius`] with the given value as bottom left and bottom right.
pub fn bottom(value: impl Into<Pixels>) -> Radius {
Radius::default().bottom(value)
}
/// Creates a new [`Radius`] with the given value as top left and bottom left.
pub fn left(value: impl Into<Pixels>) -> Radius {
Radius::default().left(value)
}
/// Creates a new [`Radius`] with the given value as top right and bottom right.
pub fn right(value: impl Into<Pixels>) -> Radius {
Radius::default().right(value)
}
impl Radius {
/// Creates a new [`Radius`] with the same value for each corner.
pub fn new(value: impl Into<Pixels>) -> Self {
let value = value.into().0;
Self {
top_left: value,
top_right: value,
bottom_right: value,
bottom_left: value,
}
}
/// Sets the top left value of the [`Radius`].
pub fn top_left(self, value: impl Into<Pixels>) -> Self {
Self {
top_left: value.into().0,
..self
}
}
/// Sets the top right value of the [`Radius`].
pub fn top_right(self, value: impl Into<Pixels>) -> Self {
Self {
top_right: value.into().0,
..self
}
}
/// Sets the bottom right value of the [`Radius`].
pub fn bottom_right(self, value: impl Into<Pixels>) -> Self {
Self {
bottom_right: value.into().0,
..self
}
}
/// Sets the bottom left value of the [`Radius`].
pub fn bottom_left(self, value: impl Into<Pixels>) -> Self {
Self {
bottom_left: value.into().0,
..self
}
}
/// Sets the top left and top right values of the [`Radius`].
pub fn top(self, value: impl Into<Pixels>) -> Self {
let value = value.into().0;
Self {
top_left: value,
top_right: value,
..self
}
}
/// Sets the bottom left and bottom right values of the [`Radius`].
pub fn bottom(self, value: impl Into<Pixels>) -> Self {
let value = value.into().0;
Self {
bottom_left: value,
bottom_right: value,
..self
}
}
/// Sets the top left and bottom left values of the [`Radius`].
pub fn left(self, value: impl Into<Pixels>) -> Self {
let value = value.into().0;
Self {
top_left: value,
bottom_left: value,
..self
}
}
/// Sets the top right and bottom right values of the [`Radius`].
pub fn right(self, value: impl Into<Pixels>) -> Self {
let value = value.into().0;
Self {
top_right: value,
bottom_right: value,
..self
}
}
}
impl From<f32> for Radius {
fn from(w: f32) -> Self {
Self([w; 4])
fn from(radius: f32) -> Self {
Self {
top_left: radius,
top_right: radius,
bottom_right: radius,
bottom_left: radius,
}
}
}
@ -80,14 +253,13 @@ impl From<i32> for Radius {
}
}
impl From<[f32; 4]> for Radius {
fn from(radi: [f32; 4]) -> Self {
Self(radi)
}
}
impl From<Radius> for [f32; 4] {
fn from(radi: Radius) -> Self {
radi.0
[
radi.top_left,
radi.top_right,
radi.bottom_right,
radi.bottom_left,
]
}
}

View file

@ -1,22 +0,0 @@
/// The border radii for the corners of a graphics primitive in the order:
/// top-left, top-right, bottom-right, bottom-left.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct BorderRadius([f32; 4]);
impl From<f32> for BorderRadius {
fn from(w: f32) -> Self {
Self([w; 4])
}
}
impl From<[f32; 4]> for BorderRadius {
fn from(radi: [f32; 4]) -> Self {
Self(radi)
}
}
impl From<BorderRadius> for [f32; 4] {
fn from(radi: BorderRadius) -> Self {
radi.0
}
}

View file

@ -43,22 +43,18 @@ impl Color {
///
/// In debug mode, it will panic if the values are not in the correct
/// range: 0.0 - 1.0
pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
const fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
debug_assert!(
(0.0..=1.0).contains(&r),
"Red component must be on [0, 1]"
r >= 0.0 && r <= 1.0,
"Red component must be in [0, 1] range."
);
debug_assert!(
(0.0..=1.0).contains(&g),
"Green component must be on [0, 1]"
g >= 0.0 && g <= 1.0,
"Green component must be in [0, 1] range."
);
debug_assert!(
(0.0..=1.0).contains(&b),
"Blue component must be on [0, 1]"
);
debug_assert!(
(0.0..=1.0).contains(&a),
"Alpha component must be on [0, 1]"
b >= 0.0 && b <= 1.0,
"Blue component must be in [0, 1] range."
);
Color { r, g, b, a }
@ -71,22 +67,17 @@ impl Color {
/// Creates a [`Color`] from its RGBA components.
pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
Color { r, g, b, a }
Color::new(r, g, b, a)
}
/// Creates a [`Color`] from its RGB8 components.
pub fn from_rgb8(r: u8, g: u8, b: u8) -> Color {
pub const fn from_rgb8(r: u8, g: u8, b: u8) -> Color {
Color::from_rgba8(r, g, b, 1.0)
}
/// Creates a [`Color`] from its RGB8 components and an alpha value.
pub fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Color {
Color {
r: f32::from(r) / 255.0,
g: f32::from(g) / 255.0,
b: f32::from(b) / 255.0,
a,
}
pub const fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Color {
Color::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, a)
}
/// Creates a [`Color`] from its linear RGBA components.
@ -109,6 +100,53 @@ impl Color {
}
}
/// Parses a [`Color`] from a hex string.
///
/// Supported formats are `#rrggbb`, `#rrggbbaa`, `#rgb`, and `#rgba`.
/// The starting "#" is optional. Both uppercase and lowercase are supported.
///
/// If you have a static color string, using the [`color!`] macro should be preferred
/// since it leverages hexadecimal literal notation and arithmetic directly.
///
/// [`color!`]: crate::color!
pub fn parse(s: &str) -> Option<Color> {
let hex = s.strip_prefix('#').unwrap_or(s);
let parse_channel = |from: usize, to: usize| {
let num =
usize::from_str_radix(&hex[from..=to], 16).ok()? as f32 / 255.0;
// If we only got half a byte (one letter), expand it into a full byte (two letters)
Some(if from == to { num + num * 16.0 } else { num })
};
Some(match hex.len() {
3 => Color::from_rgb(
parse_channel(0, 0)?,
parse_channel(1, 1)?,
parse_channel(2, 2)?,
),
4 => Color::from_rgba(
parse_channel(0, 0)?,
parse_channel(1, 1)?,
parse_channel(2, 2)?,
parse_channel(3, 3)?,
),
6 => Color::from_rgb(
parse_channel(0, 1)?,
parse_channel(2, 3)?,
parse_channel(4, 5)?,
),
8 => Color::from_rgba(
parse_channel(0, 1)?,
parse_channel(2, 3)?,
parse_channel(4, 5)?,
parse_channel(6, 7)?,
),
_ => None?,
})
}
/// Converts the [`Color`] into its RGBA8 equivalent.
#[must_use]
pub fn into_rgba8(self) -> [u8; 4] {
@ -179,34 +217,29 @@ impl From<[f32; 4]> for Color {
///
/// ```
/// # use iced_core::{Color, color};
/// assert_eq!(color!(0, 0, 0), Color::from_rgb(0., 0., 0.));
/// assert_eq!(color!(0, 0, 0, 0.), Color::from_rgba(0., 0., 0., 0.));
/// assert_eq!(color!(0xffffff), Color::from_rgb(1., 1., 1.));
/// assert_eq!(color!(0xffffff, 0.), Color::from_rgba(1., 1., 1., 0.));
/// assert_eq!(color!(0, 0, 0), Color::BLACK);
/// assert_eq!(color!(0, 0, 0, 0.0), Color::TRANSPARENT);
/// assert_eq!(color!(0xffffff), Color::from_rgb(1.0, 1.0, 1.0));
/// assert_eq!(color!(0xffffff, 0.), Color::from_rgba(1.0, 1.0, 1.0, 0.0));
/// assert_eq!(color!(0x0000ff), Color::from_rgba(0.0, 0.0, 1.0, 1.0));
/// ```
#[macro_export]
macro_rules! color {
($r:expr, $g:expr, $b:expr) => {
color!($r, $g, $b, 1.0)
$crate::Color::from_rgb8($r, $g, $b)
};
($r:expr, $g:expr, $b:expr, $a:expr) => {
$crate::Color {
r: $r as f32 / 255.0,
g: $g as f32 / 255.0,
b: $b as f32 / 255.0,
a: $a,
}
};
($hex:expr) => {{
color!($hex, 1.0)
}};
($r:expr, $g:expr, $b:expr, $a:expr) => {{ $crate::Color::from_rgba8($r, $g, $b, $a) }};
($hex:expr) => {{ $crate::color!($hex, 1.0) }};
($hex:expr, $a:expr) => {{
let hex = $hex as u32;
debug_assert!(hex <= 0xffffff, "color! value must not exceed 0xffffff");
let r = (hex & 0xff0000) >> 16;
let g = (hex & 0xff00) >> 8;
let b = (hex & 0xff);
color!(r, g, b, $a)
$crate::color!(r as u8, g as u8, b as u8, $a)
}};
}
@ -274,4 +307,23 @@ mod tests {
assert_relative_eq!(result.b, 0.3);
assert_relative_eq!(result.a, 1.0);
}
#[test]
fn parse() {
let tests = [
("#ff0000", [255, 0, 0, 255]),
("00ff0080", [0, 255, 0, 128]),
("#F80", [255, 136, 0, 255]),
("#00f1", [0, 0, 255, 17]),
];
for (arg, expected) in tests {
assert_eq!(
Color::parse(arg).expect("color must parse").into_rgba8(),
expected
);
}
assert!(Color::parse("invalid").is_none());
}
}

View file

@ -1,4 +1,3 @@
use crate::event::{self, Event};
use crate::layout;
use crate::mouse;
use crate::overlay;
@ -6,11 +5,10 @@ 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::any::Any;
use std::borrow::Borrow;
/// A generic [`Widget`].
@ -95,6 +93,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
///
/// ```no_run
/// # mod iced {
/// # pub use iced_core::Function;
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// #
/// # pub mod widget {
@ -121,7 +120,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// use counter::Counter;
///
/// use iced::widget::row;
/// use iced::Element;
/// use iced::{Element, Function};
///
/// struct ManyCounters {
/// counters: Vec<Counter>,
@ -144,7 +143,7 @@ impl<'a, Message, Theme, Renderer> Element<'a, Message, Theme, Renderer> {
/// // Here we turn our `Element<counter::Message>` into
/// // an `Element<Message>` by combining the `index` and the
/// // message of the `element`.
/// counter.map(move |message| Message::Counter(index, message))
/// counter.map(Message::Counter.with(index))
/// }),
/// )
/// .into()
@ -305,80 +304,26 @@ where
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<B>,
operation: &mut dyn widget::Operation,
) {
struct MapOperation<'a, B> {
operation: &'a mut dyn widget::Operation<B>,
}
impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
fn container(
&mut self,
id: Option<&widget::Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(
&mut dyn widget::Operation<T>,
),
) {
self.operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut MapOperation { operation });
});
}
fn focusable(
&mut self,
state: &mut dyn widget::operation::Focusable,
id: Option<&widget::Id>,
) {
self.operation.focusable(state, id);
}
fn scrollable(
&mut self,
state: &mut dyn widget::operation::Scrollable,
id: Option<&widget::Id>,
bounds: Rectangle,
translation: Vector,
) {
self.operation.scrollable(state, id, bounds, translation);
}
fn text_input(
&mut self,
state: &mut dyn widget::operation::TextInput,
id: Option<&widget::Id>,
) {
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
self.operation.custom(state, id);
}
}
self.widget.operate(
tree,
layout,
renderer,
&mut MapOperation { operation },
);
self.widget.operate(tree, layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
tree: &mut Tree,
event: Event,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
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,
@ -390,8 +335,6 @@ where
);
shell.merge(local_shell, &self.mapper);
status
}
fn draw(
@ -452,8 +395,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,
{
@ -495,27 +438,27 @@ where
state: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
operation: &mut dyn widget::Operation,
) {
self.element
.widget
.operate(state, layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
state: &mut Tree,
event: Event,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
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(

View file

@ -1,4 +1,5 @@
//! Handle events of a user interface.
use crate::input_method;
use crate::keyboard;
use crate::mouse;
use crate::touch;
@ -19,31 +20,13 @@ pub enum Event {
Mouse(mouse::Event),
/// A window event
Window(window::Id, window::Event),
Window(window::Event),
/// A touch event
Touch(touch::Event),
/// A platform specific event
PlatformSpecific(PlatformSpecific),
}
/// A platform specific event
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PlatformSpecific {
/// A MacOS specific event
MacOS(MacOS),
}
/// Describes an event specific to MacOS
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MacOS {
/// Triggered when the app receives an URL from the system
///
/// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_
///
/// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19
ReceivedUrl(String),
/// An input method event
InputMethod(input_method::Event),
}
/// The status of an [`Event`] after being processed.

View file

@ -7,6 +7,73 @@ use rustc_hash::FxHasher;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
/// A raster image that can be drawn.
#[derive(Debug, Clone, PartialEq)]
pub struct Image<H = Handle> {
/// The handle of the image.
pub handle: H,
/// The filter method of the image.
pub filter_method: FilterMethod,
/// The rotation to be applied to the image; on its center.
pub rotation: Radians,
/// The opacity of the image.
///
/// 0 means transparent. 1 means opaque.
pub opacity: f32,
/// If set to `true`, the image will be snapped to the pixel grid.
///
/// This can avoid graphical glitches, specially when using
/// [`FilterMethod::Nearest`].
pub snap: bool,
}
impl Image<Handle> {
/// Creates a new [`Image`] with the given handle.
pub fn new(handle: impl Into<Handle>) -> Self {
Self {
handle: handle.into(),
filter_method: FilterMethod::default(),
rotation: Radians(0.0),
opacity: 1.0,
snap: false,
}
}
/// Sets the filter method of the [`Image`].
pub fn filter_method(mut self, filter_method: FilterMethod) -> Self {
self.filter_method = filter_method;
self
}
/// Sets the rotation of the [`Image`].
pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
self.rotation = rotation.into();
self
}
/// Sets the opacity of the [`Image`].
pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
self.opacity = opacity.into();
self
}
/// Sets whether the [`Image`] should be snapped to the pixel grid.
pub fn snap(mut self, snap: bool) -> Self {
self.snap = snap;
self
}
}
impl From<&Handle> for Image {
fn from(handle: &Handle) -> Self {
Image::new(handle.clone())
}
}
/// A handle of some image data.
#[derive(Clone, PartialEq, Eq)]
pub enum Handle {
@ -101,6 +168,12 @@ where
}
}
impl From<&Handle> for Handle {
fn from(value: &Handle) -> Self {
value.clone()
}
}
impl std::fmt::Debug for Handle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@ -166,14 +239,6 @@ pub trait Renderer: crate::Renderer {
/// Returns the dimensions of an image for the given [`Handle`].
fn measure_image(&self, handle: &Self::Handle) -> Size<u32>;
/// Draws an image with the given [`Handle`] and inside the provided
/// `bounds`.
fn draw_image(
&mut self,
handle: Self::Handle,
filter_method: FilterMethod,
bounds: Rectangle,
rotation: Radians,
opacity: f32,
);
/// Draws an [`Image`] inside the provided `bounds`.
fn draw_image(&mut self, image: Image<Self::Handle>, bounds: Rectangle);
}

221
core/src/input_method.rs Normal file
View file

@ -0,0 +1,221 @@
//! Listen to input method events.
use crate::{Pixels, Point};
use std::ops::Range;
/// The input method strategy of a widget.
#[derive(Debug, Clone, PartialEq)]
pub enum InputMethod<T = String> {
/// Input method is disabled.
Disabled,
/// Input method is enabled.
Enabled {
/// The position at which the input method dialog should be placed.
position: Point,
/// The [`Purpose`] of the input method.
purpose: Purpose,
/// The preedit to overlay on top of the input method dialog, if needed.
///
/// Ideally, your widget will show pre-edits on-the-spot; but, since that can
/// be tricky, you can instead provide the current pre-edit here and the
/// runtime will display it as an overlay (i.e. "Over-the-spot IME").
preedit: Option<Preedit<T>>,
},
}
/// The pre-edit of an [`InputMethod`].
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Preedit<T = String> {
/// The current content.
pub content: T,
/// The selected range of the content.
pub selection: Option<Range<usize>>,
/// The text size of the content.
pub text_size: Option<Pixels>,
}
impl<T> Preedit<T> {
/// Creates a new empty [`Preedit`].
pub fn new() -> Self
where
T: Default,
{
Self::default()
}
/// Turns a [`Preedit`] into its owned version.
pub fn to_owned(&self) -> Preedit
where
T: AsRef<str>,
{
Preedit {
content: self.content.as_ref().to_owned(),
selection: self.selection.clone(),
text_size: self.text_size,
}
}
}
impl Preedit {
/// Borrows the contents of a [`Preedit`].
pub fn as_ref(&self) -> Preedit<&str> {
Preedit {
content: &self.content,
selection: self.selection.clone(),
text_size: self.text_size,
}
}
}
/// The purpose of an [`InputMethod`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Purpose {
/// No special hints for the IME (default).
#[default]
Normal,
/// The IME is used for secure input (e.g. passwords).
Secure,
/// The IME is used to input into a terminal.
///
/// For example, that could alter OSK on Wayland to show extra buttons.
Terminal,
}
impl InputMethod {
/// Merges two [`InputMethod`] strategies, prioritizing the first one when both open:
/// ```
/// # use iced_core::input_method::{InputMethod, Purpose, Preedit};
/// # use iced_core::Point;
///
/// let open = InputMethod::Enabled {
/// position: Point::ORIGIN,
/// purpose: Purpose::Normal,
/// preedit: Some(Preedit { content: "1".to_owned(), selection: None, text_size: None }),
/// };
///
/// let open_2 = InputMethod::Enabled {
/// position: Point::ORIGIN,
/// purpose: Purpose::Secure,
/// preedit: Some(Preedit { content: "2".to_owned(), selection: None, text_size: None }),
/// };
///
/// let mut ime = InputMethod::Disabled;
///
/// ime.merge(&open);
/// assert_eq!(ime, open);
///
/// ime.merge(&open_2);
/// assert_eq!(ime, open);
/// ```
pub fn merge<T: AsRef<str>>(&mut self, other: &InputMethod<T>) {
if let InputMethod::Enabled { .. } = self {
return;
}
*self = other.to_owned();
}
/// Returns true if the [`InputMethod`] is open.
pub fn is_enabled(&self) -> bool {
matches!(self, Self::Enabled { .. })
}
}
impl<T> InputMethod<T> {
/// Turns an [`InputMethod`] into its owned version.
pub fn to_owned(&self) -> InputMethod
where
T: AsRef<str>,
{
match self {
Self::Disabled => InputMethod::Disabled,
Self::Enabled {
position,
purpose,
preedit,
} => InputMethod::Enabled {
position: *position,
purpose: *purpose,
preedit: preedit.as_ref().map(Preedit::to_owned),
},
}
}
}
/// Describes [input method](https://en.wikipedia.org/wiki/Input_method) events.
///
/// This is also called a "composition event".
///
/// Most keypresses using a latin-like keyboard layout simply generate a
/// [`keyboard::Event::KeyPressed`](crate::keyboard::Event::KeyPressed).
/// However, one couldn't possibly have a key for every single
/// unicode character that the user might want to type. The solution operating systems employ is
/// to allow the user to type these using _a sequence of keypresses_ instead.
///
/// A prominent example of this is accents—many keyboard layouts allow you to first click the
/// "accent key", and then the character you want to apply the accent to. In this case, some
/// platforms will generate the following event sequence:
///
/// ```ignore
/// // Press "`" key
/// Ime::Preedit("`", Some((0, 0)))
/// // Press "E" key
/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
/// Ime::Commit("é")
/// ```
///
/// Additionally, certain input devices are configured to display a candidate box that allow the
/// user to select the desired character interactively. (To properly position this box, you must use
/// [`Shell::request_input_method`](crate::Shell::request_input_method).)
///
/// An example of a keyboard layout which uses candidate boxes is pinyin. On a latin keyboard the
/// following event sequence could be obtained:
///
/// ```ignore
/// // Press "A" key
/// Ime::Preedit("a", Some((1, 1)))
/// // Press "B" key
/// Ime::Preedit("a b", Some((3, 3)))
/// // Press left arrow key
/// Ime::Preedit("a b", Some((1, 1)))
/// // Press space key
/// Ime::Preedit("啊b", Some((3, 3)))
/// // Press space key
/// Ime::Preedit("", None) // Synthetic event generated to clear preedit.
/// Ime::Commit("啊不")
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Event {
/// Notifies when the IME was opened.
///
/// After getting this event you could receive [`Preedit`][Self::Preedit] and
/// [`Commit`][Self::Commit] events. You should also start performing IME related requests
/// like [`Shell::request_input_method`].
///
/// [`Shell::request_input_method`]: crate::Shell::request_input_method
Opened,
/// Notifies when a new composing text should be set at the cursor position.
///
/// The value represents a pair of the preedit string and the cursor begin position and end
/// position. When it's `None`, the cursor should be hidden. When `String` is an empty string
/// this indicates that preedit was cleared.
///
/// The cursor range is byte-wise indexed.
Preedit(String, Option<Range<usize>>),
/// Notifies when text should be inserted into the editor widget.
///
/// Right before this event, an empty [`Self::Preedit`] event will be issued.
Commit(String),
/// Notifies when the IME was disabled.
///
/// After receiving this event you won't get any more [`Preedit`][Self::Preedit] or
/// [`Commit`][Self::Commit] events until the next [`Opened`][Self::Opened] event. You should
/// also stop issuing IME related requests like [`Shell::request_input_method`] and clear
/// pending preedit text.
///
/// [`Shell::request_input_method`]: crate::Shell::request_input_method
Closed,
}

View file

@ -1,5 +1,6 @@
use crate::keyboard::{Key, Location, Modifiers};
use crate::SmolStr;
use crate::keyboard::key;
use crate::keyboard::{Key, Location, Modifiers};
/// A keyboard event.
///
@ -14,6 +15,12 @@ pub enum Event {
/// The key pressed.
key: Key,
/// The key pressed with all keyboard modifiers applied, except Ctrl.
modified_key: Key,
/// The physical key pressed.
physical_key: key::Physical,
/// The location of the key.
location: Location,
@ -29,6 +36,12 @@ pub enum Event {
/// The key released.
key: Key,
/// The key released with all keyboard modifiers applied, except Ctrl.
modified_key: Key,
/// The physical key released.
physical_key: key::Physical,
/// The location of the key.
location: Location,

View file

@ -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`].
@ -203,7 +209,7 @@ pub enum Named {
Standby,
/// The WakeUp key. (`KEYCODE_WAKEUP`)
WakeUp,
/// Initate the multi-candidate mode.
/// Initiate the multi-candidate mode.
AllCandidates,
Alphanumeric,
/// Initiate the Code Input mode to allow characters to be entered by
@ -742,3 +748,536 @@ pub enum Named {
/// General-purpose function key.
F35,
}
/// Code representing the location of a physical key.
///
/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few
/// exceptions:
/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and
/// "SuperRight" here.
/// - The key that the specification calls "Super" is reported as `Unidentified` here.
///
/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(missing_docs)]
#[non_exhaustive]
pub enum Code {
/// <kbd>`</kbd> on a US keyboard. This is also called a backtick or grave.
/// This is the <kbd>半角</kbd>/<kbd>全角</kbd>/<kbd>漢字</kbd>
/// (hankaku/zenkaku/kanji) key on Japanese keyboards
Backquote,
/// Used for both the US <kbd>\\</kbd> (on the 101-key layout) and also for the key
/// located between the <kbd>"</kbd> and <kbd>Enter</kbd> keys on row C of the 102-,
/// 104- and 106-key layouts.
/// Labeled <kbd>#</kbd> on a UK (102) keyboard.
Backslash,
/// <kbd>[</kbd> on a US keyboard.
BracketLeft,
/// <kbd>]</kbd> on a US keyboard.
BracketRight,
/// <kbd>,</kbd> on a US keyboard.
Comma,
/// <kbd>0</kbd> on a US keyboard.
Digit0,
/// <kbd>1</kbd> on a US keyboard.
Digit1,
/// <kbd>2</kbd> on a US keyboard.
Digit2,
/// <kbd>3</kbd> on a US keyboard.
Digit3,
/// <kbd>4</kbd> on a US keyboard.
Digit4,
/// <kbd>5</kbd> on a US keyboard.
Digit5,
/// <kbd>6</kbd> on a US keyboard.
Digit6,
/// <kbd>7</kbd> on a US keyboard.
Digit7,
/// <kbd>8</kbd> on a US keyboard.
Digit8,
/// <kbd>9</kbd> on a US keyboard.
Digit9,
/// <kbd>=</kbd> on a US keyboard.
Equal,
/// Located between the left <kbd>Shift</kbd> and <kbd>Z</kbd> keys.
/// Labeled <kbd>\\</kbd> on a UK keyboard.
IntlBackslash,
/// Located between the <kbd>/</kbd> and right <kbd>Shift</kbd> keys.
/// Labeled <kbd>\\</kbd> (ro) on a Japanese keyboard.
IntlRo,
/// Located between the <kbd>=</kbd> and <kbd>Backspace</kbd> keys.
/// Labeled <kbd>¥</kbd> (yen) on a Japanese keyboard. <kbd>\\</kbd> on a
/// Russian keyboard.
IntlYen,
/// <kbd>a</kbd> on a US keyboard.
/// Labeled <kbd>q</kbd> on an AZERTY (e.g., French) keyboard.
KeyA,
/// <kbd>b</kbd> on a US keyboard.
KeyB,
/// <kbd>c</kbd> on a US keyboard.
KeyC,
/// <kbd>d</kbd> on a US keyboard.
KeyD,
/// <kbd>e</kbd> on a US keyboard.
KeyE,
/// <kbd>f</kbd> on a US keyboard.
KeyF,
/// <kbd>g</kbd> on a US keyboard.
KeyG,
/// <kbd>h</kbd> on a US keyboard.
KeyH,
/// <kbd>i</kbd> on a US keyboard.
KeyI,
/// <kbd>j</kbd> on a US keyboard.
KeyJ,
/// <kbd>k</kbd> on a US keyboard.
KeyK,
/// <kbd>l</kbd> on a US keyboard.
KeyL,
/// <kbd>m</kbd> on a US keyboard.
KeyM,
/// <kbd>n</kbd> on a US keyboard.
KeyN,
/// <kbd>o</kbd> on a US keyboard.
KeyO,
/// <kbd>p</kbd> on a US keyboard.
KeyP,
/// <kbd>q</kbd> on a US keyboard.
/// Labeled <kbd>a</kbd> on an AZERTY (e.g., French) keyboard.
KeyQ,
/// <kbd>r</kbd> on a US keyboard.
KeyR,
/// <kbd>s</kbd> on a US keyboard.
KeyS,
/// <kbd>t</kbd> on a US keyboard.
KeyT,
/// <kbd>u</kbd> on a US keyboard.
KeyU,
/// <kbd>v</kbd> on a US keyboard.
KeyV,
/// <kbd>w</kbd> on a US keyboard.
/// Labeled <kbd>z</kbd> on an AZERTY (e.g., French) keyboard.
KeyW,
/// <kbd>x</kbd> on a US keyboard.
KeyX,
/// <kbd>y</kbd> on a US keyboard.
/// Labeled <kbd>z</kbd> on a QWERTZ (e.g., German) keyboard.
KeyY,
/// <kbd>z</kbd> on a US keyboard.
/// Labeled <kbd>w</kbd> on an AZERTY (e.g., French) keyboard, and <kbd>y</kbd> on a
/// QWERTZ (e.g., German) keyboard.
KeyZ,
/// <kbd>-</kbd> on a US keyboard.
Minus,
/// <kbd>.</kbd> on a US keyboard.
Period,
/// <kbd>'</kbd> on a US keyboard.
Quote,
/// <kbd>;</kbd> on a US keyboard.
Semicolon,
/// <kbd>/</kbd> on a US keyboard.
Slash,
/// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>.
AltLeft,
/// <kbd>Alt</kbd>, <kbd>Option</kbd>, or <kbd>⌥</kbd>.
/// This is labeled <kbd>AltGr</kbd> on many keyboard layouts.
AltRight,
/// <kbd>Backspace</kbd> or <kbd>⌫</kbd>.
/// Labeled <kbd>Delete</kbd> on Apple keyboards.
Backspace,
/// <kbd>CapsLock</kbd> or <kbd>⇪</kbd>
CapsLock,
/// The application context menu key, which is typically found between the right
/// <kbd>Super</kbd> key and the right <kbd>Control</kbd> key.
ContextMenu,
/// <kbd>Control</kbd> or <kbd>⌃</kbd>
ControlLeft,
/// <kbd>Control</kbd> or <kbd>⌃</kbd>
ControlRight,
/// <kbd>Enter</kbd> or <kbd>↵</kbd>. Labeled <kbd>Return</kbd> on Apple keyboards.
Enter,
/// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
SuperLeft,
/// The Windows, <kbd>⌘</kbd>, <kbd>Command</kbd>, or other OS symbol key.
SuperRight,
/// <kbd>Shift</kbd> or <kbd>⇧</kbd>
ShiftLeft,
/// <kbd>Shift</kbd> or <kbd>⇧</kbd>
ShiftRight,
/// <kbd> </kbd> (space)
Space,
/// <kbd>Tab</kbd> or <kbd>⇥</kbd>
Tab,
/// Japanese: <kbd>変</kbd> (henkan)
Convert,
/// Japanese: <kbd>カタカナ</kbd>/<kbd>ひらがな</kbd>/<kbd>ローマ字</kbd>
/// (katakana/hiragana/romaji)
KanaMode,
/// Korean: HangulMode <kbd>한/영</kbd> (han/yeong)
///
/// Japanese (Mac keyboard): <kbd>か</kbd> (kana)
Lang1,
/// Korean: Hanja <kbd>한</kbd> (hanja)
///
/// Japanese (Mac keyboard): <kbd>英</kbd> (eisu)
Lang2,
/// Japanese (word-processing keyboard): Katakana
Lang3,
/// Japanese (word-processing keyboard): Hiragana
Lang4,
/// Japanese (word-processing keyboard): Zenkaku/Hankaku
Lang5,
/// Japanese: <kbd>無変換</kbd> (muhenkan)
NonConvert,
/// <kbd>⌦</kbd>. The forward delete key.
/// Note that on Apple keyboards, the key labelled <kbd>Delete</kbd> on the main part of
/// the keyboard is encoded as [`Backspace`].
///
/// [`Backspace`]: Self::Backspace
Delete,
/// <kbd>Page Down</kbd>, <kbd>End</kbd>, or <kbd>↘</kbd>
End,
/// <kbd>Help</kbd>. Not present on standard PC keyboards.
Help,
/// <kbd>Home</kbd> or <kbd>↖</kbd>
Home,
/// <kbd>Insert</kbd> or <kbd>Ins</kbd>. Not present on Apple keyboards.
Insert,
/// <kbd>Page Down</kbd>, <kbd>PgDn</kbd>, or <kbd>⇟</kbd>
PageDown,
/// <kbd>Page Up</kbd>, <kbd>PgUp</kbd>, or <kbd>⇞</kbd>
PageUp,
/// <kbd>↓</kbd>
ArrowDown,
/// <kbd>←</kbd>
ArrowLeft,
/// <kbd>→</kbd>
ArrowRight,
/// <kbd>↑</kbd>
ArrowUp,
/// On the Mac, this is used for the numpad <kbd>Clear</kbd> key.
NumLock,
/// <kbd>0 Ins</kbd> on a keyboard. <kbd>0</kbd> on a phone or remote control
Numpad0,
/// <kbd>1 End</kbd> on a keyboard. <kbd>1</kbd> or <kbd>1 QZ</kbd> on a phone or remote
/// control
Numpad1,
/// <kbd>2 ↓</kbd> on a keyboard. <kbd>2 ABC</kbd> on a phone or remote control
Numpad2,
/// <kbd>3 PgDn</kbd> on a keyboard. <kbd>3 DEF</kbd> on a phone or remote control
Numpad3,
/// <kbd>4 ←</kbd> on a keyboard. <kbd>4 GHI</kbd> on a phone or remote control
Numpad4,
/// <kbd>5</kbd> on a keyboard. <kbd>5 JKL</kbd> on a phone or remote control
Numpad5,
/// <kbd>6 →</kbd> on a keyboard. <kbd>6 MNO</kbd> on a phone or remote control
Numpad6,
/// <kbd>7 Home</kbd> on a keyboard. <kbd>7 PQRS</kbd> or <kbd>7 PRS</kbd> on a phone
/// or remote control
Numpad7,
/// <kbd>8 ↑</kbd> on a keyboard. <kbd>8 TUV</kbd> on a phone or remote control
Numpad8,
/// <kbd>9 PgUp</kbd> on a keyboard. <kbd>9 WXYZ</kbd> or <kbd>9 WXY</kbd> on a phone
/// or remote control
Numpad9,
/// <kbd>+</kbd>
NumpadAdd,
/// Found on the Microsoft Natural Keyboard.
NumpadBackspace,
/// <kbd>C</kbd> or <kbd>A</kbd> (All Clear). Also for use with numpads that have a
/// <kbd>Clear</kbd> key that is separate from the <kbd>NumLock</kbd> key. On the Mac, the
/// numpad <kbd>Clear</kbd> key is encoded as [`NumLock`].
///
/// [`NumLock`]: Self::NumLock
NumpadClear,
/// <kbd>C</kbd> (Clear Entry)
NumpadClearEntry,
/// <kbd>,</kbd> (thousands separator). For locales where the thousands separator
/// is a "." (e.g., Brazil), this key may generate a <kbd>.</kbd>.
NumpadComma,
/// <kbd>. Del</kbd>. For locales where the decimal separator is "," (e.g.,
/// Brazil), this key may generate a <kbd>,</kbd>.
NumpadDecimal,
/// <kbd>/</kbd>
NumpadDivide,
NumpadEnter,
/// <kbd>=</kbd>
NumpadEqual,
/// <kbd>#</kbd> on a phone or remote control device. This key is typically found
/// below the <kbd>9</kbd> key and to the right of the <kbd>0</kbd> key.
NumpadHash,
/// <kbd>M</kbd> Add current entry to the value stored in memory.
NumpadMemoryAdd,
/// <kbd>M</kbd> Clear the value stored in memory.
NumpadMemoryClear,
/// <kbd>M</kbd> Replace the current entry with the value stored in memory.
NumpadMemoryRecall,
/// <kbd>M</kbd> Replace the value stored in memory with the current entry.
NumpadMemoryStore,
/// <kbd>M</kbd> Subtract current entry from the value stored in memory.
NumpadMemorySubtract,
/// <kbd>*</kbd> on a keyboard. For use with numpads that provide mathematical
/// operations (<kbd>+</kbd>, <kbd>-</kbd> <kbd>*</kbd> and <kbd>/</kbd>).
///
/// Use `NumpadStar` for the <kbd>*</kbd> key on phones and remote controls.
NumpadMultiply,
/// <kbd>(</kbd> Found on the Microsoft Natural Keyboard.
NumpadParenLeft,
/// <kbd>)</kbd> Found on the Microsoft Natural Keyboard.
NumpadParenRight,
/// <kbd>*</kbd> on a phone or remote control device.
///
/// This key is typically found below the <kbd>7</kbd> key and to the left of
/// the <kbd>0</kbd> key.
///
/// Use <kbd>"NumpadMultiply"</kbd> for the <kbd>*</kbd> key on
/// numeric keypads.
NumpadStar,
/// <kbd>-</kbd>
NumpadSubtract,
/// <kbd>Esc</kbd> or <kbd>⎋</kbd>
Escape,
/// <kbd>Fn</kbd> This is typically a hardware key that does not generate a separate code.
Fn,
/// <kbd>FLock</kbd> or <kbd>FnLock</kbd>. Function Lock key. Found on the Microsoft
/// Natural Keyboard.
FnLock,
/// <kbd>PrtScr SysRq</kbd> or <kbd>Print Screen</kbd>
PrintScreen,
/// <kbd>Scroll Lock</kbd>
ScrollLock,
/// <kbd>Pause Break</kbd>
Pause,
/// Some laptops place this key to the left of the <kbd>↑</kbd> key.
///
/// This also the "back" button (triangle) on Android.
BrowserBack,
BrowserFavorites,
/// Some laptops place this key to the right of the <kbd>↑</kbd> key.
BrowserForward,
/// The "home" button on Android.
BrowserHome,
BrowserRefresh,
BrowserSearch,
BrowserStop,
/// <kbd>Eject</kbd> or <kbd>⏏</kbd>. This key is placed in the function section on some Apple
/// keyboards.
Eject,
/// Sometimes labelled <kbd>My Computer</kbd> on the keyboard
LaunchApp1,
/// Sometimes labelled <kbd>Calculator</kbd> on the keyboard
LaunchApp2,
LaunchMail,
MediaPlayPause,
MediaSelect,
MediaStop,
MediaTrackNext,
MediaTrackPrevious,
/// This key is placed in the function section on some Apple keyboards, replacing the
/// <kbd>Eject</kbd> key.
Power,
Sleep,
AudioVolumeDown,
AudioVolumeMute,
AudioVolumeUp,
WakeUp,
// Legacy modifier key. Also called "Super" in certain places.
Meta,
// Legacy modifier key.
Hyper,
Turbo,
Abort,
Resume,
Suspend,
/// Found on Suns USB keyboard.
Again,
/// Found on Suns USB keyboard.
Copy,
/// Found on Suns USB keyboard.
Cut,
/// Found on Suns USB keyboard.
Find,
/// Found on Suns USB keyboard.
Open,
/// Found on Suns USB keyboard.
Paste,
/// Found on Suns USB keyboard.
Props,
/// Found on Suns USB keyboard.
Select,
/// Found on Suns USB keyboard.
Undo,
/// Use for dedicated <kbd>ひらがな</kbd> key found on some Japanese word processing keyboards.
Hiragana,
/// Use for dedicated <kbd>カタカナ</kbd> key found on some Japanese word processing keyboards.
Katakana,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F1,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F2,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F3,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F4,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F5,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F6,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F7,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F8,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F9,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F10,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F11,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F12,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F13,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F14,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F15,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F16,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F17,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F18,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F19,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F20,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F21,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F22,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F23,
/// General-purpose function key.
/// Usually found at the top of the keyboard.
F24,
/// General-purpose function key.
F25,
/// General-purpose function key.
F26,
/// General-purpose function key.
F27,
/// General-purpose function key.
F28,
/// General-purpose function key.
F29,
/// General-purpose function key.
F30,
/// General-purpose function key.
F31,
/// General-purpose function key.
F32,
/// General-purpose function key.
F33,
/// General-purpose function key.
F34,
/// General-purpose function key.
F35,
}
/// Contains the platform-native physical key identifier.
///
/// The exact values vary from platform to platform (which is part of why this is a per-platform
/// enum), but the values are primarily tied to the key's physical location on the keyboard.
///
/// This enum is primarily used to store raw keycodes when Winit doesn't map a given native
/// physical key identifier to a meaningful [`Code`] variant. In the presence of identifiers we
/// haven't mapped for you yet, this lets you use use [`Code`] to:
///
/// - Correctly match key press and release events.
/// - On non-web platforms, support assigning keybinds to virtually any key through a UI.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum NativeCode {
/// An unidentified code.
Unidentified,
/// An Android "scancode".
Android(u32),
/// A macOS "scancode".
MacOS(u16),
/// A Windows "scancode".
Windows(u16),
/// An XKB "keycode".
Xkb(u32),
}
/// Represents the location of a physical key.
///
/// This type is a superset of [`Code`], including an [`Unidentified`][Self::Unidentified]
/// variant.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Physical {
/// A known key code
Code(Code),
/// This variant is used when the key cannot be translated to a [`Code`]
///
/// The native keycode is provided (if available) so you're able to more reliably match
/// key-press and key-release events by hashing the [`Physical`] key. It is also possible to use
/// this for keybinds for non-standard keys, but such keybinds are tied to a given platform.
Unidentified(NativeCode),
}
impl PartialEq<Code> for Physical {
#[inline]
fn eq(&self, rhs: &Code) -> bool {
match self {
Physical::Code(code) => code == rhs,
Physical::Unidentified(_) => false,
}
}
}
impl PartialEq<Physical> for Code {
#[inline]
fn eq(&self, rhs: &Physical) -> bool {
rhs == self
}
}
impl PartialEq<NativeCode> for Physical {
#[inline]
fn eq(&self, rhs: &NativeCode) -> bool {
match self {
Physical::Unidentified(code) => code == rhs,
Physical::Code(_) => false,
}
}
}
impl PartialEq<Physical> for NativeCode {
#[inline]
fn eq(&self, rhs: &Physical) -> bool {
rhs == self
}
}

View file

@ -33,7 +33,7 @@ impl Modifiers {
/// This is normally the main modifier to be used for hotkeys.
///
/// On macOS, this is equivalent to `Self::LOGO`.
/// Ohterwise, this is equivalent to `Self::CTRL`.
/// Otherwise, this is equivalent to `Self::CTRL`.
pub const COMMAND: Self = if cfg!(target_os = "macos") {
Self::LOGO
} else {
@ -84,4 +84,28 @@ impl Modifiers {
is_pressed
}
/// Returns true if the "jump key" is pressed in the [`Modifiers`].
///
/// The "jump key" is the modifier key used to widen text motions. It is the `Alt`
/// key in macOS and the `Ctrl` key in other platforms.
pub fn jump(self) -> bool {
if cfg!(target_os = "macos") {
self.alt()
} else {
self.control()
}
}
/// Returns true if the "command key" is pressed on a macOS device.
///
/// This is relevant for macOS-specific actions (e.g. `⌘ + ArrowLeft` moves the cursor
/// to the beginning of the line).
pub fn macos_command(self) -> bool {
if cfg!(target_os = "macos") {
self.logo()
} else {
false
}
}
}

View file

@ -79,10 +79,11 @@ where
let max_cross = axis.cross(limits.max());
let mut fill_main_sum = 0;
let mut cross = match axis {
Axis::Vertical if width == Length::Shrink => 0.0,
Axis::Horizontal if height == Length::Shrink => 0.0,
_ => max_cross,
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),
_ => (max_cross, false),
};
let mut available = axis.main(limits.max()) - total_spacing;
@ -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();
@ -97,7 +102,8 @@ where
axis.pack(size.width.fill_factor(), size.height.fill_factor())
};
if fill_main_factor == 0 {
if fill_main_factor == 0 && (!cross_compress || fill_cross_factor == 0)
{
let (max_width, max_height) = axis.pack(
available,
if fill_cross_factor == 0 {
@ -120,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;
}
}
}
@ -134,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();
@ -145,6 +189,12 @@ where
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 {
@ -177,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;

View file

@ -103,12 +103,13 @@ impl Node {
}
/// Translates the [`Node`] by the given translation.
pub fn translate(self, translation: impl Into<Vector>) -> Self {
let translation = translation.into();
pub fn translate(mut self, translation: impl Into<Vector>) -> Self {
self.translate_mut(translation);
self
}
Self {
bounds: self.bounds + translation,
..self
}
/// Translates the [`Node`] by the given translation.
pub fn translate_mut(&mut self, translation: impl Into<Vector>) {
self.bounds = self.bounds + translation.into();
}
}

View file

@ -77,8 +77,8 @@ impl From<f32> for Length {
}
}
impl From<u16> for Length {
fn from(units: u16) -> Self {
Length::Fixed(f32::from(units))
impl From<u32> for Length {
fn from(units: u32) -> Self {
Length::Fixed(units as f32)
}
}

View file

@ -10,16 +10,19 @@
html_logo_url = "https://raw.githubusercontent.com/iced-rs/iced/9ab6923e943f784985e9ef9ca28b10278297225d/docs/logo.svg"
)]
pub mod alignment;
pub mod animation;
pub mod border;
pub mod clipboard;
pub mod event;
pub mod font;
pub mod gradient;
pub mod image;
pub mod input_method;
pub mod keyboard;
pub mod layout;
pub mod mouse;
pub mod overlay;
pub mod padding;
pub mod renderer;
pub mod svg;
pub mod text;
@ -35,11 +38,11 @@ mod color;
mod content_fit;
mod element;
mod length;
mod padding;
mod pixels;
mod point;
mod rectangle;
mod rotation;
mod settings;
mod shadow;
mod shell;
mod size;
@ -48,6 +51,7 @@ mod vector;
pub use alignment::Alignment;
pub use angle::{Degrees, Radians};
pub use animation::Animation;
pub use background::Background;
pub use border::Border;
pub use clipboard::Clipboard;
@ -57,6 +61,8 @@ pub use element::Element;
pub use event::Event;
pub use font::Font;
pub use gradient::Gradient;
pub use image::Image;
pub use input_method::InputMethod;
pub use layout::Layout;
pub use length::Length;
pub use overlay::Overlay;
@ -66,9 +72,11 @@ 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;
pub use svg::Svg;
pub use text::Text;
pub use theme::Theme;
pub use transformation::Transformation;
@ -76,3 +84,69 @@ pub use vector::Vector;
pub use widget::Widget;
pub use smol_str::SmolStr;
/// A function that can _never_ be called.
///
/// This is useful to turn generic types into anything
/// you want by coercing them into a type with no possible
/// values.
pub fn never<T>(never: std::convert::Infallible) -> T {
match never {}
}
/// A trait extension for binary functions (`Fn(A, B) -> O`).
///
/// It enables you to use a bunch of nifty functional programming paradigms
/// that work well with iced.
pub trait Function<A, B, O> {
/// Applies the given first argument to a binary function and returns
/// a new function that takes the other argument.
///
/// This lets you partially "apply" a function—equivalent to currying,
/// but it only works with binary functions. If you want to apply an
/// arbitrary number of arguments, create a little struct for them.
///
/// # When is this useful?
/// Sometimes you will want to identify the source or target
/// of some message in your user interface. This can be achieved through
/// normal means by defining a closure and moving the identifier
/// inside:
///
/// ```rust
/// # let element: Option<()> = Some(());
/// # enum Message { ButtonPressed(u32, ()) }
/// let id = 123;
///
/// # let _ = {
/// element.map(move |result| Message::ButtonPressed(id, result))
/// # };
/// ```
///
/// That's quite a mouthful. [`with`](Self::with) lets you write:
///
/// ```rust
/// # use iced_core::Function;
/// # let element: Option<()> = Some(());
/// # enum Message { ButtonPressed(u32, ()) }
/// let id = 123;
///
/// # let _ = {
/// element.map(Message::ButtonPressed.with(id))
/// # };
/// ```
///
/// Effectively creating the same closure that partially applies
/// the `id` to the message—but much more concise!
fn with(self, prefix: A) -> impl Fn(B) -> O;
}
impl<F, A, B, O> Function<A, B, O> for F
where
F: Fn(A, B) -> O,
Self: Sized,
A: Clone,
{
fn with(self, prefix: A) -> impl Fn(B) -> O {
move |result| self(prefix.clone(), result)
}
}

View file

@ -1,17 +1,21 @@
//! Track mouse clicks.
use crate::mouse::Button;
use crate::time::Instant;
use crate::Point;
use crate::{Point, Transformation};
use std::ops::Mul;
/// A mouse click.
#[derive(Debug, Clone, Copy)]
pub struct Click {
kind: Kind,
button: Button,
position: Point,
time: Instant,
}
/// The kind of mouse click.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
/// A single click
Single,
@ -36,11 +40,17 @@ impl Kind {
impl Click {
/// Creates a new [`Click`] with the given position and previous last
/// [`Click`].
pub fn new(position: Point, previous: Option<Click>) -> Click {
pub fn new(
position: Point,
button: Button,
previous: Option<Click>,
) -> Click {
let time = Instant::now();
let kind = if let Some(previous) = previous {
if previous.is_consecutive(position, time) {
if previous.is_consecutive(position, time)
&& button == previous.button
{
previous.kind.next()
} else {
Kind::Single
@ -51,6 +61,7 @@ impl Click {
Click {
kind,
button,
position,
time,
}
@ -73,9 +84,22 @@ impl Click {
None
};
self.position == new_position
self.position.distance(new_position) < 6.0
&& duration
.map(|duration| duration.as_millis() <= 300)
.unwrap_or(false)
}
}
impl Mul<Transformation> for Click {
type Output = Click;
fn mul(self, transformation: Transformation) -> Click {
Click {
kind: self.kind,
button: self.button,
position: self.position * transformation,
time: self.time,
}
}
}

View file

@ -1,4 +1,4 @@
use crate::{Point, Rectangle, Vector};
use crate::{Point, Rectangle, Transformation, Vector};
/// The mouse cursor state.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
@ -6,6 +6,9 @@ pub enum Cursor {
/// The cursor has a defined position.
Available(Point),
/// The cursor has a defined position, but it's levitating over a layer above.
Levitating(Point),
/// The cursor is currently unavailable (i.e. out of bounds or busy).
#[default]
Unavailable,
@ -16,7 +19,7 @@ impl Cursor {
pub fn position(self) -> Option<Point> {
match self {
Cursor::Available(position) => Some(position),
Cursor::Unavailable => None,
Cursor::Levitating(_) | Cursor::Unavailable => None,
}
}
@ -49,4 +52,41 @@ impl Cursor {
pub fn is_over(self, bounds: Rectangle) -> bool {
self.position_over(bounds).is_some()
}
/// Returns true if the [`Cursor`] is levitating over a layer above.
pub fn is_levitating(self) -> bool {
matches!(self, Self::Levitating(_))
}
/// Makes the [`Cursor`] levitate over a layer above.
pub fn levitate(self) -> Self {
match self {
Self::Available(position) => Self::Levitating(position),
_ => self,
}
}
/// Brings the [`Cursor`] back to the current layer.
pub fn land(self) -> Self {
match self {
Cursor::Levitating(position) => Cursor::Available(position),
_ => self,
}
}
}
impl std::ops::Mul<Transformation> for Cursor {
type Output = Self;
fn mul(self, transformation: Transformation) -> Self {
match self {
Self::Available(position) => {
Self::Available(position * transformation)
}
Self::Levitating(position) => {
Self::Levitating(position * transformation)
}
Self::Unavailable => Self::Unavailable,
}
}
}

View file

@ -13,6 +13,13 @@ pub enum Interaction {
Grabbing,
ResizingHorizontally,
ResizingVertically,
ResizingDiagonallyUp,
ResizingDiagonallyDown,
NotAllowed,
ZoomIn,
ZoomOut,
Cell,
Move,
Copy,
Help,
}

View file

@ -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>
@ -41,7 +40,7 @@ where
&mut self,
_layout: Layout<'_>,
_renderer: &Renderer,
_operation: &mut dyn widget::Operation<Message>,
_operation: &mut dyn widget::Operation,
) {
}
@ -52,21 +51,20 @@ where
/// * the computed [`Layout`] of the [`Overlay`]
/// * the current cursor position
/// * a mutable `Message` list, allowing the [`Overlay`] to produce
/// new messages based on user interaction.
/// new messages based on user interaction.
/// * the `Renderer`
/// * a [`Clipboard`], if available
///
/// By default, it does nothing.
fn on_event(
fn update(
&mut self,
_event: Event,
_event: &Event,
_layout: Layout<'_>,
_cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
) -> event::Status {
event::Status::Ignored
) {
}
/// Returns the current [`mouse::Interaction`] of the [`Overlay`].

View file

@ -1,13 +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, Vector};
use std::any::Any;
use crate::{Clipboard, Event, Layout, Point, Rectangle, Shell, Size};
/// A generic [`Overlay`].
#[allow(missing_debug_implementations)]
@ -52,17 +49,17 @@ where
}
/// Processes a runtime [`Event`].
pub fn on_event(
pub fn update(
&mut self,
event: Event,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
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`].
@ -94,7 +91,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
operation: &mut dyn widget::Operation,
) {
self.overlay.operate(layout, renderer, operation);
}
@ -133,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,
{
@ -146,74 +143,24 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<B>,
operation: &mut dyn widget::Operation,
) {
struct MapOperation<'a, B> {
operation: &'a mut dyn widget::Operation<B>,
}
impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
fn container(
&mut self,
id: Option<&widget::Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(
&mut dyn widget::Operation<T>,
),
) {
self.operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut MapOperation { operation });
});
}
fn focusable(
&mut self,
state: &mut dyn widget::operation::Focusable,
id: Option<&widget::Id>,
) {
self.operation.focusable(state, id);
}
fn scrollable(
&mut self,
state: &mut dyn widget::operation::Scrollable,
id: Option<&widget::Id>,
bounds: Rectangle,
translation: Vector,
) {
self.operation.scrollable(state, id, bounds, translation);
}
fn text_input(
&mut self,
state: &mut dyn widget::operation::TextInput,
id: Option<&widget::Id>,
) {
self.operation.text_input(state, id);
}
fn custom(&mut self, state: &mut dyn Any, id: Option<&widget::Id>) {
self.operation.custom(state, id);
}
}
self.content
.operate(layout, renderer, &mut MapOperation { operation });
self.content.operate(layout, renderer, operation);
}
fn on_event(
fn update(
&mut self,
event: Event,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
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,
@ -223,8 +170,6 @@ where
);
shell.merge(local_shell, self.mapper);
event_status
}
fn mouse_interaction(
@ -258,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))

View file

@ -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,29 +72,18 @@ where
)
}
fn on_event(
fn update(
&mut self,
event: Event,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
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, layout, cursor, renderer, clipboard, shell);
}
}
fn draw(
@ -132,7 +120,7 @@ where
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation<Message>,
operation: &mut dyn widget::Operation,
) {
operation.container(None, layout.bounds(), &mut |operation| {
self.children.iter_mut().zip(layout.children()).for_each(
@ -157,11 +145,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()

View file

@ -1,4 +1,5 @@
use crate::Size;
//! Space stuff around the perimeter.
use crate::{Pixels, Size};
/// An amount of space to pad for each side of a box
///
@ -9,7 +10,6 @@ use crate::Size;
/// #
/// let padding = Padding::from(20); // 20px on all sides
/// let padding = Padding::from([10, 20]); // top/bottom, left/right
/// let padding = Padding::from([5, 10, 15, 20]); // top, right, bottom, left
/// ```
///
/// Normally, the `padding` method of a widget will ask for an `Into<Padding>`,
@ -31,9 +31,8 @@ use crate::Size;
///
/// let widget = Widget::new().padding(20); // 20px on all sides
/// let widget = Widget::new().padding([10, 20]); // top/bottom, left/right
/// let widget = Widget::new().padding([5, 10, 15, 20]); // top, right, bottom, left
/// ```
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq, Default)]
pub struct Padding {
/// Top padding
pub top: f32,
@ -45,6 +44,31 @@ pub struct Padding {
pub left: f32,
}
/// Create a [`Padding`] that is equal on all sides.
pub fn all(padding: impl Into<Pixels>) -> Padding {
Padding::new(padding.into().0)
}
/// Create some top [`Padding`].
pub fn top(padding: impl Into<Pixels>) -> Padding {
Padding::default().top(padding)
}
/// Create some bottom [`Padding`].
pub fn bottom(padding: impl Into<Pixels>) -> Padding {
Padding::default().bottom(padding)
}
/// Create some left [`Padding`].
pub fn left(padding: impl Into<Pixels>) -> Padding {
Padding::default().left(padding)
}
/// Create some right [`Padding`].
pub fn right(padding: impl Into<Pixels>) -> Padding {
Padding::default().right(padding)
}
impl Padding {
/// Padding of zero
pub const ZERO: Padding = Padding {
@ -54,7 +78,7 @@ impl Padding {
left: 0.0,
};
/// Create a Padding that is equal on all sides
/// Create a [`Padding`] that is equal on all sides.
pub const fn new(padding: f32) -> Padding {
Padding {
top: padding,
@ -64,6 +88,46 @@ impl Padding {
}
}
/// Sets the [`top`] of the [`Padding`].
///
/// [`top`]: Self::top
pub fn top(self, top: impl Into<Pixels>) -> Self {
Self {
top: top.into().0,
..self
}
}
/// Sets the [`bottom`] of the [`Padding`].
///
/// [`bottom`]: Self::bottom
pub fn bottom(self, bottom: impl Into<Pixels>) -> Self {
Self {
bottom: bottom.into().0,
..self
}
}
/// Sets the [`left`] of the [`Padding`].
///
/// [`left`]: Self::left
pub fn left(self, left: impl Into<Pixels>) -> Self {
Self {
left: left.into().0,
..self
}
}
/// Sets the [`right`] of the [`Padding`].
///
/// [`right`]: Self::right
pub fn right(self, right: impl Into<Pixels>) -> Self {
Self {
right: right.into().0,
..self
}
}
/// Returns the total amount of vertical [`Padding`].
pub fn vertical(self) -> f32 {
self.top + self.bottom
@ -111,17 +175,6 @@ impl From<[u16; 2]> for Padding {
}
}
impl From<[u16; 4]> for Padding {
fn from(p: [u16; 4]) -> Self {
Padding {
top: f32::from(p[0]),
right: f32::from(p[1]),
bottom: f32::from(p[2]),
left: f32::from(p[3]),
}
}
}
impl From<f32> for Padding {
fn from(p: f32) -> Self {
Padding {
@ -144,19 +197,14 @@ impl From<[f32; 2]> for Padding {
}
}
impl From<[f32; 4]> for Padding {
fn from(p: [f32; 4]) -> Self {
Padding {
top: p[0],
right: p[1],
bottom: p[2],
left: p[3],
}
}
}
impl From<Padding> for Size {
fn from(padding: Padding) -> Self {
Self::new(padding.horizontal(), padding.vertical())
}
}
impl From<Pixels> for Padding {
fn from(pixels: Pixels) -> Self {
Self::from(pixels.0)
}
}

View file

@ -6,18 +6,23 @@
/// (e.g. `impl Into<Pixels>`) and, since `Pixels` implements `From` both for
/// `f32` and `u16`, you should be able to provide both integers and float
/// literals as needed.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
pub struct Pixels(pub f32);
impl Pixels {
/// Zero pixels.
pub const ZERO: Self = Self(0.0);
}
impl From<f32> for Pixels {
fn from(amount: f32) -> Self {
Self(amount)
}
}
impl From<u16> for Pixels {
fn from(amount: u16) -> Self {
Self(f32::from(amount))
impl From<u32> for Pixels {
fn from(amount: u32) -> Self {
Self(amount as f32)
}
}
@ -27,6 +32,30 @@ impl From<Pixels> for f32 {
}
}
impl std::ops::Add for Pixels {
type Output = Pixels;
fn add(self, rhs: Self) -> Self {
Pixels(self.0 + rhs.0)
}
}
impl std::ops::Add<f32> for Pixels {
type Output = Pixels;
fn add(self, rhs: f32) -> Self {
Pixels(self.0 + rhs)
}
}
impl std::ops::Mul for Pixels {
type Output = Pixels;
fn mul(self, rhs: Self) -> Self {
Pixels(self.0 * rhs.0)
}
}
impl std::ops::Mul<f32> for Pixels {
type Output = Pixels;
@ -34,3 +63,27 @@ impl std::ops::Mul<f32> for Pixels {
Pixels(self.0 * rhs)
}
}
impl std::ops::Div for Pixels {
type Output = Pixels;
fn div(self, rhs: Self) -> Self {
Pixels(self.0 / rhs.0)
}
}
impl std::ops::Div<f32> for Pixels {
type Output = Pixels;
fn div(self, rhs: f32) -> Self {
Pixels(self.0 / rhs)
}
}
impl std::ops::Div<u32> for Pixels {
type Output = Pixels;
fn div(self, rhs: u32) -> Self {
Pixels(self.0 / rhs as f32)
}
}

View file

@ -1,4 +1,4 @@
use crate::{Point, Radians, Size, Vector};
use crate::{Padding, Point, Radians, Size, Vector};
/// An axis-aligned rectangle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
@ -47,6 +47,62 @@ impl Rectangle<f32> {
}
}
/// Creates a new square [`Rectangle`] with the center at the origin and
/// with the given radius.
pub fn with_radius(radius: f32) -> Self {
Self {
x: -radius,
y: -radius,
width: radius * 2.0,
height: radius * 2.0,
}
}
/// Creates a new axis-aligned [`Rectangle`] from the given vertices; returning the
/// rotation in [`Radians`] that must be applied to the axis-aligned [`Rectangle`]
/// to obtain the desired result.
pub fn with_vertices(
top_left: Point,
top_right: Point,
bottom_left: Point,
) -> (Rectangle, Radians) {
let width = (top_right.x - top_left.x).hypot(top_right.y - top_left.y);
let height =
(bottom_left.x - top_left.x).hypot(bottom_left.y - top_left.y);
let rotation =
(top_right.y - top_left.y).atan2(top_right.x - top_left.x);
let rotation = if rotation < 0.0 {
2.0 * std::f32::consts::PI + rotation
} else {
rotation
};
let position = {
let center = Point::new(
(top_right.x + bottom_left.x) / 2.0,
(top_right.y + bottom_left.y) / 2.0,
);
let rotation = -rotation - std::f32::consts::PI * 2.0;
Point::new(
center.x + (top_left.x - center.x) * rotation.cos()
- (top_left.y - center.y) * rotation.sin(),
center.y
+ (top_left.x - center.x) * rotation.sin()
+ (top_left.y - center.y) * rotation.cos(),
)
};
(
Rectangle::new(position, Size::new(width, height)),
Radians(rotation),
)
}
/// Returns the [`Point`] at the center of the [`Rectangle`].
pub fn center(&self) -> Point {
Point::new(self.center_x(), self.center_y())
@ -87,6 +143,20 @@ impl Rectangle<f32> {
&& point.y < self.y + self.height
}
/// Returns the minimum distance from the given [`Point`] to any of the edges
/// of the [`Rectangle`].
pub fn distance(&self, point: Point) -> f32 {
let center = self.center();
let distance_x =
((point.x - center.x).abs() - self.width / 2.0).max(0.0);
let distance_y =
((point.y - center.y).abs() - self.height / 2.0).max(0.0);
distance_x.hypot(distance_y)
}
/// Returns true if the current [`Rectangle`] is completely within the given
/// `container`.
pub fn is_within(&self, container: &Rectangle) -> bool {
@ -164,12 +234,26 @@ impl Rectangle<f32> {
}
/// Expands the [`Rectangle`] a given amount.
pub fn expand(self, amount: f32) -> Self {
pub fn expand(self, padding: impl Into<Padding>) -> Self {
let padding = padding.into();
Self {
x: self.x - amount,
y: self.y - amount,
width: self.width + amount * 2.0,
height: self.height + amount * 2.0,
x: self.x - padding.left,
y: self.y - padding.top,
width: self.width + padding.horizontal(),
height: self.height + padding.vertical(),
}
}
/// Shrinks the [`Rectangle`] a given amount.
pub fn shrink(self, padding: impl Into<Padding>) -> Self {
let padding = padding.into();
Self {
x: self.x + padding.left,
y: self.y + padding.top,
width: self.width - padding.horizontal(),
height: self.height - padding.vertical(),
}
}

View file

@ -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.
@ -69,7 +70,7 @@ pub struct Quad {
/// The bounds of the [`Quad`].
pub bounds: Rectangle,
/// The [`Border`] of the [`Quad`].
/// The [`Border`] of the [`Quad`]. The border is drawn on the inside of the [`Quad`].
pub border: Border,
/// The [`Shadow`] of the [`Quad`].
@ -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>;
}

View file

@ -1,11 +1,10 @@
use crate::alignment;
use crate::image;
use crate::image::{self, Image};
use crate::renderer::{self, Renderer};
use crate::svg;
use crate::text::{self, Text};
use crate::{
Background, Color, Font, Pixels, Point, Radians, Rectangle, Size,
Transformation,
Background, Color, Font, Pixels, Point, Rectangle, Size, Transformation,
};
impl Renderer for () {
@ -77,9 +76,14 @@ impl text::Paragraph for () {
fn with_text(_text: Text<&str>) -> Self {}
fn with_spans<Link>(
_text: Text<&[text::Span<'_, Link, Self::Font>], Self::Font>,
) -> Self {
}
fn resize(&mut self, _new_bounds: Size) {}
fn compare(&self, _text: Text<&str>) -> text::Difference {
fn compare(&self, _text: Text<()>) -> text::Difference {
text::Difference::None
}
@ -102,6 +106,14 @@ impl text::Paragraph for () {
fn hit_test(&self, _point: Point) -> Option<text::Hit> {
None
}
fn hit_span(&self, _point: Point) -> Option<usize> {
None
}
fn span_bounds(&self, _index: usize) -> Vec<Rectangle> {
vec![]
}
}
impl text::Editor for () {
@ -109,6 +121,10 @@ impl text::Editor for () {
fn with_text(_text: &str) -> Self {}
fn is_empty(&self) -> bool {
true
}
fn cursor(&self) -> text::editor::Cursor {
text::editor::Cursor::Caret(Point::ORIGIN)
}
@ -121,7 +137,7 @@ impl text::Editor for () {
None
}
fn line(&self, _index: usize) -> Option<&str> {
fn line(&self, _index: usize) -> Option<text::editor::Line<'_>> {
None
}
@ -145,6 +161,7 @@ impl text::Editor for () {
_new_font: Self::Font,
_new_size: Pixels,
_new_line_height: text::LineHeight,
_new_wrapping: text::Wrapping,
_new_highlighter: &mut impl text::Highlighter,
) {
}
@ -161,21 +178,13 @@ impl text::Editor for () {
}
impl image::Renderer for () {
type Handle = ();
type Handle = image::Handle;
fn measure_image(&self, _handle: &Self::Handle) -> Size<u32> {
Size::default()
}
fn draw_image(
&mut self,
_handle: Self::Handle,
_filter_method: image::FilterMethod,
_bounds: Rectangle,
_rotation: Radians,
_opacity: f32,
) {
}
fn draw_image(&mut self, _image: Image, _bounds: Rectangle) {}
}
impl svg::Renderer for () {
@ -183,13 +192,5 @@ impl svg::Renderer for () {
Size::default()
}
fn draw_svg(
&mut self,
_handle: svg::Handle,
_color: Option<Color>,
_bounds: Rectangle,
_rotation: Radians,
_opacity: f32,
) {
}
fn draw_svg(&mut self, _svg: svg::Svg, _bounds: Rectangle) {}
}

48
core/src/settings.rs Normal file
View file

@ -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,
}
}
}

View file

@ -1,3 +1,5 @@
use crate::InputMethod;
use crate::event;
use crate::window;
/// A connection to the state of a shell.
@ -9,7 +11,9 @@ use crate::window;
#[derive(Debug)]
pub struct Shell<'a, Message> {
messages: &'a mut Vec<Message>,
redraw_request: Option<window::RedrawRequest>,
event_status: event::Status,
redraw_request: window::RedrawRequest,
input_method: InputMethod,
is_layout_invalid: bool,
are_widgets_invalid: bool,
}
@ -19,9 +23,11 @@ impl<'a, Message> Shell<'a, Message> {
pub fn new(messages: &'a mut Vec<Message>) -> Self {
Self {
messages,
redraw_request: None,
event_status: event::Status::Ignored,
redraw_request: window::RedrawRequest::Wait,
is_layout_invalid: false,
are_widgets_invalid: false,
input_method: InputMethod::Disabled,
}
}
@ -35,24 +41,75 @@ 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) {
match self.redraw_request {
None => {
self.redraw_request = Some(request);
}
Some(current) if request < current => {
self.redraw_request = Some(request);
}
_ => {}
}
/// 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 = window::RedrawRequest::NextFrame;
}
/// Requests a new frame to be drawn at the given [`window::RedrawRequest`].
pub fn request_redraw_at(
&mut self,
redraw_request: impl Into<window::RedrawRequest>,
) {
self.redraw_request = self.redraw_request.min(redraw_request.into());
}
/// Returns the request a redraw should happen, if any.
pub fn redraw_request(&self) -> Option<window::RedrawRequest> {
pub fn redraw_request(&self) -> window::RedrawRequest {
self.redraw_request
}
/// Replaces the redraw request of the [`Shell`]; without conflict resolution.
///
/// This is useful if you want to overwrite the redraw request to a previous value.
/// Since it's a fairly advanced use case and should rarely be used, it is a static
/// method.
pub fn replace_redraw_request(
shell: &mut Self,
redraw_request: window::RedrawRequest,
) {
shell.redraw_request = redraw_request;
}
/// Requests the current [`InputMethod`] strategy.
///
/// __Important__: This request will only be honored by the
/// [`Shell`] only during a [`window::Event::RedrawRequested`].
pub fn request_input_method<T: AsRef<str>>(
&mut self,
ime: &InputMethod<T>,
) {
self.input_method.merge(ime);
}
/// Returns the current [`InputMethod`] strategy.
pub fn input_method(&self) -> &InputMethod {
&self.input_method
}
/// Returns the current [`InputMethod`] strategy.
pub fn input_method_mut(&mut self) -> &mut InputMethod {
&mut self.input_method
}
/// Returns whether the current layout is invalid or not.
pub fn is_layout_invalid(&self) -> bool {
self.is_layout_invalid
@ -95,14 +152,14 @@ 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);
}
self.is_layout_invalid =
self.is_layout_invalid || other.is_layout_invalid;
self.are_widgets_invalid =
self.are_widgets_invalid || other.are_widgets_invalid;
self.redraw_request = self.redraw_request.min(other.redraw_request);
self.event_status = self.event_status.merge(other.event_status);
self.input_method.merge(&other.input_method);
}
}

View file

@ -99,6 +99,20 @@ impl<T> From<Size<T>> for Vector<T> {
}
}
impl<T> std::ops::Add for Size<T>
where
T: std::ops::Add<Output = T>,
{
type Output = Size<T>;
fn add(self, rhs: Self) -> Self::Output {
Size {
width: self.width + rhs.width,
height: self.height + rhs.height,
}
}
}
impl<T> std::ops::Sub for Size<T>
where
T: std::ops::Sub<Output = T>,

View file

@ -7,6 +7,66 @@ use std::hash::{Hash, Hasher as _};
use std::path::PathBuf;
use std::sync::Arc;
/// A raster image that can be drawn.
#[derive(Debug, Clone, PartialEq)]
pub struct Svg<H = Handle> {
/// The handle of the [`Svg`].
pub handle: H,
/// The [`Color`] filter to be applied to the [`Svg`].
///
/// If some [`Color`] is set, the whole [`Svg`] will be
/// painted with it—ignoring any intrinsic colors.
///
/// This can be useful for coloring icons programmatically
/// (e.g. with a theme).
pub color: Option<Color>,
/// The rotation to be applied to the image; on its center.
pub rotation: Radians,
/// The opacity of the [`Svg`].
///
/// 0 means transparent. 1 means opaque.
pub opacity: f32,
}
impl Svg<Handle> {
/// Creates a new [`Svg`] with the given handle.
pub fn new(handle: impl Into<Handle>) -> Self {
Self {
handle: handle.into(),
color: None,
rotation: Radians(0.0),
opacity: 1.0,
}
}
/// Sets the [`Color`] filter of the [`Svg`].
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.color = Some(color.into());
self
}
/// Sets the rotation of the [`Svg`].
pub fn rotation(mut self, rotation: impl Into<Radians>) -> Self {
self.rotation = rotation.into();
self
}
/// Sets the opacity of the [`Svg`].
pub fn opacity(mut self, opacity: impl Into<f32>) -> Self {
self.opacity = opacity.into();
self
}
}
impl From<&Handle> for Svg {
fn from(handle: &Handle) -> Self {
Svg::new(handle.clone())
}
}
/// A handle of Svg data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Handle {
@ -95,12 +155,5 @@ pub trait Renderer: crate::Renderer {
fn measure_svg(&self, handle: &Handle) -> Size<u32>;
/// Draws an SVG with the given [`Handle`], an optional [`Color`] filter, and inside the provided `bounds`.
fn draw_svg(
&mut self,
handle: Handle,
color: Option<Color>,
bounds: Rectangle,
rotation: Radians,
opacity: f32,
);
fn draw_svg(&mut self, svg: Svg, bounds: Rectangle);
}

View file

@ -1,16 +1,18 @@
//! Draw and interact with text.
mod paragraph;
pub mod editor;
pub mod highlighter;
pub mod paragraph;
pub use editor::Editor;
pub use highlighter::Highlighter;
pub use paragraph::Paragraph;
use crate::alignment;
use crate::{Color, Pixels, Point, Rectangle, Size};
use crate::{
Background, Border, Color, Padding, Pixels, Point, Rectangle, Size,
};
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
/// A paragraph.
@ -39,6 +41,9 @@ pub struct Text<Content = String, Font = crate::Font> {
/// The [`Shaping`] strategy of the [`Text`].
pub shaping: Shaping,
/// The [`Wrapping`] strategy of the [`Text`].
pub wrapping: Wrapping,
}
/// The shaping strategy of some text.
@ -65,6 +70,22 @@ pub enum Shaping {
Advanced,
}
/// The wrapping strategy of some text.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Wrapping {
/// No wrapping.
None,
/// Wraps at the word level.
///
/// This is the default.
#[default]
Word,
/// Wraps at the glyph level.
Glyph,
/// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself.
WordOrGlyph,
}
/// The height of a line of text in a paragraph.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LineHeight {
@ -221,3 +242,303 @@ pub trait Renderer: crate::Renderer {
clip_bounds: Rectangle,
);
}
/// A span of text.
#[derive(Debug, Clone)]
pub struct Span<'a, Link = (), Font = crate::Font> {
/// The [`Fragment`] of text.
pub text: Fragment<'a>,
/// The size of the [`Span`] in [`Pixels`].
pub size: Option<Pixels>,
/// The [`LineHeight`] of the [`Span`].
pub line_height: Option<LineHeight>,
/// The font of the [`Span`].
pub font: Option<Font>,
/// The [`Color`] of the [`Span`].
pub color: Option<Color>,
/// The link of the [`Span`].
pub link: Option<Link>,
/// The [`Highlight`] of the [`Span`].
pub highlight: Option<Highlight>,
/// The [`Padding`] of the [`Span`].
///
/// Currently, it only affects the bounds of the [`Highlight`].
pub padding: Padding,
/// Whether the [`Span`] should be underlined or not.
pub underline: bool,
/// Whether the [`Span`] should be struck through or not.
pub strikethrough: bool,
}
/// A text highlight.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Highlight {
/// The [`Background`] of the highlight.
pub background: Background,
/// The [`Border`] of the highlight.
pub border: Border,
}
impl<'a, Link, Font> Span<'a, Link, Font> {
/// Creates a new [`Span`] of text with the given text fragment.
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
Self {
text: fragment.into_fragment(),
..Self::default()
}
}
/// Sets the size of the [`Span`].
pub fn size(mut self, size: impl Into<Pixels>) -> Self {
self.size = Some(size.into());
self
}
/// Sets the [`LineHeight`] of the [`Span`].
pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
self.line_height = Some(line_height.into());
self
}
/// Sets the font of the [`Span`].
pub fn font(mut self, font: impl Into<Font>) -> Self {
self.font = Some(font.into());
self
}
/// Sets the font of the [`Span`], if any.
pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
self.font = font.map(Into::into);
self
}
/// Sets the [`Color`] of the [`Span`].
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.color = Some(color.into());
self
}
/// Sets the [`Color`] of the [`Span`], if any.
pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
self.color = color.map(Into::into);
self
}
/// Sets the link of the [`Span`].
pub fn link(mut self, link: impl Into<Link>) -> Self {
self.link = Some(link.into());
self
}
/// Sets the link of the [`Span`], if any.
pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
self.link = link.map(Into::into);
self
}
/// Sets the [`Background`] of the [`Span`].
pub fn background(self, background: impl Into<Background>) -> Self {
self.background_maybe(Some(background))
}
/// Sets the [`Background`] of the [`Span`], if any.
pub fn background_maybe(
mut self,
background: Option<impl Into<Background>>,
) -> Self {
let Some(background) = background else {
return self;
};
match &mut self.highlight {
Some(highlight) => {
highlight.background = background.into();
}
None => {
self.highlight = Some(Highlight {
background: background.into(),
border: Border::default(),
});
}
}
self
}
/// Sets the [`Border`] of the [`Span`].
pub fn border(self, border: impl Into<Border>) -> Self {
self.border_maybe(Some(border))
}
/// Sets the [`Border`] of the [`Span`], if any.
pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
let Some(border) = border else {
return self;
};
match &mut self.highlight {
Some(highlight) => {
highlight.border = border.into();
}
None => {
self.highlight = Some(Highlight {
border: border.into(),
background: Background::Color(Color::TRANSPARENT),
});
}
}
self
}
/// Sets the [`Padding`] of the [`Span`].
///
/// It only affects the [`background`] and [`border`] of the
/// [`Span`], currently.
///
/// [`background`]: Self::background
/// [`border`]: Self::border
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
self.padding = padding.into();
self
}
/// Sets whether the [`Span`] should be underlined or not.
pub fn underline(mut self, underline: bool) -> Self {
self.underline = underline;
self
}
/// Sets whether the [`Span`] should be struck through or not.
pub fn strikethrough(mut self, strikethrough: bool) -> Self {
self.strikethrough = strikethrough;
self
}
/// Turns the [`Span`] into a static one.
pub fn to_static(self) -> Span<'static, Link, Font> {
Span {
text: Cow::Owned(self.text.into_owned()),
size: self.size,
line_height: self.line_height,
font: self.font,
color: self.color,
link: self.link,
highlight: self.highlight,
padding: self.padding,
underline: self.underline,
strikethrough: self.strikethrough,
}
}
}
impl<Link, Font> Default for Span<'_, Link, Font> {
fn default() -> Self {
Self {
text: Cow::default(),
size: None,
line_height: None,
font: None,
color: None,
link: None,
highlight: None,
padding: Padding::default(),
underline: false,
strikethrough: false,
}
}
}
impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
fn from(value: &'a str) -> Self {
Span::new(value)
}
}
impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
fn eq(&self, other: &Self) -> bool {
self.text == other.text
&& self.size == other.size
&& self.line_height == other.line_height
&& self.font == other.font
&& self.color == other.color
}
}
/// A fragment of [`Text`].
///
/// This is just an alias to a string that may be either
/// borrowed or owned.
pub type Fragment<'a> = Cow<'a, str>;
/// A trait for converting a value to some text [`Fragment`].
pub trait IntoFragment<'a> {
/// Converts the value to some text [`Fragment`].
fn into_fragment(self) -> Fragment<'a>;
}
impl<'a> IntoFragment<'a> for Fragment<'a> {
fn into_fragment(self) -> Fragment<'a> {
self
}
}
impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self)
}
}
impl<'a> IntoFragment<'a> for &'a str {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self)
}
}
impl<'a> IntoFragment<'a> for &'a String {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self.as_str())
}
}
impl<'a> IntoFragment<'a> for String {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self)
}
}
macro_rules! into_fragment {
($type:ty) => {
impl<'a> IntoFragment<'a> for $type {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self.to_string())
}
}
impl<'a> IntoFragment<'a> for &$type {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self.to_string())
}
}
};
}
into_fragment!(char);
into_fragment!(bool);
into_fragment!(u8);
into_fragment!(u16);
into_fragment!(u32);
into_fragment!(u64);
into_fragment!(u128);
into_fragment!(usize);
into_fragment!(i8);
into_fragment!(i16);
into_fragment!(i32);
into_fragment!(i64);
into_fragment!(i128);
into_fragment!(isize);
into_fragment!(f32);
into_fragment!(f64);

View file

@ -1,8 +1,9 @@
//! Edit text.
use crate::text::highlighter::{self, Highlighter};
use crate::text::LineHeight;
use crate::text::{LineHeight, Wrapping};
use crate::{Pixels, Point, Rectangle, Size};
use std::borrow::Cow;
use std::sync::Arc;
/// A component that can be used by widgets to edit multi-line text.
@ -13,6 +14,9 @@ pub trait Editor: Sized + Default {
/// Creates a new [`Editor`] laid out with the given text.
fn with_text(text: &str) -> Self;
/// Returns true if the [`Editor`] has no contents.
fn is_empty(&self) -> bool;
/// Returns the current [`Cursor`] of the [`Editor`].
fn cursor(&self) -> Cursor;
@ -25,7 +29,7 @@ pub trait Editor: Sized + Default {
fn selection(&self) -> Option<String>;
/// Returns the text of the given line in the [`Editor`], if it exists.
fn line(&self, index: usize) -> Option<&str>;
fn line(&self, index: usize) -> Option<Line<'_>>;
/// Returns the amount of lines in the [`Editor`].
fn line_count(&self) -> usize;
@ -47,6 +51,7 @@ pub trait Editor: Sized + Default {
new_font: Self::Font,
new_size: Pixels,
new_line_height: LineHeight,
new_wrapping: Wrapping,
new_highlighter: &mut impl Highlighter,
);
@ -70,6 +75,8 @@ pub enum Action {
SelectWord,
/// Select the line at the current cursor.
SelectLine,
/// Select the entire buffer.
SelectAll,
/// Perform an [`Edit`].
Edit(Edit),
/// Click the [`Editor`] at the given [`Point`].
@ -183,3 +190,41 @@ pub enum Cursor {
/// Cursor selecting a range of text
Selection(Vec<Rectangle>),
}
/// A line of an [`Editor`].
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Line<'a> {
/// The raw text of the [`Line`].
pub text: Cow<'a, str>,
/// The line ending of the [`Line`].
pub ending: LineEnding,
}
/// The line ending of a [`Line`].
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum LineEnding {
/// Use `\n` for line ending (POSIX-style)
#[default]
Lf,
/// Use `\r\n` for line ending (Windows-style)
CrLf,
/// Use `\r` for line ending (many legacy systems)
Cr,
/// Use `\n\r` for line ending (some legacy systems)
LfCr,
/// No line ending
None,
}
impl LineEnding {
/// Gets the string representation of the [`LineEnding`].
pub fn as_str(self) -> &'static str {
match self {
Self::Lf => "\n",
Self::CrLf => "\r\n",
Self::Cr => "\r",
Self::LfCr => "\n\r",
Self::None => "",
}
}
}

View file

@ -1,6 +1,7 @@
//! Draw paragraphs.
use crate::alignment;
use crate::text::{Difference, Hit, Text};
use crate::{Point, Size};
use crate::text::{Difference, Hit, Span, Text};
use crate::{Point, Rectangle, Size};
/// A text paragraph.
pub trait Paragraph: Sized + Default {
@ -10,12 +11,17 @@ pub trait Paragraph: Sized + Default {
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
fn with_text(text: Text<&str, Self::Font>) -> Self;
/// Creates a new [`Paragraph`] laid out with the given [`Text`].
fn with_spans<Link>(
text: Text<&[Span<'_, Link, Self::Font>], Self::Font>,
) -> Self;
/// Lays out the [`Paragraph`] with some new boundaries.
fn resize(&mut self, new_bounds: Size);
/// Compares the [`Paragraph`] with some desired [`Text`] and returns the
/// [`Difference`].
fn compare(&self, text: Text<&str, Self::Font>) -> Difference;
fn compare(&self, text: Text<(), Self::Font>) -> Difference;
/// Returns the horizontal alignment of the [`Paragraph`].
fn horizontal_alignment(&self) -> alignment::Horizontal;
@ -31,22 +37,18 @@ pub trait Paragraph: Sized + Default {
/// [`Paragraph`], returning information about the nearest character.
fn hit_test(&self, point: Point) -> Option<Hit>;
/// Tests whether the provided point is within the boundaries of a
/// [`Span`] in the [`Paragraph`], returning the index of the [`Span`]
/// that was hit.
fn hit_span(&self, point: Point) -> Option<usize>;
/// Returns all bounds for the provided [`Span`] index of the [`Paragraph`].
/// A [`Span`] can have multiple bounds for each line it's on.
fn span_bounds(&self, index: usize) -> Vec<Rectangle>;
/// Returns the distance to the given grapheme index in the [`Paragraph`].
fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
/// Updates the [`Paragraph`] to match the given [`Text`], if needed.
fn update(&mut self, text: Text<&str, Self::Font>) {
match self.compare(text) {
Difference::None => {}
Difference::Bounds => {
self.resize(text.bounds);
}
Difference::Shape => {
*self = Self::with_text(text);
}
}
}
/// Returns the minimum width that can fit the contents of the [`Paragraph`].
fn min_width(&self) -> f32 {
self.min_bounds().width
@ -57,3 +59,84 @@ pub trait Paragraph: Sized + Default {
self.min_bounds().height
}
}
/// A [`Paragraph`] of plain text.
#[derive(Debug, Clone, Default)]
pub struct Plain<P: Paragraph> {
raw: P,
content: String,
}
impl<P: Paragraph> Plain<P> {
/// Creates a new [`Plain`] paragraph.
pub fn new(text: Text<&str, P::Font>) -> Self {
let content = text.content.to_owned();
Self {
raw: P::with_text(text),
content,
}
}
/// Updates the plain [`Paragraph`] to match the given [`Text`], if needed.
pub fn update(&mut self, text: Text<&str, P::Font>) {
if self.content != text.content {
text.content.clone_into(&mut self.content);
self.raw = P::with_text(text);
return;
}
match self.raw.compare(Text {
content: (),
bounds: text.bounds,
size: text.size,
line_height: text.line_height,
font: text.font,
horizontal_alignment: text.horizontal_alignment,
vertical_alignment: text.vertical_alignment,
shaping: text.shaping,
wrapping: text.wrapping,
}) {
Difference::None => {}
Difference::Bounds => {
self.raw.resize(text.bounds);
}
Difference::Shape => {
self.raw = P::with_text(text);
}
}
}
/// Returns the horizontal alignment of the [`Paragraph`].
pub fn horizontal_alignment(&self) -> alignment::Horizontal {
self.raw.horizontal_alignment()
}
/// Returns the vertical alignment of the [`Paragraph`].
pub fn vertical_alignment(&self) -> alignment::Vertical {
self.raw.vertical_alignment()
}
/// Returns the minimum boundaries that can fit the contents of the
/// [`Paragraph`].
pub fn min_bounds(&self) -> Size {
self.raw.min_bounds()
}
/// Returns the minimum width that can fit the contents of the
/// [`Paragraph`].
pub fn min_width(&self) -> f32 {
self.raw.min_width()
}
/// Returns the minimum height that can fit the contents of the
/// [`Paragraph`].
pub fn min_height(&self) -> f32 {
self.raw.min_height()
}
/// Returns the cached [`Paragraph`].
pub fn raw(&self) -> &P {
&self.raw
}
}

View file

@ -3,6 +3,8 @@ pub mod palette;
pub use palette::Palette;
use crate::Color;
use std::fmt;
use std::sync::Arc;
@ -164,15 +166,18 @@ impl Default for Theme {
fn default() -> Self {
#[cfg(feature = "auto-detect-theme")]
{
use once_cell::sync::Lazy;
use std::sync::LazyLock;
static DEFAULT: Lazy<Theme> =
Lazy::new(|| match dark_light::detect() {
static DEFAULT: LazyLock<Theme> = LazyLock::new(|| {
match dark_light::detect()
.unwrap_or(dark_light::Mode::Unspecified)
{
dark_light::Mode::Dark => Theme::Dark,
dark_light::Mode::Light | dark_light::Mode::Default => {
dark_light::Mode::Light | dark_light::Mode::Unspecified => {
Theme::Light
}
});
}
});
DEFAULT.clone()
}
@ -246,3 +251,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,
}
}

View file

@ -1,11 +1,12 @@
//! Define the colors of a theme.
use crate::{color, Color};
use crate::{Color, color};
use once_cell::sync::Lazy;
use palette::color_difference::Wcag21RelativeContrast;
use palette::rgb::Rgb;
use palette::{FromColor, Hsl, Mix};
use std::sync::LazyLock;
/// A color palette.
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -18,6 +19,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,
}
@ -27,46 +30,20 @@ impl Palette {
pub const LIGHT: Self = Self {
background: Color::WHITE,
text: Color::BLACK,
primary: Color::from_rgb(
0x5E as f32 / 255.0,
0x7C as f32 / 255.0,
0xE2 as f32 / 255.0,
),
success: Color::from_rgb(
0x12 as f32 / 255.0,
0x66 as f32 / 255.0,
0x4F as f32 / 255.0,
),
danger: Color::from_rgb(
0xC3 as f32 / 255.0,
0x42 as f32 / 255.0,
0x3F as f32 / 255.0,
),
primary: color!(0x5865F2),
success: color!(0x12664f),
warning: color!(0xffc14e),
danger: color!(0xc3423f),
};
/// The built-in dark variant of a [`Palette`].
pub const DARK: Self = Self {
background: Color::from_rgb(
0x20 as f32 / 255.0,
0x22 as f32 / 255.0,
0x25 as f32 / 255.0,
),
background: color!(0x2B2D31),
text: Color::from_rgb(0.90, 0.90, 0.90),
primary: Color::from_rgb(
0x5E as f32 / 255.0,
0x7C as f32 / 255.0,
0xE2 as f32 / 255.0,
),
success: Color::from_rgb(
0x12 as f32 / 255.0,
0x66 as f32 / 255.0,
0x4F as f32 / 255.0,
),
danger: Color::from_rgb(
0xC3 as f32 / 255.0,
0x42 as f32 / 255.0,
0x3F as f32 / 255.0,
),
primary: color!(0x5865F2),
success: color!(0x12664f),
warning: color!(0xffc14e),
danger: color!(0xc3423f),
};
/// The built-in [Dracula] variant of a [`Palette`].
@ -77,6 +54,7 @@ impl Palette {
text: color!(0xf8f8f2), // FOREGROUND
primary: color!(0xbd93f9), // PURPLE
success: color!(0x50fa7b), // GREEN
warning: color!(0xf1fa8c), // YELLOW
danger: color!(0xff5555), // RED
};
@ -88,6 +66,7 @@ impl Palette {
text: color!(0xeceff4), // nord6
primary: color!(0x8fbcbb), // nord7
success: color!(0xa3be8c), // nord14
warning: color!(0xebcb8b), // nord13
danger: color!(0xbf616a), // nord11
};
@ -99,6 +78,7 @@ impl Palette {
text: color!(0x657b83), // base00
primary: color!(0x2aa198), // cyan
success: color!(0x859900), // green
warning: color!(0xb58900), // yellow
danger: color!(0xdc322f), // red
};
@ -110,6 +90,7 @@ impl Palette {
text: color!(0x839496), // base0
primary: color!(0x2aa198), // cyan
success: color!(0x859900), // green
warning: color!(0xb58900), // yellow
danger: color!(0xdc322f), // red
};
@ -121,6 +102,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
};
@ -132,6 +114,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
};
@ -143,6 +126,7 @@ impl Palette {
text: color!(0x4c4f69), // Text
primary: color!(0x1e66f5), // Blue
success: color!(0x40a02b), // Green
warning: color!(0xdf8e1d), // Yellow
danger: color!(0xd20f39), // Red
};
@ -154,6 +138,7 @@ impl Palette {
text: color!(0xc6d0f5), // Text
primary: color!(0x8caaee), // Blue
success: color!(0xa6d189), // Green
warning: color!(0xe5c890), // Yellow
danger: color!(0xe78284), // Red
};
@ -165,6 +150,7 @@ impl Palette {
text: color!(0xcad3f5), // Text
primary: color!(0x8aadf4), // Blue
success: color!(0xa6da95), // Green
warning: color!(0xeed49f), // Yellow
danger: color!(0xed8796), // Red
};
@ -176,6 +162,7 @@ impl Palette {
text: color!(0xcdd6f4), // Text
primary: color!(0x89b4fa), // Blue
success: color!(0xa6e3a1), // Green
warning: color!(0xf9e2af), // Yellow
danger: color!(0xf38ba8), // Red
};
@ -187,6 +174,7 @@ impl Palette {
text: color!(0x9aa5ce), // Text
primary: color!(0x2ac3de), // Blue
success: color!(0x9ece6a), // Green
warning: color!(0xe0af68), // Yellow
danger: color!(0xf7768e), // Red
};
@ -198,6 +186,7 @@ impl Palette {
text: color!(0x9aa5ce), // Text
primary: color!(0x2ac3de), // Blue
success: color!(0x9ece6a), // Green
warning: color!(0xe0af68), // Yellow
danger: color!(0xf7768e), // Red
};
@ -209,6 +198,7 @@ impl Palette {
text: color!(0x565a6e), // Text
primary: color!(0x166775), // Blue
success: color!(0x485e30), // Green
warning: color!(0x8f5e15), // Yellow
danger: color!(0x8c4351), // Red
};
@ -216,10 +206,11 @@ impl Palette {
///
/// [Kanagawa]: https://github.com/rebelot/kanagawa.nvim
pub const KANAGAWA_WAVE: Self = Self {
background: color!(0x363646), // Sumi Ink 3
text: color!(0xCD7BA), // Fuji White
primary: color!(0x2D4F67), // Wave Blue 2
background: color!(0x1f1f28), // Sumi Ink 3
text: color!(0xDCD7BA), // Fuji White
primary: color!(0x7FB4CA), // Wave Blue
success: color!(0x76946A), // Autumn Green
warning: color!(0xff9e3b), // Ronin Yellow
danger: color!(0xC34043), // Autumn Red
};
@ -231,6 +222,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
};
@ -240,8 +232,9 @@ impl Palette {
pub const KANAGAWA_LOTUS: Self = Self {
background: color!(0xf2ecbc), // Lotus White 3
text: color!(0x545464), // Lotus Ink 1
primary: color!(0xc9cbd1), // Lotus Violet 3
primary: color!(0x4d699b), // Lotus Blue
success: color!(0x6f894e), // Lotus Green
warning: color!(0xe98a00), // Lotus Orange 2
danger: color!(0xc84053), // Lotus Red
};
@ -253,6 +246,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)
};
@ -264,6 +258,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)
};
@ -275,6 +270,7 @@ impl Palette {
text: color!(0xd0d0d0),
primary: color!(0x00b4ff),
success: color!(0x00c15a),
warning: color!(0xbe95ff), // Base 14
danger: color!(0xf62d0f),
};
@ -286,6 +282,7 @@ impl Palette {
text: color!(0xfecdb2),
primary: color!(0xd1d1e0),
success: color!(0xb1b695),
warning: color!(0xf5d76e), // Honey
danger: color!(0xe06b75),
};
}
@ -301,6 +298,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.
@ -308,92 +307,92 @@ pub struct Extended {
}
/// The built-in light variant of an [`Extended`] palette.
pub static EXTENDED_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::LIGHT));
pub static EXTENDED_LIGHT: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::LIGHT));
/// The built-in dark variant of an [`Extended`] palette.
pub static EXTENDED_DARK: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::DARK));
pub static EXTENDED_DARK: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::DARK));
/// The built-in Dracula variant of an [`Extended`] palette.
pub static EXTENDED_DRACULA: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::DRACULA));
pub static EXTENDED_DRACULA: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::DRACULA));
/// The built-in Nord variant of an [`Extended`] palette.
pub static EXTENDED_NORD: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::NORD));
pub static EXTENDED_NORD: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::NORD));
/// The built-in Solarized Light variant of an [`Extended`] palette.
pub static EXTENDED_SOLARIZED_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::SOLARIZED_LIGHT));
pub static EXTENDED_SOLARIZED_LIGHT: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::SOLARIZED_LIGHT));
/// The built-in Solarized Dark variant of an [`Extended`] palette.
pub static EXTENDED_SOLARIZED_DARK: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::SOLARIZED_DARK));
pub static EXTENDED_SOLARIZED_DARK: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::SOLARIZED_DARK));
/// The built-in Gruvbox Light variant of an [`Extended`] palette.
pub static EXTENDED_GRUVBOX_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::GRUVBOX_LIGHT));
pub static EXTENDED_GRUVBOX_LIGHT: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::GRUVBOX_LIGHT));
/// The built-in Gruvbox Dark variant of an [`Extended`] palette.
pub static EXTENDED_GRUVBOX_DARK: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::GRUVBOX_DARK));
pub static EXTENDED_GRUVBOX_DARK: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::GRUVBOX_DARK));
/// The built-in Catppuccin Latte variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_LATTE: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE));
pub static EXTENDED_CATPPUCCIN_LATTE: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_LATTE));
/// The built-in Catppuccin Frappé variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_FRAPPE: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE));
pub static EXTENDED_CATPPUCCIN_FRAPPE: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_FRAPPE));
/// The built-in Catppuccin Macchiato variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_MACCHIATO: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO));
pub static EXTENDED_CATPPUCCIN_MACCHIATO: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MACCHIATO));
/// The built-in Catppuccin Mocha variant of an [`Extended`] palette.
pub static EXTENDED_CATPPUCCIN_MOCHA: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA));
pub static EXTENDED_CATPPUCCIN_MOCHA: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::CATPPUCCIN_MOCHA));
/// The built-in Tokyo Night variant of an [`Extended`] palette.
pub static EXTENDED_TOKYO_NIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT));
pub static EXTENDED_TOKYO_NIGHT: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT));
/// The built-in Tokyo Night Storm variant of an [`Extended`] palette.
pub static EXTENDED_TOKYO_NIGHT_STORM: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM));
pub static EXTENDED_TOKYO_NIGHT_STORM: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_STORM));
/// The built-in Tokyo Night variant of an [`Extended`] palette.
pub static EXTENDED_TOKYO_NIGHT_LIGHT: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT));
pub static EXTENDED_TOKYO_NIGHT_LIGHT: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::TOKYO_NIGHT_LIGHT));
/// The built-in Kanagawa Wave variant of an [`Extended`] palette.
pub static EXTENDED_KANAGAWA_WAVE: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::KANAGAWA_WAVE));
pub static EXTENDED_KANAGAWA_WAVE: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::KANAGAWA_WAVE));
/// The built-in Kanagawa Dragon variant of an [`Extended`] palette.
pub static EXTENDED_KANAGAWA_DRAGON: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::KANAGAWA_DRAGON));
pub static EXTENDED_KANAGAWA_DRAGON: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::KANAGAWA_DRAGON));
/// The built-in Kanagawa Lotus variant of an [`Extended`] palette.
pub static EXTENDED_KANAGAWA_LOTUS: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::KANAGAWA_LOTUS));
pub static EXTENDED_KANAGAWA_LOTUS: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::KANAGAWA_LOTUS));
/// The built-in Moonfly variant of an [`Extended`] palette.
pub static EXTENDED_MOONFLY: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::MOONFLY));
pub static EXTENDED_MOONFLY: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::MOONFLY));
/// The built-in Nightfly variant of an [`Extended`] palette.
pub static EXTENDED_NIGHTFLY: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::NIGHTFLY));
pub static EXTENDED_NIGHTFLY: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::NIGHTFLY));
/// The built-in Oxocarbon variant of an [`Extended`] palette.
pub static EXTENDED_OXOCARBON: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::OXOCARBON));
pub static EXTENDED_OXOCARBON: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::OXOCARBON));
/// The built-in Ferra variant of an [`Extended`] palette.
pub static EXTENDED_FERRA: Lazy<Extended> =
Lazy::new(|| Extended::generate(Palette::FERRA));
pub static EXTENDED_FERRA: LazyLock<Extended> =
LazyLock::new(|| Extended::generate(Palette::FERRA));
impl Extended {
/// Generates an [`Extended`] palette from a simple [`Palette`].
@ -411,6 +410,11 @@ impl Extended {
palette.background,
palette.text,
),
warning: Warning::generate(
palette.warning,
palette.background,
palette.text,
),
danger: Danger::generate(
palette.danger,
palette.background,
@ -450,22 +454,30 @@ impl Pair {
pub struct Background {
/// The base background color.
pub base: Pair,
/// The weakest version of the base background color.
pub weakest: Pair,
/// A weaker version of the base background color.
pub weak: Pair,
/// A stronger version of the base background color.
pub strong: Pair,
/// The strongest version of the base background color.
pub strongest: Pair,
}
impl Background {
/// Generates a set of [`Background`] colors from the base and text colors.
pub fn new(base: Color, text: Color) -> Self {
let weak = mix(base, text, 0.15);
let strong = mix(base, text, 0.40);
let weakest = deviate(base, 0.03);
let weak = muted(deviate(base, 0.1));
let strong = muted(deviate(base, 0.2));
let strongest = muted(deviate(base, 0.3));
Self {
base: Pair::new(base, text),
weakest: Pair::new(weakest, text),
weak: Pair::new(weak, text),
strong: Pair::new(strong, text),
strongest: Pair::new(strongest, text),
}
}
}
@ -546,6 +558,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 {
@ -599,10 +636,18 @@ fn deviate(color: Color, amount: f32) -> Color {
if is_dark(color) {
lighten(color, amount)
} else {
darken(color, amount)
darken(color, amount * 0.8)
}
}
fn muted(color: Color) -> Color {
let mut hsl = to_hsl(color);
hsl.saturation = hsl.saturation.min(0.5);
from_hsl(hsl)
}
fn mix(a: Color, b: Color, factor: f32) -> Color {
let a_lin = Rgb::from(a).into_linear();
let b_lin = Rgb::from(b).into_linear();
@ -613,16 +658,25 @@ fn mix(a: Color, b: Color, factor: f32) -> Color {
fn readable(background: Color, text: Color) -> Color {
if is_readable(background, text) {
text
} else {
let white_contrast = relative_contrast(background, Color::WHITE);
let black_contrast = relative_contrast(background, Color::BLACK);
return text;
}
if white_contrast >= black_contrast {
Color::WHITE
} else {
Color::BLACK
}
let improve = if is_dark(background) { lighten } else { darken };
// TODO: Compute factor from relative contrast value
let candidate = improve(text, 0.1);
if is_readable(background, candidate) {
return candidate;
}
let white_contrast = relative_contrast(background, Color::WHITE);
let black_contrast = relative_contrast(background, Color::BLACK);
if white_contrast >= black_contrast {
mix(Color::WHITE, background, 0.05)
} else {
mix(Color::BLACK, background, 0.05)
}
}

View file

@ -3,3 +3,28 @@
pub use web_time::Duration;
pub use web_time::Instant;
pub use web_time::SystemTime;
/// Creates a [`Duration`] representing the given amount of milliseconds.
pub fn milliseconds(milliseconds: u64) -> Duration {
Duration::from_millis(milliseconds)
}
/// Creates a [`Duration`] representing the given amount of seconds.
pub fn seconds(seconds: u64) -> Duration {
Duration::from_secs(seconds)
}
/// Creates a [`Duration`] representing the given amount of minutes.
pub fn minutes(minutes: u64) -> Duration {
seconds(minutes * 60)
}
/// Creates a [`Duration`] representing the given amount of hours.
pub fn hours(hours: u64) -> Duration {
minutes(hours * 60)
}
/// Creates a [`Duration`] representing the given amount of days.
pub fn days(days: u64) -> Duration {
hours(days * 24)
}

View file

@ -18,9 +18,17 @@ impl<T> Vector<T> {
impl Vector {
/// The zero [`Vector`].
pub const ZERO: Self = Self::new(0.0, 0.0);
}
/// The unit [`Vector`].
pub const UNIT: Self = Self::new(0.0, 0.0);
impl<T> std::ops::Neg for Vector<T>
where
T: std::ops::Neg<Output = T>,
{
type Output = Self;
fn neg(self) -> Self::Output {
Self::new(-self.x, -self.y)
}
}
impl<T> std::ops::Add for Vector<T>

View file

@ -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.
///
@ -27,18 +26,18 @@ use crate::{Clipboard, Length, Rectangle, Shell, Size, Vector};
/// widget:
///
/// - [`bezier_tool`], a Paint-like tool for drawing Bézier curves using
/// [`lyon`].
/// [`lyon`].
/// - [`custom_widget`], a demonstration of how to build a custom widget that
/// draws a circle.
/// draws a circle.
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
/// `Mesh2D` primitive in [`iced_wgpu`].
///
/// [examples]: https://github.com/iced-rs/iced/tree/0.12/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.12/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.12/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.12/examples/geometry
/// [examples]: https://github.com/iced-rs/iced/tree/0.13/examples
/// [`bezier_tool`]: https://github.com/iced-rs/iced/tree/0.13/examples/bezier_tool
/// [`custom_widget`]: https://github.com/iced-rs/iced/tree/0.13/examples/custom_widget
/// [`geometry`]: https://github.com/iced-rs/iced/tree/0.13/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.12/wgpu
/// [`iced_wgpu`]: https://github.com/iced-rs/iced/tree/0.13/wgpu
pub trait Widget<Message, Theme, Renderer>
where
Renderer: crate::Renderer,
@ -96,7 +95,7 @@ where
Vec::new()
}
/// Reconciliates the [`Widget`] with the provided [`Tree`].
/// Reconciles the [`Widget`] with the provided [`Tree`].
fn diff(&self, _tree: &mut Tree) {}
/// Applies an [`Operation`] to the [`Widget`].
@ -105,25 +104,24 @@ where
_state: &mut Tree,
_layout: Layout<'_>,
_renderer: &Renderer,
_operation: &mut dyn Operation<Message>,
_operation: &mut dyn Operation,
) {
}
/// Processes a runtime [`Event`].
///
/// By default, it does nothing.
fn on_event(
fn update(
&mut self,
_state: &mut Tree,
_event: Event,
_event: &Event,
_layout: Layout<'_>,
_cursor: mouse::Cursor,
_renderer: &Renderer,
_clipboard: &mut dyn Clipboard,
_shell: &mut Shell<'_, Message>,
_viewport: &Rectangle,
) -> event::Status {
event::Status::Ignored
) {
}
/// Returns the current [`mouse::Interaction`] of the [`Widget`].

View file

@ -12,11 +12,12 @@ use crate::{Rectangle, Vector};
use std::any::Any;
use std::fmt;
use std::rc::Rc;
use std::marker::PhantomData;
use std::sync::Arc;
/// A piece of logic that can traverse the widget tree of an application in
/// order to query or update some widget state.
pub trait Operation<T> {
pub trait Operation<T = ()>: Send {
/// Operates on a widget that contains other widgets.
///
/// The `operate_on_children` function can be called to return control to
@ -29,23 +30,45 @@ pub trait Operation<T> {
);
/// 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> {
@ -53,6 +76,72 @@ pub trait Operation<T> {
}
}
impl<T, O> Operation<O> for Box<T>
where
T: Operation<O> + ?Sized,
{
fn container(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<O>),
) {
self.as_mut().container(id, bounds, operate_on_children);
}
fn focusable(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
state: &mut dyn Focusable,
) {
self.as_mut().focusable(id, bounds, state);
}
fn scrollable(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: Vector,
state: &mut dyn Scrollable,
) {
self.as_mut().scrollable(
id,
bounds,
content_bounds,
translation,
state,
);
}
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,
id: Option<&Id>,
bounds: Rectangle,
state: &mut dyn Any,
) {
self.as_mut().custom(id, bounds, state);
}
fn finish(&self) -> Outcome<O> {
self.as_ref().finish()
}
}
/// The result of an [`Operation`].
pub enum Outcome<T> {
/// The [`Operation`] produced no result.
@ -78,23 +167,103 @@ where
}
}
/// Wraps the [`Operation`] in a black box, erasing its returning type.
pub fn black_box<'a, T, O>(
operation: &'a mut dyn Operation<T>,
) -> impl Operation<O> + 'a
where
T: 'a,
{
struct BlackBox<'a, T> {
operation: &'a mut dyn Operation<T>,
}
impl<T, O> Operation<O> for BlackBox<'_, T> {
fn container(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<O>),
) {
self.operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut BlackBox { operation });
});
}
fn focusable(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
state: &mut dyn Focusable,
) {
self.operation.focusable(id, bounds, state);
}
fn scrollable(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: Vector,
state: &mut dyn Scrollable,
) {
self.operation.scrollable(
id,
bounds,
content_bounds,
translation,
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(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> {
Outcome::None
}
}
BlackBox { operation }
}
/// Maps the output of an [`Operation`] using the given function.
pub fn map<A, B>(
operation: Box<dyn Operation<A>>,
f: impl Fn(A) -> B + 'static,
operation: impl Operation<A>,
f: impl Fn(A) -> B + Send + Sync + 'static,
) -> impl Operation<B>
where
A: 'static,
B: 'static,
{
#[allow(missing_debug_implementations)]
struct Map<A, B> {
operation: Box<dyn Operation<A>>,
f: Rc<dyn Fn(A) -> B>,
struct Map<O, A, B> {
operation: O,
f: Arc<dyn Fn(A) -> B + Send + Sync>,
}
impl<A, B> Operation<B> for Map<A, B>
impl<O, A, B> Operation<B> for Map<O, A, B>
where
O: Operation<A>,
A: 'static,
B: 'static,
{
@ -108,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>,
@ -124,63 +293,109 @@ 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, translation);
self.operation.scrollable(
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,
state: &mut dyn TextInput,
id: Option<&Id>,
bounds: Rectangle,
state: &mut dyn TextInput,
) {
self.operation.text_input(state, id);
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);
}
}
let Self { operation, .. } = self;
MapRef {
operation: operation.as_mut(),
}
.container(id, bounds, operate_on_children);
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, translation);
self.operation.scrollable(
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<B> {
@ -197,7 +412,114 @@ where
Map {
operation,
f: Rc::new(f),
f: Arc::new(f),
}
}
/// Chains the output of an [`Operation`] with the provided function to
/// build a new [`Operation`].
pub fn then<A, B, O>(
operation: impl Operation<A> + 'static,
f: fn(A) -> O,
) -> impl Operation<B>
where
A: 'static,
B: Send + 'static,
O: Operation<B> + 'static,
{
struct Chain<T, O, A, B>
where
T: Operation<A>,
O: Operation<B>,
{
operation: T,
next: fn(A) -> O,
_result: PhantomData<B>,
}
impl<T, O, A, B> Operation<B> for Chain<T, O, A, B>
where
T: Operation<A> + 'static,
O: Operation<B> + 'static,
A: 'static,
B: Send + 'static,
{
fn container(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
) {
self.operation.container(id, bounds, &mut |operation| {
operate_on_children(&mut black_box(operation));
});
}
fn focusable(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
state: &mut dyn Focusable,
) {
self.operation.focusable(id, bounds, state);
}
fn scrollable(
&mut self,
id: Option<&Id>,
bounds: Rectangle,
content_bounds: Rectangle,
translation: crate::Vector,
state: &mut dyn Scrollable,
) {
self.operation.scrollable(
id,
bounds,
content_bounds,
translation,
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(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> {
match self.operation.finish() {
Outcome::None => Outcome::None,
Outcome::Some(value) => {
Outcome::Chain(Box::new((self.next)(value)))
}
Outcome::Chain(operation) => {
Outcome::Chain(Box::new(then(operation, self.next)))
}
}
}
}
Chain {
operation,
next: f,
_result: PhantomData,
}
}

View file

@ -1,7 +1,7 @@
//! Operate on widgets that can be focused.
use crate::widget::operation::{Operation, Outcome};
use crate::widget::Id;
use crate::Rectangle;
use crate::widget::Id;
use crate::widget::operation::{self, Operation, Outcome};
/// The internal state of a widget that can be focused.
pub trait Focusable {
@ -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();
@ -56,22 +61,47 @@ pub fn focus<T>(target: Id) -> impl Operation<T> {
Focus { target }
}
/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
/// provided function to build a new [`Operation`].
pub fn count<T, O>(f: fn(Count) -> O) -> impl Operation<T>
where
O: Operation<T> + 'static,
{
struct CountFocusable<O> {
count: Count,
next: fn(Count) -> O,
/// Produces an [`Operation`] that unfocuses the focused widget.
pub fn unfocus<T>() -> impl Operation<T> {
struct Unfocus;
impl<T> Operation<T> for Unfocus {
fn focusable(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
state: &mut dyn Focusable,
) {
state.unfocus();
}
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self);
}
}
impl<T, O> Operation<T> for CountFocusable<O>
where
O: Operation<T> + 'static,
{
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
Unfocus
}
/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
/// provided function to build a new [`Operation`].
pub fn count() -> impl Operation<Count> {
struct CountFocusable {
count: Count,
}
impl Operation<Count> for CountFocusable {
fn focusable(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
state: &mut dyn Focusable,
) {
if state.is_focused() {
self.count.focused = Some(self.count.total);
}
@ -83,33 +113,40 @@ where
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
operate_on_children: &mut dyn FnMut(&mut dyn Operation<Count>),
) {
operate_on_children(self);
}
fn finish(&self) -> Outcome<T> {
Outcome::Chain(Box::new((self.next)(self.count)))
fn finish(&self) -> Outcome<Count> {
Outcome::Some(self.count)
}
}
CountFocusable {
count: Count::default(),
next: f,
}
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the previous focusable widget.
/// - if not found, focuses the last focusable widget.
pub fn focus_previous<T>() -> impl Operation<T> {
pub fn focus_previous<T>() -> impl Operation<T>
where
T: Send + 'static,
{
struct FocusPrevious {
count: Count,
current: usize,
}
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;
}
@ -136,20 +173,28 @@ pub fn focus_previous<T>() -> impl Operation<T> {
}
}
count(|count| FocusPrevious { count, current: 0 })
operation::then(count(), |count| FocusPrevious { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget, and
/// - if found, focuses the next focusable widget.
/// - if not found, focuses the first focusable widget.
pub fn focus_next<T>() -> impl Operation<T> {
pub fn focus_next<T>() -> impl Operation<T>
where
T: Send + 'static,
{
struct FocusNext {
count: Count,
current: usize,
}
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(),
@ -170,7 +215,7 @@ pub fn focus_next<T>() -> impl Operation<T> {
}
}
count(|count| FocusNext { count, current: 0 })
operation::then(count(), |count| FocusNext { count, current: 0 })
}
/// Produces an [`Operation`] that searches for the current focused widget
@ -181,7 +226,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();
}
@ -207,3 +257,48 @@ pub fn find_focused() -> impl Operation<Id> {
FindFocused { focused: None }
}
/// Produces an [`Operation`] that searches for the focusable widget
/// and stores whether it is focused or not. This ignores widgets that
/// do not have an ID.
pub fn is_focused(target: Id) -> impl Operation<bool> {
struct IsFocused {
target: Id,
is_focused: Option<bool>,
}
impl Operation<bool> for IsFocused {
fn focusable(
&mut self,
id: Option<&Id>,
_bounds: Rectangle,
state: &mut dyn Focusable,
) {
if id.is_some_and(|id| *id == self.target) {
self.is_focused = Some(state.is_focused());
}
}
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<bool>),
) {
if self.is_focused.is_some() {
return;
}
operate_on_children(self);
}
fn finish(&self) -> Outcome<bool> {
self.is_focused.map_or(Outcome::None, Outcome::Some)
}
}
IsFocused {
target,
is_focused: None,
}
}

View file

@ -9,6 +9,14 @@ pub trait Scrollable {
/// Scroll the widget to the given [`AbsoluteOffset`] along the horizontal & vertical axis.
fn scroll_to(&mut self, offset: AbsoluteOffset);
/// Scroll the widget by the given [`AbsoluteOffset`] along the horizontal & vertical axis.
fn scroll_by(
&mut self,
offset: AbsoluteOffset,
bounds: Rectangle,
content_bounds: Rectangle,
);
}
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
@ -31,10 +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);
@ -65,10 +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);
@ -79,6 +89,41 @@ pub fn scroll_to<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
ScrollTo { target, offset }
}
/// Produces an [`Operation`] that scrolls the widget with the given [`Id`] by
/// the provided [`AbsoluteOffset`].
pub fn scroll_by<T>(target: Id, offset: AbsoluteOffset) -> impl Operation<T> {
struct ScrollBy {
target: Id,
offset: AbsoluteOffset,
}
impl<T> Operation<T> for ScrollBy {
fn container(
&mut self,
_id: Option<&Id>,
_bounds: Rectangle,
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
) {
operate_on_children(self);
}
fn scrollable(
&mut self,
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);
}
}
}
ScrollBy { target, offset }
}
/// The amount of absolute offset in each direction of a [`Scrollable`].
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct AbsoluteOffset {

View file

@ -1,7 +1,7 @@
//! Operate on widgets that have text input.
use crate::widget::operation::Operation;
use crate::widget::Id;
use crate::Rectangle;
use crate::widget::Id;
use crate::widget::operation::Operation;
/// The internal state of a widget that has text input.
pub trait TextInput {
@ -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();

View file

@ -1,27 +1,68 @@
//! Write some text for your users to read.
//! Text widgets display information through writing.
//!
//! # Example
//! ```no_run
//! # mod iced { pub mod widget { pub fn text<T>(t: T) -> iced_core::widget::Text<'static, iced_core::Theme, ()> { unimplemented!() } }
//! # pub use iced_core::color; }
//! # pub type State = ();
//! # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
//! use iced::widget::text;
//! use iced::color;
//!
//! enum Message {
//! // ...
//! }
//!
//! fn view(state: &State) -> Element<'_, Message> {
//! text("Hello, this is iced!")
//! .size(20)
//! .color(color!(0x0000ff))
//! .into()
//! }
//! ```
use crate::alignment;
use crate::layout;
use crate::mouse;
use crate::renderer;
use crate::text::{self, Paragraph};
use crate::text;
use crate::text::paragraph::{self, Paragraph};
use crate::widget::tree::{self, Tree};
use crate::{
Color, Element, Layout, Length, Pixels, Point, Rectangle, Size, Theme,
Widget,
};
use std::borrow::Cow;
pub use text::{LineHeight, Shaping, Wrapping};
pub use text::{LineHeight, Shaping};
/// A paragraph of text.
/// A bunch of text.
///
/// # Example
/// ```no_run
/// # mod iced { pub mod widget { pub fn text<T>(t: T) -> iced_core::widget::Text<'static, iced_core::Theme, ()> { unimplemented!() } }
/// # pub use iced_core::color; }
/// # pub type State = ();
/// # pub type Element<'a, Message> = iced_core::Element<'a, Message, iced_core::Theme, ()>;
/// use iced::widget::text;
/// use iced::color;
///
/// enum Message {
/// // ...
/// }
///
/// fn view(state: &State) -> Element<'_, Message> {
/// text("Hello, this is iced!")
/// .size(20)
/// .color(color!(0x0000ff))
/// .into()
/// }
/// ```
#[allow(missing_debug_implementations)]
pub struct Text<'a, Theme, Renderer>
where
Theme: Catalog,
Renderer: text::Renderer,
{
fragment: Fragment<'a>,
fragment: text::Fragment<'a>,
size: Option<Pixels>,
line_height: LineHeight,
width: Length,
@ -30,6 +71,7 @@ where
vertical_alignment: alignment::Vertical,
font: Option<Renderer::Font>,
shaping: Shaping,
wrapping: Wrapping,
class: Theme::Class<'a>,
}
@ -39,7 +81,7 @@ where
Renderer: text::Renderer,
{
/// Create a new fragment of [`Text`] with the given contents.
pub fn new(fragment: impl IntoFragment<'a>) -> Self {
pub fn new(fragment: impl text::IntoFragment<'a>) -> Self {
Text {
fragment: fragment.into_fragment(),
size: None,
@ -49,7 +91,8 @@ where
height: Length::Shrink,
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
shaping: Shaping::Basic,
shaping: Shaping::default(),
wrapping: Wrapping::default(),
class: Theme::default(),
}
}
@ -86,21 +129,27 @@ where
self
}
/// Centers the [`Text`], both horizontally and vertically.
pub fn center(self) -> Self {
self.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center)
}
/// Sets the [`alignment::Horizontal`] of the [`Text`].
pub fn horizontal_alignment(
pub fn align_x(
mut self,
alignment: alignment::Horizontal,
alignment: impl Into<alignment::Horizontal>,
) -> Self {
self.horizontal_alignment = alignment;
self.horizontal_alignment = alignment.into();
self
}
/// Sets the [`alignment::Vertical`] of the [`Text`].
pub fn vertical_alignment(
pub fn align_y(
mut self,
alignment: alignment::Vertical,
alignment: impl Into<alignment::Vertical>,
) -> Self {
self.vertical_alignment = alignment;
self.vertical_alignment = alignment.into();
self
}
@ -110,6 +159,12 @@ where
self
}
/// Sets the [`Wrapping`] strategy of the [`Text`].
pub fn wrapping(mut self, wrapping: Wrapping) -> Self {
self.wrapping = wrapping;
self
}
/// Sets the style of the [`Text`].
#[must_use]
pub fn style(mut self, style: impl Fn(&Theme) -> Style + 'a) -> Self
@ -149,10 +204,10 @@ where
/// The internal state of a [`Text`] widget.
#[derive(Debug, Default)]
pub struct State<P: Paragraph>(P);
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,
@ -162,7 +217,9 @@ where
}
fn state(&self) -> tree::State {
tree::State::new(State(Renderer::Paragraph::default()))
tree::State::new(State::<Renderer::Paragraph>(
paragraph::Plain::default(),
))
}
fn size(&self) -> Size<Length> {
@ -191,6 +248,7 @@ where
self.horizontal_alignment,
self.vertical_alignment,
self.shaping,
self.wrapping,
)
}
@ -207,7 +265,17 @@ where
let state = tree.state.downcast_ref::<State<Renderer::Paragraph>>();
let style = theme.style(&self.class);
draw(renderer, defaults, layout, state, style, viewport);
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);
}
}
@ -225,6 +293,7 @@ pub fn layout<Renderer>(
horizontal_alignment: alignment::Horizontal,
vertical_alignment: alignment::Vertical,
shaping: Shaping,
wrapping: Wrapping,
) -> layout::Node
where
Renderer: text::Renderer,
@ -235,7 +304,7 @@ where
let size = size.unwrap_or_else(|| renderer.default_size());
let font = font.unwrap_or_else(|| renderer.default_font());
let State(ref mut paragraph) = state;
let State(paragraph) = state;
paragraph.update(text::Text {
content,
@ -246,6 +315,7 @@ where
horizontal_alignment,
vertical_alignment,
shaping,
wrapping,
});
paragraph.min_bounds()
@ -266,13 +336,12 @@ pub fn draw<Renderer>(
renderer: &mut Renderer,
style: &renderer::Style,
layout: Layout<'_>,
state: &State<Renderer::Paragraph>,
paragraph: &Renderer::Paragraph,
appearance: Style,
viewport: &Rectangle,
) where
Renderer: text::Renderer,
{
let State(ref paragraph) = state;
let bounds = layout.bounds();
let x = match paragraph.horizontal_alignment() {
@ -330,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.
///
@ -367,80 +436,42 @@ impl Catalog for Theme {
}
}
/// A fragment of [`Text`].
///
/// This is just an alias to a string that may be either
/// borrowed or owned.
pub type Fragment<'a> = Cow<'a, str>;
/// A trait for converting a value to some text [`Fragment`].
pub trait IntoFragment<'a> {
/// Converts the value to some text [`Fragment`].
fn into_fragment(self) -> Fragment<'a>;
/// The default text styling; color is inherited.
pub fn default(_theme: &Theme) -> Style {
Style { color: None }
}
impl<'a> IntoFragment<'a> for Fragment<'a> {
fn into_fragment(self) -> Fragment<'a> {
self
/// Text with the default base color.
pub fn base(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().text),
}
}
impl<'a, 'b> IntoFragment<'a> for &'a Fragment<'b> {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self)
/// Text conveying some important information, like an action.
pub fn primary(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().primary),
}
}
impl<'a> IntoFragment<'a> for &'a str {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self)
/// Text conveying some secondary information, like a footnote.
pub fn secondary(theme: &Theme) -> Style {
Style {
color: Some(theme.extended_palette().secondary.strong.color),
}
}
impl<'a> IntoFragment<'a> for &'a String {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Borrowed(self.as_str())
/// Text conveying some positive information, like a successful event.
pub fn success(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().success),
}
}
impl<'a> IntoFragment<'a> for String {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self)
/// Text conveying some negative information, like an error.
pub fn danger(theme: &Theme) -> Style {
Style {
color: Some(theme.palette().danger),
}
}
macro_rules! into_fragment {
($type:ty) => {
impl<'a> IntoFragment<'a> for $type {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self.to_string())
}
}
impl<'a> IntoFragment<'a> for &$type {
fn into_fragment(self) -> Fragment<'a> {
Fragment::Owned(self.to_string())
}
}
};
}
into_fragment!(char);
into_fragment!(bool);
into_fragment!(u8);
into_fragment!(u16);
into_fragment!(u32);
into_fragment!(u64);
into_fragment!(u128);
into_fragment!(usize);
into_fragment!(i8);
into_fragment!(i16);
into_fragment!(i32);
into_fragment!(i64);
into_fragment!(i128);
into_fragment!(isize);
into_fragment!(f32);
into_fragment!(f64);

View file

@ -46,7 +46,7 @@ impl Tree {
}
}
/// Reconciliates the current tree with the provided [`Widget`].
/// Reconciles the current tree with the provided [`Widget`].
///
/// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the
/// [`Widget`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
@ -81,7 +81,7 @@ impl Tree {
);
}
/// Reconciliates the children of the tree with the provided list of widgets using custom
/// Reconciles the children of the tree with the provided list of widgets using custom
/// logic both for diffing and creating new widget state.
pub fn diff_children_custom<T>(
&mut self,
@ -107,7 +107,7 @@ impl Tree {
}
}
/// Reconciliates the `current_children` with the provided list of widgets using
/// Reconciles the `current_children` with the provided list of widgets using
/// custom logic both for diffing and creating new widget state.
///
/// The algorithm will try to minimize the impact of diffing by querying the

View file

@ -1,7 +1,9 @@
//! Build window-based GUI applications.
pub mod icon;
pub mod screenshot;
pub mod settings;
mod direction;
mod event;
mod id;
mod level;
@ -10,6 +12,7 @@ mod position;
mod redraw_request;
mod user_attention;
pub use direction::Direction;
pub use event::Event;
pub use icon::Icon;
pub use id::Id;
@ -17,5 +20,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;

View file

@ -0,0 +1,27 @@
/// The cardinal directions relative to the center of a window.
#[derive(Debug, Clone, Copy)]
pub enum Direction {
/// Points to the top edge of a window.
North,
/// Points to the bottom edge of a window.
South,
/// Points to the right edge of a window.
East,
/// Points to the left edge of a window.
West,
/// Points to the top-right corner of a window.
NorthEast,
/// Points to the top-left corner of a window.
NorthWest,
/// Points to the bottom-right corner of a window.
SouthEast,
/// Points to the bottom-left corner of a window.
SouthWest,
}

View file

@ -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>,
@ -23,20 +23,10 @@ pub enum Event {
Closed,
/// A window was moved.
Moved {
/// The new logical x location of the window
x: i32,
/// The new logical y location of the window
y: i32,
},
Moved(Point),
/// A window was resized.
Resized {
/// The new logical width of the window
width: u32,
/// The new logical height of the window
height: u32,
},
Resized(Size),
/// A window redraw was requested.
///
@ -56,17 +46,29 @@ pub enum Event {
///
/// When the user hovers multiple files at once, this event will be emitted
/// for each file separately.
///
/// ## Platform-specific
///
/// - **Wayland:** Not implemented.
FileHovered(PathBuf),
/// A file has been dropped into the window.
///
/// When the user drops multiple files at once, this event will be emitted
/// for each file separately.
///
/// ## Platform-specific
///
/// - **Wayland:** Not implemented.
FileDropped(PathBuf),
/// A file was hovered, but has exited the window.
///
/// There will be a single `FilesHoveredLeft` event triggered even if
/// multiple files were hovered.
///
/// ## Platform-specific
///
/// - **Wayland:** Not implemented.
FilesHoveredLeft,
}

View file

@ -1,10 +1,8 @@
use std::fmt;
use std::hash::Hash;
use std::sync::atomic::{self, AtomicU64};
/// The id of the window.
///
/// Internally Iced reserves `window::Id::MAIN` for the first window spawned.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Id(u64);
@ -12,11 +10,14 @@ pub struct Id(u64);
static COUNT: AtomicU64 = AtomicU64::new(1);
impl Id {
/// The reserved window [`Id`] for the first window in an Iced application.
pub const MAIN: Self = Id(0);
/// Creates a new unique window [`Id`].
pub fn unique() -> Id {
Id(COUNT.fetch_add(1, atomic::Ordering::Relaxed))
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}

View file

@ -8,6 +8,15 @@ pub enum RedrawRequest {
/// Redraw at the given time.
At(Instant),
/// No redraw is needed.
Wait,
}
impl From<Instant> for RedrawRequest {
fn from(time: Instant) -> Self {
Self::At(time)
}
}
#[cfg(test)]
@ -34,5 +43,8 @@ mod tests {
assert!(RedrawRequest::At(now) <= RedrawRequest::At(now));
assert!(RedrawRequest::At(now) <= RedrawRequest::At(later));
assert!(RedrawRequest::At(later) >= RedrawRequest::At(now));
assert!(RedrawRequest::Wait > RedrawRequest::NextFrame);
assert!(RedrawRequest::Wait > RedrawRequest::At(later));
}
}

View file

@ -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,
}

View file

@ -24,16 +24,23 @@ mod platform;
#[path = "settings/other.rs"]
mod platform;
use crate::window::{Icon, Level, Position};
use crate::Size;
use crate::window::{Icon, Level, Position};
pub use platform::PlatformSpecific;
/// The window settings of an application.
#[derive(Debug, Clone)]
pub struct Settings {
/// The initial logical dimensions of the window.
pub size: Size,
/// Whether the window should start maximized.
pub maximized: bool,
/// Whether the window should start fullscreen.
pub fullscreen: bool,
/// The initial position of the window.
pub position: Position,
@ -79,6 +86,8 @@ impl Default for Settings {
fn default() -> Self {
Self {
size: Size::new(1024.0, 768.0),
maximized: false,
fullscreen: false,
position: Position::default(),
min_size: None,
max_size: None,

View file

@ -8,4 +8,10 @@ pub struct PlatformSpecific {
/// As a best practice, it is suggested to select an application id that match
/// the basename of the applications .desktop file.
pub application_id: String,
/// Whether bypass the window manager mapping for x11 windows
///
/// This flag is particularly useful for creating UI elements that need precise
/// positioning and immediate display without window manager interference.
pub override_redirect: bool,
}

View file

@ -1,25 +1,27 @@
//! Platform specific settings for Windows.
use raw_window_handle::RawWindowHandle;
/// The platform specific window settings of an application.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlatformSpecific {
/// Parent window
pub parent: Option<RawWindowHandle>,
/// Drag and drop support
pub drag_and_drop: bool,
/// Whether show or hide the window icon in the taskbar.
pub skip_taskbar: bool,
/// Shows or hides the background drop shadow for undecorated windows.
///
/// The shadow is hidden by default.
/// Enabling the shadow causes a thin 1px line to appear on the top of the window.
pub undecorated_shadow: bool,
}
impl Default for PlatformSpecific {
fn default() -> Self {
Self {
parent: None,
drag_and_drop: true,
skip_taskbar: false,
undecorated_shadow: false,
}
}
}