oxygengine_user_interface/
system.rs

1use 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}