#![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 limit;
pub mod overlap;
pub mod padding;
pub mod prelude;
pub mod proportion;
pub mod stacks;
pub mod visibility;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Default)]
pub enum Alignment {
#[default]
Begin,
Center,
End,
Full,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum Anchor {
Begin,
#[default]
Center,
End,
}
#[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 fn anchor(mut self, anchor: (Anchor, Anchor), size: (f32, f32)) -> Self {
self = match anchor.0 {
Anchor::Begin => self.shrink_begin_x(self.w - size.0),
Anchor::Center => self.shrink_center_x(self.w - size.0),
Anchor::End => self.shrink_end_x(self.w - size.0),
};
match anchor.1 {
Anchor::Begin => self.shrink_begin_y(self.h - size.1),
Anchor::Center => self.shrink_center_y(self.h - size.1),
Anchor::End => self.shrink_end_y(self.h - size.1),
}
}
pub fn intersect(self, other: Self) -> Self {
let x1 = self.x.max(other.x);
let y1 = self.y.max(other.y);
let x2 = (self.x + self.w).min(other.x + other.w);
let y2 = (self.y + self.h).min(other.y + other.h);
Self::new(x1, y1, (x2 - x1).max(0.0), (y2 - y1).max(0.0))
}
}
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![]
}
fn get_visible_children(&self) -> Vec<TdIndex> {
self.get_children()
}
}
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()
}
}
pub trait UiWalker {
fn enter(&mut self, node: &mut dyn UiNode, rect: Rect, index: TdIndex);
fn leave(&mut self, node: &mut dyn UiNode, rect: Rect, index: TdIndex);
}
#[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 {
for v in self.cache.values_mut() {
*v = Default::default();
}
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_visible_children());
}
for v in min_stack.iter().rev() {
let min = self.arena[*v].calculate_min_size(self);
self.cache.entry(*v).or_default().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).or_default().rect = rect;
}
}
is_good
}
pub fn walk_root(&mut self, walker: &mut impl UiWalker, use_visible: bool) {
self.walk_node(self.root, walker, use_visible);
}
pub fn walk_node(&mut self, index: TdIndex, walker: &mut impl UiWalker, use_visible: bool) {
let rect = self.cache[&index].rect;
walker.enter(self.arena[index].as_mut(), rect, index);
let children = if use_visible {
self.arena[index].get_visible_children()
} else {
self.arena[index].get_children()
};
for child in children {
self.walk_node(child, walker, use_visible);
}
walker.leave(self.arena[index].as_mut(), rect, index);
}
}
#[macro_export]
macro_rules! ui {
($tree:expr, $($node:tt)*) => {
ui!(@node $tree;; $($node)*)
};
(@node $tree:expr;; $ha:tt $va: tt $node:expr) => {
$node.with_align((ui!(@align $ha), ui!(@align $va)))
};
(@node $tree:expr;; $ha:tt $va: tt $node:expr => [ $($child:tt)* ]) => {
ui!(@child $tree;; $node.with_align((ui!(@align $ha), ui!(@align $va))) => [ $($child)* ])
};
(@child $tree:expr;; $node:expr => [ ]) => {
$node
};
(
@child
$tree:expr;;
$node:expr
=> [
$ha:tt
$va: tt
$child:expr
=> [ $($grand:tt)* ], $($rest:tt)*
]
) => {
ui!(
@child
$tree;;
$node.with_child(
ui!(
@node
$tree;;
$ha
$va
$child
=> [ $($grand)* ]
),
$tree
)
=> [ $($rest)* ]
)
};
(
@child
$tree:expr;;
$node:expr
=> [
$ha:tt
$va: tt
$child:expr
=> [ $($grand:tt)* ]
]
) => {
ui!(
@child
$tree;;
$node.with_child(
ui!(
@node
$tree;;
$ha
$va
$child
=> [ $($grand)* ]
),
$tree
)
=> []
)
};
(@child
$tree:expr;;
$node:expr
=> [
$ha:tt
$va: tt
$child:expr
, $($rest:tt)*
]
) => {
ui!(
@child
$tree;;
$node.with_child(
ui!(
@node
$tree;;
$ha
$va
$child
=> [ ]
),
$tree
)
=> [ $($rest)* ]
)
};
(
@child
$tree:expr;;
$node:expr
=> [
$ha:tt
$va: tt
$child:expr
]
) => {
ui!(
@child
$tree;;
$node.with_child(
ui!(
@node
$tree;;
$ha
$va
$child
=> [ ]
),
$tree
)
=> []
)
};
(@align <) => {
Alignment::Begin
};
(@align -) => {
Alignment::Center
};
(@align >) => {
Alignment::End
};
(@align +) => {
Alignment::Full
};
(@align $a:tt) => {
compile_error!("Invalid alignment. < - > + are allowed.")
};
}