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.
This commit is contained in:
Jonathan Johnson 2023-11-12 07:55:28 -08:00
parent b3bef345a5
commit 849710dbb1
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
5 changed files with 57 additions and 17 deletions

View file

@ -187,13 +187,13 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
Stack::columns( Stack::columns(
swatch(color.clone(), "Surface", on_color.clone()) swatch(color.clone(), "Surface", on_color.clone())
.and(swatch( .and(swatch(
theme.map_each(|theme| theme.dim_color), theme.map_each(|theme| theme.bright_color),
"Dim Surface", "Bright Surface",
on_color.clone(), on_color.clone(),
)) ))
.and(swatch( .and(swatch(
theme.map_each(|theme| theme.bright_color), theme.map_each(|theme| theme.dim_color),
"Bright Surface", "Dim Surface",
on_color.clone(), on_color.clone(),
)), )),
) )
@ -245,6 +245,11 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
theme.map_each(|theme| theme.outline_variant), theme.map_each(|theme| theme.outline_variant),
"Outline Variant", "Outline Variant",
color, color,
))
.and(swatch(
theme.map_each(|theme| theme.opaque_widget),
"Opaque Widget",
on_color,
)), )),
) )
.expand(), .expand(),
@ -255,11 +260,17 @@ fn surface_theme(theme: Dynamic<SurfaceTheme>) -> impl MakeWidget {
fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget { fn color_theme(theme: Dynamic<ColorTheme>, label: &str) -> impl MakeWidget {
let color = theme.map_each(|theme| theme.color); 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 on_color = theme.map_each(|theme| theme.on_color);
let container = theme.map_each(|theme| theme.container); let container = theme.map_each(|theme| theme.container);
let on_container = theme.map_each(|theme| theme.on_container); let on_container = theme.map_each(|theme| theme.on_container);
Stack::rows( Stack::rows(
swatch(color.clone(), label, on_color.clone()) swatch(color.clone(), label, on_color.clone())
.and(swatch(
dim_color.clone(),
&format!("{label} Dim"),
on_color.clone(),
))
.and(swatch( .and(swatch(
on_color.clone(), on_color.clone(),
&format!("On {label}"), &format!("On {label}"),

View file

@ -990,6 +990,9 @@ pub struct SurfaceTheme {
/// The background color for highest-level container widgets. /// The background color for highest-level container widgets.
pub highest_container: Color, pub highest_container: Color,
/// The default background color for widgets that are opaque.
pub opaque_widget: Color,
/// The default text/content color. /// The default text/content color.
pub on_color: Color, pub on_color: Color,
/// A variation of the text/content color that is de-emphasized. /// A variation of the text/content color that is de-emphasized.
@ -1006,13 +1009,14 @@ impl SurfaceTheme {
#[must_use] #[must_use]
pub fn light_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self { pub fn light_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self {
Self { Self {
color: neutral.color(98), color: neutral.color(97),
dim_color: neutral_variant.color(70), dim_color: neutral.color(70),
bright_color: neutral.color(100), bright_color: neutral.color(99),
lowest_container: neutral.color(100), opaque_widget: neutral_variant.color(75),
low_container: neutral.color(96), lowest_container: neutral.color(95),
container: neutral.color(95), low_container: neutral.color(92),
high_container: neutral.color(90), container: neutral.color(90),
high_container: neutral.color(85),
highest_container: neutral.color(80), highest_container: neutral.color(80),
on_color: neutral.color(10), on_color: neutral.color(10),
on_color_variant: neutral_variant.color(30), on_color_variant: neutral_variant.color(30),
@ -1027,8 +1031,9 @@ impl SurfaceTheme {
pub fn dark_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self { pub fn dark_from_sources(neutral: ColorSource, neutral_variant: ColorSource) -> Self {
Self { Self {
color: neutral.color(10), color: neutral.color(10),
dim_color: neutral_variant.color(2), dim_color: neutral.color(2),
bright_color: neutral.color(11), bright_color: neutral.color(11),
opaque_widget: neutral_variant.color(40),
lowest_container: neutral.color(15), lowest_container: neutral.color(15),
low_container: neutral.color(20), low_container: neutral.color(20),
container: neutral.color(25), container: neutral.color(25),
@ -1047,6 +1052,8 @@ impl SurfaceTheme {
pub struct ColorTheme { pub struct ColorTheme {
/// The primary color, used for high-emphasis content. /// The primary color, used for high-emphasis content.
pub color: Color, 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. /// The color for content that sits atop the primary color.
pub on_color: Color, pub on_color: Color,
/// The backgrond color for containers. /// The backgrond color for containers.
@ -1061,6 +1068,7 @@ impl ColorTheme {
pub fn light_from_source(source: ColorSource) -> Self { pub fn light_from_source(source: ColorSource) -> Self {
Self { Self {
color: source.color(40), color: source.color(40),
color_dim: source.color(30),
on_color: source.color(100), on_color: source.color(100),
container: source.color(90), container: source.color(90),
on_container: source.color(10), on_container: source.color(10),
@ -1071,10 +1079,11 @@ impl ColorTheme {
#[must_use] #[must_use]
pub fn dark_from_source(source: ColorSource) -> Self { pub fn dark_from_source(source: ColorSource) -> Self {
Self { Self {
color: source.color(80), color: source.color(70),
color_dim: source.color(60),
on_color: source.color(10), on_color: source.color(10),
container: source.color(30), container: source.color(30),
on_container: source.color(80), on_container: source.color(90),
} }
} }
} }

View file

@ -476,3 +476,22 @@ impl ComponentDefinition for DisabledOutlineColor {
context.theme().surface.outline_variant 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::<Global>("opaque_color"))
}
}
impl ComponentDefinition for OpaqueWidgetColor {
type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Color {
context.theme().surface.opaque_widget
}
}

View file

@ -12,7 +12,7 @@ use crate::animation::{AnimationHandle, AnimationTarget, Spawn};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::names::Name; use crate::names::Name;
use crate::styles::components::{ use crate::styles::components::{
AutoFocusableControls, Easing, IntrinsicPadding, SurfaceColor, TextColor, AutoFocusableControls, Easing, IntrinsicPadding, OpaqueWidgetColor, SurfaceColor, TextColor,
}; };
use crate::styles::{ColorExt, ComponentGroup, Styles}; use crate::styles::{ColorExt, ComponentGroup, Styles};
use crate::utils::ModifiersExt; use crate::utils::ModifiersExt;
@ -323,7 +323,7 @@ impl ComponentGroup for Button {
define_components! { define_components! {
Button { Button {
/// The background color of the 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). /// The background color of the button when it is active (depressed).
ButtonActiveBackground(Color, "active_background_color", .surface.color) ButtonActiveBackground(Color, "active_background_color", .surface.color)
/// The background color of the button when the mouse cursor is hovering over /// The background color of the button when the mouse cursor is hovering over

View file

@ -13,6 +13,7 @@ use kludgine::{Color, Origin};
use crate::animation::{LinearInterpolate, PercentBetween}; use crate::animation::{LinearInterpolate, PercentBetween};
use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext}; use crate::context::{EventContext, GraphicsContext, LayoutContext, WidgetContext};
use crate::styles::components::OpaqueWidgetColor;
use crate::styles::{ComponentDefinition, ComponentName, Dimension, Group, NamedComponent}; use crate::styles::{ComponentDefinition, ComponentName, Dimension, Group, NamedComponent};
use crate::value::{Dynamic, IntoDynamic, IntoValue, Value}; use crate::value::{Dynamic, IntoDynamic, IntoValue, Value};
use crate::widget::{EventHandling, Widget, HANDLED}; use crate::widget::{EventHandling, Widget, HANDLED};
@ -406,7 +407,7 @@ impl ComponentDefinition for InactiveTrackColor {
type ComponentType = Color; type ComponentType = Color;
fn default_value(&self, context: &WidgetContext<'_, '_>) -> Self::ComponentType { 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)
} }
} }