Merge branch 'master' into feature/test-recorder
This commit is contained in:
commit
26c9dc1709
83 changed files with 2627 additions and 1208 deletions
|
|
@ -90,12 +90,12 @@ impl Color {
|
|||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
r: gamma_component(r),
|
||||
g: gamma_component(g),
|
||||
b: gamma_component(b),
|
||||
Self::new(
|
||||
gamma_component(r),
|
||||
gamma_component(g),
|
||||
gamma_component(b),
|
||||
a,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/// Parses a [`Color`] from a hex string.
|
||||
|
|
@ -195,6 +195,13 @@ impl Color {
|
|||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the relative luminance of the [`Color`].
|
||||
/// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
||||
pub fn relative_luminance(self) -> f32 {
|
||||
let linear = self.into_linear();
|
||||
0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 3]> for Color {
|
||||
|
|
|
|||
|
|
@ -532,3 +532,49 @@ where
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Theme, Renderer> From<Option<T>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
T: Into<Self>,
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn from(element: Option<T>) -> Self {
|
||||
struct Void;
|
||||
|
||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Void
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size {
|
||||
width: Length::Fixed(0.0),
|
||||
height: Length::Fixed(0.0),
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut Tree,
|
||||
_renderer: &Renderer,
|
||||
_limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout::Node::new(Size::ZERO)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
_renderer: &mut Renderer,
|
||||
_theme: &Theme,
|
||||
_style: &renderer::Style,
|
||||
_layout: Layout<'_>,
|
||||
_cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
element.map(T::into).unwrap_or_else(|| Element::new(Void))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,22 +52,22 @@ impl Node {
|
|||
/// Aligns the [`Node`] in the given space.
|
||||
pub fn align(
|
||||
mut self,
|
||||
horizontal_alignment: Alignment,
|
||||
vertical_alignment: Alignment,
|
||||
align_x: Alignment,
|
||||
align_y: Alignment,
|
||||
space: Size,
|
||||
) -> Self {
|
||||
self.align_mut(horizontal_alignment, vertical_alignment, space);
|
||||
self.align_mut(align_x, align_y, space);
|
||||
self
|
||||
}
|
||||
|
||||
/// Mutable reference version of [`Self::align`].
|
||||
pub fn align_mut(
|
||||
&mut self,
|
||||
horizontal_alignment: Alignment,
|
||||
vertical_alignment: Alignment,
|
||||
align_x: Alignment,
|
||||
align_y: Alignment,
|
||||
space: Size,
|
||||
) {
|
||||
match horizontal_alignment {
|
||||
match align_x {
|
||||
Alignment::Start => {}
|
||||
Alignment::Center => {
|
||||
self.bounds.x += (space.width - self.bounds.width) / 2.0;
|
||||
|
|
@ -77,7 +77,7 @@ impl Node {
|
|||
}
|
||||
}
|
||||
|
||||
match vertical_alignment {
|
||||
match align_y {
|
||||
Alignment::Start => {}
|
||||
Alignment::Center => {
|
||||
self.bounds.y += (space.height - self.bounds.height) / 2.0;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ impl Length {
|
|||
|
||||
/// Adapts the [`Length`] so it can contain the other [`Length`] and
|
||||
/// match its fluidity.
|
||||
#[inline]
|
||||
pub fn enclose(self, other: Length) -> Self {
|
||||
match (self, other) {
|
||||
(Length::Shrink, Length::Fill | Length::FillPortion(_)) => other,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Radians, Vector};
|
||||
use crate::{Length, Radians, Vector};
|
||||
|
||||
/// An amount of space in 2 dimensions.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
|
||||
|
|
@ -66,6 +66,15 @@ impl Size {
|
|||
}
|
||||
}
|
||||
|
||||
impl Size<Length> {
|
||||
/// Returns true if either `width` or `height` are 0-sized.
|
||||
#[inline]
|
||||
pub fn is_void(&self) -> bool {
|
||||
matches!(self.width, Length::Fixed(0.0))
|
||||
|| matches!(self.height, Length::Fixed(0.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<[T; 2]> for Size<T> {
|
||||
fn from([width, height]: [T; 2]) -> Self {
|
||||
Size { width, height }
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ impl Palette {
|
|||
text: Color::BLACK,
|
||||
primary: color!(0x5865F2),
|
||||
success: color!(0x12664f),
|
||||
warning: color!(0xffc14e),
|
||||
warning: color!(0xb77e33),
|
||||
danger: color!(0xc3423f),
|
||||
};
|
||||
|
||||
|
|
@ -453,9 +453,15 @@ pub struct Background {
|
|||
/// The weakest version of the base background color.
|
||||
pub weakest: Pair,
|
||||
/// A weaker version of the base background color.
|
||||
pub weaker: Pair,
|
||||
/// A weak version of the base background color.
|
||||
pub weak: Pair,
|
||||
/// A stronger version of the base background color.
|
||||
/// A neutral version of the base background color, between weak and strong.
|
||||
pub neutral: Pair,
|
||||
/// A strong version of the base background color.
|
||||
pub strong: Pair,
|
||||
/// A stronger version of the base background color.
|
||||
pub stronger: Pair,
|
||||
/// The strongest version of the base background color.
|
||||
pub strongest: Pair,
|
||||
}
|
||||
|
|
@ -464,15 +470,21 @@ impl Background {
|
|||
/// Generates a set of [`Background`] colors from the base and text colors.
|
||||
pub fn new(base: Color, text: Color) -> Self {
|
||||
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));
|
||||
let weaker = deviate(base, 0.07);
|
||||
let weak = deviate(base, 0.1);
|
||||
let neutral = deviate(base, 0.125);
|
||||
let strong = deviate(base, 0.15);
|
||||
let stronger = deviate(base, 0.175);
|
||||
let strongest = deviate(base, 0.20);
|
||||
|
||||
Self {
|
||||
base: Pair::new(base, text),
|
||||
weakest: Pair::new(weakest, text),
|
||||
weaker: Pair::new(weaker, text),
|
||||
weak: Pair::new(weak, text),
|
||||
neutral: Pair::new(neutral, text),
|
||||
strong: Pair::new(strong, text),
|
||||
stronger: Pair::new(stronger, text),
|
||||
strongest: Pair::new(strongest, text),
|
||||
}
|
||||
}
|
||||
|
|
@ -517,9 +529,11 @@ pub struct Secondary {
|
|||
impl Secondary {
|
||||
/// Generates a set of [`Secondary`] colors from the base and text colors.
|
||||
pub fn generate(base: Color, text: Color) -> Self {
|
||||
let base = mix(base, text, 0.2);
|
||||
let weak = mix(base, text, 0.1);
|
||||
let strong = mix(base, text, 0.3);
|
||||
let factor = if is_dark(base) { 0.2 } else { 0.4 };
|
||||
|
||||
let weak = mix(deviate(base, 0.1), text, factor);
|
||||
let strong = mix(deviate(base, 0.3), text, factor);
|
||||
let base = mix(deviate(base, 0.2), text, factor);
|
||||
|
||||
Self {
|
||||
base: Pair::new(base, text),
|
||||
|
|
@ -604,53 +618,55 @@ impl Danger {
|
|||
}
|
||||
}
|
||||
|
||||
struct Hsl {
|
||||
h: f32,
|
||||
s: f32,
|
||||
struct Oklch {
|
||||
l: f32,
|
||||
c: f32,
|
||||
h: f32,
|
||||
a: f32,
|
||||
}
|
||||
|
||||
fn darken(color: Color, amount: f32) -> Color {
|
||||
let mut hsl = to_hsl(color);
|
||||
let mut oklch = to_oklch(color);
|
||||
|
||||
hsl.l = if hsl.l - amount < 0.0 {
|
||||
// We try to bump the chroma a bit for more colorful palettes
|
||||
if oklch.c > 0.0 && oklch.c < (1.0 - oklch.l) / 2.0 {
|
||||
// Formula empirically and cluelessly derived
|
||||
oklch.c *= 1.0 + (0.2 / oklch.c).min(100.0) * amount;
|
||||
}
|
||||
|
||||
oklch.l = if oklch.l - amount < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
hsl.l - amount
|
||||
oklch.l - amount
|
||||
};
|
||||
|
||||
from_hsl(hsl)
|
||||
from_oklch(oklch)
|
||||
}
|
||||
|
||||
fn lighten(color: Color, amount: f32) -> Color {
|
||||
let mut hsl = to_hsl(color);
|
||||
let mut oklch = to_oklch(color);
|
||||
|
||||
hsl.l = if hsl.l + amount > 1.0 {
|
||||
// We try to bump the chroma a bit for more colorful palettes
|
||||
// Formula empirically and cluelessly derived
|
||||
oklch.c *= 1.0 + 2.0 * amount / oklch.l.max(0.05);
|
||||
|
||||
oklch.l = if oklch.l + amount > 1.0 {
|
||||
1.0
|
||||
} else {
|
||||
hsl.l + amount
|
||||
oklch.l + amount
|
||||
};
|
||||
|
||||
from_hsl(hsl)
|
||||
from_oklch(oklch)
|
||||
}
|
||||
|
||||
fn deviate(color: Color, amount: f32) -> Color {
|
||||
if is_dark(color) {
|
||||
lighten(color, amount)
|
||||
} else {
|
||||
darken(color, amount * 0.8)
|
||||
darken(color, amount)
|
||||
}
|
||||
}
|
||||
|
||||
fn muted(color: Color) -> Color {
|
||||
let mut hsl = to_hsl(color);
|
||||
|
||||
hsl.s = hsl.s.min(0.5);
|
||||
|
||||
from_hsl(hsl)
|
||||
}
|
||||
|
||||
fn mix(a: Color, b: Color, factor: f32) -> Color {
|
||||
let b_amount = factor.clamp(0.0, 1.0);
|
||||
let a_amount = 1.0 - b_amount;
|
||||
|
|
@ -680,6 +696,12 @@ fn readable(background: Color, text: Color) -> Color {
|
|||
return candidate;
|
||||
}
|
||||
|
||||
let candidate = improve(text, 0.2);
|
||||
|
||||
if is_readable(background, candidate) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
let white_contrast = relative_contrast(background, Color::WHITE);
|
||||
let black_contrast = relative_contrast(background, Color::BLACK);
|
||||
|
||||
|
|
@ -691,85 +713,71 @@ fn readable(background: Color, text: Color) -> Color {
|
|||
}
|
||||
|
||||
fn is_dark(color: Color) -> bool {
|
||||
to_hsl(color).l < 0.6
|
||||
to_oklch(color).l < 0.6
|
||||
}
|
||||
|
||||
fn is_readable(a: Color, b: Color) -> bool {
|
||||
relative_contrast(a, b) >= 7.0
|
||||
relative_contrast(a, b) >= 6.0
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/WCAG21/#dfn-contrast-ratio
|
||||
fn relative_contrast(a: Color, b: Color) -> f32 {
|
||||
let lum_a = relative_luminance(a);
|
||||
let lum_b = relative_luminance(b);
|
||||
let lum_a = a.relative_luminance();
|
||||
let lum_b = b.relative_luminance();
|
||||
(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/Oklab_color_space#Conversions_between_color_spaces
|
||||
fn to_oklch(color: Color) -> Oklch {
|
||||
let [r, g, b, alpha] = color.into_linear();
|
||||
|
||||
// linear RGB → LMS
|
||||
let l = 0.41222146 * r + 0.53633255 * g + 0.051445995 * b;
|
||||
let m = 0.2119035 * r + 0.6806995 * g + 0.10739696 * b;
|
||||
let s = 0.08830246 * r + 0.28171885 * g + 0.6299787 * b;
|
||||
|
||||
// Nonlinear transform (cube root)
|
||||
let l_ = l.cbrt();
|
||||
let m_ = m.cbrt();
|
||||
let s_ = s.cbrt();
|
||||
|
||||
// LMS → Oklab
|
||||
let l = 0.21045426 * l_ + 0.7936178 * m_ - 0.004072047 * s_;
|
||||
let a = 1.9779985 * l_ - 2.4285922 * m_ + 0.4505937 * s_;
|
||||
let b = 0.025904037 * l_ + 0.78277177 * m_ - 0.80867577 * s_;
|
||||
|
||||
// Oklab → Oklch
|
||||
let c = (a * a + b * b).sqrt();
|
||||
let h = b.atan2(a); // radians
|
||||
|
||||
Oklch { l, c, h, a: alpha }
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
|
||||
fn to_hsl(color: Color) -> Hsl {
|
||||
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);
|
||||
// https://en.wikipedia.org/wiki/Oklab_color_space#Conversions_between_color_spaces
|
||||
fn from_oklch(oklch: Oklch) -> Color {
|
||||
let Oklch { l, c, h, a: alpha } = oklch;
|
||||
|
||||
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 a = c * h.cos();
|
||||
let b = c * h.sin();
|
||||
|
||||
let s = if l == 0.0 || l == 1.0 {
|
||||
0.0
|
||||
} else {
|
||||
(x_max - l) / l.min(1.0 - l)
|
||||
};
|
||||
// Oklab → LMS (nonlinear)
|
||||
let l_ = l + 0.39633778 * a + 0.21580376 * b;
|
||||
let m_ = l - 0.105561346 * a - 0.06385417 * b;
|
||||
let s_ = l - 0.08948418 * a - 1.2914855 * b;
|
||||
|
||||
Hsl {
|
||||
h,
|
||||
s,
|
||||
l,
|
||||
a: color.a,
|
||||
}
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB
|
||||
fn from_hsl(hsl: Hsl) -> Color {
|
||||
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,
|
||||
}
|
||||
// Cubing back
|
||||
let l = l_ * l_ * l_;
|
||||
let m = m_ * m_ * m_;
|
||||
let s = s_ * s_ * s_;
|
||||
|
||||
let r = 4.0767417 * l - 3.3077116 * m + 0.23096994 * s;
|
||||
let g = -1.268438 * l + 2.6097574 * m - 0.34131938 * s;
|
||||
let b = -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s;
|
||||
|
||||
Color::from_linear_rgba(
|
||||
r.clamp(0.0, 1.0),
|
||||
g.clamp(0.0, 1.0),
|
||||
b.clamp(0.0, 1.0),
|
||||
alpha,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ pub fn primary(theme: &Theme) -> Style {
|
|||
/// Text conveying some secondary information, like a footnote.
|
||||
pub fn secondary(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.extended_palette().secondary.strong.color),
|
||||
color: Some(theme.extended_palette().secondary.base.color),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -452,6 +452,13 @@ pub fn success(theme: &Theme) -> Style {
|
|||
}
|
||||
}
|
||||
|
||||
/// Text conveying some mildly negative information, like a warning.
|
||||
pub fn warning(theme: &Theme) -> Style {
|
||||
Style {
|
||||
color: Some(theme.palette().warning),
|
||||
}
|
||||
}
|
||||
|
||||
/// Text conveying some negative information, like an error.
|
||||
pub fn danger(theme: &Theme) -> Style {
|
||||
Style {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue