#![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))
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct NodeCache {
pub min_size: (f32, f32),
pub rect: Rect,
}
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);
}
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);
}
#[doc = concat!("[`", stringify!(UiNode), "::get_visible_children`]")]
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);
}
}
pub struct PartialTree {
arena: Arena<Box<dyn UiNode>>,
}
impl PartialTree {
pub fn new() -> Self {
Self {
arena: Arena::new(),
}
}
pub fn add_node(&mut self, node: impl UiNode) -> TdIndex {
self.arena.insert(Box::new(node) as Box<dyn UiNode>)
}
pub fn complete(self, root: TdIndex) -> UiTree {
let cache = self.arena.iter().map(|(id, _)| (id, Default::default())).collect();
UiTree {
arena: self.arena,
root,
cache,
}
}
}
impl Default for PartialTree {
fn default() -> Self {
Self::new()
}
}
#[macro_export]
macro_rules! ui {
($tree:expr, $($node:tt)*) => {
$crate::ui!(@@_node $tree;; $($node)*)
};
(%%, $($node:tt)*) => {
{
let mut tree = $crate::PartialTree::new();
let root = $crate::ui!(@@_node &mut tree;; $($node)*);
(root, tree.complete(root))
}
};
($($random:tt)*, $($node:tt)*) => {
compile_error!(concat!(
"Expected either a mutable reference to a tree or `%%`, found `",
stringify!($($random)*),
"` where the tree should have been."
));
};
(@@_node $tree:expr;; $binding:ident = $ha:tt | $va:tt $node:expr) => {
{
let __node_idx = $tree.add_node($crate::ui!(@@_align $ha | $va $node));
$binding = __node_idx;
__node_idx
}
};
(@@_node $tree:expr;; $binding:ident = $ha:tt | $va:tt $node:expr => [ $($child:tt)* ]) => {
{
let __ui_node = $crate::ui!(
@@_child
$tree;;
$crate::ui!(@@_align $ha | $va $node)
=> [ $($child)* ]
);
let __node_idx = $tree.add_node(__ui_node);
$binding = __node_idx;
__node_idx
}
};
(@@_node $tree:expr;; $ha:tt | $va:tt $node:expr) => {
{
let __node_idx = $tree.add_node($crate::ui!(@@_align $ha | $va $node));
__node_idx
}
};
(@@_node $tree:expr;; $ha:tt | $va:tt $node:expr => [ $($child:tt)* ]) => {
{
let __ui_node = $crate::ui!(
@@_child
$tree;;
$crate::ui!(@@_align $ha | $va $node)
=> [ $($child)* ]
);
let __node_idx = $tree.add_node(__ui_node);
__node_idx
}
};
(@@_child $tree:expr;; $node:expr => [ ]) => {
$node
};
(
@@_child
$tree:expr;;
$node:expr
=> [
$binding:ident =
$ha:tt |
$va:tt
$child:expr
$(=> [ $($grand:tt)* ])?
$(, $($rest:tt)*)?
]
) => {
{
let __node_idx = $crate::ui!(
@@_node
$tree;;
$binding =
$ha |
$va
$child
$(=> [ $($grand)* ])?
);
$crate::ui!(
@@_child
$tree;;
$node.with_child(__node_idx)
=> [ $($($rest)*)? ]
)
}
};
(
@@_child
$tree:expr;;
$node:expr
=> [
$ha:tt |
$va:tt
$child:expr
$(=> [ $($grand:tt)* ])?
$(, $($rest:tt)*)?
]
) => {
{
let __node_idx = $crate::ui!(
@@_node
$tree;;
$ha |
$va
$child
$(=> [ $($grand)* ])?
);
$crate::ui!(
@@_child
$tree;;
$node.with_child(__node_idx)
=> [ $($($rest)*)? ]
)
}
};
(@@_align $ha:tt | $va: tt $node:expr) => {
$node.with_align(($crate::ui!(@@_align $ha), $crate::ui!(@@_align $va)))
};
(@@_align <) => {
$crate::Alignment::Begin
};
(@@_align -) => {
$crate::Alignment::Center
};
(@@_align >) => {
$crate::Alignment::End
};
(@@_align +) => {
$crate::Alignment::Full
};
(@@_align $a:tt) => {
compile_error!("Invalid alignment. Only `<`, `-`, `>`, and `+` are allowed.")
};
(@@_node $tree:expr;; $($tt:tt)*) => {
compile_error!(concat!(
"Invalid node syntax. A node should have an optional assignment, two alignment \
characters, separated by a pipe, and a node expression, optionally followed by \
a fat arrow and children in square brackets.\nExample:\n\
`target = -|< Node::new() => [ ]`\nFound: `",
$(stringify!($tt),)*
"`."
))
};
(@@_child $tree:expr;; $node:expr => [ $($tt:tt)* ]) => {
compile_error!(
"Invalid child node syntax. A node should have an optional assignment, two alignment \
characters, separated by a pipe, and a node expression, optionally followed by \
a fat arrow and children in square brackets."
)
};
($($tt:tt)*) => {
compile_error!(concat!(
"Expected a tree followed by a `,`, followed by a node, found `",
stringify!($($tt)*),
"`."
))
};
}