//! `Textable` is a property to display text on an item use super::writeable::ModifyText; use crate::prelude::*; use anyhow::Result; use super::texturedvertex::TexturedVertex; use std::{ convert::TryFrom, sync::{ atomic::{AtomicI32, AtomicU32, Ordering::SeqCst}, Arc, RwLock, }, }; /// `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 for TextAlignment { type Error = anyhow::Error; fn try_from(text: String) -> Result { 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 ))) } }) } } /// `Textable` gives the ability to display text inside an item pub struct Textable { framable: Arc, text_alignment: RwLock, text: RwLock, vertex_count: AtomicU32, height_ratio: RwLock, character_size: AtomicU32, descriptor_set: RwLock>, buffer: Arc>>>>, ui_layer: AtomicI32, } impl Textable { /// Factory method for `Textable`, returns `Arc`. /// /// # Arguments /// /// * `framable` is a `Arc` 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( framable: Arc, text: String, height_ratio: f32, text_alignment: TextAlignment, text_color: Color, ) -> Result> { let set = framable.gui_handler().color_descriptor(text_color)?; let buffer = if text.is_empty() { None } else { Some(Self::create_text_buffer( framable.gui_handler().device(), 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 || textable_clone.update_text()), ); Ok(textable) } fn create_text_buffer( device: &Arc, letter_count: u32, ) -> Result>> { 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` instance that is going to be added pub fn add(self: &Arc) -> Result<()> { self.framable .gui_handler() .add_textable(self.ui_layer.load(SeqCst), self.clone()) } /// Delete method, has to be explicitly called, otherwise it will remain in memory. /// /// # Arguments /// /// * `textable` is a `&Arc` instance that is going to be deleted pub fn delete(self: &Arc) -> Result<()> { self.framable .gui_handler() .delete_textable(self.ui_layer.load(SeqCst), self) } pub fn set_ui_layer(&self, ui_layer: i32) { self.ui_layer.store(ui_layer, SeqCst); } pub fn clear_callback(self: &Arc) { 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, text_color: Color) -> Result<()> { let set = self.framable.gui_handler().color_descriptor(text_color)?; *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, text_alignment: TextAlignment) -> Result<()> { *self.text_alignment.write().unwrap() = text_alignment; self.update_text()?; 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>> { self.buffer.read().unwrap().clone() } /// Returns the internal vulkan descriptor set pub fn descriptor_set(&self) -> Arc { 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, text: impl ToString) -> Result<()> { let text = text.to_string(); if text.is_empty() { self.vertex_count.store(0, SeqCst); *self.text.write().unwrap() = String::new(); self.framable.gui_handler().enqueue_text_update({ 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()?; } 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, height_ratio: f32) -> Result<()> { *self.height_ratio.write().unwrap() = height_ratio; self.update_text()?; 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) -> Result<()> { self.calculate_text_size(); let (x, y) = self.calc_pos_from_alignment(); self.create_buffer(x, y)?; 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, win_x: f32, win_y: f32) -> Result<()> { let weak_buffer = Arc::downgrade(&self.buffer); let text = self.text.read().unwrap().clone(); let character_size = self.character_size.load(SeqCst); let ortho = self.framable.ortho(); let device = self.framable.gui_handler().device().clone(); 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 { position: ortho * cgmath::Vector4::new( win_x + offset, win_y + letter_height, priority, 1.0, ), texture_coordinates: cgmath::Vector2::new( inverse_row * x, inverse_col + inverse_col * y, ), }; i += 1; buffer_mapping[i] = TexturedVertex { position: ortho * cgmath::Vector4::new( win_x + offset + letter_width, win_y + letter_height, priority, 1.0, ), texture_coordinates: cgmath::Vector2::new( inverse_row + inverse_row * x, inverse_col + inverse_col * y, ), }; i += 1; buffer_mapping[i] = TexturedVertex { position: ortho * cgmath::Vector4::new( win_x + offset + letter_width, win_y, priority, 1.0, ), texture_coordinates: cgmath::Vector2::new( inverse_row + inverse_row * x, inverse_col * y, ), }; i += 1; buffer_mapping[i] = TexturedVertex { position: ortho * cgmath::Vector4::new( win_x + offset + letter_width, win_y, priority, 1.0, ), texture_coordinates: cgmath::Vector2::new( inverse_row + inverse_row * x, inverse_col * y, ), }; i += 1; buffer_mapping[i] = TexturedVertex { position: ortho * cgmath::Vector4::new(win_x + offset, win_y, priority, 1.0), texture_coordinates: cgmath::Vector2::new( inverse_row * x, inverse_col * y, ), }; i += 1; buffer_mapping[i] = TexturedVertex { position: ortho * cgmath::Vector4::new( win_x + offset, win_y + letter_height, priority, 1.0, ), texture_coordinates: cgmath::Vector2::new( inverse_row * x, inverse_col + inverse_col * y, ), }; i += 1; offset += letter_width; } } } Ok(()) }); self.framable .gui_handler() .enqueue_text_update(async_buffer_creation); Ok(()) } fn text_changed_through_write(&self) -> Result<()> { 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()?; Ok(()) } } impl ModifyText for Textable { fn set_text(&self, text: String) -> Result<()> { *self.text.write().unwrap() = text; self.text_changed_through_write() } fn add_letter(&self, letter: char) -> Result { self.text.write().unwrap().push(letter); self.text_changed_through_write()?; Ok(self.text.read().unwrap().clone()) } fn remove_last(&self) -> Result> { self.text.write().unwrap().pop(); self.text_changed_through_write()?; let text = self.text.read().unwrap().clone(); if text.is_empty() { Ok(None) } else { Ok(Some(text)) } } }