#[cfg(all(not(alloc_frugal), widgets_unstripped))]
use crate::style::WidgetStyle;
#[cfg(all(not(alloc_frugal), widgets_unstripped))]
use crate::widget::Widget;
#[cfg(all(not(alloc_frugal), widgets_unstripped))]
pub(crate) fn apply_active_theme(widget: &mut dyn Widget) {
let kind_name = crate::widget::capability::factory_name_for_kind(widget.kind());
if kind_name.is_empty() {
log::debug!(
"theme: {:?} has no resolvable factory name; leaving its own style in place",
widget.kind()
);
return;
}
let Some(theme_style) = crate::theme::resolved_theme_style(kind_name) else {
return;
};
let mut style = widget.style().clone();
let caller_authored = !style.theme_derived && style != WidgetStyle::default();
style.merge_theme(&theme_style);
if !caller_authored {
style.theme_derived = true;
}
widget.set_style(style);
}
#[cfg(all(device_profile, not(widgets_unstripped)))]
pub(crate) fn apply_active_theme(_widget: &mut dyn crate::widget::Widget) {}
#[cfg(alloc_frugal)]
pub(crate) fn apply_active_theme(_widget: &mut dyn crate::widget::Widget) {}
#[cfg(all(test, not(alloc_frugal)))]
mod tests {
use super::*;
use crate::core::{Color, Rect};
use crate::theme::{self, global_theme_manager, AppearanceMode};
fn guard() -> std::sync::MutexGuard<'static, ()> {
theme::theme_test_guard()
}
#[test]
fn a_widget_with_no_style_gets_the_theme() {
let _guard = guard();
let mut manager = global_theme_manager();
assert!(manager.set_appearance(AppearanceMode::Light));
drop(manager);
let primary = global_theme_manager().current_theme().expect("active").colors.primary;
let mut button = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
assert_eq!(button.style().background_color, None, "before: unset");
apply_active_theme(&mut button);
assert_eq!(
button.style().background_color,
Some(primary),
"a button's fill must come from the theme's primary token"
);
}
#[test]
fn an_explicit_style_is_not_overwritten() {
let _guard = guard();
let explicit = Color::rgb(1, 2, 3);
let mut button = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
button.set_background_color(Some(explicit));
apply_active_theme(&mut button);
assert_eq!(
button.style().background_color,
Some(explicit),
"the theme must not replace a colour the caller set"
);
}
#[test]
fn switching_appearance_changes_what_a_new_control_gets() {
let _guard = guard();
{
let mut manager = global_theme_manager();
assert!(manager.set_appearance(AppearanceMode::Light));
}
let mut light = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
apply_active_theme(&mut light);
{
let mut manager = global_theme_manager();
assert!(manager.set_appearance(AppearanceMode::Dark));
}
let mut dark = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
apply_active_theme(&mut dark);
assert_ne!(
light.style().background_color,
dark.style().background_color,
"a light and a dark theme must fill a button differently"
);
global_theme_manager().set_appearance(AppearanceMode::Light);
}
#[test]
fn reapplying_after_a_switch_replaces_the_previous_themes_colours() {
let _guard = guard();
global_theme_manager().set_appearance(AppearanceMode::Light);
let mut button = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
apply_active_theme(&mut button);
let light_fill = button.style().background_color;
assert!(light_fill.is_some(), "the first application must style the button");
global_theme_manager().set_appearance(AppearanceMode::Dark);
apply_active_theme(&mut button);
let dark_fill = button.style().background_color;
assert_ne!(
light_fill, dark_fill,
"re-applying after a switch must replace the previous theme's colour, not be
silently refused because the field is already set"
);
global_theme_manager().set_appearance(AppearanceMode::Light);
}
#[test]
fn a_callers_colour_survives_repeated_theme_applications() {
let _guard = guard();
let explicit = Color::rgb(1, 2, 3);
global_theme_manager().set_appearance(AppearanceMode::Light);
let mut button = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
button.set_background_color(Some(explicit));
apply_active_theme(&mut button);
global_theme_manager().set_appearance(AppearanceMode::Dark);
apply_active_theme(&mut button);
assert_eq!(
button.style().background_color,
Some(explicit),
"a colour the caller set must survive a theme switch"
);
global_theme_manager().set_appearance(AppearanceMode::Light);
}
#[test]
fn a_high_contrast_override_reaches_a_new_control() {
use crate::style::HighContrastMode;
let _guard = guard();
global_theme_manager().set_high_contrast(HighContrastMode::WhiteOnBlack);
let mut button = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
apply_active_theme(&mut button);
assert_eq!(button.style().background_color, Some(Color::BLACK));
assert_eq!(button.style().text_color, Some(Color::WHITE));
global_theme_manager().set_high_contrast(HighContrastMode::None);
}
#[test]
fn different_kinds_get_different_treatments() {
let _guard = guard();
{
let mut manager = global_theme_manager();
assert!(manager.set_appearance(AppearanceMode::Light));
}
let mut button = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
let mut label = crate::widget::Label::new("hi".to_string(), Rect::new(0, 0, 10, 10));
apply_active_theme(&mut button);
apply_active_theme(&mut label);
assert_ne!(
button.style().background_color,
label.style().background_color,
"a filled action and a plain label must not share a background"
);
}
}