519 lines
14 KiB
Rust
519 lines
14 KiB
Rust
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<SlotMap<DefaultKey, String>> = 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<PanelItemInitData>;
|
|
|
|
fn apply_cursor_material(&self, model_part: &Arc<ModelPart>);
|
|
fn apply_surface_material(&self, surface: SurfaceId, model_part: &Arc<ModelPart>);
|
|
|
|
fn close_toplevel(&self);
|
|
fn auto_size_toplevel(&self);
|
|
fn set_toplevel_size(&self, size: Vector2<u32>);
|
|
fn set_toplevel_focused_visuals(&self, focused: bool);
|
|
|
|
fn pointer_motion(&self, surface: &SurfaceId, position: Vector2<f32>);
|
|
fn pointer_button(&self, surface: &SurfaceId, button: u32, pressed: bool);
|
|
fn pointer_scroll(
|
|
&self,
|
|
surface: &SurfaceId,
|
|
scroll_distance: Option<Vector2<f32>>,
|
|
scroll_steps: Option<Vector2<f32>>,
|
|
);
|
|
|
|
fn keyboard_key(&self, surface: &SurfaceId, keymap_id: u64, key: u32, pressed: bool);
|
|
|
|
fn touch_down(&self, surface: &SurfaceId, id: u32, position: Vector2<f32>);
|
|
fn touch_move(&self, id: u32, position: Vector2<f32>);
|
|
fn touch_up(&self, id: u32);
|
|
fn reset_input(&self);
|
|
}
|
|
|
|
pub fn panel_item_from_node(node: &Node) -> Option<Arc<dyn PanelItemTrait>> {
|
|
let ItemType::Panel(panel_item) = &node.get_aspect::<Item>().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<Node>);
|
|
fn send_acceptor_item_created(&self, node: &Node, item: &Arc<Node>);
|
|
}
|
|
|
|
pub struct PanelItem<B: Backend> {
|
|
pub node: Weak<Node>,
|
|
pub backend: Box<B>,
|
|
}
|
|
impl<B: Backend> PanelItem<B> {
|
|
pub fn create(backend: Box<B>, pid: Option<i32>) -> (Arc<Node>, Arc<PanelItem<B>>) {
|
|
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<dyn PanelItemTrait> = 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<B: Backend> PanelItem<B> {
|
|
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<u32>) {
|
|
let Some(node) = self.node.upgrade() else {
|
|
return;
|
|
};
|
|
panel_item_client::toplevel_size_changed(&node, size);
|
|
}
|
|
|
|
pub fn set_cursor(&self, geometry: Option<Geometry>) {
|
|
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<B: Backend> Aspect for PanelItem<B> {
|
|
impl_aspect_for_panel_item_aspect! {}
|
|
}
|
|
#[allow(unused)]
|
|
impl<B: Backend> PanelItemAspect for PanelItem<B> {
|
|
#[doc = "Apply the cursor as a material to a model."]
|
|
fn apply_cursor_material(
|
|
node: Arc<Node>,
|
|
_calling_client: Arc<Client>,
|
|
model_part: Arc<Node>,
|
|
) -> Result<()> {
|
|
let Some(panel_item) = panel_item_from_node(&node) else {
|
|
return Ok(());
|
|
};
|
|
let model_part = model_part.get_aspect::<ModelPart>()?;
|
|
|
|
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<Node>,
|
|
calling_client: Arc<Client>,
|
|
surface: SurfaceId,
|
|
model_part: Arc<Node>,
|
|
) -> Result<()> {
|
|
let Some(panel_item) = panel_item_from_node(&node) else {
|
|
return Ok(());
|
|
};
|
|
let model_part = model_part.get_aspect::<ModelPart>()?;
|
|
|
|
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<Node>, _calling_client: Arc<Client>) -> 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<Node>, _calling_client: Arc<Client>) -> 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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
size: mint::Vector2<u32>,
|
|
) -> 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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
surface: SurfaceId,
|
|
position: mint::Vector2<f32>,
|
|
) -> 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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
surface: SurfaceId,
|
|
scroll_distance: mint::Vector2<f32>,
|
|
scroll_steps: mint::Vector2<f32>,
|
|
) -> 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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
surface: SurfaceId,
|
|
uid: u32,
|
|
position: mint::Vector2<f32>,
|
|
) -> 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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
uid: u32,
|
|
position: mint::Vector2<f32>,
|
|
) -> 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<Node>, _calling_client: Arc<Client>, 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<Node>, _calling_client: Arc<Client>) -> 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<Node>, _calling_client: Arc<Client>, item: Arc<Node>) -> Result<()> {
|
|
super::acceptor_capture_item_flex(node, item)
|
|
}
|
|
}
|
|
|
|
impl<B: Backend> PanelItemTrait for PanelItem<B> {
|
|
fn backend(&self) -> &dyn Backend {
|
|
self.backend.as_ref()
|
|
}
|
|
fn send_ui_item_created(&self, node: &Node, item: &Arc<Node>) {
|
|
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<Node>) {
|
|
let Ok(init_data) = self.backend.start_data() else {
|
|
return;
|
|
};
|
|
let _ = panel_item_acceptor_client::capture_item(node, item, init_data);
|
|
}
|
|
}
|
|
impl<B: Backend> Drop for PanelItem<B> {
|
|
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<Node>, calling_client: Arc<Client>) -> 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/<item_type>/acceptor/<name>`."]
|
|
fn create_panel_item_acceptor(
|
|
node: Arc<Node>,
|
|
calling_client: Arc<Client>,
|
|
id: u64,
|
|
parent: Arc<Node>,
|
|
transform: Transform,
|
|
field: Arc<Node>,
|
|
) -> 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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
keymap: String,
|
|
) -> Result<u64> {
|
|
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<Node>,
|
|
_calling_client: Arc<Client>,
|
|
keymap_id: u64,
|
|
) -> Result<String> {
|
|
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())
|
|
}
|
|
}
|