oxygengine-user-interface 0.15.0

User Interface module for Oxygen Engine
Documentation
use crate::{
    component::UserInterfaceView,
    resource::{ApplicationData, UserInterfaceRes},
};
use core::{
    app::AppLifeCycle,
    ecs::{Join, Read, ReadExpect, System, Write, WriteStorage},
};
use input::resource::{InputController, TriggerState};
use raui_core::{
    application::Application,
    interactive::default_interactions_engine::{Interaction, PointerButton},
    layout::default_layout_engine::DefaultLayoutEngine,
    widget::{
        component::interactive::navigation::{NavSignal, NavTextChange},
        setup as core_setup,
        utils::Vec2,
    },
};
use raui_material::setup as material_setup;
use std::collections::HashMap;

#[derive(Default)]
pub struct UserInterfaceSystem {
    last_pointer_pos: Vec2,
}

impl<'s> System<'s> for UserInterfaceSystem {
    type SystemData = (
        ReadExpect<'s, AppLifeCycle>,
        Write<'s, UserInterfaceRes>,
        Read<'s, InputController>,
        WriteStorage<'s, UserInterfaceView>,
    );

    fn run(&mut self, (life_cycle, mut res, input, mut views): Self::SystemData) {
        let ui: &mut UserInterfaceRes = &mut *res;

        ui.data = std::mem::take(&mut ui.data)
            .into_iter()
            .filter(|(k, _)| views.join().any(|v| k == v.app_id()))
            .collect::<HashMap<_, _>>();
        for view in (&mut views).join() {
            if !ui.data.contains_key(view.app_id()) {
                let mut application = Application::new();
                application.setup(core_setup);
                application.setup(material_setup);
                if let Some(setup) = ui.setup {
                    setup(&mut application);
                }
                ui.data.insert(
                    view.app_id().to_owned(),
                    ApplicationData {
                        application,
                        interactions: Default::default(),
                        coords_mapping: Default::default(),
                    },
                );
            }
            if view.dirty {
                view.dirty = false;
                let app = ui.application_mut(view.app_id()).unwrap();
                let root = app
                    .deserialize_node(view.root().clone())
                    .expect("Could not deserialize UI node");
                app.apply(root);
            }
        }

        let pointer_pos = Vec2 {
            x: input.axis_or_default(&ui.pointer_axis_x),
            y: input.axis_or_default(&ui.pointer_axis_y),
        };
        let pointer_moved = (pointer_pos.x - self.last_pointer_pos.x).abs() > 1.0e-6
            || (pointer_pos.y - self.last_pointer_pos.y).abs() > 1.0e-6;
        self.last_pointer_pos = pointer_pos;
        let pointer_trigger = input.trigger_or_default(&ui.pointer_action_trigger);
        let pointer_context = input.trigger_or_default(&ui.pointer_context_trigger);
        let mut text = input
            .text()
            .chars()
            .filter_map(|c| {
                if !c.is_control() {
                    Some(NavTextChange::InsertCharacter(c))
                } else if c == '\n' || c == '\r' {
                    Some(NavTextChange::NewLine)
                } else {
                    None
                }
            })
            .collect::<Vec<_>>();
        let accept = input.trigger_or_default(&ui.navigate_accept);
        let cancel = input.trigger_or_default(&ui.navigate_cancel);
        let up = input.trigger_or_default(&ui.navigate_up) == TriggerState::Pressed;
        let down = input.trigger_or_default(&ui.navigate_down) == TriggerState::Pressed;
        let left = input.trigger_or_default(&ui.navigate_left) == TriggerState::Pressed;
        let right = input.trigger_or_default(&ui.navigate_right) == TriggerState::Pressed;
        let prev = input.trigger_or_default(&ui.navigate_prev) == TriggerState::Pressed;
        let next = input.trigger_or_default(&ui.navigate_next) == TriggerState::Pressed;
        if input.trigger_or_default(&ui.text_move_cursor_left) == TriggerState::Pressed {
            text.push(NavTextChange::MoveCursorLeft);
        }
        if input.trigger_or_default(&ui.text_move_cursor_right) == TriggerState::Pressed {
            text.push(NavTextChange::MoveCursorRight);
        }
        if input.trigger_or_default(&ui.text_move_cursor_start) == TriggerState::Pressed {
            text.push(NavTextChange::MoveCursorStart);
        }
        if input.trigger_or_default(&ui.text_move_cursor_end) == TriggerState::Pressed {
            text.push(NavTextChange::MoveCursorEnd);
        }
        if input.trigger_or_default(&ui.text_delete_left) == TriggerState::Pressed {
            text.push(NavTextChange::DeleteLeft);
        }
        if input.trigger_or_default(&ui.text_delete_right) == TriggerState::Pressed {
            text.push(NavTextChange::DeleteRight);
        }
        for data in ui.data.values_mut() {
            let pointer_pos = data.coords_mapping.real_to_virtual_vec2(pointer_pos);
            if pointer_moved {
                data.interactions
                    .interact(Interaction::PointerMove(pointer_pos));
            }
            match pointer_trigger {
                TriggerState::Pressed => {
                    data.interactions.interact(Interaction::PointerDown(
                        PointerButton::Trigger,
                        pointer_pos,
                    ));
                }
                TriggerState::Released => {
                    data.interactions
                        .interact(Interaction::PointerUp(PointerButton::Trigger, pointer_pos));
                }
                _ => {}
            }
            match pointer_context {
                TriggerState::Pressed => {
                    data.interactions.interact(Interaction::PointerDown(
                        PointerButton::Context,
                        pointer_pos,
                    ));
                }
                TriggerState::Released => {
                    data.interactions
                        .interact(Interaction::PointerUp(PointerButton::Context, pointer_pos));
                }
                _ => {}
            }
            for change in &text {
                data.interactions
                    .interact(Interaction::Navigate(NavSignal::TextChange(*change)));
            }
            match accept {
                TriggerState::Pressed => {
                    data.interactions
                        .interact(Interaction::Navigate(NavSignal::Accept(true)));
                }
                TriggerState::Released => {
                    data.interactions
                        .interact(Interaction::Navigate(NavSignal::Accept(false)));
                }
                _ => {}
            }
            match cancel {
                TriggerState::Pressed => {
                    data.interactions
                        .interact(Interaction::Navigate(NavSignal::Cancel(true)));
                }
                TriggerState::Released => {
                    data.interactions
                        .interact(Interaction::Navigate(NavSignal::Cancel(false)));
                }
                _ => {}
            }
            if up {
                data.interactions
                    .interact(Interaction::Navigate(NavSignal::Up));
            }
            if down {
                data.interactions
                    .interact(Interaction::Navigate(NavSignal::Down));
            }
            if left {
                data.interactions
                    .interact(Interaction::Navigate(NavSignal::Left));
            }
            if right {
                data.interactions
                    .interact(Interaction::Navigate(NavSignal::Right));
            }
            if prev {
                data.interactions
                    .interact(Interaction::Navigate(NavSignal::Prev));
            }
            if next {
                data.interactions
                    .interact(Interaction::Navigate(NavSignal::Next));
            }
        }

        let mut meta = views.join().collect::<Vec<_>>();
        meta.sort_by(|a, b| a.input_order.cmp(&b.input_order));
        let mut captured = false;
        for view in meta {
            if let Some(data) = ui.data.get_mut(view.app_id()) {
                data.application.animations_delta_time = life_cycle.delta_time_seconds();
                data.application.process();
                data.application
                    .layout(&data.coords_mapping, &mut DefaultLayoutEngine)
                    .unwrap_or_default();
                if captured {
                    data.interactions.clear_queue(true);
                }
                data.interactions.deselect_when_no_button_found =
                    view.deselect_when_no_button_found;
                if let Ok(result) = data.application.interact(&mut data.interactions) {
                    if view.capture_input && result.is_any() {
                        captured = true;
                    }
                }
            }
        }
        res.last_frame_captured = captured;
    }
}