Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_feathers/src/theme.rs
6595 views
1
//! A framework for theming.
2
use bevy_app::Propagate;
3
use bevy_color::{palettes, Color};
4
use bevy_ecs::{
5
change_detection::DetectChanges,
6
component::Component,
7
lifecycle::Insert,
8
observer::On,
9
query::Changed,
10
reflect::{ReflectComponent, ReflectResource},
11
resource::Resource,
12
system::{Commands, Query, Res},
13
};
14
use bevy_log::warn_once;
15
use bevy_platform::collections::HashMap;
16
use bevy_reflect::{prelude::ReflectDefault, Reflect};
17
use bevy_text::TextColor;
18
use bevy_ui::{BackgroundColor, BorderColor};
19
20
/// A collection of properties that make up a theme.
21
#[derive(Default, Clone, Reflect, Debug)]
22
#[reflect(Default, Debug)]
23
pub struct ThemeProps {
24
/// Map of design tokens to colors.
25
pub color: HashMap<String, Color>,
26
// Other style property types to be added later.
27
}
28
29
/// The currently selected user interface theme. Overwriting this resource changes the theme.
30
#[derive(Resource, Default, Reflect, Debug)]
31
#[reflect(Resource, Default, Debug)]
32
pub struct UiTheme(pub ThemeProps);
33
34
impl UiTheme {
35
/// Lookup a color by design token. If the theme does not have an entry for that token,
36
/// logs a warning and returns an error color.
37
pub fn color<'a>(&self, token: &'a str) -> Color {
38
let color = self.0.color.get(token);
39
match color {
40
Some(c) => *c,
41
None => {
42
warn_once!("Theme color {} not found.", token);
43
// Return a bright obnoxious color to make the error obvious.
44
palettes::basic::FUCHSIA.into()
45
}
46
}
47
}
48
49
/// Associate a design token with a given color.
50
pub fn set_color(&mut self, token: impl Into<String>, color: Color) {
51
self.0.color.insert(token.into(), color);
52
}
53
}
54
55
/// Component which causes the background color of an entity to be set based on a theme color.
56
#[derive(Component, Clone, Copy)]
57
#[require(BackgroundColor)]
58
#[component(immutable)]
59
#[derive(Reflect)]
60
#[reflect(Component, Clone)]
61
pub struct ThemeBackgroundColor(pub &'static str);
62
63
/// Component which causes the border color of an entity to be set based on a theme color.
64
/// Only supports setting all borders to the same color.
65
#[derive(Component, Clone, Copy)]
66
#[require(BorderColor)]
67
#[component(immutable)]
68
#[derive(Reflect)]
69
#[reflect(Component, Clone)]
70
pub struct ThemeBorderColor(pub &'static str);
71
72
/// Component which causes the inherited text color of an entity to be set based on a theme color.
73
#[derive(Component, Clone, Copy)]
74
#[component(immutable)]
75
#[derive(Reflect)]
76
#[reflect(Component, Clone)]
77
pub struct ThemeFontColor(pub &'static str);
78
79
/// A marker component that is used to indicate that the text entity wants to opt-in to using
80
/// inherited text styles.
81
#[derive(Component, Reflect)]
82
#[reflect(Component)]
83
pub struct ThemedText;
84
85
pub(crate) fn update_theme(
86
mut q_background: Query<(&mut BackgroundColor, &ThemeBackgroundColor)>,
87
mut q_border: Query<(&mut BorderColor, &ThemeBorderColor)>,
88
theme: Res<UiTheme>,
89
) {
90
if theme.is_changed() {
91
// Update all background colors
92
for (mut bg, theme_bg) in q_background.iter_mut() {
93
bg.0 = theme.color(theme_bg.0);
94
}
95
96
// Update all border colors
97
for (mut border, theme_border) in q_border.iter_mut() {
98
border.set_all(theme.color(theme_border.0));
99
}
100
}
101
}
102
103
pub(crate) fn on_changed_background(
104
ev: On<Insert, ThemeBackgroundColor>,
105
mut q_background: Query<
106
(&mut BackgroundColor, &ThemeBackgroundColor),
107
Changed<ThemeBackgroundColor>,
108
>,
109
theme: Res<UiTheme>,
110
) {
111
// Update background colors where the design token has changed.
112
if let Ok((mut bg, theme_bg)) = q_background.get_mut(ev.entity()) {
113
bg.0 = theme.color(theme_bg.0);
114
}
115
}
116
117
pub(crate) fn on_changed_border(
118
ev: On<Insert, ThemeBorderColor>,
119
mut q_border: Query<(&mut BorderColor, &ThemeBorderColor), Changed<ThemeBorderColor>>,
120
theme: Res<UiTheme>,
121
) {
122
// Update background colors where the design token has changed.
123
if let Ok((mut border, theme_border)) = q_border.get_mut(ev.entity()) {
124
border.set_all(theme.color(theme_border.0));
125
}
126
}
127
128
/// An observer which looks for changes to the [`ThemeFontColor`] component on an entity, and
129
/// propagates downward the text color to all participating text entities.
130
pub(crate) fn on_changed_font_color(
131
ev: On<Insert, ThemeFontColor>,
132
font_color: Query<&ThemeFontColor>,
133
theme: Res<UiTheme>,
134
mut commands: Commands,
135
) {
136
if let Ok(token) = font_color.get(ev.entity()) {
137
let color = theme.color(token.0);
138
commands
139
.entity(ev.entity())
140
.insert(Propagate(TextColor(color)));
141
}
142
}
143
144