Merge branch 'master' into beacon
This commit is contained in:
commit
8bd5de72ea
371 changed files with 33138 additions and 12950 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
148
core/src/animation.rs
Normal 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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
221
core/src/input_method.rs
Normal 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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
||||
|
|
|
|||
|
|
@ -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 Sun’s USB keyboard.
|
||||
Again,
|
||||
/// Found on Sun’s USB keyboard.
|
||||
Copy,
|
||||
/// Found on Sun’s USB keyboard.
|
||||
Cut,
|
||||
/// Found on Sun’s USB keyboard.
|
||||
Find,
|
||||
/// Found on Sun’s USB keyboard.
|
||||
Open,
|
||||
/// Found on Sun’s USB keyboard.
|
||||
Paste,
|
||||
/// Found on Sun’s USB keyboard.
|
||||
Props,
|
||||
/// Found on Sun’s USB keyboard.
|
||||
Select,
|
||||
/// Found on Sun’s 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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ pub enum Interaction {
|
|||
Grabbing,
|
||||
ResizingHorizontally,
|
||||
ResizingVertically,
|
||||
ResizingDiagonallyUp,
|
||||
ResizingDiagonallyDown,
|
||||
NotAllowed,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
Cell,
|
||||
Move,
|
||||
Copy,
|
||||
Help,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
48
core/src/settings.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
327
core/src/text.rs
327
core/src/text.rs
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 => "",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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`].
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
27
core/src/window/direction.rs
Normal file
27
core/src/window/direction.rs
Normal 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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
108
core/src/window/screenshot.rs
Normal file
108
core/src/window/screenshot.rs
Normal 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,
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 application’s .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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue