spitfire_gui/
interactions.rs1use raui_core::{
2 application::Application,
3 interactive::{
4 InteractionsEngine,
5 default_interactions_engine::{
6 DefaultInteractionsEngine, DefaultInteractionsEngineResult, Interaction, PointerButton,
7 },
8 },
9 layout::CoordsMapping,
10 widget::{
11 component::interactive::navigation::{NavJump, NavScroll, NavSignal, NavTextChange},
12 utils::Vec2,
13 },
14};
15use spitfire_input::{ArrayInputCombinator, InputActionRef, InputCharactersRef, InputCombinator};
16
17const ZERO_THRESHOLD: f32 = 1.0e-6;
18
19#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum GuiCardinalDirection {
21 #[default]
22 None,
23 Up,
24 Down,
25 Left,
26 Right,
27}
28
29impl GuiCardinalDirection {
30 pub fn new(direction: [f32; 2], mut threshold: f32) -> Self {
31 threshold = threshold.max(ZERO_THRESHOLD);
32 let length = (direction[0] * direction[0] + direction[1] * direction[1]).sqrt();
33 if length < threshold {
34 GuiCardinalDirection::None
35 } else {
36 let normalized = [direction[0] / length, direction[1] / length];
37 let abs_x = normalized[0].abs();
38 let abs_y = normalized[1].abs();
39 if abs_x > abs_y {
40 if normalized[0] > 0.0 {
41 GuiCardinalDirection::Right
42 } else {
43 GuiCardinalDirection::Left
44 }
45 } else {
46 if normalized[1] > 0.0 {
47 GuiCardinalDirection::Down
48 } else {
49 GuiCardinalDirection::Up
50 }
51 }
52 }
53 }
54}
55
56pub struct GuiDefaultDirectionCombinator(pub InputCombinator<GuiCardinalDirection>);
57
58impl GuiDefaultDirectionCombinator {
59 pub fn new(
60 left: InputActionRef,
61 right: InputActionRef,
62 up: InputActionRef,
63 down: InputActionRef,
64 threshold: f32,
65 ) -> Self {
66 Self(InputCombinator::new(move || {
67 let direction = [
68 if left.get().is_pressed() {
69 -1.0
70 } else if right.get().is_pressed() {
71 1.0
72 } else {
73 0.0
74 },
75 if up.get().is_pressed() {
76 1.0
77 } else if down.get().is_pressed() {
78 -1.0
79 } else {
80 0.0
81 },
82 ];
83 GuiCardinalDirection::new(direction, threshold)
84 }))
85 }
86
87 pub fn get(&self) -> GuiCardinalDirection {
88 self.0.get()
89 }
90}
91
92#[derive(Default)]
93pub struct GuiInteractionsInputs {
94 pub trigger: InputActionRef,
95 pub context: InputActionRef,
96 pub cancel: InputActionRef,
97 pub direction: InputCombinator<GuiCardinalDirection>,
98 pub prev: InputActionRef,
99 pub next: InputActionRef,
100 pub text: InputCharactersRef,
101 pub text_start: InputActionRef,
102 pub text_end: InputActionRef,
103 pub text_delete_left: InputActionRef,
104 pub text_delete_right: InputActionRef,
105 pub pointer_position: ArrayInputCombinator<2>,
106 pub pointer_trigger: InputActionRef,
107 pub pointer_context: InputActionRef,
108 pub scroll: ArrayInputCombinator<2>,
109}
110
111#[derive(Default)]
112pub struct GuiInteractionsEngine {
113 pub inputs: GuiInteractionsInputs,
114 pub engine: DefaultInteractionsEngine,
115 cached_pointer_position: Vec2,
116}
117
118impl GuiInteractionsEngine {
119 pub fn maintain(&mut self, mapping: &CoordsMapping) {
120 if self.engine.focused_text_input().is_some() {
121 if let Some(mut text) = self.inputs.text.write() {
122 for character in text.take().chars() {
123 self.engine
124 .interact(Interaction::Navigate(NavSignal::TextChange(
125 NavTextChange::InsertCharacter(character),
126 )));
127 }
128 }
129 match self.inputs.direction.get() {
130 GuiCardinalDirection::Left => {
131 self.engine
132 .interact(Interaction::Navigate(NavSignal::TextChange(
133 NavTextChange::MoveCursorLeft,
134 )));
135 }
136 GuiCardinalDirection::Right => {
137 self.engine
138 .interact(Interaction::Navigate(NavSignal::TextChange(
139 NavTextChange::MoveCursorRight,
140 )));
141 }
142 _ => {}
143 }
144 if self.inputs.text_start.get().is_pressed() {
145 self.engine
146 .interact(Interaction::Navigate(NavSignal::TextChange(
147 NavTextChange::MoveCursorStart,
148 )));
149 }
150 if self.inputs.text_end.get().is_pressed() {
151 self.engine
152 .interact(Interaction::Navigate(NavSignal::TextChange(
153 NavTextChange::MoveCursorEnd,
154 )));
155 }
156 if self.inputs.text_delete_left.get().is_pressed() {
157 self.engine
158 .interact(Interaction::Navigate(NavSignal::TextChange(
159 NavTextChange::DeleteLeft,
160 )));
161 }
162 if self.inputs.text_delete_right.get().is_pressed() {
163 self.engine
164 .interact(Interaction::Navigate(NavSignal::TextChange(
165 NavTextChange::DeleteRight,
166 )));
167 }
168 if self.inputs.trigger.get().is_pressed() {
169 self.engine
170 .interact(Interaction::Navigate(NavSignal::TextChange(
171 NavTextChange::NewLine,
172 )));
173 }
174 } else {
175 match self.inputs.direction.get() {
176 GuiCardinalDirection::Up => {
177 self.engine.interact(Interaction::Navigate(NavSignal::Up));
178 }
179 GuiCardinalDirection::Down => {
180 self.engine.interact(Interaction::Navigate(NavSignal::Down));
181 }
182 GuiCardinalDirection::Left => {
183 self.engine.interact(Interaction::Navigate(NavSignal::Left));
184 }
185 GuiCardinalDirection::Right => {
186 self.engine
187 .interact(Interaction::Navigate(NavSignal::Right));
188 }
189 _ => {}
190 }
191 if self.inputs.prev.get().is_pressed() {
192 self.engine.interact(Interaction::Navigate(NavSignal::Prev));
193 }
194 if self.inputs.next.get().is_pressed() {
195 self.engine.interact(Interaction::Navigate(NavSignal::Next));
196 }
197 let trigger = self.inputs.trigger.get();
198 if trigger.is_pressed() {
199 self.engine
200 .interact(Interaction::Navigate(NavSignal::Accept(true)));
201 }
202 if trigger.is_released() {
203 self.engine
204 .interact(Interaction::Navigate(NavSignal::Accept(false)));
205 }
206 let context = self.inputs.context.get();
207 if context.is_pressed() {
208 self.engine
209 .interact(Interaction::Navigate(NavSignal::Context(true)));
210 }
211 if context.is_released() {
212 self.engine
213 .interact(Interaction::Navigate(NavSignal::Context(false)));
214 }
215 let cancel = self.inputs.cancel.get();
216 if cancel.is_pressed() {
217 self.engine
218 .interact(Interaction::Navigate(NavSignal::Cancel(true)));
219 }
220 if cancel.is_released() {
221 self.engine
222 .interact(Interaction::Navigate(NavSignal::Cancel(false)));
223 }
224 }
225 let pointer_position = {
226 let [x, y] = self.inputs.pointer_position.get();
227 let position = mapping.real_to_virtual_vec2(Vec2 { x, y }, false);
228 if (position.x - self.cached_pointer_position.x).abs() > ZERO_THRESHOLD
229 || (position.y - self.cached_pointer_position.y).abs() > ZERO_THRESHOLD
230 {
231 self.cached_pointer_position = position;
232 self.engine.interact(Interaction::PointerMove(position));
233 }
234 position
235 };
236 let pointer_trigger = self.inputs.pointer_trigger.get();
237 let pointer_context = self.inputs.pointer_context.get();
238 if pointer_trigger.is_pressed() {
239 self.engine.interact(Interaction::PointerDown(
240 PointerButton::Trigger,
241 pointer_position,
242 ));
243 }
244 if pointer_trigger.is_released() {
245 self.engine.interact(Interaction::PointerUp(
246 PointerButton::Trigger,
247 pointer_position,
248 ));
249 }
250 if pointer_context.is_pressed() {
251 self.engine.interact(Interaction::PointerDown(
252 PointerButton::Context,
253 pointer_position,
254 ));
255 }
256 if pointer_context.is_released() {
257 self.engine.interact(Interaction::PointerUp(
258 PointerButton::Context,
259 pointer_position,
260 ));
261 }
262 {
263 let [x, y] = self.inputs.scroll.get();
264 if x.abs() > ZERO_THRESHOLD || y.abs() > ZERO_THRESHOLD {
265 self.engine
266 .interact(Interaction::Navigate(NavSignal::Jump(NavJump::Scroll(
267 NavScroll::Units(Vec2 { x: -x, y: -y }, true),
268 ))));
269 }
270 }
271 }
272}
273
274impl InteractionsEngine<DefaultInteractionsEngineResult, ()> for GuiInteractionsEngine {
275 fn perform_interactions(
276 &mut self,
277 app: &mut Application,
278 ) -> Result<DefaultInteractionsEngineResult, ()> {
279 self.engine.perform_interactions(app)
280 }
281}