bevy_immediate/ui/
activated.rs1use bevy_ecs::{
2 entity::Entity,
3 lifecycle,
4 observer::On,
5 query::Has,
6 system::{Commands, Query, ResMut},
7};
8use bevy_input::{
9 ButtonState,
10 keyboard::{KeyCode, KeyboardInput},
11};
12use bevy_input_focus::FocusedInput;
13use bevy_picking::events::{Click, Pointer};
14use bevy_platform::collections::HashSet;
15use bevy_ui::InteractionDisabled;
16
17use crate::{CapSet, ImmCapAccessRequests, ImmCapability, ImmEntity, ImplCap};
18
19#[derive(Clone, Copy, PartialEq, Eq, Hash)]
21pub struct CapabilityUiActivated;
22
23impl ImmCapability for CapabilityUiActivated {
24 fn build<CM: CapSet>(app: &mut bevy_app::App, cap_req: &mut ImmCapAccessRequests<CM>) {
25 if !app.is_plugin_added::<TrackClickedPlugin>() {
26 app.add_plugins(TrackClickedPlugin);
27 }
28
29 cap_req.request_component_read::<TrackActivated>(app.world_mut());
30 cap_req.request_resource_read::<TrackActivetedEntitiesResource>(app.world_mut());
31 }
32}
33
34pub trait ImmUiActivated {
36 fn activated(&mut self) -> bool;
38}
39
40impl<Cap: CapSet> ImmUiActivated for ImmEntity<'_, '_, '_, Cap>
41where
42 Cap: ImplCap<CapabilityUiActivated>,
43{
44 fn activated(&mut self) -> bool {
45 'correct: {
46 if !self.cap_entity_contains::<TrackActivated>() {
47 break 'correct;
48 }
49
50 let activated = self
51 .cap_get_resource::<TrackActivetedEntitiesResource>()
52 .expect("Capability should be available")
53 .into_inner()
54 .activated
55 .contains(&self.entity());
56 return activated;
57 }
58
59 self.entity_commands().insert_if_new(TrackActivated);
61 false
62 }
63}
64
65pub struct TrackClickedPlugin;
69
70impl bevy_app::Plugin for TrackClickedPlugin {
71 fn build(&self, app: &mut bevy_app::App) {
72 app.insert_resource(TrackActivetedEntitiesResource::default());
73 app.add_systems(bevy_app::First, reset_activated);
74 app.add_observer(track_clicked_insert);
75 }
76}
77
78fn track_clicked_insert(trigger: On<lifecycle::Add, TrackActivated>, mut commands: Commands) {
80 let entity = trigger.event().entity;
81 commands
82 .entity(entity)
83 .observe(button_on_key_event)
84 .observe(button_on_pointer_click);
85}
86
87#[derive(bevy_ecs::component::Component, Default)]
89#[component(storage = "SparseSet")]
90pub struct TrackActivated;
91
92#[derive(bevy_ecs::resource::Resource, Default)]
93struct TrackActivetedEntitiesResource {
94 pub activated: HashSet<Entity>,
95}
96
97fn reset_activated(mut res: ResMut<TrackActivetedEntitiesResource>) {
98 res.activated.clear();
99}
100
101fn button_on_key_event(
107 event: On<FocusedInput<KeyboardInput>>,
108 q_state: Query<Has<InteractionDisabled>>,
109 mut activated: ResMut<TrackActivetedEntitiesResource>,
110) {
111 if let Ok(disabled) = q_state.get(event.focused_entity)
112 && !disabled
113 {
114 let input_event = &event.input;
115 if !input_event.repeat
116 && input_event.state == ButtonState::Pressed
117 && (input_event.key_code == KeyCode::Enter || input_event.key_code == KeyCode::Space)
118 {
119 activated.activated.insert(event.focused_entity);
120 }
121 }
122}
123
124fn button_on_pointer_click(
125 mut click: On<Pointer<Click>>,
126 mut q_state: Query<Has<InteractionDisabled>>,
127 mut activated: ResMut<TrackActivetedEntitiesResource>,
128) {
129 if let Ok(disabled) = q_state.get_mut(click.entity) {
131 click.propagate(false);
132 if !disabled {
133 activated.activated.insert(click.entity);
134 }
135 }
136}