oxygengine_user_interface/
system.rs1use crate::{
2 component::UserInterfaceView,
3 resource::{input_mappings::*, ApplicationData, UserInterface},
4 ui_theme_asset_protocol::UiThemeAsset,
5 FeedProcessContext,
6};
7use core::{
8 app::AppLifeCycle,
9 assets::{asset::AssetId, database::AssetsDatabase},
10 ecs::{AccessType, Comp, ResQuery, ResQueryItem, Universe, UnsafeRef, UnsafeScope, WorldRef},
11};
12use input::{
13 component::InputStackInstance,
14 resources::stack::{InputStack, InputStackListener},
15};
16use raui_core::{
17 application::{Application, ProcessContext},
18 interactive::default_interactions_engine::{Interaction, PointerButton},
19 layout::default_layout_engine::DefaultLayoutEngine,
20 widget::{
21 component::interactive::navigation::{NavSignal, NavTextChange},
22 setup as core_setup,
23 utils::Vec2,
24 },
25};
26use raui_material::{setup as material_setup, theme::ThemeProps};
27use std::collections::HashMap;
28
29#[derive(Default)]
30pub struct UserInterfaceSystemCache {
31 themes_cache: HashMap<String, ThemeProps>,
32 themes_table: HashMap<AssetId, String>,
33}
34
35impl UserInterfaceSystemCache {
36 pub fn theme(&self, id: &str) -> Option<&ThemeProps> {
37 self.themes_cache.get(id)
38 }
39}
40
41pub type UserInterfaceSystemResources<'a, Q> = (
42 Q,
43 WorldRef,
44 &'a AppLifeCycle,
45 &'a AssetsDatabase,
46 &'a InputStack,
47 &'a mut UserInterface,
48 &'a mut UserInterfaceSystemCache,
49 Comp<&'a mut UserInterfaceView>,
50 Comp<&'a InputStackInstance>,
51);
52
53pub fn user_interface_system<Q>(universe: &mut Universe)
54where
55 Q: AccessType + ResQuery + 'static,
56 ResQueryItem<Q>: FeedProcessContext,
57{
58 let mut cache = universe.expect_resource_mut::<UserInterfaceSystemCache>();
59 {
60 let assets = universe.expect_resource::<AssetsDatabase>();
61 for id in assets.lately_loaded_protocol("ui-theme") {
62 let id = *id;
63 let asset = assets
64 .asset_by_id(id)
65 .expect("trying to use not loaded UI theme asset");
66 let path = asset.path().to_owned();
67 let asset = asset
68 .get::<UiThemeAsset>()
69 .expect("trying to use non UI theme asset");
70 cache.themes_cache.insert(path.clone(), asset.get().props());
71 cache.themes_table.insert(id, path);
72 }
73 for id in assets.lately_unloaded_protocol("ui-theme") {
74 if let Some(path) = cache.themes_table.remove(id) {
75 cache.themes_cache.remove(&path);
76 }
77 }
78 }
79
80 let mut ui = universe.expect_resource_mut::<UserInterface>();
81 let scope = UnsafeScope;
82 let meta = {
83 let world = universe.world();
84 let input_stack = universe.expect_resource::<InputStack>();
85
86 ui.data.retain(|k, _| {
87 world
88 .query::<&UserInterfaceView>()
89 .iter()
90 .any(|(_, v)| k == v.app_id())
91 });
92
93 for (_, (view, input)) in world
94 .query::<(&mut UserInterfaceView, Option<&InputStackInstance>)>()
95 .iter()
96 {
97 if !ui.data.contains_key(view.app_id()) {
98 let mut application = Application::new();
99 application.setup(core_setup);
100 application.setup(material_setup);
101 if let Some(setup_application) = ui.setup_application {
102 setup_application(&mut application);
103 }
104 ui.data.insert(
105 view.app_id().to_owned(),
106 ApplicationData {
107 application,
108 interactions: Default::default(),
109 coords_mapping: Default::default(),
110 signals_received: Default::default(),
111 },
112 );
113 }
114
115 if let Some(listener) = input
116 .and_then(|input| input.as_listener())
117 .and_then(|id| input_stack.listener(id))
118 {
119 apply_inputs(
120 ui.get_mut(view.app_id()).unwrap(),
121 listener,
122 &mut view.last_pointer_pos,
123 );
124 }
125
126 if view.dirty {
127 view.dirty = false;
128 let app = ui.application_mut(view.app_id()).unwrap();
129 let mut root = app
130 .deserialize_node(view.root().clone())
131 .expect("Could not deserialize UI node");
132 if let Some(theme) = view.theme() {
133 if let Some(theme) = cache.themes_cache.get(theme) {
134 if let Some(p) = root.shared_props_mut() {
135 p.write(theme.clone());
136 }
137 }
138 }
139 app.apply(root);
140 }
141 }
142
143 let result = world
144 .query::<&UserInterfaceView>()
145 .iter()
146 .map(|(_, v)| unsafe { UnsafeRef::upgrade(&scope, v) })
147 .collect::<Vec<_>>();
148 result
149 };
150
151 let dt = universe
152 .expect_resource::<AppLifeCycle>()
153 .delta_time_seconds();
154 let mut context = ProcessContext::new();
155 let extras = universe.query_resources::<Q>();
156 extras.feed_process_context(&mut context);
157
158 for view in meta {
159 if let Some(data) = ui.data.get_mut(unsafe { view.read().app_id() }) {
160 data.application.animations_delta_time = dt;
161 data.application.process_with_context(&mut context);
162 data.application
163 .layout(&data.coords_mapping, &mut DefaultLayoutEngine)
164 .unwrap_or_default();
165 data.interactions.deselect_when_no_button_found =
166 unsafe { view.read().deselect_when_no_button_found };
167 let _ = data.application.interact(&mut data.interactions);
168 data.signals_received = data.application.consume_signals();
169 }
170 }
171}
172
173fn apply_inputs(
174 data: &mut ApplicationData,
175 listener: &InputStackListener,
176 last_pointer_pos: &mut Vec2,
177) {
178 let pointer_pos = listener.axes_state_or_default::<2>(NAV_POINTER_AXES);
179 let pointer_pos = Vec2 {
180 x: pointer_pos[0],
181 y: pointer_pos[1],
182 };
183 let pointer_moved = (pointer_pos.x - last_pointer_pos.x).abs() > 1.0e-6
184 || (pointer_pos.y - last_pointer_pos.y).abs() > 1.0e-6;
185 *last_pointer_pos = pointer_pos;
186 let pointer_pos = data.coords_mapping.real_to_virtual_vec2(pointer_pos, false);
187 if pointer_moved {
188 data.interactions
189 .interact(Interaction::PointerMove(pointer_pos));
190 }
191
192 let trigger = listener.trigger_state_or_default(NAV_POINTER_ACTION_TRIGGER);
193 if trigger.is_pressed() {
194 data.interactions.interact(Interaction::PointerDown(
195 PointerButton::Trigger,
196 pointer_pos,
197 ));
198 } else if trigger.is_released() {
199 data.interactions
200 .interact(Interaction::PointerUp(PointerButton::Trigger, pointer_pos));
201 }
202
203 let trigger = listener.trigger_state_or_default(NAV_POINTER_CONTEXT_TRIGGER);
204 if trigger.is_pressed() {
205 data.interactions.interact(Interaction::PointerDown(
206 PointerButton::Context,
207 pointer_pos,
208 ));
209 } else if trigger.is_released() {
210 data.interactions
211 .interact(Interaction::PointerUp(PointerButton::Context, pointer_pos));
212 }
213
214 for c in listener.text_state_or_default().chars() {
215 if !c.is_control() {
216 data.interactions
217 .interact(Interaction::Navigate(NavSignal::TextChange(
218 NavTextChange::InsertCharacter(c),
219 )));
220 } else if c == '\n' || c == '\r' {
221 data.interactions
222 .interact(Interaction::Navigate(NavSignal::TextChange(
223 NavTextChange::NewLine,
224 )));
225 }
226 }
227 if listener
228 .trigger_state_or_default(NAV_TEXT_MOVE_CURSOR_LEFT_TRIGGER)
229 .is_pressed()
230 {
231 data.interactions
232 .interact(Interaction::Navigate(NavSignal::TextChange(
233 NavTextChange::MoveCursorLeft,
234 )));
235 }
236 if listener
237 .trigger_state_or_default(NAV_TEXT_MOVE_CURSOR_RIGHT_TRIGGER)
238 .is_pressed()
239 {
240 data.interactions
241 .interact(Interaction::Navigate(NavSignal::TextChange(
242 NavTextChange::MoveCursorRight,
243 )));
244 }
245 if listener
246 .trigger_state_or_default(NAV_TEXT_MOVE_CURSOR_START_TRIGGER)
247 .is_pressed()
248 {
249 data.interactions
250 .interact(Interaction::Navigate(NavSignal::TextChange(
251 NavTextChange::MoveCursorStart,
252 )));
253 }
254 if listener
255 .trigger_state_or_default(NAV_TEXT_MOVE_CURSOR_END_TRIGGER)
256 .is_pressed()
257 {
258 data.interactions
259 .interact(Interaction::Navigate(NavSignal::TextChange(
260 NavTextChange::MoveCursorEnd,
261 )));
262 }
263 if listener
264 .trigger_state_or_default(NAV_TEXT_DELETE_LEFT_TRIGGER)
265 .is_pressed()
266 {
267 data.interactions
268 .interact(Interaction::Navigate(NavSignal::TextChange(
269 NavTextChange::DeleteLeft,
270 )));
271 }
272 if listener
273 .trigger_state_or_default(NAV_TEXT_DELETE_RIGHT_TRIGGER)
274 .is_pressed()
275 {
276 data.interactions
277 .interact(Interaction::Navigate(NavSignal::TextChange(
278 NavTextChange::DeleteRight,
279 )));
280 }
281
282 let trigger = listener.trigger_state_or_default(NAV_ACCEPT_TRIGGER);
283 if trigger.is_pressed() {
284 data.interactions
285 .interact(Interaction::Navigate(NavSignal::Accept(true)));
286 } else if trigger.is_released() {
287 data.interactions
288 .interact(Interaction::Navigate(NavSignal::Accept(false)));
289 }
290
291 let trigger = listener.trigger_state_or_default(NAV_CANCEL_TRIGGER);
292 if trigger.is_pressed() {
293 data.interactions
294 .interact(Interaction::Navigate(NavSignal::Cancel(true)));
295 } else if trigger.is_released() {
296 data.interactions
297 .interact(Interaction::Navigate(NavSignal::Cancel(false)));
298 }
299
300 if listener
301 .trigger_state_or_default(NAV_UP_TRIGGER)
302 .is_pressed()
303 {
304 data.interactions
305 .interact(Interaction::Navigate(NavSignal::Up));
306 }
307 if listener
308 .trigger_state_or_default(NAV_DOWN_TRIGGER)
309 .is_pressed()
310 {
311 data.interactions
312 .interact(Interaction::Navigate(NavSignal::Down));
313 }
314 if listener
315 .trigger_state_or_default(NAV_LEFT_TRIGGER)
316 .is_pressed()
317 {
318 data.interactions
319 .interact(Interaction::Navigate(NavSignal::Left));
320 }
321 if listener
322 .trigger_state_or_default(NAV_RIGHT_TRIGGER)
323 .is_pressed()
324 {
325 data.interactions
326 .interact(Interaction::Navigate(NavSignal::Right));
327 }
328 if listener
329 .trigger_state_or_default(NAV_PREV_TRIGGER)
330 .is_pressed()
331 {
332 data.interactions
333 .interact(Interaction::Navigate(NavSignal::Prev));
334 }
335 if listener
336 .trigger_state_or_default(NAV_NEXT_TRIGGER)
337 .is_pressed()
338 {
339 data.interactions
340 .interact(Interaction::Navigate(NavSignal::Next));
341 }
342}