From 81f6f8c4d3b439dccd8a54810d20ebe79bf3473c Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Fri, 10 Nov 2023 18:11:31 -0800 Subject: [PATCH] Theme example reacts --- Cargo.lock | 2 +- examples/theme.rs | 239 ++++++++++++++++++++++++++++++------------- src/context.rs | 12 +++ src/styles.rs | 39 ------- src/widget.rs | 4 +- src/widgets/stack.rs | 2 +- 6 files changed, 183 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0c43f536a..1b73ab4cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,7 +105,7 @@ dependencies = [ [[package]] name = "appit" version = "0.1.0" -source = "git+https://github.com/khonsulabs/appit#043bfe2c78524d6a06ed159289ea1cd7a62b0fec" +source = "git+https://github.com/khonsulabs/appit#5ed0d923ded6520950d14b3b869cbcac89452f5c" dependencies = [ "raw-window-handle 0.5.2", "winit", diff --git a/examples/theme.rs b/examples/theme.rs index 29bcbb176..7b526189f 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -1,30 +1,97 @@ 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::widgets::label::LabelBackground; -use gooey::widgets::{Label, Stack}; +use gooey::widgets::{Input, Label, Stack}; use gooey::Run; 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 { - let default_theme = ThemePair::default(); - Stack::columns( - theme(default_theme.dark, "Dark") - .and(theme(default_theme.light, "Light")) - .and(fixed_themes( - default_theme.primary_fixed, - default_theme.secondary_fixed, - default_theme.tertiary_fixed, - )), + 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 (neutral_variant, neutral_variant_editor) = + color_editor(PRIMARY_HUE, 0.001, "Neutral Variant"); + + 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() .run() } +fn color_editor( + initial_hue: f32, + initial_saturation: f32, + label: &str, +) -> (Dynamic, impl MakeWidget) { + let hue_text = Dynamic::new(initial_hue.to_string()); + let hue = hue_text.map_each(|hue| hue.parse::().unwrap_or_default()); + let saturation_text = Dynamic::new(initial_saturation.to_string()); + let saturation = saturation_text.map_each(|sat| sat.parse::().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( - primary: FixedTheme, - secondary: FixedTheme, - tertiary: FixedTheme, + primary: Dynamic, + secondary: Dynamic, + tertiary: Dynamic, ) -> impl MakeWidget { Stack::rows( Label::new("Fixed") @@ -35,85 +102,118 @@ fn fixed_themes( .expand() } -fn fixed_theme(theme: FixedTheme, label: &str) -> impl MakeWidget { +fn fixed_theme(theme: Dynamic, label: &str) -> impl MakeWidget { + let color = theme.map_each(|theme| theme.color); + let on_color = theme.map_each(|theme| theme.on_color); Stack::columns( - swatch(theme.color, &format!("{label} Fixed"), theme.on_color) + swatch(color.clone(), &format!("{label} Fixed"), on_color.clone()) .and(swatch( - theme.dim_color, + theme.map_each(|theme| theme.dim_color), &format!("Dim {label}"), - theme.on_color, + on_color.clone(), )) .and(swatch( - theme.on_color, + on_color.clone(), &format!("On {label} Fixed"), - theme.color, + color.clone(), )) .and(swatch( - theme.on_color_variant, + theme.map_each(|theme| theme.on_color_variant), &format!("Variant On {label} Fixed"), - theme.color, + color, )), ) .expand() } -fn theme(theme: Theme, label: &str) -> impl MakeWidget { +fn theme(theme: Dynamic, label: &str) -> impl MakeWidget { Stack::rows( Label::new(label) .and( Stack::columns( - color_theme(theme.primary, "Primary") - .and(color_theme(theme.secondary, "Secondary")) - .and(color_theme(theme.tertiary, "Tertiary")) - .and(color_theme(theme.error, "Error")), + color_theme(theme.map_each(|theme| theme.primary), "Primary") + .and(color_theme( + theme.map_each(|theme| theme.secondary), + "Secondary", + )) + .and(color_theme( + theme.map_each(|theme| theme.tertiary), + "Tertiary", + )) + .and(color_theme(theme.map_each(|theme| theme.error), "Error")), ) .expand(), ) - .and(surface_and_inverse_themes(theme.surface, theme.inverse)), + .and(surface_theme(theme.map_each(|theme| theme.surface))), ) .expand() } -fn surface_and_inverse_themes(theme: SurfaceTheme, inverse: InverseTheme) -> impl MakeWidget { +fn surface_theme(theme: Dynamic) -> impl MakeWidget { + let color = theme.map_each(|theme| theme.color); + let on_color = theme.map_each(|theme| theme.on_color); Stack::rows( Stack::columns( - swatch(theme.color, "Surface", theme.on_color) - .and(swatch(theme.dim_color, "Dim Surface", theme.on_color)) - .and(swatch(theme.bright_color, "Bright Surface", theme.on_color)), + swatch(color.clone(), "Surface", on_color.clone()) + .and(swatch( + 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() - .and(inverse_theme(inverse)) .and( Stack::columns( - swatch(theme.lowest_container, "Lowest Container", theme.on_color) - .and(swatch(theme.low_container, "Low Container", theme.on_color)) - .and(swatch(theme.container, "Container", theme.on_color)) - .and(swatch( - theme.high_container, - "High Container", - theme.on_color, - )) - .and(swatch( - theme.highest_container, - "Highest Container", - theme.on_color, - )), + swatch( + theme.map_each(|theme| theme.lowest_container), + "Lowest Container", + on_color.clone(), + ) + .and(swatch( + theme.map_each(|theme| theme.low_container), + "Low Container", + on_color.clone(), + )) + .and(swatch( + 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(), ) .and( Stack::columns( - swatch(theme.on_color, "On Surface", theme.color) + swatch(on_color.clone(), "On Surface", color.clone()) .and(swatch( - theme.on_color_variant, + theme.map_each(|theme| theme.on_color_variant), "On Color Variant", - theme.color, + color.clone(), + )) + .and(swatch( + theme.map_each(|theme| theme.outline), + "Outline", + color.clone(), )) - .and(swatch(theme.outline, "Outline", theme.color)) .and(swatch( - theme.outline_variant, + theme.map_each(|theme| theme.outline_variant), "Outline Variant", - theme.color, + color, )), ) .expand(), @@ -122,38 +222,33 @@ fn surface_and_inverse_themes(theme: SurfaceTheme, inverse: InverseTheme) -> imp .expand() } -fn inverse_theme(theme: InverseTheme) -> impl MakeWidget { - Stack::columns( - swatch(theme.surface, "Inverse Surface", theme.on_surface) +fn color_theme(theme: Dynamic, label: &str) -> impl MakeWidget { + let color = theme.map_each(|theme| theme.color); + let on_color = theme.map_each(|theme| theme.on_color); + let container = theme.map_each(|theme| theme.container); + let on_container = theme.map_each(|theme| theme.on_container); + Stack::rows( + swatch(color.clone(), label, on_color.clone()) .and(swatch( - theme.on_surface, - "On Inverse Surface", - theme.surface, + on_color.clone(), + &format!("On {label}"), + color.clone(), )) - .and(swatch(theme.primary, "Inverse Primary", theme.surface)), - ) - .expand() -} - -fn color_theme(theme: ColorTheme, label: &str) -> impl MakeWidget { - Stack::rows( - swatch(theme.color, label, theme.on_color) - .and(swatch(theme.on_color, &format!("On {label}"), theme.color)) .and(swatch( - theme.container, + container.clone(), &format!("{label} Container"), - theme.on_container, + on_container.clone(), )) .and(swatch( - theme.on_container, + on_container, &format!("On {label} Container"), - theme.container, + container, )), ) .expand() } -fn swatch(background: Color, label: &str, text: Color) -> impl MakeWidget { +fn swatch(background: Dynamic, label: &str, text: Dynamic) -> impl MakeWidget { Label::new(label) .fit_horizontally() .fit_vertically() diff --git a/src/context.rs b/src/context.rs index 594eb0b8f..fd2add8a8 100644 --- a/src/context.rs +++ b/src/context.rs @@ -349,6 +349,9 @@ impl<'context, 'window> EventContext<'context, 'window> { /// /// This widget does not need to be focused. 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); } } @@ -930,6 +933,15 @@ impl<'context, 'window> WidgetContext<'context, 'window> { 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 { diff --git a/src/styles.rs b/src/styles.rs index 11518663a..7c1c20962 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -920,9 +920,6 @@ pub struct Theme { /// The theme to color surfaces. pub surface: SurfaceTheme, - - /// A theme of inverse colors to provide high contrast to other elements. - pub inverse: InverseTheme, } impl Theme { @@ -942,7 +939,6 @@ impl Theme { tertiary: ColorTheme::light_from_source(tertiary), error: ColorTheme::light_from_source(error), 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), error: ColorTheme::dark_from_source(error), 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. /// /// This type is a combination of an [`OklabHue`] and a saturation ranging from diff --git a/src/widget.rs b/src/widget.rs index 9cf564c8a..0ec309c7d 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -16,7 +16,7 @@ use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext}; use crate::styles::components::VisualOrder; -use crate::styles::{Component, NamedComponent, Styles}; +use crate::styles::{IntoComponentValue, NamedComponent, Styles}; use crate::tree::Tree; use crate::value::{IntoValue, Value}; use crate::widgets::{Align, Expand, Scroll, Style}; @@ -456,7 +456,7 @@ pub trait MakeWidget: Sized { } /// Associates a style component with `self`. - fn with(self, name: &impl NamedComponent, component: impl Into) -> Style { + fn with(self, name: &impl NamedComponent, component: impl IntoComponentValue) -> Style { let mut styles = Styles::new(); styles.insert(name, component); Style::new(styles, self) diff --git a/src/widgets/stack.rs b/src/widgets/stack.rs index 7f38052a2..d47a899c8 100644 --- a/src/widgets/stack.rs +++ b/src/widgets/stack.rs @@ -89,7 +89,7 @@ impl Stack { if let Some(expand) = guard.downcast_ref::() { let weight = expand.weight; ( - WidgetRef::Unmounted(widget.clone()), + expand.child().clone(), StackDimension::Fractional { weight }, ) } else if let Some((child, size)) =