Skip to content

Commit 7a9d5bb

Browse files
committed
refac Toggle light attachment button into specific component
1 parent 7e9dddd commit 7a9d5bb

2 files changed

Lines changed: 67 additions & 166 deletions

File tree

src/lib.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ pub(crate) mod structure;
1212
use crate::client::{poll_websocket_stream, setup_websocket_stream};
1313
use crate::io::load_crystal;
1414
use crate::structure::{update_crystal_system, UpdateStructure};
15-
use crate::ui::{camera_controls, refresh_atoms_system, setup_cameras, setup_scene};
15+
use crate::ui::reset_camera_button_interaction;
1616
use crate::ui::{
17-
handle_toggle_events, reset_camera_button_interaction, toggle_button, ToggleEvent, ToggleStates,
17+
camera_controls, refresh_atoms_system, setup_cameras, setup_scene, toggle_light_attachment,
1818
};
1919
use crate::ui::{setup_buttons, spawn_axis};
2020

@@ -36,9 +36,7 @@ pub fn run_app() {
3636
filter: "wgpu=error,bevy_render=info,bevy_ecs=trace".to_string(),
3737
custom_layer: |_| None,
3838
}))
39-
.init_resource::<ToggleStates>()
4039
.add_event::<UpdateStructure>()
41-
.add_event::<ToggleEvent>()
4240
.add_systems(Startup, load_crystal)
4341
.add_systems(Startup, setup_scene.after(load_crystal))
4442
.add_systems(
@@ -57,9 +55,8 @@ pub fn run_app() {
5755
poll_websocket_stream,
5856
update_crystal_system,
5957
refresh_atoms_system,
60-
toggle_button,
58+
toggle_light_attachment,
6159
reset_camera_button_interaction,
62-
handle_toggle_events,
6360
camera_controls,
6461
),
6562
)

src/ui.rs

Lines changed: 64 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![allow(clippy::needless_pass_by_value)]
2+
13
use std::collections::HashMap;
24

35
use bevy::input::mouse::{MouseMotion, MouseWheel};
@@ -14,45 +16,13 @@ const LAYER_CANVAS: RenderLayers = RenderLayers::layer(0);
1416
#[derive(Component)]
1517
pub(crate) struct MainCamera;
1618

17-
/// Identifier for a reusable toggle interaction.
18-
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
19-
enum ToggleId {
20-
LightAttachment,
21-
}
22-
23-
// struct AmbientLight
24-
25-
impl ToggleId {
26-
fn label(self, state: bool) -> &'static str {
27-
match (self, state) {
28-
(ToggleId::LightAttachment, true) => "Light: Attached",
29-
(ToggleId::LightAttachment, false) => "Light: Detached",
30-
}
31-
}
32-
}
33-
34-
// XXX: REVIEW: this is very oop like implementation, better?
35-
36-
/// Stores the current on/off state for each toggle.
37-
#[derive(Resource, Default)]
38-
pub(crate) struct ToggleStates {
39-
states: HashMap<ToggleId, bool>,
40-
}
41-
42-
impl ToggleStates {
43-
fn register(&mut self, id: ToggleId, initial_state: bool) {
44-
self.states.entry(id).or_insert(initial_state);
45-
}
46-
47-
fn get(&self, id: ToggleId) -> bool {
48-
self.states.get(&id).copied().unwrap_or(false)
49-
}
19+
/// Button that resets the camera to its original position/orientation.
20+
#[derive(Component)]
21+
pub(crate) struct ResetCameraButton;
5022

51-
fn toggle(&mut self, id: ToggleId) -> bool {
52-
let new_state = !self.get(id);
53-
self.states.insert(id, new_state);
54-
new_state
55-
}
23+
#[derive(Component)]
24+
pub(crate) struct LightAttachmentButton {
25+
attached: bool,
5626
}
5727

5828
/// Marks an entity that spawned the main camera.
@@ -63,25 +33,6 @@ pub(crate) struct MainCameraEntity(pub Entity);
6333
#[derive(Resource)]
6434
pub(crate) struct MainLightEntity(pub Entity);
6535

66-
/// Component identifying a toggle button instance.
67-
#[derive(Component)]
68-
pub(crate) struct ToggleButton {
69-
id: ToggleId,
70-
}
71-
72-
/// Component carried by the text to update when a toggle changes.
73-
#[derive(Component)]
74-
pub(crate) struct ToggleText {
75-
id: ToggleId,
76-
}
77-
78-
/// Event emitted whenever a toggle switches state.
79-
#[derive(Event)]
80-
pub struct ToggleEvent {
81-
id: ToggleId,
82-
pub state: bool,
83-
}
84-
8536
/// Stores camera orbit information and the original configuration so it can be restored.
8637
#[derive(Resource)]
8738
pub(crate) struct CameraRig {
@@ -93,10 +44,6 @@ pub(crate) struct CameraRig {
9344
initial_scale: Vec3,
9445
}
9546

96-
/// Button that resets the camera to its original position/orientation.
97-
#[derive(Component)]
98-
pub(crate) struct ResetCameraButton;
99-
10047
// System to set up the 3D scene
10148
pub(crate) fn setup_scene(
10249
mut commands: Commands,
@@ -148,11 +95,7 @@ pub(crate) fn setup_scene(
14895
}
14996

15097
// System to set up the camera
151-
pub fn setup_cameras(
152-
mut commands: Commands,
153-
mut toggle_states: ResMut<ToggleStates>,
154-
windows: Query<&Window>,
155-
) {
98+
pub fn setup_cameras(mut commands: Commands, windows: Query<&Window>) {
15699
let window = windows.single().unwrap();
157100
let viewport_size = UVec2::new(200, 200);
158101
let bottom_left_y = window.physical_height() - viewport_size.y - 10;
@@ -219,8 +162,6 @@ pub fn setup_cameras(
219162
))
220163
.id();
221164

222-
toggle_states.register(ToggleId::LightAttachment, true);
223-
224165
commands.insert_resource(MainCameraEntity(camera_entity));
225166
commands.insert_resource(MainLightEntity(light_entity));
226167
commands.insert_resource(CameraRig {
@@ -234,7 +175,7 @@ pub fn setup_cameras(
234175
}
235176

236177
// Setup minimal UI with toggle buttons
237-
pub fn setup_buttons(mut commands: Commands, toggle_states: Res<ToggleStates>) {
178+
pub fn setup_buttons(mut commands: Commands) {
238179
// buttons at top-left
239180
commands
240181
.spawn((
@@ -249,38 +190,29 @@ pub fn setup_buttons(mut commands: Commands, toggle_states: Res<ToggleStates>) {
249190
BackgroundColor(Color::NONE),
250191
))
251192
.with_children(|parent| {
252-
let mut spawn_button = |id: ToggleId| {
253-
let state = toggle_states.get(id);
254-
let label = id.label(state);
255-
256-
parent
257-
.spawn((
258-
Button,
259-
Node {
260-
padding: UiRect::axes(Val::Px(10.0), Val::Px(6.0)),
261-
border: UiRect::all(Val::Px(1.0)),
193+
parent
194+
.spawn((
195+
Button,
196+
Node {
197+
padding: UiRect::axes(Val::Px(10.0), Val::Px(6.0)),
198+
border: UiRect::all(Val::Px(1.0)),
199+
..default()
200+
},
201+
BorderColor(Color::srgb(0.3, 0.3, 0.3)),
202+
BackgroundColor(Color::srgb(0.15, 0.15, 0.15)),
203+
LightAttachmentButton { attached: false },
204+
))
205+
.with_children(|button| {
206+
button.spawn((
207+
Text::new("Light: Detached"),
208+
TextFont {
209+
font: default(),
210+
font_size: 12.0,
262211
..default()
263212
},
264-
BorderColor(Color::srgb(0.3, 0.3, 0.3)),
265-
BackgroundColor(Color::srgb(0.15, 0.15, 0.15)),
266-
ToggleButton { id },
267-
))
268-
.with_children(|button| {
269-
button.spawn((
270-
Text::new(label),
271-
TextFont {
272-
font: default(),
273-
font_size: 12.0,
274-
..default()
275-
},
276-
TextColor(Color::WHITE),
277-
ToggleText { id },
278-
));
279-
});
280-
};
281-
282-
let id = ToggleId::LightAttachment;
283-
spawn_button(id);
213+
TextColor(Color::WHITE),
214+
));
215+
});
284216

285217
parent
286218
.spawn((
@@ -491,33 +423,47 @@ pub(crate) fn camera_controls(
491423
}
492424
}
493425

494-
// Handle button interaction: toggle state and update label
495426
#[allow(clippy::type_complexity)]
496-
pub fn toggle_button(
427+
pub fn toggle_light_attachment(
428+
mut commands: Commands,
429+
light: Res<MainLightEntity>,
430+
camera: Res<MainCameraEntity>,
497431
mut interactions: Query<
498-
(&Interaction, &mut BackgroundColor, &ToggleButton),
499-
(Changed<Interaction>, With<Button>),
432+
(
433+
&Interaction,
434+
&mut BackgroundColor,
435+
&mut LightAttachmentButton,
436+
&Children,
437+
),
438+
(Changed<Interaction>, With<LightAttachmentButton>),
500439
>,
501-
mut texts: Query<(&ToggleText, &mut Text)>,
502-
mut toggle_states: ResMut<ToggleStates>,
503-
mut toggle_events: EventWriter<ToggleEvent>,
440+
mut texts: Query<&mut Text>,
504441
) {
505-
for (interaction, mut background, toggle_button) in &mut interactions {
506-
match *interaction {
442+
for (interaction, mut background, mut button_state, children) in &mut interactions {
443+
match interaction {
507444
Interaction::Pressed => {
508-
let new_state = toggle_states.toggle(toggle_button.id);
509-
toggle_events.write(ToggleEvent {
510-
id: toggle_button.id,
511-
state: new_state,
512-
});
445+
*background = BackgroundColor(Color::srgb(0.25, 0.25, 0.25));
513446

514-
for (text_marker, mut text) in &mut texts {
515-
if text_marker.id == toggle_button.id {
516-
text.0 = ToggleId::label(toggle_button.id, new_state).into();
447+
// Update the text inside the button
448+
for child in children.iter() {
449+
if let Ok(mut text) = texts.get_mut(child) {
450+
text.0 = if button_state.attached {
451+
"Light: Attached".into()
452+
} else {
453+
"Light: Detached".into()
454+
};
517455
}
518456
}
519457

520-
*background = BackgroundColor(Color::srgb(0.25, 0.25, 0.25));
458+
button_state.attached = !button_state.attached;
459+
460+
if button_state.attached {
461+
commands.entity(light.0).insert(ChildOf(camera.0));
462+
info!("Light attached to camera");
463+
} else {
464+
commands.entity(light.0).remove::<ChildOf>();
465+
info!("Light detached from camera");
466+
}
521467
}
522468
Interaction::Hovered => {
523469
*background = BackgroundColor(Color::srgb(0.2, 0.2, 0.2));
@@ -534,7 +480,7 @@ pub fn toggle_button(
534480
pub fn reset_camera_button_interaction(
535481
mut interactions: Query<
536482
(&Interaction, &mut BackgroundColor),
537-
(Changed<Interaction>, With<Button>, With<ResetCameraButton>),
483+
(Changed<Interaction>, With<ResetCameraButton>),
538484
>,
539485
camera_entity: Option<Res<MainCameraEntity>>,
540486
mut camera_query: Query<&mut Transform, With<Camera3d>>,
@@ -568,45 +514,3 @@ pub fn reset_camera_button_interaction(
568514
}
569515
}
570516
}
571-
572-
// Respond to toggle events by applying the desired world changes
573-
pub fn handle_toggle_events(
574-
mut toggle_events: EventReader<ToggleEvent>,
575-
camera_entity: Option<Res<MainCameraEntity>>,
576-
light_entity: Option<Res<MainLightEntity>>,
577-
global_light_xforms: Query<&GlobalTransform, With<DirectionalLight>>,
578-
mut commands: Commands,
579-
) {
580-
let Some(camera_entity) = camera_entity else {
581-
return;
582-
};
583-
let Some(light_entity) = light_entity else {
584-
return;
585-
};
586-
587-
// XXX: only single event at the moment
588-
for event in toggle_events.read() {
589-
match event.id {
590-
ToggleId::LightAttachment => {
591-
if event.state {
592-
// Re-attach to camera; use default local transform so light follows camera orientation.
593-
commands
594-
.entity(light_entity.0)
595-
.insert(ChildOf(camera_entity.0))
596-
.insert(Transform::default());
597-
} else if let Ok(global_transform) = global_light_xforms.get(light_entity.0) {
598-
let (scale, rotation, translation) =
599-
global_transform.to_scale_rotation_translation();
600-
commands.entity(light_entity.0).remove::<ChildOf>();
601-
commands.entity(light_entity.0).insert(Transform {
602-
translation,
603-
rotation,
604-
scale,
605-
});
606-
} else {
607-
commands.entity(light_entity.0).remove::<ChildOf>();
608-
}
609-
}
610-
}
611-
}
612-
}

0 commit comments

Comments
 (0)