feat(drawable): text
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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<Client>) {
|
||||
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();
|
||||
|
||||
@@ -163,7 +163,7 @@ pub fn draw_all(sk: &StereoKit, draw_ctx: &DrawContext) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
pub fn create_flex(_node: &Node, calling_client: Arc<Client>, data: &[u8]) -> Result<()> {
|
||||
let flex_vec = flexbuffers::Reader::get_root(data)?.get_vector()?;
|
||||
let node = Node::create(
|
||||
&calling_client,
|
||||
|
||||
220
src/nodes/drawable/text.rs
Normal file
220
src/nodes/drawable/text.rs
Normal file
@@ -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<Text> = Registry::new();
|
||||
|
||||
struct TextData {
|
||||
text: String,
|
||||
character_height: f32,
|
||||
text_align: TextAlign,
|
||||
bounds: Option<Vec2>,
|
||||
fit: TextFit,
|
||||
bounds_align: TextAlign,
|
||||
color: Rgba<f32>,
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
space: Arc<Spatial>,
|
||||
font_path: Option<PathBuf>,
|
||||
style: OnceCell<SendWrapper<TextStyle>>,
|
||||
|
||||
data: Mutex<TextData>,
|
||||
}
|
||||
impl Text {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn add_to(
|
||||
node: &Arc<Node>,
|
||||
font_resource_id: Option<ResourceID>,
|
||||
text: String,
|
||||
character_height: f32,
|
||||
text_align: TextAlign,
|
||||
bounds: Option<Vec2>,
|
||||
fit: TextFit,
|
||||
bounds_align: TextAlign,
|
||||
color: Rgba<f32>,
|
||||
) -> Result<Arc<Text>> {
|
||||
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<SendWrapper<TextStyle>, 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<Client>,
|
||||
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<Client>, 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<Client>, 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(())
|
||||
}
|
||||
@@ -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<Arc<Model>>,
|
||||
pub text: OnceCell<Arc<Text>>,
|
||||
|
||||
// Input
|
||||
pub input_method: OnceCell<Arc<InputMethod>>,
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -277,15 +277,15 @@ pub fn parse_transform<B: flexbuffers::Buffer>(
|
||||
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(),
|
||||
|
||||
Reference in New Issue
Block a user