//! `Framable` is a property to frame an item use crate::prelude::*; use anyhow::Result; use std::{ ffi::c_void, sync::{ Arc, RwLock, Weak, atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering::SeqCst}, }, }; /// Describes the vertical alignment for a `Framable` #[derive(Copy, Clone, PartialEq)] pub enum VerticalAlign { NotSet, Top, Middle, Bottom, } impl Default for VerticalAlign { fn default() -> Self { Self::NotSet } } /// Describes the horizontal alignment for a `Framable` #[derive(Copy, Clone, PartialEq)] pub enum HorizontalAlign { NotSet, Left, Middle, Right, } impl Default for HorizontalAlign { fn default() -> Self { Self::NotSet } } pub struct Handle { ptr: *const c_void, } impl Handle { fn ptr_eq(&self, other: &Handle) -> bool { self.ptr == other.ptr } } unsafe impl Sync for Handle {} unsafe impl Send for Handle {} impl From> for Handle { fn from(weak: Weak) -> Self { let ptr: *const T = Weak::into_raw(weak); Self { ptr: ptr as *const c_void, } } } impl Drop for Handle { fn drop(&mut self) { unsafe { Weak::from_raw(self.ptr); } } } /// `Framable` keeps track of the position and size of an item /// and calls functions on resize to keep everything aligned correctly pub struct Framable { left: AtomicI32, right: AtomicI32, top: AtomicI32, bottom: AtomicI32, vertical_alignment: RwLock, horizontal_alignment: RwLock, resize_callbacks: RwLock< Vec<( Handle, Box) -> Result<()> + Send + Sync>, )>, >, x_off: AtomicI32, y_off: AtomicI32, w: AtomicU32, h: AtomicU32, framed: AtomicBool, ui_layer: AtomicI32, // old extent, needed for size check in enable window_width: AtomicU32, window_height: AtomicU32, reference_size: RwLock>, reference_scale_position: AtomicBool, reference_scale_size: AtomicBool, resize_allowed: bool, } impl Framable { /// Factory method for `Framable`, returns `Arc` pub fn new(window_width: u32, window_height: u32, resize_allowed: bool) -> Result> { Ok(Arc::new(Framable { window_width: AtomicU32::new(window_width), window_height: AtomicU32::new(window_height), left: AtomicI32::new(0), right: AtomicI32::new(0), top: AtomicI32::new(0), bottom: AtomicI32::new(0), vertical_alignment: RwLock::new(VerticalAlign::default()), horizontal_alignment: RwLock::new(HorizontalAlign::default()), resize_callbacks: RwLock::new(Vec::new()), x_off: AtomicI32::new(0), y_off: AtomicI32::new(0), w: AtomicU32::new(0), h: AtomicU32::new(0), framed: AtomicBool::new(false), ui_layer: AtomicI32::new(0), reference_size: RwLock::new(None), reference_scale_position: AtomicBool::new(false), reference_scale_size: AtomicBool::new(false), resize_allowed, })) } /// Add method /// /// # Arguments /// /// * `framable` - is a `&Arc` instance that is going to be added pub fn add(self: &Arc, gui_handler: &mut GuiHandler<'_>) -> Result<()> { // check if window size is the same as last time if gui_handler.width() != self.window_width.load(SeqCst) || gui_handler.height() != self.window_height.load(SeqCst) { // update window size self.window_width.store(gui_handler.width(), SeqCst); self.window_height.store(gui_handler.height(), SeqCst); // force resize self.resize(gui_handler)?; } if self.resize_allowed() { gui_handler.add_framable(self.ui_layer.load(SeqCst), self.clone())?; } Ok(()) } /// Delete method, has to be explicitly called, otherwise it will remain in memory /// /// # Arguments /// /// * `framable` - is a `&Arc` instance that is going to be deleted pub fn delete(self: &Arc, gui_handler: &mut GuiHandler<'_>) -> Result<()> { if self.resize_allowed() { gui_handler.delete_framable(self.ui_layer.load(SeqCst), self)?; } Ok(()) } pub fn set_ui_layer(&self, ui_layer: i32) { self.ui_layer.store(ui_layer, SeqCst); } pub fn ui_layer(&self) -> i32 { self.ui_layer.load(SeqCst) } pub(crate) fn set_reference_size( &self, gui_handler: &mut GuiHandler<'_>, width: u32, height: u32, ) -> Result<()> { *self.reference_size.write().unwrap() = Some((width, height)); self.reference_scale_position.store(true, SeqCst); self.reference_scale_size.store(true, SeqCst); if self.is_framed() { self.resize(gui_handler)?; } Ok(()) } pub(crate) fn allow_position_scale( &self, gui_handler: &mut GuiHandler<'_>, allowed: bool, ) -> Result<()> { if allowed != self.reference_scale_position.load(SeqCst) { self.reference_scale_position.store(allowed, SeqCst); if self.is_framed() { self.resize(gui_handler)?; } } Ok(()) } pub(crate) fn allow_size_scale( &self, gui_handler: &mut GuiHandler<'_>, allowed: bool, ) -> Result<()> { if allowed != self.reference_scale_size.load(SeqCst) { self.reference_scale_size.store(allowed, SeqCst); if self.is_framed() { self.resize(gui_handler)?; } } Ok(()) } pub fn resize_allowed(&self) -> bool { self.resize_allowed } /// Method to set the frame to a certain position with certain /// width and a certain alignment pub fn set_frame( &self, gui_handler: &GuiHandler<'_>, x_off: i32, y_off: i32, w: u32, h: u32, vertical_align: VerticalAlign, horizontal_align: HorizontalAlign, ) { self.x_off.store(x_off, SeqCst); self.y_off.store(y_off, SeqCst); self.w.store(w, SeqCst); self.h.store(h, SeqCst); *self.vertical_alignment.write().unwrap() = vertical_align; *self.horizontal_alignment.write().unwrap() = horizontal_align; self.calculate_frame(gui_handler); self.framed.store(true, SeqCst); } /// Returns the left edge in pixels of this framable, /// calculated from the left of the window pub fn left(&self) -> i32 { self.left.load(SeqCst) } /// Returns the right edge in pixels of this framable, /// calculated from the left of the window pub fn right(&self) -> i32 { self.right.load(SeqCst) } /// Returns the top edge in pixels of this framable, /// calculated from the top of the window pub fn top(&self) -> i32 { self.top.load(SeqCst) } /// Returns the bottom edge in pixels of this framable, /// calculated from the top of the window pub fn bottom(&self) -> i32 { self.bottom.load(SeqCst) } pub fn position(&self) -> (i32, i32, u32, u32) { let x = self.left(); let y = self.top(); let w = self.right() - x; let h = self.bottom() - y; (x, y, w as u32, h as u32) } /// Returns `true` if `set_frame` got called, otherwise `false` pub fn is_framed(&self) -> bool { self.framed.load(SeqCst) } /// Adds a callback closure which is executed on resize pub fn add_callback( &self, handle: impl Into, callback: Box) -> Result<()> + Send + Sync>, ) { let handle = handle.into(); if cfg!(debug_assertions) && self .resize_callbacks .read() .unwrap() .iter() .any(|(h, _)| h.ptr_eq(&handle)) { panic!("could not add an object twice"); } self.resize_callbacks .write() .unwrap() .push((handle, callback)); } pub fn remove_callback(&self, handle: impl Into) { let mut callbacks = self.resize_callbacks.write().unwrap(); let handle = handle.into(); if let Some(position) = callbacks.iter().position(|(h, _)| h.ptr_eq(&handle)) { #[allow(unused_must_use)] { callbacks.remove(position); } } } // Returns vertical alignment of this framable pub fn vertical_alignment(&self) -> VerticalAlign { *self.vertical_alignment.read().unwrap() } // Returns horizontal alignment of this framable pub fn horizontal_alignment(&self) -> HorizontalAlign { *self.horizontal_alignment.read().unwrap() } fn calculate_frame(&self, gui_handler: &GuiHandler<'_>) { let width = gui_handler.width(); let height = gui_handler.height(); let y_align = match *self.vertical_alignment.read().unwrap() { VerticalAlign::Top => 0, VerticalAlign::Middle => height / 2, VerticalAlign::Bottom => height, VerticalAlign::NotSet => { if cfg!(debug_assertions) { println!("vertical alignment in framable not set"); } height / 2 } }; let x_align = match *self.horizontal_alignment.read().unwrap() { HorizontalAlign::Left => 0, HorizontalAlign::Middle => width / 2, HorizontalAlign::Right => width, HorizontalAlign::NotSet => { if cfg!(debug_assertions) { println!("horizontal alignment in framable not set"); } width / 2 } }; let scale = match self.reference_size.read().unwrap().as_ref() { Some((reference_width, reference_height)) => { let width_ratio = width as f32 / *reference_width as f32; let height_ratio = height as f32 / *reference_height as f32; let min = width_ratio.min(height_ratio); min.max(1.0) } None => 1.0, }; let (left, top) = if self.reference_scale_position.load(SeqCst) { let left = x_align as i32 + (self.x_off.load(SeqCst) as f32 * scale) as i32; let top = y_align as i32 + (self.y_off.load(SeqCst) as f32 * scale) as i32; (left, top) } else { let left = x_align as i32 + self.x_off.load(SeqCst); let top = y_align as i32 + self.y_off.load(SeqCst); (left, top) }; let (right, bottom) = if self.reference_scale_size.load(SeqCst) { let right = left + (self.w.load(SeqCst) as f32 * scale) as i32; let bottom = top + (self.h.load(SeqCst) as f32 * scale) as i32; (right, bottom) } else { let right = left + self.w.load(SeqCst) as i32; let bottom = top + self.h.load(SeqCst) as i32; (right, bottom) }; self.left.store(left, SeqCst); self.right.store(right, SeqCst); self.top.store(top, SeqCst); self.bottom.store(bottom, SeqCst); } pub fn change_position( &self, gui_handler: &mut GuiHandler<'_>, x_offset: i32, y_offset: i32, ) -> Result<()> { assert!(self.is_framed(), "framable needs to be framed first!"); self.x_off.store(x_offset, SeqCst); self.y_off.store(y_offset, SeqCst); self.resize(gui_handler)?; Ok(()) } pub fn change_position_unscaled( &self, gui_handler: &mut GuiHandler<'_>, x: i32, y: i32, ) -> Result<()> { assert!(self.is_framed(), "framable needs to be framed first!"); let left = self.left.load(SeqCst); let top = self.top.load(SeqCst); let bottom = self.bottom.load(SeqCst); let right = self.right.load(SeqCst); let width = right - left; let height = bottom - top; self.left.store(x, SeqCst); self.top.store(y, SeqCst); self.right.store(x + width, SeqCst); self.bottom.store(y + height, SeqCst); for (_, callback) in self.resize_callbacks.read().unwrap().iter() { callback(gui_handler)?; } Ok(()) } pub fn resize(&self, gui_handler: &mut GuiHandler<'_>) -> Result<()> { self.calculate_frame(gui_handler); for (_, callback) in self.resize_callbacks.read().unwrap().iter() { callback(gui_handler)?; } Ok(()) } }