Files
server/src/nodes/drawable/text.rs
2022-11-08 06:10:03 -05:00

212 lines
5.0 KiB
Rust

use crate::{
core::{client::Client, destroy_queue, registry::Registry, resource::ResourceID},
nodes::{
spatial::{find_spatial_parent, parse_transform, Spatial},
Node,
},
};
use anyhow::{anyhow, ensure, Result};
use glam::{vec3, Mat4, Vec2};
use mint::Vector2;
use once_cell::sync::OnceCell;
use parking_lot::Mutex;
use prisma::{Flatten, Rgb, Rgba};
use send_wrapper::SendWrapper;
use serde::Deserialize;
use stardust_xr::{schemas::flex::deserialize, values::Transform};
use std::{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<Vector2<f32>>,
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 client = node
.get_client()
.ok_or_else(|| anyhow!("Client not found"))?;
let text = TEXT_REGISTRY.add(Text {
space: node.spatial.get().unwrap().clone(),
font_path: font_resource_id
.and_then(|res| res.get_file(&client.base_resource_prefixes.lock().clone())),
style: OnceCell::new(),
data: Mutex::new(TextData {
text,
character_height,
text_align,
bounds: bounds.map(|b| b.into()),
fit,
bounds_align,
color,
}),
});
node.add_local_signal("set_character_height", Text::set_character_height_flex);
node.add_local_signal("set_text", 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<()> {
#[derive(Deserialize)]
struct CreateTextInfo<'a> {
name: &'a str,
parent_path: &'a str,
transform: Transform,
text: String,
font_resource: Option<ResourceID>,
character_height: f32,
text_align: TextAlign,
bounds: Option<Vector2<f32>>,
fit: TextFit,
bounds_align: TextAlign,
color: [f32; 4],
}
let info: CreateTextInfo = deserialize(data)?;
let node = Node::create(&calling_client, "/drawable/text", info.name, true);
let parent = find_spatial_parent(&calling_client, info.parent_path)?;
let transform = parse_transform(info.transform, true, true, true)?;
let color = Rgba::from_slice(&info.color);
let node = node.add_to_scenegraph();
Spatial::add_to(&node, Some(parent), transform, false)?;
Text::add_to(
&node,
info.font_resource,
info.text,
info.character_height,
info.text_align,
info.bounds,
info.fit,
info.bounds_align,
color,
)?;
Ok(())
}