481 lines
13 KiB
Rust
481 lines
13 KiB
Rust
//! `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<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 {
|
|
left: AtomicI32,
|
|
right: AtomicI32,
|
|
top: AtomicI32,
|
|
bottom: AtomicI32,
|
|
|
|
vertical_alignment: RwLock<VerticalAlign>,
|
|
horizontal_alignment: RwLock<HorizontalAlign>,
|
|
|
|
resize_callbacks: RwLock<
|
|
Vec<(
|
|
Handle,
|
|
Box<dyn Fn(&mut GuiHandler) -> 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(window_width: u32, window_height: u32, resize_allowed: bool) -> Result<Arc<Self>> {
|
|
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<Framable>` instance that is going to be added
|
|
pub fn add(self: &Arc<Self>, 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<Framable>` instance that is going to be deleted
|
|
pub fn delete(self: &Arc<Self>, 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<Handle>,
|
|
callback: Box<dyn Fn(&mut GuiHandler) -> 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, 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(())
|
|
}
|
|
}
|