ThemeMode

This commit is contained in:
Jonathan Johnson 2023-11-11 13:41:34 -08:00
parent 68339dfb62
commit 27d5baef5d
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
11 changed files with 354 additions and 68 deletions

2
Cargo.lock generated
View file

@ -481,7 +481,7 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1"
[[package]]
name = "figures"
version = "0.1.0"
source = "git+https://github.com/khonsulabs/figures#3e12470aa10d36d0b38f0441a0e89ae03ccd448b"
source = "git+https://github.com/khonsulabs/figures#7b41393c44d4def606790e340c98450b603010b4"
dependencies = [
"bytemuck",
"euclid",

View file

@ -3,7 +3,8 @@ use gooey::styles::components::{TextColor, WidgetBackground};
use gooey::styles::{ColorSource, ColorTheme, FixedTheme, SurfaceTheme, Theme, ThemePair};
use gooey::value::{Dynamic, MapEach};
use gooey::widget::MakeWidget;
use gooey::widgets::{Label, Scroll, Slider, Stack};
use gooey::widgets::{Label, Scroll, Slider, Stack, Themed};
use gooey::window::ThemeMode;
use gooey::Run;
use kludgine::Color;
@ -20,6 +21,7 @@ fn main() -> gooey::Result {
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 (theme_mode, theme_switcher) = dark_mode_slider();
let default_theme = (
&primary,
@ -42,27 +44,49 @@ fn main() -> gooey::Result {
},
);
Stack::columns(
Scroll::vertical(Stack::rows(
primary_editor
.and(secondary_editor)
.and(tertiary_editor)
.and(error_editor)
.and(neutral_editor)
.and(neutral_variant_editor),
))
.and(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),
)),
Themed::new(
default_theme.clone(),
Stack::columns(
Scroll::vertical(Stack::rows(
theme_switcher
.and(primary_editor)
.and(secondary_editor)
.and(tertiary_editor)
.and(error_editor)
.and(neutral_editor)
.and(neutral_variant_editor),
))
.and(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()
.into_window()
.with_theme_mode(theme_mode)
.run()
}
fn dark_mode_slider() -> (Dynamic<ThemeMode>, impl MakeWidget) {
let on_off = Dynamic::new(true);
let theme_mode = on_off.map_each(|dark| {
if *dark {
ThemeMode::Dark
} else {
ThemeMode::Light
}
});
(
theme_mode,
Stack::rows(Label::new("Theme Mode").and(Slider::<bool>::from_value(on_off))),
)
}
fn color_editor(
initial_hue: f32,
initial_saturation: impl Into<ZeroToOne>,

View file

@ -674,6 +674,26 @@ impl LinearInterpolate for f64 {
}
}
impl LinearInterpolate for bool {
fn lerp(&self, target: &Self, percent: f32) -> Self {
if percent >= 0.5 {
*target
} else {
*self
}
}
}
impl PercentBetween for bool {
fn percent_between(&self, min: &Self, max: &Self) -> ZeroToOne {
if *min == *max || *self == *min {
ZeroToOne::ZERO
} else {
ZeroToOne::ONE
}
}
}
#[test]
fn integer_lerps() {
#[track_caller]

View file

@ -1,4 +1,5 @@
//! Types that provide access to the Gooey runtime.
use std::borrow::Cow;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@ -6,7 +7,6 @@ use std::sync::Arc;
use kludgine::app::winit::event::{
DeviceId, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
};
use kludgine::app::winit::window;
use kludgine::figures::units::{Lp, Px, UPx};
use kludgine::figures::{IntoSigned, Point, Rect, ScreenScale, Size};
use kludgine::shapes::{Shape, StrokeOptions};
@ -15,10 +15,10 @@ use kludgine::{Color, Kludgine};
use crate::graphics::Graphics;
use crate::styles::components::{HighlightColor, VisualOrder, WidgetBackground};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, Styles, Theme, ThemePair};
use crate::value::Dynamic;
use crate::value::{Dynamic, Value};
use crate::widget::{EventHandling, ManagedWidget, WidgetId, WidgetInstance, WidgetRef};
use crate::window::sealed::WindowCommand;
use crate::window::RunningWindow;
use crate::window::{RunningWindow, ThemeMode};
use crate::ConstraintLimit;
/// A context to an event function.
@ -674,8 +674,9 @@ pub struct WidgetContext<'context, 'window> {
current_node: ManagedWidget,
redraw_status: &'context RedrawStatus,
window: &'context mut RunningWindow<'window>,
theme: &'context ThemePair,
theme: Cow<'context, ThemePair>,
pending_state: PendingState<'context>,
theme_mode: ThemeMode,
}
impl<'context, 'window> WidgetContext<'context, 'window> {
@ -684,6 +685,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
redraw_status: &'context RedrawStatus,
theme: &'context ThemePair,
window: &'context mut RunningWindow<'window>,
theme_mode: ThemeMode,
) -> Self {
Self {
pending_state: PendingState::Owned(PendingWidgetState {
@ -698,7 +700,8 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
}),
current_node,
redraw_status,
theme,
theme: Cow::Borrowed(theme),
theme_mode,
window,
}
}
@ -709,8 +712,9 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
current_node: self.current_node.clone(),
redraw_status: self.redraw_status,
window: &mut *self.window,
theme: self.theme,
theme: Cow::Borrowed(self.theme.as_ref()),
pending_state: self.pending_state.borrowed(),
theme_mode: self.theme_mode,
}
}
@ -723,12 +727,26 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
Widget: ManageWidget,
Widget::Managed: MapManagedWidget<WidgetContext<'child, 'window>>,
{
widget.manage(self).map(|current_node| WidgetContext {
current_node,
redraw_status: self.redraw_status,
window: &mut *self.window,
theme: self.theme,
pending_state: self.pending_state.borrowed(),
widget.manage(self).map(|current_node| {
let (theme, theme_mode) = current_node.overidden_theme();
let theme = if let Some(theme) = theme {
Cow::Owned(theme.get_tracked(self))
} else {
Cow::Borrowed(self.theme.as_ref())
};
let theme_mode = if let Some(mode) = theme_mode {
mode.get_tracked(self)
} else {
self.theme_mode
};
WidgetContext {
current_node,
redraw_status: self.redraw_status,
window: &mut *self.window,
theme,
pending_state: self.pending_state.borrowed(),
theme_mode,
}
})
}
@ -872,10 +890,24 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
///
/// Style queries for children will return any values matching this
/// collection.
pub fn attach_styles(&self, styles: Styles) {
pub fn attach_styles(&self, styles: Value<Styles>) {
self.current_node.attach_styles(styles);
}
/// Attaches `theme` to the widget hierarchy for this widget.
///
/// All children nodes will access this theme in their contexts.
pub fn attach_theme(&self, theme: Value<ThemePair>) {
self.current_node.attach_theme(theme);
}
/// Attaches `theme_mode` to the widget hierarchy for this widget.
///
/// All children nodes will use this theme mode.
pub fn attach_theme_mode(&self, theme_mode: Value<ThemeMode>) {
self.current_node.attach_theme_mode(theme_mode);
}
/// Queries the widget hierarchy for matching style components.
///
/// This function traverses up the widget hierarchy looking for the
@ -890,7 +922,7 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
pub fn query_styles(&self, query: &[&dyn ComponentDefaultvalue]) -> Styles {
self.current_node
.tree
.query_styles(&self.current_node, query)
.query_styles(&self.current_node, query, self)
}
/// Queries the widget hierarchy for a single style component.
@ -931,24 +963,24 @@ impl<'context, 'window> WidgetContext<'context, 'window> {
/// Returns the theme pair for the window.
#[must_use]
pub fn theme_pair(&self) -> &ThemePair {
self.theme
self.theme.as_ref()
}
/// Returns the current theme in either light or dark mode.
#[must_use]
pub fn theme(&self) -> &Theme {
match self.window.theme() {
window::Theme::Light => &self.theme.light,
window::Theme::Dark => &self.theme.dark,
match self.theme_mode {
ThemeMode::Light => &self.theme.light,
ThemeMode::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,
match self.theme_mode {
ThemeMode::Light => &self.theme.dark,
ThemeMode::Dark => &self.theme.light,
}
}
}

View file

@ -7,8 +7,10 @@ use kludgine::figures::{Point, Rect};
use crate::context::WidgetContext;
use crate::styles::components::VisualOrder;
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles};
use crate::styles::{ComponentDefaultvalue, ComponentDefinition, ComponentType, Styles, ThemePair};
use crate::value::Value;
use crate::widget::{ManagedWidget, WidgetId, WidgetInstance};
use crate::window::ThemeMode;
#[derive(Clone, Default)]
pub struct Tree {
@ -31,6 +33,8 @@ impl Tree {
parent: parent.map(ManagedWidget::id),
layout: None,
styles: None,
theme: None,
theme_mode: None,
},
);
if widget.is_default() {
@ -277,20 +281,47 @@ impl Tree {
data.nodes.get(&id).expect("missing widget").parent
}
pub(crate) fn attach_styles(&self, id: WidgetId, styles: Styles) {
pub(crate) fn attach_styles(&self, id: WidgetId, styles: Value<Styles>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get_mut(&id).expect("missing widget").styles = Some(styles);
}
pub(crate) fn attach_theme(&self, id: WidgetId, theme: Value<ThemePair>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get_mut(&id).expect("missing widget").theme = Some(theme);
}
pub(crate) fn attach_theme_mode(&self, id: WidgetId, theme: Value<ThemeMode>) {
let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
data.nodes.get_mut(&id).expect("missing widget").theme_mode = Some(theme);
}
pub(crate) fn overriden_theme(
&self,
id: WidgetId,
) -> (Option<Value<ThemePair>>, Option<Value<ThemeMode>>) {
let data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g);
(
data.nodes.get(&id).expect("missing widget").theme.clone(),
data.nodes
.get(&id)
.expect("missing widget")
.theme_mode
.clone(),
)
}
pub fn query_styles(
&self,
perspective: &ManagedWidget,
query: &[&dyn ComponentDefaultvalue],
context: &WidgetContext<'_, '_>,
) -> Styles {
self.data
.lock()
.map_or_else(PoisonError::into_inner, |g| g)
.query_styles(perspective.id(), query)
.query_styles(perspective.id(), query, context)
}
pub fn query_style<Component: ComponentDefinition>(
@ -391,19 +422,22 @@ impl TreeData {
&self,
mut perspective: WidgetId,
query: &[&dyn ComponentDefaultvalue],
context: &WidgetContext<'_, '_>,
) -> Styles {
let mut query = query.iter().map(|n| n.name()).collect::<Vec<_>>();
let mut resolved = Styles::new();
while !query.is_empty() {
let node = &self.nodes[&perspective];
if let Some(styles) = &node.styles {
query.retain(|name| {
if let Some(component) = styles.get_named(name) {
resolved.insert(name, component.clone());
false
} else {
true
}
styles.map_tracked(context, |styles| {
query.retain(|name| {
if let Some(component) = styles.get_named(name) {
resolved.insert(name, component.clone());
false
} else {
true
}
});
});
}
let Some(parent) = node.parent else { break };
@ -422,13 +456,21 @@ impl TreeData {
loop {
let node = &self.nodes[&perspective];
if let Some(styles) = &node.styles {
if let Some(component) = styles.get_named(&name) {
let Ok(value) = <Component::ComponentType>::try_from_component(component.get())
else {
break;
};
component.redraw_when_changed(context);
return value;
match styles.map_tracked(context, |styles| {
if let Some(component) = styles.get_named(&name) {
let Ok(value) =
<Component::ComponentType>::try_from_component(component.get())
else {
return Err(());
};
component.redraw_when_changed(context);
return Ok(Some(value));
}
Ok(None)
}) {
Ok(Some(value)) => return value,
Ok(None) => {}
Err(()) => break,
}
}
let Some(parent) = node.parent else { break };
@ -443,5 +485,7 @@ pub struct Node {
pub children: Vec<WidgetId>,
pub parent: Option<WidgetId>,
pub layout: Option<Rect<Px>>,
pub styles: Option<Styles>,
pub styles: Option<Value<Styles>>,
pub theme: Option<Value<ThemePair>>,
pub theme_mode: Option<Value<ThemeMode>>,
}

View file

@ -16,11 +16,11 @@ use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, Size};
use crate::context::{AsEventContext, EventContext, GraphicsContext, LayoutContext};
use crate::styles::components::VisualOrder;
use crate::styles::{IntoComponentValue, NamedComponent, Styles};
use crate::styles::{IntoComponentValue, NamedComponent, Styles, ThemePair};
use crate::tree::Tree;
use crate::value::{IntoValue, Value};
use crate::widgets::{Align, Expand, Scroll, Style};
use crate::window::{RunningWindow, Window, WindowBehavior};
use crate::window::{RunningWindow, ThemeMode, Window, WindowBehavior};
use crate::{ConstraintLimit, Run};
/// A type that makes up a graphical user interface.
@ -448,7 +448,7 @@ pub trait MakeWidget: Sized {
/// Associates `styles` with this widget.
///
/// This is equivalent to `Style::new(styles, self)`.
fn with_styles(self, styles: impl Into<Styles>) -> Style
fn with_styles(self, styles: impl IntoValue<Styles>) -> Style
where
Self: Sized,
{
@ -951,10 +951,22 @@ impl ManagedWidget {
self.tree.parent(self.id()).is_some()
}
pub(crate) fn attach_styles(&self, styles: Styles) {
pub(crate) fn attach_styles(&self, styles: Value<Styles>) {
self.tree.attach_styles(self.id(), styles);
}
pub(crate) fn attach_theme(&self, theme: Value<ThemePair>) {
self.tree.attach_theme(self.id(), theme);
}
pub(crate) fn attach_theme_mode(&self, theme: Value<ThemeMode>) {
self.tree.attach_theme_mode(self.id(), theme);
}
pub(crate) fn overidden_theme(&self) -> (Option<Value<ThemePair>>, Option<Value<ThemeMode>>) {
self.tree.overriden_theme(self.id())
}
pub(crate) fn reset_child_layouts(&self) {
self.tree.reset_child_layouts(self.id());
}

View file

@ -6,12 +6,14 @@ mod canvas;
mod expand;
mod input;
pub mod label;
mod mode_switch;
mod resize;
pub mod scroll;
mod slider;
mod space;
pub mod stack;
mod style;
mod themed;
mod tilemap;
pub use align::Align;
@ -20,10 +22,12 @@ pub use canvas::Canvas;
pub use expand::Expand;
pub use input::Input;
pub use label::Label;
pub use mode_switch::ModeSwitch;
pub use resize::Resize;
pub use scroll::Scroll;
pub use slider::Slider;
pub use space::Space;
pub use stack::Stack;
pub use style::Style;
pub use themed::Themed;
pub use tilemap::TileMap;

View file

@ -0,0 +1,31 @@
use crate::context::EventContext;
use crate::value::{IntoValue, Value};
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
use crate::window::ThemeMode;
/// A widget that applies a set of [`Styles`] to all contained widgets.
#[derive(Debug)]
pub struct ModeSwitch {
mode: Value<ThemeMode>,
child: WidgetRef,
}
impl ModeSwitch {
/// Returns a new widget that applies `mode` to all of its children.
pub fn new(mode: impl IntoValue<ThemeMode>, child: impl MakeWidget) -> Self {
Self {
mode: mode.into_value(),
child: WidgetRef::new(child),
}
}
}
impl WrapperWidget for ModeSwitch {
fn child_mut(&mut self) -> &mut WidgetRef {
&mut self.child
}
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
context.attach_theme_mode(self.mode.clone());
}
}

View file

@ -1,20 +1,21 @@
use crate::context::EventContext;
use crate::styles::Styles;
use crate::value::{IntoValue, Value};
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
/// A widget that applies a set of [`Styles`] to all contained widgets.
#[derive(Debug)]
pub struct Style {
styles: Styles,
styles: Value<Styles>,
child: WidgetRef,
}
impl Style {
/// Returns a new widget that applies `styles` to `child` and any children
/// it may have.
pub fn new(styles: impl Into<Styles>, child: impl MakeWidget) -> Self {
pub fn new(styles: impl IntoValue<Styles>, child: impl MakeWidget) -> Self {
Self {
styles: styles.into(),
styles: styles.into_value(),
child: WidgetRef::new(child),
}
}

31
src/widgets/themed.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::context::EventContext;
use crate::styles::ThemePair;
use crate::value::{IntoValue, Value};
use crate::widget::{MakeWidget, WidgetRef, WrapperWidget};
/// A widget that applies a set of [`Styles`] to all contained widgets.
#[derive(Debug)]
pub struct Themed {
theme: Value<ThemePair>,
child: WidgetRef,
}
impl Themed {
/// Returns a new widget that applies `theme` to all of its children.
pub fn new(theme: impl IntoValue<ThemePair>, child: impl MakeWidget) -> Self {
Self {
theme: theme.into_value(),
child: WidgetRef::new(child),
}
}
}
impl WrapperWidget for Themed {
fn child_mut(&mut self) -> &mut WidgetRef {
&mut self.child
}
fn mounted(&mut self, context: &mut EventContext<'_, '_>) {
context.attach_theme(self.theme.clone());
}
}

View file

@ -15,6 +15,7 @@ use kludgine::app::winit::event::{
DeviceId, ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase,
};
use kludgine::app::winit::keyboard::Key;
use kludgine::app::winit::window;
use kludgine::app::WindowBehavior as _;
use kludgine::figures::units::{Px, UPx};
use kludgine::figures::{IntoSigned, IntoUnsigned, Point, Rect, ScreenScale, Size};
@ -32,7 +33,7 @@ use crate::styles::components::LayoutOrder;
use crate::styles::ThemePair;
use crate::tree::Tree;
use crate::utils::ModifiersExt;
use crate::value::{Dynamic, DynamicReader, IntoDynamic, Value};
use crate::value::{Dynamic, DynamicReader, IntoDynamic, IntoValue, Value};
use crate::widget::{
EventHandling, ManagedWidget, Widget, WidgetId, WidgetInstance, HANDLED, IGNORED,
};
@ -105,6 +106,7 @@ where
pub theme: Value<ThemePair>,
occluded: Option<Dynamic<bool>>,
focused: Option<Dynamic<bool>>,
theme_mode: Option<Value<ThemeMode>>,
}
impl<Behavior> Default for Window<Behavior>
@ -156,6 +158,24 @@ impl Window<WidgetInstance> {
self.occluded = Some(occluded);
self
}
/// Sets the [`ThemeMode`] for this window.
///
/// If a [`ThemeMode`] is provided, the window will be set to this theme
/// mode upon creation and will not be updated while the window is running.
///
/// If a [`Dynamic`] is provided, the initial value will be ignored and the
/// dynamic will be updated when the window opens with the user's current
/// theme mode. The dynamic will also be updated any time the user's theme
/// mode changes.
///
/// Setting the [`Dynamic`]'s value will also update the window with the new
/// mode until a mode change is detected, upon which the new mode will be
/// stored.
pub fn with_theme_mode(mut self, theme_mode: impl IntoValue<ThemeMode>) -> Self {
self.theme_mode = Some(theme_mode.into_value());
self
}
}
impl<Behavior> Window<Behavior>
@ -189,6 +209,7 @@ where
theme: Value::default(),
occluded: None,
focused: None,
theme_mode: None,
}
}
}
@ -207,6 +228,7 @@ where
occluded: self.occluded,
focused: self.focused,
theme: Some(self.theme),
theme_mode: self.theme_mode,
}),
}))
}
@ -260,6 +282,7 @@ struct GooeyWindow<T> {
max_inner_size: Option<Size<UPx>>,
theme: Option<DynamicReader<ThemePair>>,
current_theme: ThemePair,
theme_mode: Dynamic<ThemeMode>,
transparent: bool,
}
@ -289,6 +312,7 @@ where
&self.redraw_status,
&self.current_theme,
window,
self.theme_mode.get(),
),
kludgine,
)
@ -300,6 +324,7 @@ where
&self.redraw_status,
&self.current_theme,
window,
self.theme_mode.get(),
),
kludgine,
)
@ -313,6 +338,7 @@ where
&self.redraw_status,
&self.current_theme,
window,
self.theme_mode.get(),
),
kludgine,
)
@ -408,6 +434,14 @@ where
.theme
.take()
.expect("theme always present");
let theme_mode = match context.settings.borrow_mut().theme_mode.take() {
Some(Value::Dynamic(dynamic)) => {
dynamic.update(window.theme().into());
dynamic
}
Some(Value::Constant(_)) | None => Dynamic::new(window.theme().into()),
};
let transparent = context.settings.borrow().transparent;
let mut behavior = T::initialize(
&mut RunningWindow::new(window, &focused, &occluded),
@ -439,6 +473,7 @@ where
max_inner_size: None,
current_theme,
theme,
theme_mode,
transparent,
}
}
@ -472,9 +507,11 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
gfx: Exclusive::Owned(Graphics::new(graphics)),
};
context.redraw_when_changed(&self.theme_mode);
let mut layout_context = LayoutContext::new(&mut context);
let window_size = layout_context.gfx.size();
@ -557,12 +594,16 @@ where
fn initial_window_attributes(
context: &Self::Context,
) -> kludgine::app::WindowAttributes<WindowCommand> {
context
let mut attrs = context
.settings
.borrow_mut()
.attributes
.take()
.expect("called more than once")
.expect("called more than once");
if let Some(Value::Constant(theme_mode)) = &context.settings.borrow().theme_mode {
attrs.preferred_theme = Some((*theme_mode).into());
}
attrs
}
fn close_requested(
@ -636,6 +677,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -665,6 +707,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -731,6 +774,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -765,6 +809,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -793,6 +838,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -807,6 +853,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -847,6 +894,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -871,6 +919,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
)
@ -886,6 +935,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
),
@ -920,6 +970,7 @@ where
&self.redraw_status,
&self.current_theme,
&mut window,
self.theme_mode.get(),
),
kludgine,
);
@ -937,6 +988,14 @@ where
}
}
fn theme_changed(
&mut self,
window: kludgine::app::Window<'_, WindowCommand>,
_kludgine: &mut Kludgine,
) {
self.theme_mode.update(window.theme().into());
}
fn event(
&mut self,
mut window: kludgine::app::Window<'_, WindowCommand>,
@ -975,7 +1034,7 @@ pub(crate) mod sealed {
use crate::styles::ThemePair;
use crate::value::{Dynamic, Value};
use crate::window::WindowAttributes;
use crate::window::{ThemeMode, WindowAttributes};
pub struct Context<C> {
pub user: C,
@ -987,6 +1046,7 @@ pub(crate) mod sealed {
pub occluded: Option<Dynamic<bool>>,
pub focused: Option<Dynamic<bool>>,
pub theme: Option<Value<ThemePair>>,
pub theme_mode: Option<Value<ThemeMode>>,
pub transparent: bool,
}
@ -995,3 +1055,30 @@ pub(crate) mod sealed {
// RequestClose,
}
}
/// Controls whether the light or dark theme is applied.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ThemeMode {
/// Applies the light theme
Light,
/// Applies the dark theme
Dark,
}
impl From<window::Theme> for ThemeMode {
fn from(value: window::Theme) -> Self {
match value {
window::Theme::Light => Self::Light,
window::Theme::Dark => Self::Dark,
}
}
}
impl From<ThemeMode> for window::Theme {
fn from(value: ThemeMode) -> Self {
match value {
ThemeMode::Light => Self::Light,
ThemeMode::Dark => Self::Dark,
}
}
}