ui/src/guihandler/gui/framable.rs

460 lines
12 KiB
Rust
Raw Normal View History

2023-01-16 09:53:52 +00:00
//! `Framable` is a property to frame an item
use crate::prelude::*;
use anyhow::Result;
use std::{
ffi::c_void,
sync::{
atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering::SeqCst},
Arc, RwLock, Weak,
},
};
2023-01-27 12:46:54 +00:00
use vulkan_rs::prelude::cgmath::Matrix4;
2023-01-16 09:53:52 +00:00
/// 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<T> From<Weak<T>> for Handle {
fn from(weak: Weak<T>) -> 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 {
gui_handler: Arc<GuiHandler>,
left: AtomicI32,
right: AtomicI32,
top: AtomicI32,
bottom: AtomicI32,
vertical_alignment: RwLock<VerticalAlign>,
horizontal_alignment: RwLock<HorizontalAlign>,
resize_callbacks: RwLock<Vec<(Handle, Box<dyn Fn() -> 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<Option<(u32, u32)>>,
reference_scale_position: AtomicBool,
reference_scale_size: AtomicBool,
resize_allowed: bool,
}
impl Framable {
/// Factory method for `Framable`, returns `Arc<Framable>`
pub fn new(gui_handler: Arc<GuiHandler>, resize_allowed: bool) -> Result<Arc<Self>> {
Ok(Arc::new(Framable {
window_width: AtomicU32::new(gui_handler.width()),
window_height: AtomicU32::new(gui_handler.height()),
gui_handler,
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<Framable>` instance that is going to be added
pub fn add(self: &Arc<Self>) -> Result<()> {
// check if window size is the same as last time
if self.gui_handler.width() != self.window_width.load(SeqCst)
|| self.gui_handler.height() != self.window_height.load(SeqCst)
{
// update window size
self.window_width.store(self.gui_handler.width(), SeqCst);
self.window_height.store(self.gui_handler.height(), SeqCst);
// force resize
self.resize()?;
}
if self.resize_allowed() {
self.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<Framable>` instance that is going to be deleted
pub fn delete(self: &Arc<Self>) -> Result<()> {
if self.resize_allowed() {
self.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, 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()?;
}
Ok(())
}
pub(crate) fn allow_position_scale(&self, allowed: bool) -> Result<()> {
if allowed != self.reference_scale_position.load(SeqCst) {
self.reference_scale_position.store(allowed, SeqCst);
if self.is_framed() {
self.resize()?;
}
}
Ok(())
}
pub(crate) fn allow_size_scale(&self, allowed: bool) -> Result<()> {
if allowed != self.reference_scale_size.load(SeqCst) {
self.reference_scale_size.store(allowed, SeqCst);
if self.is_framed() {
self.resize()?;
}
}
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,
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();
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)
}
2024-04-21 05:41:08 +00:00
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)
}
2023-01-16 09:53:52 +00:00
/// Returns `true` if `set_frame` got called, otherwise `false`
pub fn is_framed(&self) -> bool {
self.framed.load(SeqCst)
}
/// Returns the ortho 4x4 matrix, which describes the window
pub fn ortho(&self) -> Matrix4<f32> {
self.gui_handler.ortho()
}
/// Adds a callback closure which is executed on resize
pub fn add_callback(
&self,
handle: impl Into<Handle>,
callback: Box<dyn Fn() -> 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<Handle>) {
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) {
let width = self.gui_handler.width();
let height = self.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, 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()?;
Ok(())
}
2024-04-21 06:28:17 +00:00
pub fn change_position_unscaled(&self, x: i32, y: i32) -> Result<()> {
assert!(self.is_framed(), "framable needs to be framed first!");
self.left.store(x, SeqCst);
self.top.store(y, SeqCst);
for (_, callback) in self.resize_callbacks.read().unwrap().iter() {
callback()?;
}
Ok(())
}
2023-01-16 09:53:52 +00:00
pub fn resize(&self) -> Result<()> {
self.calculate_frame();
for (_, callback) in self.resize_callbacks.read().unwrap().iter() {
callback()?;
}
Ok(())
}
pub(crate) fn gui_handler(&self) -> &Arc<GuiHandler> {
&self.gui_handler
}
}