From 849710dbb11e101fdb21c9e78a608988426ef911 Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Sun, 12 Nov 2023 07:55:28 -0800 Subject: [PATCH] Diverging from material Introducing two new colors: - ColorTheme::color_dim, for dimmed/disabled primary colors - SurfaceTheme::opaque_widget, for buttons. In material design, a button's background color uses the Highest Container role, which seems incorrect because then buttons wouldn't have a different color when placed inside of the highest level container. Rather than remove a container level, I added one more tone using the neutral variant. Other changes are just gut feelings to have a slightly richer dark theme. I feel like material is a little muddy in dark mode. --- examples/theme.rs | 19 +++++++++++++++---- src/styles.rs | 29 +++++++++++++++++++---------- src/styles/components.rs | 19 +++++++++++++++++++ src/widgets/button.rs | 4 ++-- src/widgets/slider.rs | 3 ++- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/examples/theme.rs b/examples/theme.rs index 39f28dfe4..bdbc92db1 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -187,13 +187,13 @@ fn surface_theme(theme: Dynamic) -> impl MakeWidget { Stack::columns( swatch(color.clone(), "Surface", on_color.clone()) .and(swatch( - theme.map_each(|theme| theme.dim_color), - "Dim Surface", + theme.map_each(|theme| theme.bright_color), + "Bright Surface", on_color.clone(), )) .and(swatch( - theme.map_each(|theme| theme.bright_color), - "Bright Surface", + theme.map_each(|theme| theme.dim_color), + "Dim Surface", on_color.clone(), )), ) @@ -245,6 +245,11 @@ fn surface_theme(theme: Dynamic) -> impl MakeWidget { theme.map_each(|theme| theme.outline_variant), "Outline Variant", color, + )) + .and(swatch( + theme.map_each(|theme| theme.opaque_widget), + "Opaque Widget", + on_color, )), ) .expand(), @@ -255,11 +260,17 @@ fn surface_theme(theme: Dynamic) -> impl MakeWidget { fn color_theme(theme: Dynamic, label: &str) -> impl MakeWidget { let color = theme.map_each(|theme| theme.color); + let dim_color = theme.map_each(|theme| theme.color_dim); 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( + dim_color.clone(), + &format!("{label} Dim"), + on_color.clone(), + )) .and(swatch( on_color.clone(), &format!("On {label}"), diff --git a/src/styles.rs b/src/styles.rs index 931ccc71f..05c71e361 100644 --- a/src/styles.rs +++ b/src/styles.rs @@ -990,6 +990,9 @@ pub struct SurfaceTheme { /// The background color for highest-level container widgets. pub highest_container: Color, + /// The default background color for widgets that are opaque. + pub opaque_widget: Color, + /// The default text/content color. pub on_color: Color, /// A variation of the text/content color that is de-emphasized. @@ -1006,13 +1009,14 @@ impl SurfaceTheme { #[must_use] pub fn light_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self { Self { - color: neutral.color(98), - dim_color: neutral_variant.color(70), - bright_color: neutral.color(100), - lowest_container: neutral.color(100), - low_container: neutral.color(96), - container: neutral.color(95), - high_container: neutral.color(90), + color: neutral.color(97), + dim_color: neutral.color(70), + bright_color: neutral.color(99), + opaque_widget: neutral_variant.color(75), + lowest_container: neutral.color(95), + low_container: neutral.color(92), + container: neutral.color(90), + high_container: neutral.color(85), highest_container: neutral.color(80), on_color: neutral.color(10), on_color_variant: neutral_variant.color(30), @@ -1027,8 +1031,9 @@ impl SurfaceTheme { pub fn dark_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self { Self { color: neutral.color(10), - dim_color: neutral_variant.color(2), + dim_color: neutral.color(2), bright_color: neutral.color(11), + opaque_widget: neutral_variant.color(40), lowest_container: neutral.color(15), low_container: neutral.color(20), container: neutral.color(25), @@ -1047,6 +1052,8 @@ impl SurfaceTheme { pub struct ColorTheme { /// The primary color, used for high-emphasis content. pub color: Color, + /// The primary color, dimmed for de-emphasized or disabled content. + pub color_dim: Color, /// The color for content that sits atop the primary color. pub on_color: Color, /// The backgrond color for containers. @@ -1061,6 +1068,7 @@ impl ColorTheme { pub fn light_from_source(source: ColorSource) -> Self { Self { color: source.color(40), + color_dim: source.color(30), on_color: source.color(100), container: source.color(90), on_container: source.color(10), @@ -1071,10 +1079,11 @@ impl ColorTheme { #[must_use] pub fn dark_from_source(source: ColorSource) -> Self { Self { - color: source.color(80), + color: source.color(70), + color_dim: source.color(60), on_color: source.color(10), container: source.color(30), - on_container: source.color(80), + on_container: source.color(90), } } } diff --git a/src/styles/components.rs b/src/styles/components.rs index c30f49876..8decd3099 100644 --- a/src/styles/components.rs +++ b/src/styles/components.rs @@ -476,3 +476,22 @@ impl ComponentDefinition for DisabledOutlineColor { context.theme().surface.outline_variant } } + +/// A [`Color`] to be used as a background color for widgets that render an +/// opaque background. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub struct OpaqueWidgetColor; + +impl NamedComponent for OpaqueWidgetColor { + fn name(&self) -> Cow<'_, ComponentName> { + Cow::Owned(ComponentName::named::("opaque_color")) + } +} + +impl ComponentDefinition for OpaqueWidgetColor { + type ComponentType = Color; + + fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color { + context.theme().surface.opaque_widget + } +} diff --git a/src/widgets/button.rs b/src/widgets/button.rs index 395337205..a47bd4ff7 100644 --- a/src/widgets/button.rs +++ b/src/widgets/button.rs @@ -12,7 +12,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, Spawn}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::names::Name; use crate::styles::components::{ - AutoFocusableControls, Easing, IntrinsicPadding, SurfaceColor, TextColor, + AutoFocusableControls, Easing, IntrinsicPadding, OpaqueWidgetColor, SurfaceColor, TextColor, }; use crate::styles::{ColorExt, ComponentGroup, Styles}; use crate::utils::ModifiersExt; @@ -323,7 +323,7 @@ impl ComponentGroup for Button { define_components! { Button { /// The background color of the button. - ButtonBackground(Color, "background_color", .surface.highest_container) // TODO highest_container seems wrong, but it's what material uses. Perhaps we should add another color so that buttons don't blend with the highest container level. + ButtonBackground(Color, "background_color", |context| context.query_style(&OpaqueWidgetColor)) /// The background color of the button when it is active (depressed). ButtonActiveBackground(Color, "active_background_color", .surface.color) /// The background color of the button when the mouse cursor is hovering over diff --git a/src/widgets/slider.rs b/src/widgets/slider.rs index 0b2c46a4d..9472bdb36 100644 --- a/src/widgets/slider.rs +++ b/src/widgets/slider.rs @@ -13,6 +13,7 @@ use kludgine::{Color, Origin}; use crate::animation::{LinearInterpolate, PercentBetween}; use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext}; +use crate::styles::components::OpaqueWidgetColor; use crate::styles::{ComponentDefinition, ComponentName, Dimension, Group, NamedComponent}; use crate::value::{Dynamic, IntoDynamic, IntoValue, Value}; use crate::widget::{EventHandling, Widget, HANDLED}; @@ -406,7 +407,7 @@ impl ComponentDefinition for InactiveTrackColor { type ComponentType = Color; fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType { - context.theme().surface.highest_container // TODO this is the same as ButtonBackground. This should be abstracted into its own component both can depend on. + context.query_style(&OpaqueWidgetColor) } }