Theme example reacts

This commit is contained in:
Jonathan Johnson 2023-11-10 18:11:31 -08:00
parent e471cb0ea5
commit 81f6f8c4d3
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
6 changed files with 185 additions and 117 deletions

2
Cargo.lock generated
View file

@ -105,7 +105,7 @@ dependencies = [
[[package]] [[package]]
name = "appit" name = "appit"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/khonsulabs/appit#043bfe2c78524d6a06ed159289ea1cd7a62b0fec" source = "git+https://github.com/khonsulabs/appit#5ed0d923ded6520950d14b3b869cbcac89452f5c"
dependencies = [ dependencies = [
"raw-window-handle 0.5.2", "raw-window-handle 0.5.2",
"winit", "winit",

View file

@ -1,30 +1,97 @@
use gooey::styles::components::TextColor; use gooey::styles::components::TextColor;
use gooey::styles::{ColorTheme, FixedTheme, InverseTheme, SurfaceTheme, Theme, ThemePair}; use gooey::styles::{ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair};
use gooey::value::{Dynamic, MapEach};
use gooey::widget::MakeWidget; use gooey::widget::MakeWidget;
use gooey::widgets::label::LabelBackground; use gooey::widgets::label::LabelBackground;
use gooey::widgets::{Label, Stack}; use gooey::widgets::{Input, Label, Stack};
use gooey::Run; use gooey::Run;
use kludgine::Color; use kludgine::Color;
const PRIMARY_HUE: f32 = -120.;
const SECONDARY_HUE: f32 = 0.;
const TERTIARY_HUE: f32 = -30.;
const ERROR_HUE: f32 = 30.;
fn main() -> gooey::Result { fn main() -> gooey::Result {
let default_theme = ThemePair::default(); let (primary, primary_editor) = color_editor(PRIMARY_HUE, 0.8, "Primary");
Stack::columns( let (secondary, secondary_editor) = color_editor(SECONDARY_HUE, 0.3, "Secondary");
theme(default_theme.dark, "Dark") let (tertiary, tertiary_editor) = color_editor(TERTIARY_HUE, 0.3, "Tertiary");
.and(theme(default_theme.light, "Light")) let (error, error_editor) = color_editor(ERROR_HUE, 0.8, "Error");
.and(fixed_themes( let (neutral, neutral_editor) = color_editor(PRIMARY_HUE, 0.001, "Neutral");
default_theme.primary_fixed, let (neutral_variant, neutral_variant_editor) =
default_theme.secondary_fixed, color_editor(PRIMARY_HUE, 0.001, "Neutral Variant");
default_theme.tertiary_fixed,
)), let default_theme = (
&primary,
&secondary,
&tertiary,
&error,
&neutral,
&neutral_variant,
)
.map_each(
|(primary, secondary, tertiary, error, neutral, neutral_variant)| {
ThemePair::from_sources(
*primary,
*secondary,
*tertiary,
*error,
*neutral,
*neutral_variant,
)
},
);
Stack::rows(
Stack::columns(
primary_editor
.and(secondary_editor)
.and(tertiary_editor)
.and(error_editor)
.and(neutral_editor)
.and(neutral_variant_editor),
)
.and(Stack::columns(
theme(default_theme.map_each(|theme| theme.dark), "Dark")
.and(theme(default_theme.map_each(|theme| theme.light), "Light"))
.and(fixed_themes(
default_theme.map_each(|theme| theme.primary_fixed),
default_theme.map_each(|theme| theme.secondary_fixed),
default_theme.map_each(|theme| theme.tertiary_fixed),
)),
)),
) )
.expand() .expand()
.run() .run()
} }
fn color_editor(
initial_hue: f32,
initial_saturation: f32,
label: &str,
) -> (Dynamic<ColorSource>, impl MakeWidget) {
let hue_text = Dynamic::new(initial_hue.to_string());
let hue = hue_text.map_each(|hue| hue.parse::<f32>().unwrap_or_default());
let saturation_text = Dynamic::new(initial_saturation.to_string());
let saturation = saturation_text.map_each(|sat| sat.parse::<f32>().unwrap_or_default());
let color =
(&hue, &saturation).map_each(|(hue, saturation)| ColorSource::new(*hue, *saturation));
(
color,
Stack::rows(
Label::new(label)
.and(Input::new(hue_text))
.and(Input::new(saturation_text)),
)
.expand(),
)
}
fn fixed_themes( fn fixed_themes(
primary: FixedTheme, primary: Dynamic<FixedTheme>,
secondary: FixedTheme, secondary: Dynamic<FixedTheme>,
tertiary: FixedTheme, tertiary: Dynamic<FixedTheme>,
) -> impl MakeWidget { ) -> impl MakeWidget {
Stack::rows( Stack::rows(
Label::new("Fixed") Label::new("Fixed")
@ -35,85 +102,118 @@ fn fixed_themes(
.expand() .expand()
} }
fn fixed_theme(theme: FixedTheme, label: &str) -> impl MakeWidget { fn fixed_theme(theme: Dynamic<FixedTheme>, label: &str) -> impl MakeWidget {
let color = theme.map_each(|theme| theme.color);
let on_color = theme.map_each(|theme| theme.on_color);
Stack::columns( Stack::columns(
swatch(theme.color, &format!("{label} Fixed"), theme.on_color) swatch(color.clone(), &format!("{label} Fixed"), on_color.clone())
.and(swatch( .and(swatch(
theme.dim_color, theme.map_each(|theme| theme.dim_color),
&format!("Dim {label}"), &format!("Dim {label}"),
theme.on_color, on_color.clone(),
)) ))
.and(swatch( .and(swatch(
theme.on_color, on_color.clone(),
&format!("On {label} Fixed"), &format!("On {label} Fixed"),
theme.color, color.clone(),
)) ))
.and(swatch( .and(swatch(
theme.on_color_variant, theme.map_each(|theme| theme.on_color_variant),
&format!("Variant On {label} Fixed"), &format!("Variant On {label} Fixed"),
theme.color, color,
)), )),
) )
.expand() .expand()
} }
fn theme(theme: Theme, label: &str) -> impl MakeWidget { fn theme(theme: Dynamic<Theme>, label: &str) -> impl MakeWidget {
Stack::rows( Stack::rows(
Label::new(label) Label::new(label)
.and( .and(
Stack::columns( Stack::columns(
color_theme(theme.primary, "Primary") color_theme(theme.map_each(|theme| theme.primary), "Primary")
.and(color_theme(theme.secondary, "Secondary")) .and(color_theme(
.and(color_theme(theme.tertiary, "Tertiary")) theme.map_each(|theme| theme.secondary),
.and(color_theme(theme.error, "Error")), "Secondary",
))
.and(color_theme(
theme.map_each(|theme| theme.tertiary),
"Tertiary",
))
.and(color_theme(theme.map_each(|theme| theme.error), "Error")),
) )
.expand(), .expand(),
) )
.and(surface_and_inverse_themes(theme.surface, theme.inverse)), .and(surface_theme(theme.map_each(|theme| theme.surface))),
) )
.expand() .expand()
} }
fn surface_and_inverse_themes(theme: SurfaceTheme, inverse: InverseTheme) -> impl MakeWidget { fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
let color = theme.map_each(|theme| theme.color);
let on_color = theme.map_each(|theme| theme.on_color);
Stack::rows( Stack::rows(
Stack::columns( Stack::columns(
swatch(theme.color, "Surface", theme.on_color) swatch(color.clone(), "Surface", on_color.clone())
.and(swatch(theme.dim_color, "Dim Surface", theme.on_color)) .and(swatch(
.and(swatch(theme.bright_color, "Bright Surface", theme.on_color)), theme.map_each(|theme| theme.dim_color),
"Dim Surface",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.bright_color),
"Bright Surface",
on_color.clone(),
)),
) )
.expand() .expand()
.and(inverse_theme(inverse))
.and( .and(
Stack::columns( Stack::columns(
swatch(theme.lowest_container, "Lowest Container", theme.on_color) swatch(
.and(swatch(theme.low_container, "Low Container", theme.on_color)) theme.map_each(|theme| theme.lowest_container),
.and(swatch(theme.container, "Container", theme.on_color)) "Lowest Container",
.and(swatch( on_color.clone(),
theme.high_container, )
"High Container", .and(swatch(
theme.on_color, theme.map_each(|theme| theme.low_container),
)) "Low Container",
.and(swatch( on_color.clone(),
theme.highest_container, ))
"Highest Container", .and(swatch(
theme.on_color, theme.map_each(|theme| theme.container),
)), "Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.high_container),
"High Container",
on_color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.highest_container),
"Highest Container",
on_color.clone(),
)),
) )
.expand(), .expand(),
) )
.and( .and(
Stack::columns( Stack::columns(
swatch(theme.on_color, "On Surface", theme.color) swatch(on_color.clone(), "On Surface", color.clone())
.and(swatch( .and(swatch(
theme.on_color_variant, theme.map_each(|theme| theme.on_color_variant),
"On Color Variant", "On Color Variant",
theme.color, color.clone(),
)) ))
.and(swatch(theme.outline, "Outline", theme.color))
.and(swatch( .and(swatch(
theme.outline_variant, theme.map_each(|theme| theme.outline),
"Outline",
color.clone(),
))
.and(swatch(
theme.map_each(|theme| theme.outline_variant),
"Outline Variant", "Outline Variant",
theme.color, color,
)), )),
) )
.expand(), .expand(),
@ -122,38 +222,33 @@ fn surface_and_inverse_themes(theme: SurfaceTheme, inverse: InverseTheme) -> imp
.expand() .expand()
} }
fn inverse_theme(theme: InverseTheme) -> impl MakeWidget { fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget {
Stack::columns( let color = theme.map_each(|theme| theme.color);
swatch(theme.surface, "Inverse Surface", theme.on_surface) let on_color = theme.map_each(|theme| theme.on_color);
.and(swatch( let container = theme.map_each(|theme| theme.container);
theme.on_surface, let on_container = theme.map_each(|theme| theme.on_container);
"On Inverse Surface",
theme.surface,
))
.and(swatch(theme.primary, "Inverse Primary", theme.surface)),
)
.expand()
}
fn color_theme(theme: ColorTheme, label: &str) -> impl MakeWidget {
Stack::rows( Stack::rows(
swatch(theme.color, label, theme.on_color) swatch(color.clone(), label, on_color.clone())
.and(swatch(theme.on_color, &format!("On {label}"), theme.color))
.and(swatch( .and(swatch(
theme.container, on_color.clone(),
&format!("{label} Container"), &format!("On {label}"),
theme.on_container, color.clone(),
)) ))
.and(swatch( .and(swatch(
theme.on_container, container.clone(),
&format!("{label} Container"),
on_container.clone(),
))
.and(swatch(
on_container,
&format!("On {label} Container"), &format!("On {label} Container"),
theme.container, container,
)), )),
) )
.expand() .expand()
} }
fn swatch(background: Color, label: &str, text: Color) -> impl MakeWidget { fn swatch(background: Dynamic<Color>, label: &str, text: Dynamic<Color>) -> impl MakeWidget {
Label::new(label) Label::new(label)
.fit_horizontally() .fit_horizontally()
.fit_vertically() .fit_vertically()

View file

@ -349,6 +349,9 @@ impl<'context, 'window> EventContext<'context, 'window> {
/// ///
/// This widget does not need to be focused. /// This widget does not need to be focused.
pub fn advance_focus(&mut self, direction: VisualOrder) { pub fn advance_focus(&mut self, direction: VisualOrder) {
// TODO check to see if the current node has an explicit next_focus (or
// if we're going in the opposite direction, previous_focus).
self.pending_state.focus = self.next_focus_after(self.current_node.clone(), direction); self.pending_state.focus = self.next_focus_after(self.current_node.clone(), direction);
} }
} }
@ -930,6 +933,15 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
window::Theme::Dark => &self.theme.dark, window::Theme::Dark => &self.theme.dark,
} }
} }
/// Returns the opposite theme of [`Self::theme()`].
#[must_use]
pub fn inverse_theme(&self) -> &Theme {
match self.window.theme() {
window::Theme::Light => &self.theme.dark,
window::Theme::Dark => &self.theme.light,
}
}
} }
pub(crate) struct WindowHandle { pub(crate) struct WindowHandle {

View file

@ -920,9 +920,6 @@ pub struct Theme {
/// The theme to color surfaces. /// The theme to color surfaces.
pub surface: SurfaceTheme, pub surface: SurfaceTheme,
/// A theme of inverse colors to provide high contrast to other elements.
pub inverse: InverseTheme,
} }
impl Theme { impl Theme {
@ -942,7 +939,6 @@ impl Theme {
tertiary: ColorTheme::light_from_source(tertiary), tertiary: ColorTheme::light_from_source(tertiary),
error: ColorTheme::light_from_source(error), error: ColorTheme::light_from_source(error),
surface: SurfaceTheme::light_from_sources(neutral, neutral_variant), surface: SurfaceTheme::light_from_sources(neutral, neutral_variant),
inverse: InverseTheme::light_from_sources(primary, neutral),
} }
} }
@ -962,7 +958,6 @@ impl Theme {
tertiary: ColorTheme::dark_from_source(tertiary), tertiary: ColorTheme::dark_from_source(tertiary),
error: ColorTheme::dark_from_source(error), error: ColorTheme::dark_from_source(error),
surface: SurfaceTheme::dark_from_sources(neutral, neutral_variant), surface: SurfaceTheme::dark_from_sources(neutral, neutral_variant),
inverse: InverseTheme::dark_from_sources(primary, neutral),
} }
} }
} }
@ -1105,40 +1100,6 @@ impl FixedTheme {
} }
} }
/// An inverse color theme for displaying highly contrasted elements.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InverseTheme {
/// An inverse surface color.
pub surface: Color,
/// The default color for content atop an inverted surface.
pub on_surface: Color,
/// The inverted primary color.
pub primary: Color,
// TODO why not inverse for the other colorthemes?
}
impl InverseTheme {
/// Returns the light-mode, inverse theme for given sources.
#[must_use]
pub fn light_from_sources(primary: ColorSource, surface: ColorSource) -> Self {
Self {
surface: surface.color(30),
on_surface: surface.color(90),
primary: primary.color(80),
}
}
/// Returns the dark-mode, inverse theme for given sources.
#[must_use]
pub fn dark_from_sources(primary: ColorSource, surface: ColorSource) -> Self {
Self {
surface: surface.color(90),
on_surface: surface.color(10),
primary: primary.color(40),
}
}
}
/// A source for [`Color`]s. /// A source for [`Color`]s.
/// ///
/// This type is a combination of an [`OklabHue`] and a saturation ranging from /// This type is a combination of an [`OklabHue`] and a saturation ranging from

View file

@ -16,7 +16,7 @@ use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::components::VisualOrder; use crate::styles::components::VisualOrder;
use crate::styles::{Component, NamedComponent, Styles}; use crate::styles::{IntoComponentValue, NamedComponent, Styles};
use crate::tree::Tree; use crate::tree::Tree;
use crate::value::{IntoValue, Value}; use crate::value::{IntoValue, Value};
use crate::widgets::{Align, Expand, Scroll, Style}; use crate::widgets::{Align, Expand, Scroll, Style};
@ -456,7 +456,7 @@ pub trait MakeWidget: Sized {
} }
/// Associates a style component with `self`. /// Associates a style component with `self`.
fn with(self, name: &impl NamedComponent, component: impl Into<Component>) -> Style { fn with(self, name: &impl NamedComponent, component: impl IntoComponentValue) -> Style {
let mut styles = Styles::new(); let mut styles = Styles::new();
styles.insert(name, component); styles.insert(name, component);
Style::new(styles, self) Style::new(styles, self)

View file

@ -89,7 +89,7 @@ impl Stack {
if let Some(expand) = guard.downcast_ref::<Expand>() { if let Some(expand) = guard.downcast_ref::<Expand>() {
let weight = expand.weight; let weight = expand.weight;
( (
WidgetRef::Unmounted(widget.clone()), expand.child().clone(),
StackDimension::Fractional { weight }, StackDimension::Fractional { weight },
) )
} else if let Some((child, size)) = } else if let Some((child, size)) =