Skip to content

Commit d80425a

Browse files
committed
Add bsn_reconcile example
1 parent d742acc commit d80425a

File tree

2 files changed

+202
-0
lines changed

2 files changed

+202
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2825,6 +2825,10 @@ wasm = false
28252825
name = "bsn"
28262826
path = "examples/scene/bsn.rs"
28272827

2828+
[[example]]
2829+
name = "bsn_reconcile"
2830+
path = "examples/scene/bsn_reconcile.rs"
2831+
28282832
[[example]]
28292833
name = "ui_scene"
28302834
path = "examples/scene/ui_scene.rs"

examples/scene/bsn_reconcile.rs

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
//! This example shows off reconciliation using the `bevy_feathers` widgets.
2+
//!
3+
//! It also serves as a test suite to see how the widget states behave when the scene is reconciled.
4+
//!
5+
//! Run this with subsecond hot patching to enable bsn!-macro hot reloading:
6+
//! `BEVY_ASSET_ROOT="." dx serve --hot-patch --example bsn_reconcile --features=hotpatching`
7+
//!
8+
9+
use bevy::{
10+
color::palettes,
11+
core_widgets::{
12+
callback, Activate, CoreRadio, CoreRadioGroup, CoreWidgetsPlugins, SliderPrecision,
13+
SliderStep, SliderValue, ValueChange,
14+
},
15+
feathers::{
16+
controls::{
17+
button, checkbox, color_slider, color_swatch, radio, slider, toggle_switch,
18+
ButtonProps, CheckboxProps, ColorChannel, ColorSliderProps, SliderProps,
19+
ToggleSwitchProps,
20+
},
21+
dark_theme::create_dark_theme,
22+
theme::{ThemeBackgroundColor, ThemedText, UiTheme},
23+
tokens, FeathersPlugin,
24+
},
25+
input_focus::{
26+
tab_navigation::{TabGroup, TabNavigationPlugin},
27+
InputDispatchPlugin,
28+
},
29+
prelude::*,
30+
scene2::prelude::{Scene, *},
31+
ui::Checked,
32+
};
33+
34+
/// A struct to hold the state of various widgets shown in the demo.
35+
#[derive(Resource)]
36+
struct DemoWidgetStates {
37+
controlled_slider_value: f32,
38+
hsl_color: Hsla,
39+
}
40+
41+
fn main() {
42+
App::new()
43+
.add_plugins((
44+
DefaultPlugins,
45+
CoreWidgetsPlugins,
46+
InputDispatchPlugin,
47+
TabNavigationPlugin,
48+
FeathersPlugin,
49+
))
50+
.insert_resource(UiTheme(create_dark_theme()))
51+
.insert_resource(DemoWidgetStates {
52+
controlled_slider_value: 20.0,
53+
hsl_color: palettes::tailwind::AMBER_800.into(),
54+
})
55+
.add_systems(Startup, setup)
56+
.add_systems(Update, reconcile_ui)
57+
.run();
58+
}
59+
60+
#[derive(Component)]
61+
struct UiRoot;
62+
63+
fn setup(mut commands: Commands) {
64+
commands.spawn(Camera2d);
65+
commands.spawn(UiRoot);
66+
}
67+
68+
fn reconcile_ui(
69+
mut commands: Commands,
70+
ui_root_query: Single<Entity, With<UiRoot>>,
71+
state: Res<DemoWidgetStates>,
72+
) {
73+
// Reconcile the UI on every frame
74+
commands
75+
.entity(ui_root_query.entity())
76+
.reconcile_scene(demo_root(&state));
77+
}
78+
79+
fn demo_root(state: &DemoWidgetStates) -> impl Scene {
80+
let DemoWidgetStates {
81+
controlled_slider_value,
82+
hsl_color,
83+
} = *state;
84+
85+
bsn! {
86+
Node {
87+
width: Val::Percent(100.0),
88+
height: Val::Percent(100.0),
89+
align_items: AlignItems::Start,
90+
justify_content: JustifyContent::Start,
91+
display: Display::Flex,
92+
flex_direction: FlexDirection::Row,
93+
column_gap: Val::Px(10.0),
94+
}
95+
TabGroup
96+
ThemeBackgroundColor(tokens::WINDOW_BG)
97+
[
98+
Node {
99+
display: Display::Flex,
100+
flex_direction: FlexDirection::Column,
101+
align_items: AlignItems::Stretch,
102+
justify_content: JustifyContent::Start,
103+
padding: UiRect::all(Val::Px(8.0)),
104+
row_gap: Val::Px(8.0),
105+
width: Val::Percent(30.),
106+
min_width: Val::Px(200.),
107+
} [
108+
(
109+
:button(ButtonProps {
110+
on_click: callback(|_: In<Activate>| {
111+
info!("Button clicked!");
112+
}),
113+
..default()
114+
}) [(Text("Click me!") ThemedText)]
115+
),
116+
(
117+
:checkbox(CheckboxProps::default())
118+
[(Text("Checkbox") ThemedText)]
119+
),
120+
(
121+
Node {
122+
display: Display::Flex,
123+
flex_direction: FlexDirection::Column,
124+
row_gap: Val::Px(4.0),
125+
}
126+
CoreRadioGroup {
127+
// Update radio button states based on notification from radio group.
128+
on_change: callback(
129+
|ent: In<Activate>, q_radio: Query<Entity, With<CoreRadio>>, mut commands: Commands| {
130+
for radio in q_radio.iter() {
131+
if radio == ent.0.0 {
132+
commands.entity(radio).insert(Checked);
133+
} else {
134+
commands.entity(radio).remove::<Checked>();
135+
}
136+
}
137+
},
138+
),
139+
}
140+
[
141+
:radio [(Text("One") ThemedText)],
142+
:radio [(Text("Two") ThemedText)],
143+
]
144+
),
145+
:toggle_switch(ToggleSwitchProps::default()),
146+
Node {
147+
flex_direction: FlexDirection::Column,
148+
row_gap: Val::Px(4.0),
149+
} [
150+
// Uncontrolled slider (the slider widget owns the state)
151+
(
152+
:slider(SliderProps {
153+
max: 1.0,
154+
..default()
155+
})
156+
SliderStep(0.1)
157+
SliderPrecision(3)
158+
),
159+
// Controlled slider (the caller owns the state)
160+
(
161+
:slider(SliderProps {
162+
max: 100.0,
163+
on_change: callback(|change: In<ValueChange<f32>>, mut state: ResMut<DemoWidgetStates>| {
164+
state.controlled_slider_value = change.value;
165+
}),
166+
..default()
167+
})
168+
SliderValue(controlled_slider_value)
169+
SliderStep(10.)
170+
SliderPrecision(2)
171+
),
172+
(
173+
Node {
174+
justify_content: JustifyContent::SpaceBetween,
175+
} [
176+
Text("Hsl"),
177+
(color_swatch() BackgroundColor(hsl_color)),
178+
]
179+
),
180+
// Controlled color slider
181+
(
182+
:color_slider(
183+
ColorSliderProps {
184+
on_change: callback(
185+
|change: In<ValueChange<f32>>, mut color: ResMut<DemoWidgetStates>| {
186+
color.hsl_color.hue = change.value;
187+
},
188+
),
189+
channel: ColorChannel::HslHue
190+
}
191+
)
192+
SliderValue({hsl_color.hue})
193+
),
194+
]
195+
],
196+
]
197+
}
198+
}

0 commit comments

Comments
 (0)