1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
//! UI feedback: a message for every focus move and activation, for the host to turn into sound.
use bevy::input_focus::InputFocus;
use bevy::prelude::*;
use bevy::ui_widgets::{Activate, ValueChange};
use crate::skin::Role;
/// Something a player did to a widget, written the frame it happens. The crate plays no sound: read
/// these and play your own, so every action answers on a second channel.
///
/// ```
/// # use bevy::prelude::*;
/// # use bevy_gooey::*;
/// fn click(mut feedback: MessageReader<UiFeedback>) {
/// for f in feedback.read() {
/// match f.kind {
/// UiFeedbackKind::Focus => { /* a soft tick */ }
/// UiFeedbackKind::Activate => { /* a firm click */ }
/// }
/// }
/// }
/// ```
#[derive(Message, Clone, Copy, Debug, PartialEq, Eq)]
pub struct UiFeedback {
/// The widget.
pub entity: Entity,
/// What happened to it.
pub kind: UiFeedbackKind,
}
/// What happened, in a [`UiFeedback`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
#[reflect(Debug, PartialEq, Clone)]
pub enum UiFeedbackKind {
/// Focus moved onto the widget.
Focus,
/// The widget was activated (a button clicked, Enter, Space or the pad's South button), a
/// toggle flipped, or a choice changed its option.
Activate,
}
/// Writes [`UiFeedbackKind::Activate`] when a button is activated. A global observer.
pub(crate) fn feedback_on_activate(
event: On<Activate>,
roles: Query<&Role>,
mut out: MessageWriter<UiFeedback>,
) {
if matches!(
roles.get(event.entity),
Ok(Role::Button
| Role::ToastAction
| Role::StatusItem
| Role::MenuTitle
| Role::MenuItem
| Role::PaletteRow)
) {
out.write(UiFeedback {
entity: event.entity,
kind: UiFeedbackKind::Activate,
});
}
}
/// Writes [`UiFeedbackKind::Activate`] when a toggle flips. A global observer.
pub(crate) fn feedback_on_toggle(
event: On<ValueChange<bool>>,
roles: Query<&Role>,
mut out: MessageWriter<UiFeedback>,
) {
if roles.get(event.source) == Ok(&Role::Toggle) {
out.write(UiFeedback {
entity: event.source,
kind: UiFeedbackKind::Activate,
});
}
}
/// Writes [`UiFeedbackKind::Activate`] when a choice changes its option, by a step, a jump or a
/// cycle, or a tabs widget turns its page: once per change, never for a press at an end that
/// changes nothing. A global observer.
pub(crate) fn feedback_on_choice(
event: On<ValueChange<usize>>,
roles: Query<&Role>,
mut out: MessageWriter<UiFeedback>,
) {
if matches!(
roles.get(event.source),
Ok(Role::Choice | Role::Tabs | Role::ToggleGroup)
) {
out.write(UiFeedback {
entity: event.source,
kind: UiFeedbackKind::Activate,
});
}
}
/// Writes [`UiFeedbackKind::Focus`] when focus moves onto a button, toggle, slider, choice or tab.
/// Runs in [`crate::GooeySystems::Navigate`], after [`crate::navigate_by_input`].
pub fn feedback_on_focus(
focus: Res<InputFocus>,
roles: Query<&Role>,
mut out: MessageWriter<UiFeedback>,
) {
if !focus.is_changed() {
return;
}
if let Some(entity) = focus.get()
&& matches!(
roles.get(entity),
Ok(Role::Button
| Role::ToastAction
| Role::Toggle
| Role::Slider
| Role::Choice
| Role::Binding
| Role::Tab
| Role::GroupOption
| Role::StatusItem
| Role::TextField
| Role::MenuTitle
| Role::MenuItem
| Role::SplitDivider)
)
{
out.write(UiFeedback {
entity,
kind: UiFeedbackKind::Focus,
});
}
}