use crate::{ core::{ client::{get_env, state, Client, INTERNAL_CLIENT}, registry::Registry, }, nodes::{ drawable::model::ModelPart, items::{Item, ItemType, TypeInfo}, spatial::Spatial, Message, Node, }, }; use color_eyre::eyre::{eyre, Result}; use glam::Mat4; use lazy_static::lazy_static; use mint::Vector2; use nanoid::nanoid; use rustc_hash::FxHashMap; use serde::{ de::{Deserializer, Error, SeqAccess, Visitor}, ser::Serializer, Deserialize, Serialize, }; use stardust_xr::schemas::flex::{deserialize, serialize}; use std::sync::{Arc, Weak}; use tracing::debug; lazy_static! { pub static ref ITEM_TYPE_INFO_PANEL: TypeInfo = TypeInfo { type_name: "panel", aliased_local_signals: vec![ "apply_surface_material", "close_toplevel", "auto_size_toplevel", "set_toplevel_size", "set_toplevel_focused_visuals", "pointer_motion", "pointer_button", "pointer_scroll", "keyboard_keymap", "keyboard_key", "touch_down", "touch_move", "touch_up", "reset_touches", ], aliased_local_methods: vec![], aliased_remote_signals: vec![ "toplevel_parent_changed", "toplevel_title_changed", "toplevel_app_id_changed", "toplevel_fullscreen_active", "toplevel_move_request", "toplevel_resize_request", "toplevel_size_changed", "set_cursor", "new_child", "reposition_child", "drop_child", ], ui: Default::default(), items: Registry::new(), acceptors: Registry::new(), }; } /// An ID for a surface inside this panel item #[derive(Debug, Clone)] #[allow(dead_code)] pub enum SurfaceID { Cursor, Toplevel, Child(String), } impl Default for SurfaceID { fn default() -> Self { Self::Toplevel } } impl<'de> serde::Deserialize<'de> for SurfaceID { fn deserialize>(deserializer: D) -> Result { deserializer.deserialize_seq(SurfaceIDVisitor) } } struct SurfaceIDVisitor; impl<'de> Visitor<'de> for SurfaceIDVisitor { type Value = SurfaceID; fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str("idk") } fn visit_seq>(self, mut seq: A) -> Result { let Some(discrim) = seq.next_element()? else { return Err(A::Error::missing_field("discrim")); }; // idk if you wanna check for extraneous elements // I didn't bother match discrim { "Cursor" => Ok(SurfaceID::Cursor), "Toplevel" => Ok(SurfaceID::Toplevel), "Child" => { let Some(text) = seq.next_element()? else { return Err(A::Error::missing_field("child_text")); }; Ok(SurfaceID::Child(text)) } _ => Err(A::Error::unknown_variant( discrim, &["Cursor", "Toplevel", "Child"], )), } } } impl serde::Serialize for SurfaceID { fn serialize(&self, serializer: S) -> Result { match self { Self::Cursor => ["Cursor"].serialize(serializer), Self::Toplevel => ["Toplevel"].serialize(serializer), Self::Child(text) => ["Child", text].serialize(serializer), } } } /// The origin and size of the surface's "solid" part. #[derive(Debug, Serialize, Clone, Copy)] pub struct Geometry { pub origin: Vector2, pub size: Vector2, } /// The state of the panel item's toplevel. #[derive(Debug, Clone, Serialize)] pub struct ToplevelInfo { /// The UID of the panel item of the parent of this toplevel, if it exists pub parent: Option, /// Equivalent to the window title pub title: Option, /// Application identifier, see pub app_id: Option, /// Current size in pixels pub size: Vector2, /// Recommended minimum size in pixels pub min_size: Option>, /// Recommended maximum size in pixels pub max_size: Option>, /// Surface geometry pub logical_rectangle: Geometry, } /// Data on positioning a child #[derive(Debug, Clone, Serialize)] pub struct ChildInfo { pub parent: SurfaceID, pub geometry: Geometry, } /// The init data for the panel item. #[derive(Debug, Clone, Serialize)] pub struct PanelItemInitData { /// The cursor, if applicable. pub cursor: Option, /// Size of the toplevel surface in pixels. pub toplevel: ToplevelInfo, /// Vector of childs that already exist pub children: FxHashMap, /// The surface, if any, that has exclusive input to the pointer. pub pointer_grab: Option, /// The surface, if any, that has exclusive input to the keyboard. pub keyboard_grab: Option, } pub trait Backend: Send + Sync + 'static { fn start_data(&self) -> Result; 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_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec); 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_touches(&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: Backend + Send + Sync + 'static { fn uid(&self) -> &str; fn serialize_start_data(&self, id: &str) -> Result; } pub struct PanelItem { pub uid: String, node: Weak, pub backend: Box, } impl PanelItem { pub fn create(backend: Box, pid: Option) -> Arc> { debug!(?pid, "Create panel item"); let startup_settings = pid .and_then(|pid| get_env(pid).ok()) .and_then(|env| state(&env)); let uid = nanoid!(); let node = Node::create_parent_name(&INTERNAL_CLIENT, "/item/panel/item", &uid, true) .add_to_scenegraph() .unwrap(); 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 { uid: uid.clone(), node: Arc::downgrade(&node), backend, }); let generic_panel_item: Arc = panel_item.clone(); Item::add_to( &node, uid, &ITEM_TYPE_INFO_PANEL, ItemType::Panel(generic_panel_item), ); node.add_local_signal("apply_surface_material", Self::apply_surface_material_flex); node.add_local_signal("close_toplevel", Self::close_toplevel_flex); node.add_local_signal("auto_size_toplevel", Self::auto_size_toplevel_flex); node.add_local_signal("set_toplevel_size", Self::set_toplevel_size_flex); node.add_local_signal("pointer_motion", Self::pointer_motion_flex); node.add_local_signal("pointer_button", Self::pointer_button_flex); node.add_local_signal("pointer_scroll", Self::pointer_scroll_flex); node.add_local_signal("keyboard_key", Self::keyboard_keys_flex); node.add_local_signal("touch_down", Self::touch_down_flex); node.add_local_signal("touch_move", Self::touch_move_flex); node.add_local_signal("touch_up", Self::touch_up_flex); node.add_local_signal("reset_touches", Self::reset_touches_flex); panel_item } pub fn drop_toplevel(&self) { let Some(node) = self.node.upgrade() else { return; }; node.destroy(); } } // Remote signals impl PanelItem { pub fn toplevel_parent_changed(&self, parent: &str) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("toplevel_parent_changed", serialize(parent).unwrap()); } pub fn toplevel_title_changed(&self, title: &str) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("toplevel_title_changed", serialize(title).unwrap()); } pub fn toplevel_app_id_changed(&self, app_id: &str) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("toplevel_app_id_changed", serialize(app_id).unwrap()); } pub fn toplevel_fullscreen_active(&self, active: bool) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("toplevel_fullscreen_active", serialize(active).unwrap()); } pub fn toplevel_move_request(&self) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("toplevel_move_request", Vec::::new()); } pub fn toplevel_resize_request(&self, up: bool, down: bool, left: bool, right: bool) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal( "toplevel_resize_request", serialize((up, down, left, right)).unwrap(), ); } pub fn toplevel_size_changed(&self, size: Vector2) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("toplevel_size_changed", serialize(size).unwrap()); } pub fn set_cursor(&self, geometry: Option) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("set_cursor", serialize(geometry).unwrap()); } pub fn new_child(&self, uid: &str, info: ChildInfo) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("new_child", serialize((uid, info)).unwrap()); } pub fn reposition_child(&self, uid: &str, geometry: Geometry) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("reposition_child", serialize((uid, geometry)).unwrap()); } pub fn drop_child(&self, uid: &str) { let Some(node) = self.node.upgrade() else { return; }; let _ = node.send_remote_signal("drop_child", serialize(uid).unwrap()); } } // Local signals macro_rules! flex_no_args { ($fn_name: ident, $trait_fn: ident) => { fn $fn_name( node: Arc, _calling_client: Arc, _message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.$trait_fn(); Ok(()) } }; } macro_rules! flex_deserialize { ($fn_name: ident, $trait_fn: ident) => { fn $fn_name(node: Arc, _calling_client: Arc, message: Message) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; panel_item.$trait_fn(deserialize(message.as_ref())?); Ok(()) } }; } impl PanelItem { fn apply_surface_material_flex( node: Arc, calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; #[derive(Debug, Deserialize)] struct SurfaceMaterialInfo<'a> { surface: SurfaceID, model_node_path: &'a str, } let info: SurfaceMaterialInfo = deserialize(message.as_ref())?; let model_node = calling_client .scenegraph .get_node(info.model_node_path) .ok_or_else(|| eyre!("Model node not found"))?; let model_part = model_node.get_aspect::()?; debug!(?info, "Apply surface material"); panel_item.apply_surface_material(info.surface, &model_part); Ok(()) } flex_no_args!(close_toplevel_flex, close_toplevel); flex_no_args!(auto_size_toplevel_flex, auto_size_toplevel); flex_deserialize!(set_toplevel_size_flex, set_toplevel_size); fn pointer_motion_flex( node: Arc, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; let (surface_id, position): (SurfaceID, Vector2) = deserialize(message.as_ref())?; debug!(?surface_id, ?position, "Pointer deactivate"); panel_item.pointer_motion(&surface_id, position); Ok(()) } fn pointer_button_flex( node: Arc, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; let (surface_id, button, state): (SurfaceID, u32, u32) = deserialize(message.as_ref())?; debug!(?surface_id, button, state, "Pointer button"); panel_item.pointer_button(&surface_id, button, state != 0); Ok(()) } fn pointer_scroll_flex( node: Arc, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; #[derive(Debug, Deserialize)] struct PointerScrollInfo { surface_id: SurfaceID, axis_continuous: Option>, axis_discrete: Option>, } let info: PointerScrollInfo = deserialize(message.as_ref())?; debug!(?info, "Pointer scroll"); panel_item.pointer_scroll(&info.surface_id, info.axis_continuous, info.axis_discrete); Ok(()) } fn keyboard_keys_flex( node: Arc, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; let (surface_id, keymap_id, keys): (SurfaceID, &str, Vec) = deserialize(message.as_ref())?; debug!(?keys, "Set keyboard key state"); panel_item.keyboard_keys(&surface_id, keymap_id, keys); Ok(()) } pub fn grab_keyboard(&self, sid: Option) { let Some(node) = self.node.upgrade() else { return; }; let Ok(message) = serialize(sid) else { return }; let _ = node.send_remote_signal("grab_keyboard", message); } fn touch_down_flex( node: Arc, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; let (surface_id, id, position): (SurfaceID, u32, Vector2) = deserialize(message.as_ref())?; debug!(?surface_id, id, ?position, "Touch down"); panel_item.touch_down(&surface_id, id, position); Ok(()) } fn touch_move_flex( node: Arc, _calling_client: Arc, message: Message, ) -> Result<()> { let Some(panel_item) = panel_item_from_node(&node) else { return Ok(()); }; let (id, position): (u32, Vector2) = deserialize(message.as_ref())?; debug!(?position, "Touch move"); panel_item.touch_move(id, position); Ok(()) } flex_deserialize!(touch_up_flex, touch_up); flex_no_args!(reset_touches_flex, reset_touches); } impl PanelItemTrait for PanelItem { fn uid(&self) -> &str { &self.uid } fn serialize_start_data(&self, id: &str) -> Result { Ok(serialize((id, self.start_data()?))?.into()) } } impl Backend for PanelItem { fn start_data(&self) -> Result { self.backend.start_data() } fn apply_surface_material(&self, surface: SurfaceID, model_part: &Arc) { self.backend.apply_surface_material(surface, model_part) } fn close_toplevel(&self) { self.backend.close_toplevel() } fn auto_size_toplevel(&self) { self.backend.auto_size_toplevel() } fn set_toplevel_size(&self, size: Vector2) { self.backend.set_toplevel_size(size) } fn set_toplevel_focused_visuals(&self, focused: bool) { self.backend.set_toplevel_focused_visuals(focused) } fn pointer_motion(&self, surface: &SurfaceID, position: Vector2) { self.backend.pointer_motion(surface, position) } fn pointer_button(&self, surface: &SurfaceID, button: u32, pressed: bool) { self.backend.pointer_button(surface, button, pressed) } fn pointer_scroll( &self, surface: &SurfaceID, scroll_distance: Option>, scroll_steps: Option>, ) { self.backend .pointer_scroll(surface, scroll_distance, scroll_steps) } fn keyboard_keys(&self, surface: &SurfaceID, keymap_id: &str, keys: Vec) { self.backend.keyboard_keys(surface, keymap_id, keys) } fn touch_down(&self, surface: &SurfaceID, id: u32, position: Vector2) { self.backend.touch_down(surface, id, position) } fn touch_move(&self, id: u32, position: Vector2) { self.backend.touch_move(id, position) } fn touch_up(&self, id: u32) { self.backend.touch_up(id) } fn reset_touches(&self) { self.backend.reset_touches() } } impl Drop for PanelItem { fn drop(&mut self) { // Dropped panel item, basically just a debug breakpoint place } }