ui/src/guihandler/gui/iconizable.rs

277 lines
8.3 KiB
Rust

//! `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<Image>),
}
impl From<String> 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<AssetPath> for IconBuilderType {
fn from(assetpath: AssetPath) -> Self {
IconBuilderType::Path(assetpath)
}
}
impl From<Arc<Image>> for IconBuilderType {
fn from(image: Arc<Image>) -> Self {
IconBuilderType::Image(image)
}
}
impl From<&Arc<Image>> for IconBuilderType {
fn from(image: &Arc<Image>) -> Self {
IconBuilderType::Image(image.clone())
}
}
/// `Iconizable` gives the ability to display a icon as icon on an UI item
pub struct Iconizable {
framable: Arc<Framable>,
buffer: Arc<Buffer<TexturedVertex>>,
icon: RwLock<Arc<Image>>,
descriptor_set: Arc<DescriptorSet>,
margin: AtomicU32,
ui_layer: AtomicI32,
}
impl Iconizable {
/// Factory method for `Iconizable`, returns `Arc<Iconizable>`
///
/// # Arguments
///
/// * `framable` is a `Arc<Framable>` instance
/// * `icon` is a reference to an `Arc<Image>`
pub fn new(
framable: Arc<Framable>,
icon_builder: impl Into<IconBuilderType>,
) -> Result<Arc<Self>> {
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),
});
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<Iconizable>` instance that is going to be added
pub fn add(self: &Arc<Self>) -> 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<Iconizable>` instance that is going to be deleted
pub fn delete(self: &Arc<Self>) -> 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<Self>) {
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 parent_left = self.framable.left();
let parent_right = self.framable.right();
let parent_top = self.framable.top();
let parent_bottom = self.framable.bottom();
let parent_width = parent_right as i32 - parent_left;
let parent_height = parent_bottom as i32 - parent_top;
// 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<IconBuilderType>) -> 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<Buffer<TexturedVertex>> {
&self.buffer
}
/// Returns the internal descriptor set
pub fn descriptor_set(&self) -> &Arc<DescriptorSet> {
&self.descriptor_set
}
pub fn icon(&self) -> Arc<Image> {
self.icon.read().unwrap().clone()
}
}