#[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 state = widget.widget_state();
let Some(theme_style) = crate::theme::resolved_theme_style_for_state(kind_name, state) 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_state_override_reaches_the_control_itself() {
let _guard = guard();
let mut manager = global_theme_manager();
let mut theme = crate::theme::Theme::default();
theme.overrides.styles.insert(
"button:disabled".to_string(),
crate::theme::ThemeStyleToken {
background: Some(Color::rgba(7, 8, 9, 255)),
..Default::default()
},
);
manager.register_theme(theme);
assert!(manager.set_theme("default"));
drop(manager);
let mut button = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
button.set_enabled(false);
apply_active_theme(&mut button);
assert_eq!(
button.style().background_color,
Some(Color::rgba(7, 8, 9, 255)),
"the `button:disabled` override must reach the control, which is the state it reports"
);
let mut enabled = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
apply_active_theme(&mut enabled);
assert_ne!(
enabled.style().background_color,
Some(Color::rgba(7, 8, 9, 255)),
"an enabled button is not in the disabled state, so that override must not apply"
);
}
#[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"
);
}
#[test]
fn a_hovered_button_is_filled_differently_from_a_resting_one() {
let _guard = guard();
let mut manager = global_theme_manager();
assert!(manager.set_theme("default"), "the default preset must be registered");
drop(manager);
let mut resting = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
apply_active_theme(&mut resting);
let mut hovered = crate::widget::Button::new("ok".to_string(), Rect::new(0, 0, 10, 10));
crate::event::EventHandler::handle_event(
&mut hovered,
&crate::event::Event::MouseEnter { pos: crate::core::Point::new(5, 5) },
);
assert_eq!(
hovered.widget_state(),
crate::style::WidgetState::Hover,
"the control must actually report the state the override is keyed on"
);
apply_active_theme(&mut hovered);
assert_ne!(
hovered.style().background_color,
resting.style().background_color,
"`button:hover` in the preset must change the painted fill"
);
}
}