Skip to main content

control_events/
control_events.rs

1use beuvy_runtime::button::ButtonClickMessage;
2use beuvy_runtime::input::{InputType, InputValueChangedMessage};
3use beuvy_runtime::text::FontResource;
4use beuvy_runtime::text::set_plain_text;
5use beuvy_runtime::{
6    AddButton, AddInput, AddSelect, AddSelectOption, AddText, SelectValueChangedMessage,
7    UiKitPlugin,
8};
9use bevy::prelude::*;
10use bevy::text::TextLayout;
11
12#[derive(Component)]
13struct EventLogText;
14
15#[derive(Resource, Default)]
16struct EventLog {
17    entries: Vec<String>,
18}
19
20fn main() {
21    App::new()
22        .init_resource::<EventLog>()
23        .add_plugins(DefaultPlugins.set(WindowPlugin {
24            primary_window: Some(Window {
25                title: "beuvy-runtime control events".to_string(),
26                resolution: (1180, 720).into(),
27                ..default()
28            }),
29            ..default()
30        }))
31        .add_plugins(UiKitPlugin)
32        .add_systems(Startup, setup)
33        .add_systems(
34            Update,
35            (
36                record_button_events,
37                record_input_events,
38                record_select_events,
39            ),
40        )
41        .run();
42}
43
44fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
45    commands.spawn(Camera2d);
46    commands.insert_resource(FontResource::from_handle(
47        asset_server.load("fonts/SarasaFixedSC-Regular.ttf"),
48    ));
49
50    commands
51        .spawn((
52            Node {
53                width: Val::Percent(100.0),
54                height: Val::Percent(100.0),
55                padding: UiRect::all(Val::Px(24.0)),
56                column_gap: Val::Px(20.0),
57                overflow: Overflow::visible(),
58                ..default()
59            },
60            BackgroundColor(Color::srgb_u8(248, 250, 252)),
61        ))
62        .with_children(|parent| {
63            let mut left = parent.spawn(Node {
64                width: Val::Px(420.0),
65                padding: UiRect::all(Val::Px(16.0)),
66                row_gap: Val::Px(12.0),
67                flex_direction: FlexDirection::Column,
68                overflow: Overflow::visible(),
69                border_radius: BorderRadius::all(Val::Px(12.0)),
70                ..default()
71            });
72            left.insert(BorderColor::all(Color::srgb_u8(203, 213, 225)));
73            left.insert(BackgroundColor(Color::WHITE));
74            left.with_children(|parent| {
75                spawn_text(
76                    parent,
77                    "Interact with these controls",
78                    18.0,
79                    Color::srgb_u8(15, 23, 42),
80                );
81                parent.spawn(AddInput {
82                    name: "display_name".to_string(),
83                    placeholder: "Type a display name".to_string(),
84                    size_chars: Some(24),
85                    ..default()
86                });
87                parent.spawn(AddInput {
88                    name: "zoom".to_string(),
89                    input_type: InputType::Range,
90                    value: "35".to_string(),
91                    min: Some(0.0),
92                    max: Some(100.0),
93                    step: Some(5.0),
94                    ..default()
95                });
96                parent.spawn(AddSelect {
97                    name: "theme".to_string(),
98                    value: "light".to_string(),
99                    options: vec![
100                        option("theme_light", "light", "Light"),
101                        option("theme_dark", "dark", "Dark"),
102                        option("theme_hc", "high-contrast", "High Contrast"),
103                    ],
104                    ..default()
105                });
106                parent
107                    .spawn(Node {
108                        column_gap: Val::Px(10.0),
109                        flex_wrap: FlexWrap::Wrap,
110                        ..default()
111                    })
112                    .with_children(|parent| {
113                        parent.spawn(AddButton {
114                            name: "save".to_string(),
115                            text: "Save".to_string(),
116                            class: Some("button-root w-[120px]".to_string()),
117                            ..default()
118                        });
119                        parent.spawn(AddButton {
120                            name: "reset".to_string(),
121                            text: "Reset".to_string(),
122                            class: Some("button-root w-[120px]".to_string()),
123                            ..default()
124                        });
125                    });
126            });
127
128            let mut right = parent.spawn(Node {
129                flex_grow: 1.0,
130                padding: UiRect::all(Val::Px(16.0)),
131                row_gap: Val::Px(10.0),
132                flex_direction: FlexDirection::Column,
133                overflow: Overflow::visible(),
134                border_radius: BorderRadius::all(Val::Px(12.0)),
135                ..default()
136            });
137            right.insert(BorderColor::all(Color::srgb_u8(203, 213, 225)));
138            right.insert(BackgroundColor(Color::WHITE));
139            right.with_children(|parent| {
140                spawn_text(parent, "Event log", 18.0, Color::srgb_u8(15, 23, 42));
141                parent.spawn((
142                    EventLogText,
143                    Node {
144                        width: Val::Percent(100.0),
145                        ..default()
146                    },
147                    TextLayout::default(),
148                    AddText {
149                        text: "No events yet.\nClick, type, drag, or select.".to_string(),
150                        size: 14.0,
151                        color: Color::srgb_u8(71, 85, 105),
152                        ..default()
153                    },
154                ));
155            });
156        });
157}
158
159fn record_button_events(
160    mut commands: Commands,
161    mut events: MessageReader<ButtonClickMessage>,
162    mut log: ResMut<EventLog>,
163    labels: Query<Entity, With<EventLogText>>,
164) {
165    let mut changed = false;
166    for event in events.read() {
167        log.entries.push(format!(
168            "button:{} on {:?}",
169            event.button.name, event.entity
170        ));
171        changed = true;
172    }
173    if changed {
174        sync_event_log_text(&mut commands, &log, &labels);
175    }
176}
177
178fn record_input_events(
179    mut commands: Commands,
180    mut events: MessageReader<InputValueChangedMessage>,
181    mut log: ResMut<EventLog>,
182    labels: Query<Entity, With<EventLogText>>,
183) {
184    let mut changed = false;
185    for event in events.read() {
186        log.entries
187            .push(format!("input:{} = {}", event.name, event.value));
188        changed = true;
189    }
190    if changed {
191        sync_event_log_text(&mut commands, &log, &labels);
192    }
193}
194
195fn record_select_events(
196    mut commands: Commands,
197    mut events: MessageReader<SelectValueChangedMessage>,
198    mut log: ResMut<EventLog>,
199    labels: Query<Entity, With<EventLogText>>,
200) {
201    let mut changed = false;
202    for event in events.read() {
203        log.entries
204            .push(format!("select:{} = {}", event.name, event.value));
205        changed = true;
206    }
207    if changed {
208        sync_event_log_text(&mut commands, &log, &labels);
209    }
210}
211
212fn sync_event_log_text(
213    commands: &mut Commands,
214    log: &EventLog,
215    labels: &Query<Entity, With<EventLogText>>,
216) {
217    let Some(entity) = labels.iter().next() else {
218        return;
219    };
220    let text = log
221        .entries
222        .iter()
223        .rev()
224        .take(12)
225        .cloned()
226        .collect::<Vec<_>>()
227        .join("\n");
228    set_plain_text(commands, entity, text);
229}
230
231fn option(name: &str, value: &str, text: &str) -> AddSelectOption {
232    AddSelectOption {
233        name: name.to_string(),
234        value: value.to_string(),
235        text: text.to_string(),
236        localized_text: None,
237        localized_text_format: None,
238        disabled: false,
239    }
240}
241
242fn spawn_text(parent: &mut ChildSpawnerCommands, text: &str, size: f32, color: Color) {
243    parent.spawn((
244        Node {
245            width: Val::Percent(100.0),
246            ..default()
247        },
248        TextLayout::default(),
249        AddText {
250            text: text.to_string(),
251            size,
252            color,
253            ..default()
254        },
255    ));
256}