From 12668419ce624287ce52daf157d886f85828b590 Mon Sep 17 00:00:00 2001 From: Nova Date: Mon, 26 Sep 2022 07:09:37 -0400 Subject: [PATCH] feat(drawable): text --- Cargo.toml | 2 +- src/nodes/drawable/mod.rs | 5 +- src/nodes/drawable/model.rs | 2 +- src/nodes/drawable/text.rs | 220 ++++++++++++++++++++++++++++++++++++ src/nodes/mod.rs | 11 +- src/nodes/spatial.rs | 6 +- 6 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 src/nodes/drawable/text.rs diff --git a/Cargo.toml b/Cargo.toml index 59fc2ba..60cadab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ prisma = "0.1.1" slog = "2.7.0" slog-stdlog = "4.1.1" xkbcommon = { version = "0.5.0", default-features = false } -stardust-xr = "0.3.0" +stardust-xr = "0.3.1" stardust-xr-schemas = "0.1.0" stereokit = {default-features = false, features = ["linux-egl"], version = "0.2.0"} wayland-backend = "=0.1.0-beta.9" diff --git a/src/nodes/drawable/mod.rs b/src/nodes/drawable/mod.rs index 11e1624..4cc94a0 100644 --- a/src/nodes/drawable/mod.rs +++ b/src/nodes/drawable/mod.rs @@ -1,4 +1,5 @@ pub mod model; +pub mod text; use super::Node; use crate::core::client::Client; @@ -9,13 +10,15 @@ use stereokit::{lifecycle::DrawContext, texture::Texture, StereoKit}; pub fn create_interface(client: &Arc) { let node = Node::create(client, "", "drawable", false); - node.add_local_signal("createModel", model::create); + node.add_local_signal("createModel", model::create_flex); + node.add_local_signal("createText", text::create_flex); node.add_local_signal("setSkyFile", set_sky_file_flex); node.add_to_scenegraph(); } pub fn draw(sk: &mut StereoKit, draw_ctx: &DrawContext) { model::draw_all(sk, draw_ctx); + text::draw_all(sk, draw_ctx); let new_skytex = QUEUED_SKYTEX.lock().take(); let mut new_skylight = QUEUED_SKYLIGHT.lock().take(); diff --git a/src/nodes/drawable/model.rs b/src/nodes/drawable/model.rs index 6e4ec40..66b2efd 100644 --- a/src/nodes/drawable/model.rs +++ b/src/nodes/drawable/model.rs @@ -163,7 +163,7 @@ pub fn draw_all(sk: &StereoKit, draw_ctx: &DrawContext) { } } -pub fn create(_node: &Node, calling_client: Arc, data: &[u8]) -> Result<()> { +pub fn create_flex(_node: &Node, calling_client: Arc, data: &[u8]) -> Result<()> { let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?; let node = Node::create( &calling_client, diff --git a/src/nodes/drawable/text.rs b/src/nodes/drawable/text.rs new file mode 100644 index 0000000..20f92fd --- /dev/null +++ b/src/nodes/drawable/text.rs @@ -0,0 +1,220 @@ +use crate::{ + core::{ + client::Client, + destroy_queue, + registry::Registry, + resource::{parse_resource_id, ResourceID}, + }, + nodes::{ + spatial::{get_spatial_parent_flex, parse_transform, Spatial}, + Node, + }, +}; +use anyhow::{anyhow, ensure, Result}; +use glam::{vec3, Mat4, Vec2}; +use once_cell::sync::OnceCell; +use parking_lot::Mutex; +use prisma::{FromTuple, Rgb, Rgba}; +use send_wrapper::SendWrapper; +use stardust_xr::values::{parse_f32, parse_vec2}; +use std::{convert::TryFrom, path::PathBuf, sync::Arc}; +use stereokit::{ + font::Font, + lifecycle::DrawContext, + text::{self, TextAlign, TextFit, TextStyle}, + StereoKit, +}; + +static TEXT_REGISTRY: Registry = Registry::new(); + +struct TextData { + text: String, + character_height: f32, + text_align: TextAlign, + bounds: Option, + fit: TextFit, + bounds_align: TextAlign, + color: Rgba, +} + +pub struct Text { + space: Arc, + font_path: Option, + style: OnceCell>, + + data: Mutex, +} +impl Text { + #[allow(clippy::too_many_arguments)] + pub fn add_to( + node: &Arc, + font_resource_id: Option, + text: String, + character_height: f32, + text_align: TextAlign, + bounds: Option, + fit: TextFit, + bounds_align: TextAlign, + color: Rgba, + ) -> Result> { + ensure!( + node.spatial.get().is_some(), + "Internal: Node does not have a spatial attached!" + ); + ensure!( + node.model.get().is_none(), + "Internal: Node already has text attached!" + ); + + let text = TEXT_REGISTRY.add(Text { + space: node.spatial.get().unwrap().clone(), + font_path: font_resource_id.and_then(|res| { + res.get_file(&node.get_client().base_resource_prefixes.lock().clone()) + }), + style: OnceCell::new(), + + data: Mutex::new(TextData { + text, + character_height, + text_align, + bounds, + fit, + bounds_align, + color, + }), + }); + node.add_local_signal("setCharacterHeight", Text::set_character_height_flex); + node.add_local_signal("setText", Text::set_text_flex); + let _ = node.text.set(text.clone()); + + Ok(text) + } + + fn draw(&self, sk: &StereoKit, draw_ctx: &DrawContext) { + let style = + self.style + .get_or_try_init(|| -> Result, anyhow::Error> { + let font = if let Some(path) = self.font_path.as_deref() { + Font::from_file(sk, path) + } else { + Some(Font::default(sk)) + }; + Ok(SendWrapper::new(TextStyle::new( + sk, + font.ok_or(std::fmt::Error)?, + 1.0, + Rgba::new(Rgb::new(1.0, 1.0, 1.0), 1.0), + ))) + }); + + if let Ok(style) = style { + let data = self.data.lock(); + let transform = self.space.global_transform() + * Mat4::from_scale(vec3( + data.character_height, + data.character_height, + data.character_height, + )); + if let Some(bounds) = data.bounds { + text::draw_in( + draw_ctx, + &data.text, + transform, + bounds / data.character_height, + data.fit, + style, + data.bounds_align, + data.text_align, + vec3(0.0, 0.0, 0.0), + data.color, + ); + } else { + text::draw_at( + draw_ctx, + &data.text, + transform, + style, + data.bounds_align, + data.text_align, + vec3(0.0, 0.0, 0.0), + data.color, + ); + } + } + } + + pub fn set_character_height_flex( + node: &Node, + _calling_client: Arc, + data: &[u8], + ) -> Result<()> { + let height = flexbuffers::Reader::get_root(data)?.get_f64()? as f32; + node.text.get().unwrap().data.lock().character_height = height; + Ok(()) + } + + pub fn set_text_flex(node: &Node, _calling_client: Arc, data: &[u8]) -> Result<()> { + let text = flexbuffers::Reader::get_root(data)?.get_str()?.to_string(); + node.text.get().unwrap().data.lock().text = text; + Ok(()) + } +} +impl Drop for Text { + fn drop(&mut self) { + if let Some(style) = self.style.take() { + destroy_queue::add(style); + } + TEXT_REGISTRY.remove(self); + } +} + +pub fn draw_all(sk: &StereoKit, draw_ctx: &DrawContext) { + for text in TEXT_REGISTRY.get_valid_contents() { + text.draw(sk, draw_ctx); + } +} + +pub fn create_flex(_node: &Node, calling_client: Arc, data: &[u8]) -> Result<()> { + let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?; + let node = Node::create( + &calling_client, + "/drawable/text", + flex_vec.idx(0).get_str()?, + true, + ); + let parent = get_spatial_parent_flex(&calling_client, flex_vec.index(1)?.get_str()?)?; + let transform = parse_transform(flex_vec.index(2)?, true, true, true)?; + let text = flex_vec.index(3)?.get_str()?.to_string(); + let font_resource_id = parse_resource_id(flex_vec.index(4)?).ok(); + let character_height = flex_vec.index(5)?.get_f64()? as f32; + let text_align = TextAlign::from_bits(flex_vec.index(6)?.get_u64()? as u32) + .ok_or_else(|| anyhow!("Text align bitflag out of range"))?; + let bounds = parse_vec2(flex_vec.index(7)?).map(|bounds| bounds.into()); + let fit = TextFit::try_from(flex_vec.index(8)?.get_u64()? as u32)?; + let bounds_align = TextAlign::from_bits(flex_vec.index(9)?.get_u64()? as u32) + .ok_or_else(|| anyhow!("Bounds align bitflag out of range"))?; + let color_vec = flex_vec.index(10)?.get_vector()?; + let color = Rgba::from_tuple(( + ( + parse_f32(color_vec.index(0)?).ok_or_else(|| anyhow!("Value in color invalid"))?, + parse_f32(color_vec.index(0)?).ok_or_else(|| anyhow!("Value in color invalid"))?, + parse_f32(color_vec.index(0)?).ok_or_else(|| anyhow!("Value in color invalid"))?, + ), + parse_f32(color_vec.index(0)?).ok_or_else(|| anyhow!("Value in color invalid"))?, + )); + + let node = node.add_to_scenegraph(); + Spatial::add_to(&node, Some(parent), transform)?; + Text::add_to( + &node, + font_resource_id, + text, + character_height, + text_align, + bounds, + fit, + bounds_align, + color, + )?; + Ok(()) +} diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index c45c761..39cdafe 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -29,6 +29,7 @@ use self::alias::Alias; use self::data::{PulseReceiver, PulseSender}; use self::drawable::model::Model; +use self::drawable::text::Text; use self::fields::Field; use self::input::{InputHandler, InputMethod}; use self::items::{Item, ItemAcceptor, ItemUI}; @@ -58,6 +59,7 @@ pub struct Node { // Drawable pub model: OnceCell>, + pub text: OnceCell>, // Input pub input_method: OnceCell>, @@ -106,14 +108,15 @@ impl Node { spatial: OnceCell::new(), field: OnceCell::new(), - model: OnceCell::new(), pulse_sender: OnceCell::new(), pulse_receiver: OnceCell::new(), + model: OnceCell::new(), + text: OnceCell::new(), + input_method: OnceCell::new(), + input_handler: OnceCell::new(), item: OnceCell::new(), item_acceptor: OnceCell::new(), item_ui: OnceCell::new(), - input_method: OnceCell::new(), - input_handler: OnceCell::new(), startup_settings: OnceCell::new(), }; node.add_local_signal("destroy", Node::destroy_flex); @@ -206,7 +209,7 @@ impl Node { let data = data.to_vec(); if let Some(messenger) = client.messenger.as_ref() { - let _ = messenger.send_remote_signal(path.as_str(), method.as_str(), data.as_slice()); + messenger.send_remote_signal(path.as_str(), method.as_str(), data.as_slice()); } Ok(()) } diff --git a/src/nodes/spatial.rs b/src/nodes/spatial.rs index e9ff467..26019d6 100644 --- a/src/nodes/spatial.rs +++ b/src/nodes/spatial.rs @@ -277,15 +277,15 @@ pub fn parse_transform( let translation = translation .then(|| parse_vec3(transform_vec.idx(0))) .flatten() - .unwrap_or(Vector3::from([0.0; 3])); + .unwrap_or_else(|| Vector3::from([0.0; 3])); let rotation = rotation .then(|| parse_quat(transform_vec.idx(1))) .flatten() - .unwrap_or(Quat::IDENTITY.into()); + .unwrap_or_else(|| Quat::IDENTITY.into()); let scale = scale .then(|| parse_vec3(transform_vec.idx(2))) .flatten() - .unwrap_or(Vector3::from([1.0; 3])); + .unwrap_or_else(|| Vector3::from([1.0; 3])); Ok(Mat4::from_scale_rotation_translation( scale.into(),