Skip to content

Commit 87a24bf

Browse files
authored
Full contextual theming (#24969)
Third time's the charm, maybe? This now implements full-on contextual theming. Token lookup now happens in two stages: A theme token is converted into a semantic token, and then the combination of semantic token + theme context is used to look up a color. The set of semantic tokens is much smaller than the set of theme tokens. The theme context is an enum which indicates the surface layer of the container holding the widget. This is largely inspired by the design token architecture used by web toolkits such as Radix, Chakra or MUI. See the release notes for additional explanation. @alice-i-cecile @gagnus
1 parent 0170f7c commit 87a24bf

18 files changed

Lines changed: 810 additions & 370 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
title: "Contextual theming"
3+
pull_requests: [24969]
4+
---
5+
6+
The Feathers `ThemeProps` structure has significantly changed; if you have created a custom theme,
7+
you will need to reorganize it based on semantic tokens.
8+
9+
For a quick and easy port to start off from, you can create SemanticToken’s for every ThemeToken
10+
you have used in your custom theme. Then, create a map from these ThemeTokens to your
11+
SemanticTokens, and map these SemanticTokens to your desired colors.
12+
13+
From there, you can start combining any identical colors used to the same SemanticToken where
14+
it makes sense in your application.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
title: "Contextual theming"
3+
authors: ["@viridia"]
4+
pull_requests: [24969]
5+
---
6+
7+
Feathers now supports "contextual theming", meaning that the theme variables can change depending
8+
on the parent entity. So widgets that are inside of a dialog box or subpanel can have different
9+
colors than widgets that are on a regular panel or window background.
10+
11+
The design follows that of popular web toolkits like MUI, Radix, or Chakra. There's a new component,
12+
`ThemeContext`, which lets you select which color scheme the widget's descendants should use;
13+
currently the available schemes are `Base`, `Higher`, `Highest`, and `Floating`, which correspond
14+
to the design plans for the Bevy scene editor.
15+
16+
The theme context is used in conjunction with a new kind of design token, named `SemanticToken`.
17+
The lookup process for a color now requires two stages: the `ThemeToken` is converted into a
18+
`SemanticToken`, and then the combination of `SemanticToken` and `ThemeContext` is used to look up
19+
a color.
20+
21+
In addition to allowing context-specific color choices, this also makes it easier to design new
22+
themes! Instead of having to tediously choose colors for a hundred different theme tokens,
23+
the set of semantic tokens is much smaller, and the relationship between token and color is much
24+
more intuitive.

‎crates/bevy_feathers/src/containers/group.rs‎

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1+
use bevy_app::Propagate;
2+
use bevy_ecs::template::template;
13
use bevy_scene::{bsn, Scene};
24
use bevy_text::FontWeight;
35
use bevy_ui::{px, AlignItems, Display, FlexDirection, JustifyContent, Node, UiRect};
46

57
use crate::{
68
constants::{fonts, size},
79
font_styles::InheritableFont,
8-
rounded_corners::RoundedCorners,
9-
theme::{InheritableThemeTextColor, ThemeBackgroundColor, ThemeBorderColor},
10+
theme::{
11+
InheritableThemeTextColor, SurfaceLevel, ThemeBackgroundColor, ThemeBorderColor,
12+
ThemeContext,
13+
},
1014
tokens,
1115
};
1216

@@ -17,7 +21,12 @@ pub fn group() -> impl Scene {
1721
display: Display::Flex,
1822
flex_direction: FlexDirection::Column,
1923
align_items: AlignItems::Stretch,
24+
border: px(1),
25+
border_radius: px(4),
2026
}
27+
ThemeBackgroundColor(tokens::GROUP_BG)
28+
ThemeBorderColor(tokens::GROUP_BORDER)
29+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Highest))))
2130
}
2231
}
2332

@@ -29,18 +38,10 @@ pub fn group_header() -> impl Scene {
2938
flex_direction: FlexDirection::Row,
3039
align_items: AlignItems::Center,
3140
justify_content: JustifyContent::SpaceBetween,
32-
border: UiRect {
33-
left: px(1),
34-
top: px(1),
35-
right: px(1),
36-
},
3741
padding: UiRect::horizontal(px(10)),
3842
min_height: size::HEADER_HEIGHT,
3943
column_gap: px(4),
40-
border_radius: {RoundedCorners::Top.to_border_radius(4.0)}
4144
}
42-
ThemeBackgroundColor(tokens::GROUP_HEADER_BG)
43-
ThemeBorderColor(tokens::GROUP_HEADER_BORDER)
4445
InheritableThemeTextColor(tokens::GROUP_HEADER_TEXT)
4546
InheritableFont {
4647
font: fonts::REGULAR,
@@ -56,17 +57,10 @@ pub fn group_body() -> impl Scene {
5657
Node {
5758
display: Display::Flex,
5859
flex_direction: FlexDirection::Column,
59-
border: UiRect {
60-
left: px(1),
61-
right: px(1),
62-
bottom: px(1),
63-
},
6460
row_gap: px(4),
6561
padding: px(6),
66-
border_radius: {RoundedCorners::Bottom.to_border_radius(4.0)}
6762
}
68-
ThemeBackgroundColor(tokens::GROUP_BODY_BG)
69-
ThemeBorderColor(tokens::GROUP_BODY_BORDER)
63+
InheritableThemeTextColor(tokens::GROUP_HEADER_TEXT)
7064
InheritableFont {
7165
font: fonts::REGULAR,
7266
font_size: size::MEDIUM_FONT,

‎crates/bevy_feathers/src/containers/pane.rs‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use bevy_ecs::hierarchy::Children;
1+
use bevy_app::Propagate;
2+
use bevy_ecs::{hierarchy::Children, template::template};
23
use bevy_scene::{bsn, Scene};
34
use bevy_text::FontWeight;
45
use bevy_ui::{
@@ -9,7 +10,10 @@ use crate::{
910
constants::{fonts, size},
1011
font_styles::InheritableFont,
1112
rounded_corners::RoundedCorners,
12-
theme::{InheritableThemeTextColor, ThemeBackgroundColor, ThemeBorderColor},
13+
theme::{
14+
InheritableThemeTextColor, SurfaceLevel, ThemeBackgroundColor, ThemeBorderColor,
15+
ThemeContext,
16+
},
1317
tokens,
1418
};
1519

@@ -45,6 +49,7 @@ pub fn pane_header() -> impl Scene {
4549
ThemeBackgroundColor(tokens::PANE_HEADER_BG)
4650
ThemeBorderColor(tokens::PANE_HEADER_BORDER)
4751
InheritableThemeTextColor(tokens::PANE_HEADER_TEXT)
52+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Base))))
4853
InheritableFont {
4954
font: fonts::REGULAR,
5055
font_size: size::MEDIUM_FONT,
@@ -85,6 +90,7 @@ pub fn pane_body() -> impl Scene {
8590
padding: px(6),
8691
border_radius: {RoundedCorners::Bottom.to_border_radius(4.0)}
8792
}
93+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Higher))))
8894
ThemeBackgroundColor(tokens::PANE_BODY_BG)
8995
}
9096
}

‎crates/bevy_feathers/src/containers/subpane.rs‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use bevy_app::Propagate;
2+
use bevy_ecs::template::template;
13
use bevy_scene::{bsn, Scene};
24
use bevy_text::FontWeight;
35
use bevy_ui::{px, AlignItems, Display, FlexDirection, JustifyContent, Node, UiRect};
@@ -6,7 +8,10 @@ use crate::{
68
constants::{fonts, size},
79
font_styles::InheritableFont,
810
rounded_corners::RoundedCorners,
9-
theme::{InheritableThemeTextColor, ThemeBackgroundColor, ThemeBorderColor},
11+
theme::{
12+
InheritableThemeTextColor, SurfaceLevel, ThemeBackgroundColor, ThemeBorderColor,
13+
ThemeContext,
14+
},
1015
tokens,
1116
};
1217

@@ -42,6 +47,7 @@ pub fn subpane_header() -> impl Scene {
4247
ThemeBackgroundColor(tokens::SUBPANE_HEADER_BG)
4348
ThemeBorderColor(tokens::SUBPANE_HEADER_BORDER)
4449
InheritableThemeTextColor(tokens::SUBPANE_HEADER_TEXT)
50+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Highest))))
4551
InheritableFont {
4652
font: fonts::REGULAR,
4753
font_size: size::MEDIUM_FONT,
@@ -67,6 +73,7 @@ pub fn subpane_body() -> impl Scene {
6773
}
6874
ThemeBackgroundColor(tokens::SUBPANE_BODY_BG)
6975
ThemeBorderColor(tokens::SUBPANE_BODY_BORDER)
76+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Higher))))
7077
InheritableFont {
7178
font: fonts::REGULAR,
7279
font_size: size::MEDIUM_FONT,

‎crates/bevy_feathers/src/controls/dialog.rs‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
use bevy_app::Propagate;
12
use bevy_color::{Alpha, Srgba};
23
use bevy_ecs::{
34
event::EntityEvent, hierarchy::Children, observer::On, reflect::ReflectComponent,
4-
system::Commands,
5+
system::Commands, template::template,
56
};
67
use bevy_reflect::{prelude::ReflectDefault, Reflect};
78
use bevy_scene::{bsn, bsn_list, on, Scene, SceneComponent, SceneList};
@@ -19,7 +20,10 @@ use crate::{
1920
controls::{ButtonVariant, FeathersToolButton},
2021
display::icon,
2122
font_styles::InheritableFont,
22-
theme::{InheritableThemeTextColor, ThemeBackgroundColor, ThemeBorderColor, ThemedText},
23+
theme::{
24+
InheritableThemeTextColor, SurfaceLevel, ThemeBackgroundColor, ThemeBorderColor,
25+
ThemeContext, ThemedText,
26+
},
2327
tokens,
2428
};
2529

@@ -170,6 +174,7 @@ impl FeathersFloatingDialog {
170174
DialogDragHandle
171175
InheritableThemeTextColor(tokens::DIALOG_HEADER_TEXT)
172176
ThemeBackgroundColor(tokens::DIALOG_HEADER_BG)
177+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Higher))))
173178
InheritableFont {
174179
font: fonts::REGULAR,
175180
font_size: size::HEADER_FONT,
@@ -207,6 +212,7 @@ impl FeathersDialogHeader {
207212
justify_content: JustifyContent::SpaceBetween,
208213
padding: UiRect::all(px(6.0)),
209214
}
215+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Higher))))
210216
ThemeBackgroundColor(tokens::DIALOG_HEADER_BG)
211217
InheritableFont {
212218
font: fonts::REGULAR,
@@ -252,6 +258,7 @@ impl FeathersDialogBody {
252258
align_items: AlignItems::Stretch,
253259
padding: UiRect::all(px(6.0)),
254260
}
261+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Floating))))
255262
InheritableFont {
256263
font: fonts::REGULAR,
257264
font_size: size::MEDIUM_FONT,
@@ -278,6 +285,7 @@ impl FeathersDialogFooter {
278285
column_gap: px(6.0),
279286
padding: UiRect::all(px(6.0)),
280287
}
288+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Floating))))
281289
InheritableFont {
282290
font: fonts::REGULAR,
283291
font_size: size::MEDIUM_FONT,

‎crates/bevy_feathers/src/controls/menu.rs‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use alloc::sync::Arc;
2-
use bevy_app::{Plugin, PreUpdate};
2+
use bevy_app::{Plugin, PreUpdate, Propagate};
33
use bevy_camera::visibility::Visibility;
44
use bevy_color::{Alpha, Srgba};
55
use bevy_ecs::{
@@ -12,6 +12,7 @@ use bevy_ecs::{
1212
reflect::ReflectComponent,
1313
schedule::IntoScheduleConfigs,
1414
system::{Commands, Query, Res, ResMut},
15+
template::template,
1516
};
1617
use bevy_log::{info, warn};
1718
use bevy_picking::{hover::Hovered, PickingSystems};
@@ -35,7 +36,10 @@ use crate::{
3536
display::icon,
3637
font_styles::InheritableFont,
3738
rounded_corners::RoundedCorners,
38-
theme::{InheritableThemeTextColor, ThemeBackgroundColor, ThemeBorderColor},
39+
theme::{
40+
InheritableThemeTextColor, SurfaceLevel, ThemeBackgroundColor, ThemeBorderColor,
41+
ThemeContext,
42+
},
3943
tokens,
4044
};
4145
use bevy_input_focus::{
@@ -359,6 +363,7 @@ impl FeathersMenuPopup {
359363
Visibility::Hidden
360364
ThemeBackgroundColor(tokens::MENU_BG)
361365
ThemeBorderColor(tokens::MENU_BORDER)
366+
template(|_| Ok(Propagate(ThemeContext(SurfaceLevel::Floating))))
362367
BoxShadow::new(
363368
Srgba::BLACK.with_alpha(0.9).into(),
364369
px(0),
@@ -557,7 +562,7 @@ fn set_menuitem_colors(
557562
(true, _, _) => tokens::MENUITEM_BG_FOCUSED,
558563
(false, true, _) => tokens::MENUITEM_BG_PRESSED,
559564
(false, false, true) => tokens::MENUITEM_BG_HOVER,
560-
(false, false, false) => tokens::MENU_BG,
565+
(false, false, false) => tokens::MENUITEM_BG,
561566
};
562567

563568
let font_color_token = match disabled {

0 commit comments

Comments
 (0)