Cosmic advanced text (#103)

* wip: update to use cosmic-advanced-text

* use cosmic-advanced-text branch of iced

* fix: line height and spacing for segmented button and update to get svg fix

* fix: spin button styling & spacing

* update iced to fix segmented button border radius

* feat: example improvements

* feat: helper for loading fonts

* feat: add focus style to button

* fix: slider height and iced fixed

* feat: hash icon width and height

* cleanup

* update ci

* refactor: always use lazy feature of iced

* update iced

* update iced

* cleanup & update iced

* update iced: new slider & tiny-skia quad updates

* update iced: fixes for tiny-skia quad rendering with edge case border radius

* re-export iced_runtime & iced_widget

* merge master

* udpate iced

* update iced

* update iced

* update iced

* fix: make rectangle_tracker subscription only return update if there is some

* feat: derive macro for loading a cosmic-config

* feat (cosmic-config): iced subscription

* fix (example): update to rectangle tracker subscription

* fix (cosmic-config)

* refactor(cosmic-config-derive): add support for types with generic parameters

* fix (cosmic-config): feature gate updates for subscription helpers

* feat: support for custom & system themes + move cosmic-theme to libcosmic

* feat: sorta hacky way of creating header bars for libcosmic + update iced to get support for resizable windows in iced-sctk

* update iced

* update and reexport sctk

* fix: applet border radius

* feat (cosmic-theme): add id and name methods

* fix(cosmic-theme): reexport palette from cosmic-theme

* fix(cosmic-config-derive): allow use with reexported cosmic-config

* feat: update iced with fix and refactor applet env vars

* update iced
This commit is contained in:
Ashley Wulber 2023-05-30 12:03:15 -04:00 committed by GitHub
parent a173794bed
commit e056e8c830
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
65 changed files with 3431 additions and 405 deletions

View file

@ -0,0 +1,170 @@
use super::ColorPicker;
use crate::{Selection, ThemeConstraints};
use anyhow::{anyhow, bail, Result};
use float_cmp::approx_eq;
use palette::{Clamp, IntoColor, Lch, RelativeContrast, Srgba};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt;
/// Implementation of a Cosmic color chooser which exactly meets constraints
#[derive(Debug, Default, Clone)]
pub struct Exact<C> {
selection: Selection<C>,
constraints: ThemeConstraints,
}
impl<C> Exact<C>
where
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
{
/// create a new Exact color picker
pub fn new(selection: Selection<C>, constraints: ThemeConstraints) -> Self {
Self {
selection,
constraints,
}
}
}
impl<C> ColorPicker<C> for Exact<C>
where
C: Clone + fmt::Debug + Default + Into<Srgba> + From<Srgba> + Serialize + DeserializeOwned,
{
fn get_constraints(&self) -> ThemeConstraints {
self.constraints
}
fn get_selection(&self) -> Selection<C> {
self.selection.clone()
}
fn pick_color_graphic(
&self,
color: C,
contrast: f32,
grayscale: bool,
lighten: Option<bool>,
) -> (C, Option<anyhow::Error>) {
let mut err = None;
let res = self.pick_color(color.clone(), Some(contrast), grayscale, lighten);
if let Ok(c) = res {
return (c, err);
} else if let Err(e) = res {
err = Some(anyhow!("Graphic contrast {} failed: {}", contrast, e));
}
let res = self.pick_color(color.clone(), None, grayscale, lighten);
if let Ok(c) = res {
return (c, err);
} else if let Err(e) = res {
err = Some(e);
}
// return same color if no other color possible
(color, err)
}
fn pick_color_text(
&self,
color: C,
grayscale: bool,
lighten: Option<bool>,
) -> (C, Option<anyhow::Error>) {
let mut err = None;
// AAA
let res = self.pick_color(color.clone(), Some(7.0), grayscale, lighten);
if let Ok(c) = res {
return (c, err);
} else if let Err(e) = res {
err = Some(anyhow!("AAA text contrast failed: {}", e));
}
// AA
let res = self.pick_color(color.clone(), Some(4.5), grayscale, lighten);
if let Ok(c) = res {
return (c, err);
} else if let Err(e) = res {
err = Some(anyhow!("AA text contrast failed: {}", e));
}
let res = self.pick_color(color.clone(), None, grayscale, lighten);
if let Ok(c) = res {
return (c, err);
} else if let Err(e) = res {
err = Some(e);
}
(color, err)
}
fn pick_color(
&self,
color: C,
contrast: Option<f32>,
grayscale: bool,
lighten: Option<bool>,
) -> Result<C> {
let srgba: Srgba = color.clone().into();
let mut lch_color: Lch = srgba.into_color();
// set to grayscale
if grayscale {
lch_color.chroma = 0.0;
}
// lighten or darken
// TODO closed form solution using Lch color space contrast formula?
// for now do binary search...
if let Some(contrast) = contrast {
let (min, max) = match lighten {
Some(b) if b => (lch_color.l, 100.0),
Some(_) => (0.0, lch_color.l),
None => (0.0, 100.0),
};
let (mut l, mut r) = (min, max);
for _ in 0..100 {
let cur_guess_lightness = (l + r) / 2.0;
let mut cur_guess = lch_color;
cur_guess.l = cur_guess_lightness;
let cur_contrast = srgba.get_contrast_ratio(&cur_guess.into_color());
let contrast_dir = contrast > cur_contrast;
let lightness_dir = lch_color.l < cur_guess.l;
if approx_eq!(f32, contrast, cur_contrast, ulps = 4) {
lch_color = cur_guess;
break;
// TODO fix
} else if lightness_dir && contrast_dir || !lightness_dir && !contrast_dir {
l = cur_guess_lightness;
} else {
r = cur_guess_lightness;
}
}
// clamp to valid value in range
lch_color.clamp_self();
// verify contrast
let actual_contrast = srgba.get_contrast_ratio(&lch_color.into_color());
if !approx_eq!(f32, contrast, actual_contrast, ulps = 4) {
bail!(
"Failed to derive color with contrast {} from {:?}",
contrast,
color
);
}
Ok(C::from(lch_color.into_color()))
} else {
// maximize contrast if no constraint is given
if lch_color.l > 50.0 {
Ok(C::from(palette::named::BLACK.into_format().into_color()))
} else {
Ok(C::from(palette::named::WHITE.into_format().into_color()))
}
}
}
}

View file

@ -0,0 +1,280 @@
use crate::{Component, Container, ContainerType, Derivation, Selection, Theme, ThemeConstraints};
use anyhow::{anyhow, Result};
use palette::{IntoColor, Lcha, Shade, Srgba};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt;
pub use exact::*;
mod exact;
// TODO derive palette from Selection?
/// Color picker derives colors and theme elements
pub trait ColorPicker<
C: Into<Srgba> + From<Srgba> + Clone + fmt::Debug + Default + Serialize + DeserializeOwned,
>
{
/// try to derive a color with a given contrast, grayscale setting, and lightness direction
fn pick_color(
&self,
color: C,
contrast: Option<f32>,
grayscale: bool,
lighten: Option<bool>,
) -> Result<C>;
/// try to derive a text color with a given grayscale setting, and lightness direction
fn pick_color_text(
&self,
color: C,
grayscale: bool,
lighten: Option<bool>,
) -> (C, Option<anyhow::Error>);
/// try to derive a graphic color with a given contrast, grayscale setting, and lightness direction
fn pick_color_graphic(
&self,
color: C,
contrast: f32,
grayscale: bool,
lighten: Option<bool>,
) -> (C, Option<anyhow::Error>);
/// get the selection for this color picker
fn get_selection(&self) -> Selection<C>;
/// get the constraints for this color picker
fn get_constraints(&self) -> ThemeConstraints;
/// derive a theme from the selection and constraints
fn theme_derivation(&self) -> Derivation<Theme<C>> {
let mut theme_errors = Vec::new();
let Derivation {
derived: background,
errors: mut errs,
} = self.container_derivation(ContainerType::Background);
theme_errors.append(&mut errs);
let Derivation {
derived: primary,
errors: mut errs,
} = self.container_derivation(ContainerType::Primary);
theme_errors.append(&mut errs);
let Derivation {
derived: secondary,
mut errors,
} = self.container_derivation(ContainerType::Secondary);
theme_errors.append(&mut errors);
let Derivation {
derived: accent,
mut errors,
} = self.widget_derivation(self.get_selection().accent);
theme_errors.append(&mut errors);
let Derivation {
derived: destructive,
mut errors,
} = self.widget_derivation(self.get_selection().destructive);
theme_errors.append(&mut errors);
let Derivation {
derived: warning,
mut errors,
} = self.widget_derivation(self.get_selection().warning);
theme_errors.append(&mut errors);
let Derivation {
derived: success,
mut errors,
} = self.widget_derivation(self.get_selection().success);
theme_errors.append(&mut errors);
Derivation {
derived: Theme::new(
background,
primary,
secondary,
accent,
destructive,
warning,
success,
),
errors: theme_errors,
}
}
/// derive a container element
fn container_derivation(&self, container_type: ContainerType) -> Derivation<Container<C>> {
let selection = self.get_selection();
let constraints = self.get_constraints();
let mut errors = Vec::new();
let Selection {
background,
primary_container,
secondary_container,
..
} = selection;
let ThemeConstraints {
elevated_contrast_ratio,
divider_contrast_ratio,
divider_gray_scale,
lighten,
..
} = constraints;
let container = match container_type {
ContainerType::Background => background,
ContainerType::Primary => primary_container,
ContainerType::Secondary => secondary_container,
};
let (container_divider, err) = self.pick_color_graphic(
container.clone(),
divider_contrast_ratio,
divider_gray_scale,
Some(lighten),
);
if let Some(e) = err {
errors.push(e);
};
let (container_fg, err) = self.pick_color_text(container.clone(), true, None);
if let Some(err) = err {
let err = anyhow!("{} => \"container text\" failed: {}", container_type, err);
errors.push(err);
};
// TODO revisit this and adjust constraints for transparency
let mut container_fg_opacity_80: Srgba = container_fg.clone().into();
container_fg_opacity_80.alpha *= 0.8;
let (component_default, err) = self.pick_color_graphic(
container.clone(),
elevated_contrast_ratio,
false,
Some(lighten),
);
if let Some(e) = err {
let err = anyhow!(
"{} => \"container component\" failed: {}",
container_type,
e
);
errors.push(err);
};
let Derivation {
derived: container_component,
errors: errs,
} = self.widget_derivation(component_default);
for e in errs {
let err = anyhow!(
"{} => \"container component derivation\" failed: {}",
container_type,
e
);
errors.push(err);
}
Derivation {
derived: Container {
base: container,
divider: container_divider,
on: container_fg,
component: container_component,
},
errors,
}
}
/// derive a widget
fn widget_derivation(&self, default: C) -> Derivation<Component<C>> {
let ThemeConstraints {
divider_contrast_ratio,
divider_gray_scale,
lighten,
..
} = self.get_constraints();
let mut errors = Vec::new();
let rgba: Srgba = default.clone().into();
let lch = Lcha {
color: rgba.color.into_color(),
alpha: rgba.alpha,
};
// TODO define constraints for different states...
// & add color self methods and errors if these fail
let hover = if lighten {
lch.lighten(0.1)
} else {
lch.darken(0.1)
};
let pressed = if lighten {
hover.lighten(0.1)
} else {
hover.darken(0.1)
};
let pressed = C::from(Srgba {
color: pressed.color.into_color(),
alpha: pressed.alpha,
});
// TODO is this actually a different color? or just outlined?
let selected = default.clone();
let mut disabled: Srgba = default.clone().into();
disabled.alpha = 0.5;
let (divider, error) = self.pick_color_graphic(
pressed.clone(),
divider_contrast_ratio,
divider_gray_scale,
Some(lighten),
);
if let Some(error) = error {
errors.push(error);
}
let (text, error) = self.pick_color_text(pressed.clone(), true, None);
if let Some(error) = error {
errors.push(error);
}
let (selected_text, error) = self.pick_color_text(selected.clone(), true, None);
if let Some(error) = error {
errors.push(error);
}
let mut text_opacity_80: Srgba = text.clone().into();
text_opacity_80.alpha = 0.8;
let mut disabled_fg = text.clone().into();
disabled_fg.alpha = 0.5;
Derivation {
derived: Component {
base: default,
hover: C::from(Srgba {
color: hover.color.into_color(),
alpha: hover.alpha,
}),
pressed,
selected: selected.clone(),
selected_text: selected_text,
focus: selected.clone(), // FIXME
divider,
on: text,
disabled: disabled.into(),
on_disabled: disabled_fg.into(),
},
errors,
}
}
}