//! `Iconizable` is a property to display a icon on an item use crate::prelude::*; use anyhow::Result; use assetpath::AssetPath; use utilities::prelude::*; use vulkan_rs::prelude::*; use super::texturedvertex::TexturedVertex; use std::sync::{ atomic::{AtomicI32, AtomicU32, Ordering::SeqCst}, Arc, RwLock, }; #[derive(Clone, Debug)] pub enum IconBuilderType { Path(AssetPath), Image(Arc), } impl From for IconBuilderType { fn from(s: String) -> Self { IconBuilderType::Path(AssetPath::from(s)) } } impl From<&String> for IconBuilderType { fn from(s: &String) -> Self { IconBuilderType::Path(AssetPath::from(s.as_str())) } } impl From<&str> for IconBuilderType { fn from(s: &str) -> Self { IconBuilderType::Path(AssetPath::from(s)) } } impl From for IconBuilderType { fn from(assetpath: AssetPath) -> Self { IconBuilderType::Path(assetpath) } } impl From> for IconBuilderType { fn from(image: Arc) -> Self { IconBuilderType::Image(image) } } impl From<&Arc> for IconBuilderType { fn from(image: &Arc) -> Self { IconBuilderType::Image(image.clone()) } } #[derive(Debug, Clone)] pub struct IconizablePositioning { pub left: f32, pub right: f32, pub top: f32, pub bottom: f32, } /// `Iconizable` gives the ability to display a icon as icon on an UI item pub struct Iconizable { framable: Arc, buffer: Arc>, icon: RwLock>, descriptor_set: Arc, margin: AtomicU32, ui_layer: AtomicI32, positioning: Option, } impl Iconizable { /// Factory method for `Iconizable`, returns `Arc` /// /// # Arguments /// /// * `framable` is a `Arc` instance /// * `icon` is a reference to an `Arc` pub fn new( framable: Arc, icon_builder: impl Into, positioning: impl Into>, ) -> Result> { let icon = framable.gui_handler().request_icon(icon_builder)?; let device = framable.gui_handler().device(); let desc_pool = DescriptorPool::builder() .set_layout(framable.gui_handler().icon_descriptor_layout().clone()) .build(device.clone())?; let descriptor_set = DescriptorPool::prepare_set(&desc_pool).allocate()?; descriptor_set.update(&[DescriptorWrite::combined_samplers(0, &[&icon])])?; let buffer = Buffer::builder() .set_usage(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) .set_memory_usage(MemoryUsage::CpuOnly) .set_size(6) .build(device.clone())?; let iconizable = Arc::new(Iconizable { framable, buffer, icon: RwLock::new(icon), descriptor_set, margin: AtomicU32::new(0), ui_layer: AtomicI32::new(0), positioning: positioning.into(), }); let iconizable_clone = iconizable.clone(); let weak_iconizable = Arc::downgrade(&iconizable); iconizable.framable.add_callback( weak_iconizable, Box::new(move || iconizable_clone.update_frame()), ); Ok(iconizable) } /// Add method /// /// # Arguments /// /// * `iconizable` is a `&Arc` instance that is going to be added pub fn add(self: &Arc) -> Result<()> { self.framable .gui_handler() .add_iconizable(self.ui_layer.load(SeqCst), self.clone()) } /// Delete method, has to be explicitly called, otherwise it will remain in memory /// /// # Arguments /// /// * `iconizable` is a `&Arc` instance that is going to be deleted pub fn delete(self: &Arc) -> Result<()> { self.framable .gui_handler() .delete_iconizable(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_iconizable = Arc::downgrade(self); self.framable.remove_callback(weak_iconizable); } /// Sets the frame parameter for /// /// # Arguments /// /// * `vertical_alignment` where this icon is aligned to inside the framable /// * `horizontal_alignment` where this icon is aligned to inside the framable pub fn set_margin(&self, margin: u32) -> Result<()> { self.margin.store(margin, SeqCst); Ok(()) } /// Updates the frame pub fn update_frame(&self) -> Result<()> { assert!(self.framable.is_framed(), "framable is not framed yet"); // frame parameter let mut parent_left = self.framable.left(); let parent_right = self.framable.right(); let mut parent_top = self.framable.top(); let parent_bottom = self.framable.bottom(); let mut parent_width = parent_right as i32 - parent_left; let mut parent_height = parent_bottom as i32 - parent_top; if let Some(positioning) = &self.positioning { parent_left += ((parent_width as f32 / 2.0) * positioning.left) as i32; parent_width = (parent_width as f32 * ((positioning.left + positioning.right) / 2.0)) as i32; parent_top += ((parent_height as f32 / 2.0) * positioning.top) as i32; parent_height = (parent_height as f32 * ((positioning.top + positioning.bottom) / 2.0)) as i32; } // icon parameter let (icon_width, icon_height) = { let icon = self.icon.read().unwrap(); let width = icon.width(); let height = icon.height(); (width, height) }; // check how the icon fits best into the frame let margin = self.margin.load(SeqCst); let actual_icon_width = icon_width + 2 * margin; let actual_icon_height = icon_height + 2 * margin; let icon_ratio = actual_icon_width as f32 / actual_icon_height as f32; let parent_ratio = parent_width as f32 / parent_height as f32; let (resulting_width, resulting_height) = if parent_ratio > icon_ratio { // this case means, that the parent is wider than the icon let resulting_height = parent_height as f32; let resulting_width = resulting_height * icon_ratio; (resulting_width, resulting_height) } else if parent_ratio < icon_ratio { let resulting_width = parent_width as f32; let resulting_height = resulting_width / icon_ratio; (resulting_width, resulting_height) } else { (parent_width as f32, parent_height as f32) }; let mut top = (parent_height as f32 / 2.0) - (resulting_height / 2.0); let mut left = (parent_width as f32 / 2.0) - (resulting_width / 2.0); left += parent_left as f32; top += parent_top as f32; let right = left + resulting_width; let bottom = top + resulting_height; let ortho = self.framable.ortho(); let mut frame = self.buffer.map_complete()?; let abs_left = left + margin as f32; let abs_right = right - margin as f32; let abs_top = top + margin as f32; let abs_bottom = bottom - margin as f32; frame[0].position = ortho * cgmath::Vector4::new(abs_left, abs_bottom, 0.0, 1.0); frame[1].position = ortho * cgmath::Vector4::new(abs_right, abs_bottom, 0.0, 1.0); frame[2].position = ortho * cgmath::Vector4::new(abs_right, abs_top, 0.0, 1.0); frame[3].position = ortho * cgmath::Vector4::new(abs_right, abs_top, 0.0, 1.0); frame[4].position = ortho * cgmath::Vector4::new(abs_left, abs_top, 0.0, 1.0); frame[5].position = ortho * cgmath::Vector4::new(abs_left, abs_bottom, 0.0, 1.0); frame[0].texture_coordinates = cgmath::Vector2::new(0.0, 1.0); frame[1].texture_coordinates = cgmath::Vector2::new(1.0, 1.0); frame[2].texture_coordinates = cgmath::Vector2::new(1.0, 0.0); frame[3].texture_coordinates = cgmath::Vector2::new(1.0, 0.0); frame[4].texture_coordinates = cgmath::Vector2::new(0.0, 0.0); frame[5].texture_coordinates = cgmath::Vector2::new(0.0, 1.0); Ok(()) } /// Replace the current icon with a new one /// /// # Arguments /// /// * `icon` the new icon pub fn set_icon(&self, icon_builder: impl Into) -> Result<()> { let icon = self.framable.gui_handler().request_icon(icon_builder)?; self.descriptor_set .update(&[DescriptorWrite::combined_samplers(0, &[&icon])])?; *self.icon.write().unwrap() = icon; Ok(()) } /// Returns the internal vulkan buffer pub fn buffer(&self) -> &Arc> { &self.buffer } /// Returns the internal descriptor set pub fn descriptor_set(&self) -> &Arc { &self.descriptor_set } pub fn icon(&self) -> Arc { self.icon.read().unwrap().clone() } }