ui/src/gui_handler/gui/textable.rs

543 lines
17 KiB
Rust
Raw Normal View History

2023-01-16 09:53:52 +00:00
//! `Textable` is a property to display text on an item
use super::writeable::ModifyText;
use crate::prelude::*;
use anyhow::Result;
2023-01-16 11:58:59 +00:00
use utilities::prelude::*;
use vulkan_rs::prelude::*;
2023-01-16 09:53:52 +00:00
use super::texturedvertex::TexturedVertex;
use std::{
convert::TryFrom,
sync::{
Arc, RwLock,
atomic::{AtomicI32, AtomicU32, Ordering::SeqCst},
2023-01-16 09:53:52 +00:00
},
};
/// `TextAlignment` is used to describe where the text of `Textable` is aligned to
#[derive(Clone, Copy, Debug)]
pub enum TextAlignment {
Left,
Right,
Top,
Bottom,
Center,
}
impl Default for TextAlignment {
fn default() -> Self {
Self::Center
}
}
impl TryFrom<String> for TextAlignment {
type Error = anyhow::Error;
fn try_from(text: String) -> Result<TextAlignment> {
Ok(match text.as_str() {
"left" => Self::Left,
"right" => Self::Right,
"top" => Self::Top,
"bottom" => Self::Bottom,
"center" => Self::Center,
_ => {
return Err(anyhow::Error::msg(format!(
"Failed parsing TextAlignment from: {}",
text
)));
2023-01-16 09:53:52 +00:00
}
})
}
}
/// `Textable` gives the ability to display text inside an item
pub struct Textable {
framable: Arc<Framable>,
text_alignment: RwLock<TextAlignment>,
text: RwLock<String>,
vertex_count: AtomicU32,
height_ratio: RwLock<f32>,
character_size: AtomicU32,
descriptor_set: RwLock<Arc<DescriptorSet>>,
buffer: Arc<RwLock<Option<Arc<Buffer<TexturedVertex>>>>>,
ui_layer: AtomicI32,
}
impl Textable {
/// Factory method for `Textable`, returns `Arc<Textable>`.
///
/// # Arguments
///
/// * `framable` is a `Arc<Framable>` instance
/// * `text` the text to be displayed
/// * `height_ratio` the ratio of the height in respect to the framable height
/// * `text_alignment` where the text is aligned to
pub fn new(
gui_handler: &mut GuiHandler<'_>,
2023-01-16 09:53:52 +00:00
framable: Arc<Framable>,
text: String,
height_ratio: f32,
text_alignment: TextAlignment,
text_color: Color,
) -> Result<Arc<Self>> {
let set = gui_handler.color_descriptor(text_color)?;
2023-01-16 09:53:52 +00:00
let buffer = if text.is_empty() {
None
} else {
Some(Self::create_text_buffer(
gui_handler.device(),
2023-01-16 09:53:52 +00:00
text.len() as u32 * 6,
)?)
};
let textable = Arc::new(Textable {
vertex_count: AtomicU32::new(text.len() as u32 * 6),
text_alignment: RwLock::new(text_alignment),
text: RwLock::new(text),
height_ratio: RwLock::new(height_ratio),
character_size: AtomicU32::new(
((framable.bottom() as i32 - framable.top()) as f32 * height_ratio) as u32,
),
framable,
descriptor_set: RwLock::new(set),
buffer: Arc::new(RwLock::new(buffer)),
ui_layer: AtomicI32::new(0),
});
let textable_clone = textable.clone();
let weak_textable = Arc::downgrade(&textable);
textable.framable.add_callback(
weak_textable,
Box::new(move |gui_handler| textable_clone.update_text(gui_handler)),
2023-01-16 09:53:52 +00:00
);
Ok(textable)
}
fn create_text_buffer(
device: &Arc<Device>,
letter_count: u32,
) -> Result<Arc<Buffer<TexturedVertex>>> {
Buffer::builder()
.set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
.set_memory_usage(MemoryUsage::CpuOnly)
.set_size(letter_count as VkDeviceSize)
.build(device.clone())
}
/// Add method
///
/// # Arguments
///
/// * `textable` is a `&Arc<Textable>` instance that is going to be added
pub fn add(self: &Arc<Self>, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
gui_handler.add_textable(self.ui_layer.load(SeqCst), self.clone())
2023-01-16 09:53:52 +00:00
}
/// Delete method, has to be explicitly called, otherwise it will remain in memory.
///
/// # Arguments
///
/// * `textable` is a `&Arc<Textable>` instance that is going to be deleted
pub fn delete(self: &Arc<Self>, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
gui_handler.delete_textable(self.ui_layer.load(SeqCst), self)
2023-01-16 09:53:52 +00:00
}
pub fn set_ui_layer(&self, ui_layer: i32) {
self.ui_layer.store(ui_layer, SeqCst);
}
pub fn clear_callback(self: &Arc<Self>) {
let weak_textable = Arc::downgrade(self);
self.framable.remove_callback(weak_textable);
}
/// Changes text color
///
/// # Arguments
///
/// * `text_color` defines the color of the text
pub fn set_text_color(
&self,
gui_handler: &mut GuiHandler<'_>,
text_color: Color,
) -> Result<()> {
let set = gui_handler.color_descriptor(text_color)?;
2023-01-16 09:53:52 +00:00
*self.descriptor_set.write().unwrap() = set;
Ok(())
}
/// Sets the text alignment
///
/// # Arguments
///
/// * `text_alignment` where the text is aligned to
pub fn set_text_alignment(
&self,
gui_handler: &mut GuiHandler<'_>,
text_alignment: TextAlignment,
) -> Result<()> {
2023-01-16 09:53:52 +00:00
*self.text_alignment.write().unwrap() = text_alignment;
self.update_text(gui_handler)?;
2023-01-16 09:53:52 +00:00
Ok(())
}
/// Returns the count of vertices described by the buffer
pub fn vertex_count(&self) -> u32 {
self.vertex_count.load(SeqCst)
}
/// Returns the internal vulkan buffer
pub fn buffer(&self) -> Option<Arc<Buffer<TexturedVertex>>> {
self.buffer.read().unwrap().clone()
}
/// Returns the internal vulkan descriptor set
pub fn descriptor_set(&self) -> Arc<DescriptorSet> {
self.descriptor_set.read().unwrap().clone()
}
/// Sets the text with height ratio
///
/// # Arguments
///
/// * `text` the text to be displayed
/// * `height_ratio` the ratio of the height in respect to the framable height
pub fn set_text(&self, gui_handler: &mut GuiHandler<'_>, text: impl ToString) -> Result<()> {
2023-01-16 09:53:52 +00:00
let text = text.to_string();
if text.is_empty() {
self.vertex_count.store(0, SeqCst);
*self.text.write().unwrap() = String::new();
gui_handler.enqueue_text_update({
2023-01-16 09:53:52 +00:00
let weak_buffer = Arc::downgrade(&self.buffer);
Box::new(move || {
if let Some(buffer) = weak_buffer.upgrade() {
*buffer.write().unwrap() = None;
}
Ok(())
})
});
} else {
self.vertex_count.store(text.len() as u32 * 6, SeqCst);
*self.text.write().unwrap() = text;
self.update_text(gui_handler)?;
2023-01-16 09:53:52 +00:00
}
Ok(())
}
/// Sets the text size with the given height ratio
///
/// # Arguments
///
/// * `height_ratio` the ratio of the height in respect to the framable height
pub fn set_size(&self, gui_handler: &mut GuiHandler<'_>, height_ratio: f32) -> Result<()> {
2023-01-16 09:53:52 +00:00
*self.height_ratio.write().unwrap() = height_ratio;
self.update_text(gui_handler)?;
2023-01-16 09:53:52 +00:00
Ok(())
}
/// Returns the text
pub fn text(&self) -> String {
self.text.read().unwrap().clone()
}
fn calculate_text_size(&self) {
self.character_size.store(
((self.framable.bottom() as i32 - self.framable.top()) as f32
* *self.height_ratio.read().unwrap()) as u32,
SeqCst,
);
let width = self.framable.right() as i32 - self.framable.left();
let text_len = self.text.read().unwrap().len();
let text_width = (self.character_size.load(SeqCst) as f32 * 0.5 * text_len as f32) as u32;
if text_width > width as u32 {
self.character_size.store(
((self.framable.right() as i32 - self.framable.left()) / text_len as i32) as u32
* 2,
SeqCst,
);
}
}
/// Updates the texts buffer
pub fn update_text(&self, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
2023-01-16 09:53:52 +00:00
self.calculate_text_size();
let (x, y) = self.calc_pos_from_alignment();
self.create_buffer(gui_handler, x, y)?;
2023-01-16 09:53:52 +00:00
Ok(())
}
fn center_y(&self) -> f32 {
(self.framable.top() + self.framable.bottom()) as f32 * 0.5
- self.character_size.load(SeqCst) as f32 * 0.5
}
fn center_x(&self) -> f32 {
(self.framable.left() + self.framable.right()) as f32 * 0.5
- (self.character_size.load(SeqCst) as f32
* 0.25
* self.text.read().unwrap().len() as f32)
}
fn calc_pos_from_alignment(&self) -> (f32, f32) {
match *self.text_alignment.read().unwrap() {
TextAlignment::Left => (self.framable.left() as f32, self.center_y()),
TextAlignment::Right => (
self.framable.right() as f32
- (self.character_size.load(SeqCst) as f32
* 0.5
* self.text.read().unwrap().len() as f32),
self.center_y(),
),
TextAlignment::Top => (self.center_x(), self.framable.top() as f32),
TextAlignment::Bottom => (
self.center_x(),
(self.framable.bottom() - self.character_size.load(SeqCst) as i32) as f32,
),
TextAlignment::Center => (self.center_x(), self.center_y()),
}
}
fn create_buffer(
&self,
gui_handler: &mut GuiHandler<'_>,
win_x: f32,
win_y: f32,
) -> Result<()> {
2023-01-16 09:53:52 +00:00
let weak_buffer = Arc::downgrade(&self.buffer);
let text = self.text.read().unwrap().clone();
let character_size = self.character_size.load(SeqCst);
let ortho = gui_handler.ortho();
2023-01-16 09:53:52 +00:00
let device = gui_handler.device().clone();
2023-01-16 09:53:52 +00:00
let async_buffer_creation = Box::new(move || {
if let Some(buffer) = weak_buffer.upgrade() {
let mut offset = 0.0;
// variable to calculate letter position in bitmap font
let letters_in_row = 16;
let inverse_row = 0.0625;
let inverse_col = 0.125;
let letter_height = character_size as f32;
let letter_width = letter_height * 0.5;
let priority = 0.0;
let mut buffer = buffer.write().unwrap();
let text_buffer_len = text.len() as u32 * 6;
match buffer.as_mut() {
Some(buf) => {
if buf.size() != text_buffer_len as VkDeviceSize {
*buffer = if text.is_empty() {
None
} else {
Some(Self::create_text_buffer(&device, text_buffer_len)?)
}
}
}
None => {
if !text.is_empty() {
*buffer = Some(Self::create_text_buffer(&device, text_buffer_len)?)
}
}
}
if let Some(buffer) = buffer.as_ref() {
let mut buffer_mapping = buffer.map_complete()?;
let mut i = 0;
for letter in text.chars() {
let mod_number = if letter as u32 >= 32 {
letter as u32 - 32
} else {
0
};
// coordinates to describe letter position in bitmap font
let y = ((mod_number as f32) * inverse_row).floor();
let x = (mod_number - (y as u32 * letters_in_row)) as f32;
buffer_mapping[i] = TexturedVertex {
2024-05-13 13:03:54 +00:00
position: (ortho
2023-01-16 09:53:52 +00:00
* cgmath::Vector4::new(
win_x + offset,
win_y + letter_height,
priority,
1.0,
2024-05-13 13:03:54 +00:00
))
.xy(),
2023-01-16 09:53:52 +00:00
texture_coordinates: cgmath::Vector2::new(
inverse_row * x,
inverse_col + inverse_col * y,
),
};
i += 1;
buffer_mapping[i] = TexturedVertex {
2024-05-13 13:03:54 +00:00
position: (ortho
2023-01-16 09:53:52 +00:00
* cgmath::Vector4::new(
win_x + offset + letter_width,
win_y + letter_height,
priority,
1.0,
2024-05-13 13:03:54 +00:00
))
.xy(),
2023-01-16 09:53:52 +00:00
texture_coordinates: cgmath::Vector2::new(
inverse_row + inverse_row * x,
inverse_col + inverse_col * y,
),
};
i += 1;
buffer_mapping[i] = TexturedVertex {
2024-05-13 13:03:54 +00:00
position: (ortho
2023-01-16 09:53:52 +00:00
* cgmath::Vector4::new(
win_x + offset + letter_width,
win_y,
priority,
1.0,
2024-05-13 13:03:54 +00:00
))
.xy(),
2023-01-16 09:53:52 +00:00
texture_coordinates: cgmath::Vector2::new(
inverse_row + inverse_row * x,
inverse_col * y,
),
};
i += 1;
buffer_mapping[i] = TexturedVertex {
2024-05-13 13:03:54 +00:00
position: (ortho
2023-01-16 09:53:52 +00:00
* cgmath::Vector4::new(
win_x + offset + letter_width,
win_y,
priority,
1.0,
2024-05-13 13:03:54 +00:00
))
.xy(),
2023-01-16 09:53:52 +00:00
texture_coordinates: cgmath::Vector2::new(
inverse_row + inverse_row * x,
inverse_col * y,
),
};
i += 1;
buffer_mapping[i] = TexturedVertex {
2024-05-13 13:03:54 +00:00
position: (ortho
* cgmath::Vector4::new(win_x + offset, win_y, priority, 1.0))
.xy(),
2023-01-16 09:53:52 +00:00
texture_coordinates: cgmath::Vector2::new(
inverse_row * x,
inverse_col * y,
),
};
i += 1;
buffer_mapping[i] = TexturedVertex {
2024-05-13 13:03:54 +00:00
position: (ortho
2023-01-16 09:53:52 +00:00
* cgmath::Vector4::new(
win_x + offset,
win_y + letter_height,
priority,
1.0,
2024-05-13 13:03:54 +00:00
))
.xy(),
2023-01-16 09:53:52 +00:00
texture_coordinates: cgmath::Vector2::new(
inverse_row * x,
inverse_col + inverse_col * y,
),
};
i += 1;
offset += letter_width;
}
}
}
Ok(())
});
gui_handler.enqueue_text_update(async_buffer_creation);
2023-01-16 09:53:52 +00:00
Ok(())
}
fn text_changed_through_write(&self, gui_handler: &mut GuiHandler<'_>) -> Result<()> {
2023-01-16 09:53:52 +00:00
self.vertex_count
.store(self.text.read().unwrap().len() as u32 * 6, SeqCst);
self.character_size.store(
((self.framable.bottom() as i32 - self.framable.top()) as f32
* *self.height_ratio.read().unwrap()) as u32,
SeqCst,
);
self.update_text(gui_handler)?;
2023-01-16 09:53:52 +00:00
Ok(())
}
}
impl ModifyText for Textable {
fn set_text(&self, gui_handler: &mut GuiHandler<'_>, text: String) -> Result<()> {
2023-01-16 09:53:52 +00:00
*self.text.write().unwrap() = text;
self.text_changed_through_write(gui_handler)
2023-01-16 09:53:52 +00:00
}
fn add_letter(&self, gui_handler: &mut GuiHandler<'_>, letter: char) -> Result<String> {
2023-01-16 09:53:52 +00:00
self.text.write().unwrap().push(letter);
self.text_changed_through_write(gui_handler)?;
2023-01-16 09:53:52 +00:00
Ok(self.text.read().unwrap().clone())
}
fn remove_last(&self, gui_handler: &mut GuiHandler<'_>) -> Result<Option<String>> {
2023-01-16 09:53:52 +00:00
self.text.write().unwrap().pop();
self.text_changed_through_write(gui_handler)?;
2023-01-16 09:53:52 +00:00
let text = self.text.read().unwrap().clone();
if text.is_empty() {
Ok(None)
} else {
Ok(Some(text))
}
}
}