use super::camera::CameraItemAcceptor; use super::{create_item_acceptor_flex, register_item_ui_flex}; use crate::nodes::items::ITEM_ACCEPTOR_ASPECT_ALIAS_INFO; use crate::nodes::items::ITEM_ASPECT_ALIAS_INFO; use crate::nodes::Aspect; use crate::{ core::{ client::{get_env, state, Client, INTERNAL_CLIENT}, registry::Registry, }, nodes::{ drawable::model::ModelPart, items::{Item, ItemType, TypeInfo}, spatial::{Spatial, Transform}, Node, }, }; use color_eyre::eyre::{bail, Result}; use glam::Mat4; use lazy_static::lazy_static; use mint::Vector2; use parking_lot::Mutex; use slotmap::{DefaultKey, Key, KeyData, SlotMap}; use std::sync::{Arc, Weak}; use tracing::{debug, info}; stardust_xr_server_codegen::codegen_item_panel_protocol!(); impl Default for Geometry { fn default() -> Self { Geometry { origin: [0, 0].into(), size: [0, 0].into(), } } } lazy_static! { pub static ref KEYMAPS: Mutex> = Mutex::new(SlotMap::default()); pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo { type_name: "panel", alias_info: PANEL_ITEM_ASPECT_ALIAS_INFO.clone(), ui_node_id: INTERFACE_NODE_ID, ui: Default::default(), items: Registry::new(), acceptors: Registry::new(), add_acceptor_aspect: |node| { node.add_aspect(PanelItemUi); }, add_ui_aspect: |node| { node.add_aspect(PanelItemAcceptor); }, new_acceptor_fn: |node, acceptor, acceptor_field| { let _ = panel_item_ui_client::create_acceptor(node, acceptor, acceptor_field); } }; } pub trait Backend: Send + Sync + 'static { fn start_data(&self) -> Result; fn apply_cursor_material(&self, model_part: &Arc); fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc); fn close_toplevel(&self); fn auto_size_toplevel(&self); fn set_toplevel_size(&self, size: Vector2); fn set_toplevel_focused_visuals(&self, focused: bool); fn pointer_motion(&self, surface: &SurfaceId, position: Vector2); fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool); fn pointer_scroll( &self, surface: &SurfaceId, scroll_distance: Option>, scroll_steps: Option>, ); fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool); fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2); fn touch_move(&self, id: u32, position: Vector2); fn touch_up(&self, id: u32); fn reset_input(&self); } pub fn panel_item_from_node(node: &Node) -> Option> { let ItemType::Panel(panel_item) = &node.get_aspect::().ok()?.specialization else { return None; }; Some(panel_item.clone()) } pub trait PanelItemTrait: Send + Sync + 'static { fn backend(&self) -> &dyn Backend; fn send_ui_item_created(&self, node: &Node, item: &Arc); fn send_acceptor_item_created(&self, node: &Node, item: &Arc); } pub struct PanelItem { pub node: Weak, pub backend: Box, } impl PanelItem { pub fn create(backend: Box, pid: Option) -> (Arc, Arc>) { debug!(?pid, "Create panel item"); let startup_settings = pid .and_then(|pid| get_env(pid).ok()) .and_then(|env| state(&env)); let node = Arc::new(Node::generate(&INTERNAL_CLIENT, true)); let spatial = Spatial::add_to(&node, None, Mat4::IDENTITY, false); if let Some(startup_settings) = &startup_settings { spatial.set_local_transform(startup_settings.root); } let panel_item = Arc::new(PanelItem { node: Arc::downgrade(&node), backend, }); let generic_panel_item: Arc = panel_item.clone(); Item::add_to( &node, &ITEM_TYPE_INFO_PANEL, ItemType::Panel(generic_panel_item), ); node.add_aspect_raw(panel_item.clone()); (node, panel_item) } } // Remote signals #[allow(unused)] impl PanelItem { pub fn toplevel_parent_changed(&self, parent: u64) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::toplevel_parent_changed(&node, parent); } pub fn toplevel_title_changed(&self, title: &str) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::toplevel_title_changed(&node, title); } pub fn toplevel_app_id_changed(&self, app_id: &str) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::toplevel_app_id_changed(&node, app_id); } pub fn toplevel_fullscreen_active(&self, active: bool) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::toplevel_fullscreen_active(&node, active); } pub fn toplevel_move_request(&self) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::toplevel_move_request(&node); } pub fn toplevel_resize_request(&self, up: bool, down: bool, left: bool, right: bool) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::toplevel_resize_request(&node, up, down, left, right); } pub fn toplevel_size_changed(&self, size: Vector2) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::toplevel_size_changed(&node, size); } pub fn set_cursor(&self, geometry: Option) { let Some(node) = self.node.upgrade() else { return; }; if let Some(geometry) = geometry { panel_item_client::set_cursor(&node, &geometry); } else { panel_item_client::hide_cursor(&node); } } pub fn create_child(&self, id: u64, info: &ChildInfo) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::create_child(&node, id, info); } pub fn reposition_child(&self, id: u64, geometry: &Geometry) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::reposition_child(&node, id, geometry); } pub fn destroy_child(&self, id: u64) { let Some(node) = self.node.upgrade() else { return; }; panel_item_client::destroy_child(&node, id); } } impl Aspect for PanelItem { impl_aspect_for_panel_item_aspect! {} } #[allow(unused)] impl PanelItemAspect for PanelItem { #[doc = "Apply the cursor as a material to a model."] fn apply_cursor_material( node: Arc, _calling_client: Arc, model_part: Arc, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; let model_part = model_part.get_aspect::()?; panel_item.backend().apply_cursor_material(&model_part); Ok(()) } #[doc = "Apply a surface's visuals as a material to a model."] fn apply_surface_material( node: Arc, calling_client: Arc, surface: SurfaceId, model_part: Arc, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; let model_part = model_part.get_aspect::()?; panel_item .backend() .apply_surface_material(surface, &model_part); Ok(()) } #[doc = "Try to close the toplevel.\n \n The panel item UI handler or panel item acceptor will drop the panel item if this succeeds."] fn close_toplevel(node: Arc, _calling_client: Arc) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().close_toplevel(); Ok(()) } #[doc = "Request a resize of the surface to whatever size the 2D app wants."] fn auto_size_toplevel(node: Arc, _calling_client: Arc) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().auto_size_toplevel(); Ok(()) } #[doc = "Request a resize of the surface (in pixels)."] fn set_toplevel_size( node: Arc, _calling_client: Arc, size: mint::Vector2, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().set_toplevel_size(size); Ok(()) } #[doc = "Tell the toplevel to appear focused visually if true, or unfocused if false."] fn set_toplevel_focused_visuals( node: Arc, _calling_client: Arc, focused: bool, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().set_toplevel_focused_visuals(focused); Ok(()) } #[doc = "Send an event to set the pointer's position (in pixels, relative to top-left of surface). This will activate the pointer."] fn pointer_motion( node: Arc, _calling_client: Arc, surface: SurfaceId, position: mint::Vector2, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().pointer_motion(&surface, position); Ok(()) } #[doc = "Send an event to set a pointer button's state if the pointer's active. The `button` is from the `input_event_codes` crate (e.g. BTN_LEFT for left click)."] fn pointer_button( node: Arc, _calling_client: Arc, surface: SurfaceId, button: u32, pressed: bool, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item .backend() .pointer_button(&surface, button, pressed); Ok(()) } #[doc = "Send an event to scroll the pointer if it's active.\nScroll distance is a value in pixels corresponding to the `distance` the surface should be scrolled.\nScroll steps is a value in columns/rows corresponding to the wheel clicks of a mouse or such. This also supports fractions of a wheel click."] fn pointer_scroll( node: Arc, _calling_client: Arc, surface: SurfaceId, scroll_distance: mint::Vector2, scroll_steps: mint::Vector2, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item .backend() .pointer_scroll(&surface, Some(scroll_distance), Some(scroll_steps)); Ok(()) } #[doc = "Send an event to stop scrolling the pointer."] fn pointer_stop_scroll( node: Arc, _calling_client: Arc, surface: SurfaceId, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().pointer_scroll(&surface, None, None); Ok(()) } #[doc = "Send a series of key presses and releases (positive keycode for pressed, negative for released)."] fn keyboard_key( node: Arc, _calling_client: Arc, surface: SurfaceId, keymap_id: u64, key: u32, pressed: bool, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item .backend() .keyboard_key(&surface, keymap_id, key, pressed); Ok(()) } #[doc = "Put a touch down on this surface with the unique ID `uid` at `position` (in pixels) from top left corner of the surface."] fn touch_down( node: Arc, _calling_client: Arc, surface: SurfaceId, uid: u32, position: mint::Vector2, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().touch_down(&surface, uid, position); Ok(()) } #[doc = "Move an existing touch point."] fn touch_move( node: Arc, _calling_client: Arc, uid: u32, position: mint::Vector2, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().touch_move(uid, position); Ok(()) } #[doc = "Release a touch from its surface."] fn touch_up(node: Arc, _calling_client: Arc, uid: u32) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().touch_up(uid); Ok(()) } #[doc = "Reset all input, such as pressed keys and pointer clicks and touches. Useful for when it's newly captured into an item acceptor to make sure no input gets stuck."] fn reset_input(node: Arc, _calling_client: Arc) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.backend().reset_input(); Ok(()) } } pub struct PanelItemUi; impl Aspect for PanelItemUi { impl_aspect_for_panel_item_ui_aspect! {} } impl PanelItemUiAspect for PanelItemUi {} pub struct PanelItemAcceptor; impl Aspect for PanelItemAcceptor { impl_aspect_for_panel_item_acceptor_aspect! {} } impl PanelItemAcceptorAspect for PanelItemAcceptor { fn capture_item(node: Arc, _calling_client: Arc, item: Arc) -> Result<()> { super::acceptor_capture_item_flex(node, item) } } impl PanelItemTrait for PanelItem { fn backend(&self) -> &dyn Backend { self.backend.as_ref() } fn send_ui_item_created(&self, node: &Node, item: &Arc) { let Ok(init_data) = self.backend.start_data() else { return; }; let _ = panel_item_ui_client::create_item(node, item, init_data); } fn send_acceptor_item_created(&self, node: &Node, item: &Arc) { let Ok(init_data) = self.backend.start_data() else { return; }; let _ = panel_item_acceptor_client::capture_item(node, item, init_data); } } impl Drop for PanelItem { fn drop(&mut self) { // Dropped panel item, basically just a debug breakpoint place info!("Dropped panel item"); } } impl InterfaceAspect for Interface { #[doc = "Register this client to manage the items of a certain type and create default 3D UI for them."] fn register_panel_item_ui(node: Arc, calling_client: Arc) -> Result<()> { node.add_aspect(CameraItemAcceptor); register_item_ui_flex(calling_client, &ITEM_TYPE_INFO_PANEL) } #[doc = "Create an item acceptor to allow temporary ownership of a given type of item. Creates a node at `/item//acceptor/`."] fn create_panel_item_acceptor( node: Arc, calling_client: Arc, id: u64, parent: Arc, transform: Transform, field: Arc, ) -> Result<()> { node.add_aspect(PanelItemAcceptor); create_item_acceptor_flex( calling_client, id, parent, transform, &ITEM_TYPE_INFO_PANEL, field, ) } async fn register_keymap( _node: Arc, _calling_client: Arc, keymap: String, ) -> Result { let mut keymaps = KEYMAPS.lock(); if let Some(found_keymap_id) = keymaps .iter() .filter(|(_k, v)| *v == &keymap) .map(|(k, _v)| k) .last() { return Ok(found_keymap_id.data().as_ffi()); } let key = keymaps.insert(keymap); Ok(key.data().as_ffi()) } async fn get_keymap( _node: Arc, _calling_client: Arc, keymap_id: u64, ) -> Result { let keymaps = KEYMAPS.lock(); let Some(keymap) = keymaps.get(KeyData::from_ffi(keymap_id).into()) else { bail!("Could not find keymap. Try registering it") }; Ok(keymap.clone()) } }