#![warn(clippy::all)]
#![deny(clippy::unwrap_used)]
use std::collections::{HashMap, VecDeque};
use thunderdome::{Arena, Index as TdIndex};
pub mod clip;
pub mod grid;
pub mod overlap;
pub mod padding;
pub mod proportion;
pub mod stacks;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub enum Alignment {
#[default]
Begin,
Center,
End,
Full,
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub struct Rect {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
}
impl Rect {
pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
Self { x, y, w, h }
}
pub fn new_checked(x: f32, y: f32, w: f32, h: f32) -> Option<Self> {
if w >= 0.0 && h >= 0.0 {
Some(Self::new(x, y, w, h))
} else {
None
}
}
pub fn is_valid(&self) -> bool {
self.w >= 0.0 && self.h >= 0.0
}
pub fn is_empty(&self) -> bool {
self.w == 0.0 || self.h == 0.0
}
pub fn is_zero(&self) -> bool {
self.w == 0.0 && self.h == 0.0
}
pub fn shrink_begin_x(mut self, amount: f32) -> Self {
self.w -= amount;
self
}
pub fn shrink_end_x(mut self, amount: f32) -> Self {
self.w -= amount;
self.x += amount;
self
}
pub fn shrink_center_x(mut self, amount: f32) -> Self {
self.w -= amount;
self.x += amount * 0.5;
self
}
pub fn shrink_begin_y(mut self, amount: f32) -> Self {
self.h -= amount;
self
}
pub fn shrink_end_y(mut self, amount: f32) -> Self {
self.h -= amount;
self.y += amount;
self
}
pub fn shrink_center_y(mut self, amount: f32) -> Self {
self.h -= amount;
self.y += amount * 0.5;
self
}
pub fn align(mut self, align: (Alignment, Alignment), min: (f32, f32)) -> Self {
self = match align.0 {
Alignment::Begin => self.shrink_begin_x(self.w - min.0),
Alignment::Center => self.shrink_center_x(self.w - min.0),
Alignment::End => self.shrink_end_x(self.w - min.0),
Alignment::Full => self,
};
match align.1 {
Alignment::Begin => self.shrink_begin_y(self.h - min.1),
Alignment::Center => self.shrink_center_y(self.h - min.1),
Alignment::End => self.shrink_end_y(self.h - min.1),
Alignment::Full => self,
}
}
}
pub trait UiNode: std::any::Any {
fn get_align(&self) -> (Alignment, Alignment);
fn get_align_mut(&mut self) -> (&mut Alignment, &mut Alignment);
fn calculate_min_size(&self, tree: &UiTree) -> (f32, f32);
fn calculate_rects(&self, cache: &NodeCache, tree: &UiTree) -> Vec<Rect> {
let _ = (cache, tree);
vec![]
}
fn get_children(&self) -> Vec<TdIndex> {
vec![]
}
}
impl dyn UiNode {
pub fn downcast_ref<T: UiNode>(&self) -> Option<&T> {
(self as &dyn std::any::Any).downcast_ref()
}
pub fn downcast_mut<T: UiNode>(&mut self) -> Option<&mut T> {
(self as &mut dyn std::any::Any).downcast_mut()
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct NodeCache {
pub min_size: (f32, f32),
pub rect: Rect,
}
pub struct UiTree {
root: TdIndex,
arena: Arena<Box<dyn UiNode>>,
cache: HashMap<TdIndex, NodeCache>,
}
impl UiTree {
pub fn new(root: impl UiNode) -> Self {
let mut arena = Arena::new();
let index = arena.insert(Box::new(root) as Box<dyn UiNode>);
Self {
root: index,
arena,
cache: HashMap::new(),
}
}
pub fn add_node(&mut self, node: impl UiNode) -> TdIndex {
let index = self.arena.insert(Box::new(node) as Box<dyn UiNode>);
self.cache.insert(index, Default::default());
index
}
pub fn remove_node(&mut self, index: TdIndex) {
assert_ne!(index, self.root, "Root node cannot be removed");
let mut queue: VecDeque<_> = self.arena[index].get_children().into();
while let Some(child) = queue.pop_front() {
queue.extend(self.arena[child].get_children());
self.arena.remove(child);
self.cache.remove(&child);
}
self.arena.remove(index);
self.cache.remove(&index);
}
pub fn get_cache(&self, index: TdIndex) -> Option<&NodeCache> {
self.cache.get(&index)
}
pub fn get_node(&self, index: TdIndex) -> Option<&dyn UiNode> {
self.arena.get(index).map(|node| &**node)
}
pub fn get_node_mut(&mut self, index: TdIndex) -> Option<&mut dyn UiNode> {
self.arena.get_mut(index).map(|node| &mut **node)
}
pub fn get_root(&self) -> &dyn UiNode {
&**self.arena.get(self.root).expect("Root not valid")
}
pub fn get_root_mut(&mut self) -> &mut dyn UiNode {
&mut **self.arena.get_mut(self.root).expect("Root not valid")
}
pub fn calculate_layout(&mut self, root_rect: Rect) -> bool {
let mut visit_stack = vec![self.root];
let mut min_stack = Vec::new();
while let Some(v) = visit_stack.pop() {
min_stack.push(v);
visit_stack.extend(self.arena[v].get_children());
}
for v in min_stack.iter().rev() {
let min = self.arena[*v].calculate_min_size(self);
self.cache.entry(*v).and_modify(|e| e.min_size = min);
}
let is_good = root_rect.w >= self.cache[&self.root].min_size.0
&& root_rect.h >= self.cache[&self.root].min_size.1;
self.cache
.entry(self.root)
.and_modify(|e| e.rect = root_rect);
for v in min_stack {
let rects = self.arena[v].calculate_rects(&self.cache[&v], self);
for (child, rect) in self.arena[v].get_children().iter().zip(rects) {
self.cache.entry(*child).and_modify(|e| e.rect = rect);
}
}
is_good
}
}