Merge branch 'master' into beacon

This commit is contained in:
Héctor Ramón Jiménez 2025-04-05 18:20:31 +02:00
commit 3f67044977
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
62 changed files with 713 additions and 780 deletions

View file

@ -24,7 +24,6 @@ glam.workspace = true
lilt.workspace = true
log.workspace = true
num-traits.workspace = true
palette.workspace = true
rustc-hash.workspace = true
smol_str.workspace = true
thiserror.workspace = true
@ -36,9 +35,3 @@ dark-light.optional = true
serde.workspace = true
serde.optional = true
serde.features = ["derive"]
[target.'cfg(windows)'.dependencies]
raw-window-handle.workspace = true
[dev-dependencies]
approx = "0.5"

View file

@ -1,5 +1,3 @@
use palette::rgb::{Srgb, Srgba};
/// A color in the `sRGB` color space.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@ -243,70 +241,9 @@ macro_rules! color {
}};
}
/// Converts from palette's `Rgba` type to a [`Color`].
impl From<Srgba> for Color {
fn from(rgba: Srgba) -> Self {
Color::new(rgba.red, rgba.green, rgba.blue, rgba.alpha)
}
}
/// Converts from [`Color`] to palette's `Rgba` type.
impl From<Color> for Srgba {
fn from(c: Color) -> Self {
Srgba::new(c.r, c.g, c.b, c.a)
}
}
/// Converts from palette's `Rgb` type to a [`Color`].
impl From<Srgb> for Color {
fn from(rgb: Srgb) -> Self {
Color::new(rgb.red, rgb.green, rgb.blue, 1.0)
}
}
/// Converts from [`Color`] to palette's `Rgb` type.
impl From<Color> for Srgb {
fn from(c: Color) -> Self {
Srgb::new(c.r, c.g, c.b)
}
}
#[cfg(test)]
mod tests {
use super::*;
use palette::blend::Blend;
#[test]
fn srgba_traits() {
let c = Color::from_rgb(0.5, 0.4, 0.3);
// Round-trip conversion to the palette::Srgba type
let s: Srgba = c.into();
let r: Color = s.into();
assert_eq!(c, r);
}
#[test]
fn color_manipulation() {
use approx::assert_relative_eq;
let c1 = Color::from_rgb(0.5, 0.4, 0.3);
let c2 = Color::from_rgb(0.2, 0.5, 0.3);
// Convert to linear color for manipulation
let l1 = Srgba::from(c1).into_linear();
let l2 = Srgba::from(c2).into_linear();
// Take the lighter of each of the sRGB components
let lighter = l1.lighten(l2);
// Convert back to our Color
let result: Color = Srgba::from_linear(lighter).into();
assert_relative_eq!(result.r, 0.5);
assert_relative_eq!(result.g, 0.5);
assert_relative_eq!(result.b, 0.3);
assert_relative_eq!(result.a, 1.0);
}
#[test]
fn parse() {

View file

@ -151,39 +151,6 @@ impl<T> InputMethod<T> {
/// 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.

View file

@ -1,10 +1,6 @@
//! Define the colors of a theme.
use crate::{Color, color};
use palette::color_difference::Wcag21RelativeContrast;
use palette::rgb::Rgb;
use palette::{FromColor, Hsl, Mix};
use std::sync::LazyLock;
/// A color palette.
@ -608,13 +604,20 @@ impl Danger {
}
}
struct Hsl {
h: f32,
s: f32,
l: f32,
a: f32,
}
fn darken(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness - amount < 0.0 {
hsl.l = if hsl.l - amount < 0.0 {
0.0
} else {
hsl.lightness - amount
hsl.l - amount
};
from_hsl(hsl)
@ -623,10 +626,10 @@ fn darken(color: Color, amount: f32) -> Color {
fn lighten(color: Color, amount: f32) -> Color {
let mut hsl = to_hsl(color);
hsl.lightness = if hsl.lightness + amount > 1.0 {
hsl.l = if hsl.l + amount > 1.0 {
1.0
} else {
hsl.lightness + amount
hsl.l + amount
};
from_hsl(hsl)
@ -643,17 +646,24 @@ fn deviate(color: Color, amount: f32) -> Color {
fn muted(color: Color) -> Color {
let mut hsl = to_hsl(color);
hsl.saturation = hsl.saturation.min(0.5);
hsl.s = hsl.s.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();
let b_amount = factor.clamp(0.0, 1.0);
let a_amount = 1.0 - b_amount;
let mixed = a_lin.mix(b_lin, factor);
Rgb::from_linear(mixed).into()
let a_linear = a.into_linear().map(|c| c * a_amount);
let b_linear = b.into_linear().map(|c| c * b_amount);
Color::from_linear_rgba(
a_linear[0] + b_linear[0],
a_linear[1] + b_linear[1],
a_linear[2] + b_linear[2],
a_linear[3] + b_linear[3],
)
}
fn readable(background: Color, text: Color) -> Color {
@ -681,27 +691,85 @@ fn readable(background: Color, text: Color) -> Color {
}
fn is_dark(color: Color) -> bool {
to_hsl(color).lightness < 0.6
to_hsl(color).l < 0.6
}
fn is_readable(a: Color, b: Color) -> bool {
let a_srgb = Rgb::from(a);
let b_srgb = Rgb::from(b);
a_srgb.has_enhanced_contrast_text(b_srgb)
relative_contrast(a, b) >= 7.0
}
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
fn relative_contrast(a: Color, b: Color) -> f32 {
let a_srgb = Rgb::from(a);
let b_srgb = Rgb::from(b);
a_srgb.relative_contrast(b_srgb)
let lum_a = relative_luminance(a);
let lum_b = relative_luminance(b);
(lum_a.max(lum_b) + 0.05) / (lum_a.min(lum_b) + 0.05)
}
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
fn relative_luminance(color: Color) -> f32 {
let linear = color.into_linear();
0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
}
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
fn to_hsl(color: Color) -> Hsl {
Hsl::from_color(Rgb::from(color))
let x_max = color.r.max(color.g).max(color.b);
let x_min = color.r.min(color.g).min(color.b);
let c = x_max - x_min;
let l = x_max.midpoint(x_min);
let h = if c == 0.0 {
0.0
} else if x_max == color.r {
60.0 * ((color.g - color.b) / c).rem_euclid(6.0)
} else if x_max == color.g {
60.0 * (((color.b - color.r) / c) + 2.0)
} else {
// x_max == color.b
60.0 * (((color.r - color.g) / c) + 4.0)
};
let s = if l == 0.0 || l == 1.0 {
0.0
} else {
(x_max - l) / l.min(1.0 - l)
};
Hsl {
h,
s,
l,
a: color.a,
}
}
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
fn from_hsl(hsl: Hsl) -> Color {
Rgb::from_color(hsl).into()
let c = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s;
let h = hsl.h / 60.0;
let x = c * (1.0 - (h.rem_euclid(2.0) - 1.0).abs());
let (r1, g1, b1) = if h < 1.0 {
(c, x, 0.0)
} else if h < 2.0 {
(x, c, 0.0)
} else if h < 3.0 {
(0.0, c, x)
} else if h < 4.0 {
(0.0, x, c)
} else if h < 5.0 {
(x, 0.0, c)
} else {
// h < 6.0
(c, 0.0, x)
};
let m = hsl.l - (c / 2.0);
Color {
r: r1 + m,
g: g1 + m,
b: b1 + m,
a: hsl.a,
}
}