mirror of
https://github.com/danbulant/cushy
synced 2026-06-20 15:01:11 +00:00
ColorScheme[Builder]
This commit is contained in:
parent
40343e163f
commit
b1ae9efae2
3 changed files with 214 additions and 64 deletions
|
|
@ -2,7 +2,9 @@ use std::str::FromStr;
|
|||
|
||||
use gooey::animation::ZeroToOne;
|
||||
use gooey::styles::components::{TextColor, WidgetBackground};
|
||||
use gooey::styles::{ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair};
|
||||
use gooey::styles::{
|
||||
ColorScheme, ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair,
|
||||
};
|
||||
use gooey::value::{Dynamic, MapEach};
|
||||
use gooey::widget::MakeWidget;
|
||||
use gooey::widgets::{Input, Label, ModeSwitch, Scroll, Slider, Stack, Themed};
|
||||
|
|
@ -10,19 +12,15 @@ use gooey::window::ThemeMode;
|
|||
use gooey::Run;
|
||||
use kludgine::Color;
|
||||
|
||||
const PRIMARY_HUE: f32 = 240.;
|
||||
const SECONDARY_HUE: f32 = 0.;
|
||||
const TERTIARY_HUE: f32 = 330.;
|
||||
const ERROR_HUE: f32 = 30.;
|
||||
|
||||
fn main() -> gooey::Result {
|
||||
let (primary, primary_editor) = color_editor(PRIMARY_HUE, 0.8, "Primary");
|
||||
let (secondary, secondary_editor) = color_editor(SECONDARY_HUE, 0.3, "Secondary");
|
||||
let (tertiary, tertiary_editor) = color_editor(TERTIARY_HUE, 0.3, "Tertiary");
|
||||
let (error, error_editor) = color_editor(ERROR_HUE, 0.8, "Error");
|
||||
let (neutral, neutral_editor) = color_editor(PRIMARY_HUE, 0.001, "Neutral");
|
||||
let scheme = ColorScheme::default();
|
||||
let (primary, primary_editor) = color_editor(scheme.primary, "Primary");
|
||||
let (secondary, secondary_editor) = color_editor(scheme.secondary, "Secondary");
|
||||
let (tertiary, tertiary_editor) = color_editor(scheme.tertiary, "Tertiary");
|
||||
let (error, error_editor) = color_editor(scheme.error, "Error");
|
||||
let (neutral, neutral_editor) = color_editor(scheme.neutral, "Neutral");
|
||||
let (neutral_variant, neutral_variant_editor) =
|
||||
color_editor(PRIMARY_HUE, 0.001, "Neutral Variant");
|
||||
color_editor(scheme.neutral_variant, "Neutral Variant");
|
||||
let (theme_mode, theme_switcher) = dark_mode_slider();
|
||||
|
||||
let default_theme = (
|
||||
|
|
@ -35,14 +33,14 @@ fn main() -> gooey::Result {
|
|||
)
|
||||
.map_each(
|
||||
|(primary, secondary, tertiary, error, neutral, neutral_variant)| {
|
||||
ThemePair::from_sources(
|
||||
*primary,
|
||||
*secondary,
|
||||
*tertiary,
|
||||
*error,
|
||||
*neutral,
|
||||
*neutral_variant,
|
||||
)
|
||||
ThemePair::from(ColorScheme {
|
||||
primary: *primary,
|
||||
secondary: *secondary,
|
||||
tertiary: *tertiary,
|
||||
error: *error,
|
||||
neutral: *neutral,
|
||||
neutral_variant: *neutral_variant,
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -104,12 +102,11 @@ where
|
|||
}
|
||||
|
||||
fn color_editor(
|
||||
initial_hue: f32,
|
||||
initial_saturation: impl Into<ZeroToOne>,
|
||||
initial_color: ColorSource,
|
||||
label: &str,
|
||||
) -> (Dynamic<ColorSource>, impl MakeWidget) {
|
||||
let (hue, hue_text) = create_paired_string(initial_hue);
|
||||
let (saturation, saturation_text) = create_paired_string(initial_saturation.into());
|
||||
let (hue, hue_text) = create_paired_string(initial_color.hue.into_degrees());
|
||||
let (saturation, saturation_text) = create_paired_string(initial_color.saturation);
|
||||
|
||||
let color =
|
||||
(&hue, &saturation).map_each(|(hue, saturation)| ColorSource::new(*hue, *saturation));
|
||||
|
|
|
|||
|
|
@ -853,6 +853,12 @@ impl ZeroToOne {
|
|||
pub fn into_f32(self) -> f32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Returns the result of 1.0 - `self`.
|
||||
#[must_use]
|
||||
pub fn one_minus(self) -> Self {
|
||||
Self(1. - self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ZeroToOne {
|
||||
|
|
@ -952,7 +958,15 @@ impl Div for ZeroToOne {
|
|||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 / rhs.0)
|
||||
self / rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for ZeroToOne {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
Self(self.0 / rhs)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
217
src/styles.rs
217
src/styles.rs
|
|
@ -915,54 +915,42 @@ pub struct ThemePair {
|
|||
impl ThemePair {
|
||||
/// Returns a new theme generated from the provided color sources.
|
||||
#[must_use]
|
||||
pub fn from_sources(
|
||||
primary: ColorSource,
|
||||
secondary: ColorSource,
|
||||
tertiary: ColorSource,
|
||||
error: ColorSource,
|
||||
neutral: ColorSource,
|
||||
neutral_variant: ColorSource,
|
||||
) -> Self {
|
||||
pub fn from_scheme(scheme: &ColorScheme) -> Self {
|
||||
Self {
|
||||
light: Theme::light_from_sources(
|
||||
primary,
|
||||
secondary,
|
||||
tertiary,
|
||||
error,
|
||||
neutral,
|
||||
neutral_variant,
|
||||
scheme.primary,
|
||||
scheme.secondary,
|
||||
scheme.tertiary,
|
||||
scheme.error,
|
||||
scheme.neutral,
|
||||
scheme.neutral_variant,
|
||||
),
|
||||
dark: Theme::dark_from_sources(
|
||||
primary,
|
||||
secondary,
|
||||
tertiary,
|
||||
error,
|
||||
neutral,
|
||||
neutral_variant,
|
||||
scheme.primary,
|
||||
scheme.secondary,
|
||||
scheme.tertiary,
|
||||
scheme.error,
|
||||
scheme.neutral,
|
||||
scheme.neutral_variant,
|
||||
),
|
||||
primary_fixed: FixedTheme::from_source(primary),
|
||||
secondary_fixed: FixedTheme::from_source(secondary),
|
||||
tertiary_fixed: FixedTheme::from_source(tertiary),
|
||||
scrim: neutral.color(1),
|
||||
shadow: neutral.color(1),
|
||||
primary_fixed: FixedTheme::from_source(scheme.primary),
|
||||
secondary_fixed: FixedTheme::from_source(scheme.secondary),
|
||||
tertiary_fixed: FixedTheme::from_source(scheme.tertiary),
|
||||
scrim: scheme.neutral.color(1),
|
||||
shadow: scheme.neutral.color(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorScheme> for ThemePair {
|
||||
fn from(scheme: ColorScheme) -> Self {
|
||||
Self::from_scheme(&scheme)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ThemePair {
|
||||
fn default() -> Self {
|
||||
const PRIMARY_HUE: f32 = -120.;
|
||||
const SECONDARY_HUE: f32 = 0.;
|
||||
const TERTIARY_HUE: f32 = -30.;
|
||||
const ERROR_HUE: f32 = 30.;
|
||||
Self::from_sources(
|
||||
ColorSource::new(PRIMARY_HUE, 0.8),
|
||||
ColorSource::new(SECONDARY_HUE, 0.3),
|
||||
ColorSource::new(TERTIARY_HUE, 0.3),
|
||||
ColorSource::new(ERROR_HUE, 0.8),
|
||||
ColorSource::new(0., 0.001),
|
||||
ColorSource::new(30., 0.),
|
||||
)
|
||||
Self::from(ColorScheme::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1177,7 +1165,7 @@ impl FixedTheme {
|
|||
///
|
||||
/// The goal of this type is to allow various tones of a given hue/saturation to
|
||||
/// be generated easily.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct ColorSource {
|
||||
/// A measurement of hue, in degees, from -180 to 180.
|
||||
///
|
||||
|
|
@ -1238,7 +1226,7 @@ impl ColorSource {
|
|||
} / 180.,
|
||||
);
|
||||
|
||||
saturation_delta * hue_delta
|
||||
saturation_delta.one_minus() * hue_delta
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1574,3 +1562,154 @@ impl TryFrom<Component> for ContainerLevel {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder of [`ColorScheme`]s.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ColorSchemeBuilder {
|
||||
/// The primary color of the scheme.
|
||||
pub primary: ColorSource,
|
||||
/// The secondary color of the scheme. If not provided, a complimentary
|
||||
/// color will be chosen.
|
||||
pub secondary: Option<ColorSource>,
|
||||
/// The tertiary color of the scheme. If not provided, a complimentary
|
||||
/// color will be chosen.
|
||||
pub tertiary: Option<ColorSource>,
|
||||
/// The error color of the scheme. If not provided, red will be used unless
|
||||
/// it contrasts poorly with any of the other colors.
|
||||
pub error: Option<ColorSource>,
|
||||
/// The neutral color of the scheme. If not provided, a nearly fully
|
||||
/// desaturated variation of the primary color will be used.
|
||||
pub neutral: Option<ColorSource>,
|
||||
/// The neutral variant color of the scheme. If not provided, a mostly
|
||||
/// desaturated variation of the primary color will be used.
|
||||
pub neutral_variant: Option<ColorSource>,
|
||||
hue_shift: f32,
|
||||
}
|
||||
|
||||
impl ColorSchemeBuilder {
|
||||
/// Returns a builder for the provided hue, in degrees.
|
||||
#[must_use]
|
||||
pub fn from_hue(hue: impl Into<OklabHue>) -> Self {
|
||||
Self::new(ColorSource::new(hue, 0.8))
|
||||
}
|
||||
|
||||
/// Returns a builder for the provided primary color.
|
||||
#[must_use]
|
||||
pub fn new(primary: ColorSource) -> Self {
|
||||
Self {
|
||||
primary,
|
||||
secondary: None,
|
||||
tertiary: None,
|
||||
error: None,
|
||||
neutral: None,
|
||||
neutral_variant: None,
|
||||
hue_shift: 30.,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_secondary(&self) -> ColorSource {
|
||||
ColorSource {
|
||||
hue: self.primary.hue + self.hue_shift,
|
||||
saturation: self.primary.saturation / 2.,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_tertiary(&self, secondary: ColorSource) -> ColorSource {
|
||||
let hue_shift = (secondary.hue - self.primary.hue).into_degrees().signum() * self.hue_shift;
|
||||
ColorSource {
|
||||
hue: self.primary.hue - hue_shift,
|
||||
saturation: self.primary.saturation / 3.,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_error(&self, secondary: ColorSource, tertiary: ColorSource) -> ColorSource {
|
||||
let mut error = ColorSource::new(30., self.primary.saturation);
|
||||
while [self.primary, secondary, tertiary]
|
||||
.iter()
|
||||
.any(|c| c.contrast_between(error) < 0.10)
|
||||
{
|
||||
error.hue -= self.hue_shift;
|
||||
}
|
||||
|
||||
error
|
||||
}
|
||||
|
||||
fn generate_neutral(&self) -> ColorSource {
|
||||
ColorSource {
|
||||
hue: self.primary.hue,
|
||||
saturation: ZeroToOne::new(0.01),
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_neutral_variant(&self) -> ColorSource {
|
||||
ColorSource {
|
||||
hue: self.primary.hue,
|
||||
saturation: ZeroToOne::new(0.1),
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a color scheme from the provided colors, generating any
|
||||
/// unspecified colors.
|
||||
#[must_use]
|
||||
pub fn build(self) -> ColorScheme {
|
||||
let secondary = self.secondary.unwrap_or_else(|| self.generate_secondary());
|
||||
let tertiary = self
|
||||
.tertiary
|
||||
.unwrap_or_else(|| self.generate_tertiary(secondary));
|
||||
ColorScheme {
|
||||
primary: self.primary,
|
||||
secondary,
|
||||
tertiary,
|
||||
error: self
|
||||
.error
|
||||
.unwrap_or_else(|| self.generate_error(secondary, tertiary)),
|
||||
neutral: self.neutral.unwrap_or_else(|| self.generate_neutral()),
|
||||
neutral_variant: self
|
||||
.neutral_variant
|
||||
.unwrap_or_else(|| self.generate_neutral_variant()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A color scheme for a Gooey application.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ColorScheme {
|
||||
/// The primary accent color.
|
||||
pub primary: ColorSource,
|
||||
/// A secondary accent color.
|
||||
pub secondary: ColorSource,
|
||||
/// A tertiary accent color.
|
||||
pub tertiary: ColorSource,
|
||||
/// A color used to denote errors.
|
||||
pub error: ColorSource,
|
||||
/// A neutral color.
|
||||
pub neutral: ColorSource,
|
||||
/// A neutral color with a different tone than `neutral`.
|
||||
pub neutral_variant: ColorSource,
|
||||
}
|
||||
|
||||
impl ColorScheme {
|
||||
/// Returns a generated color scheme based on a `primary` color.
|
||||
#[must_use]
|
||||
pub fn from_primary(primary: ColorSource) -> Self {
|
||||
ColorSchemeBuilder::new(primary).build()
|
||||
}
|
||||
|
||||
/// Returns a generated color scheme based on a `primary` hue, in degrees.
|
||||
#[must_use]
|
||||
pub fn from_primary_hue(hue: impl Into<OklabHue>) -> Self {
|
||||
ColorSchemeBuilder::from_hue(hue).build()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ColorScheme {
|
||||
fn default() -> Self {
|
||||
Self::from_primary_hue(138.5)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorSource> for ColorScheme {
|
||||
fn from(primary: ColorSource) -> Self {
|
||||
ColorScheme::from_primary(primary)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue