use thunderdome::Index as TdIndex;
use crate::{Alignment, Anchor, NodeCache, Rect, UiNode, UiTree};
pub struct AspectRatio {
ratio: f32,
pub anchor: (Anchor, Anchor),
align: (Alignment, Alignment),
child: Option<TdIndex>,
}
impl AspectRatio {
pub fn new() -> Self {
Self {
ratio: 1.0,
anchor: (Anchor::Center, Anchor::Center),
align: (Alignment::Begin, Alignment::Begin),
child: None,
}
}
pub fn with_child(mut self, index: TdIndex) -> Self {
assert!(self.child.is_none());
self.child = Some(index);
self
}
pub fn with_align(mut self, align: (Alignment, Alignment)) -> Self {
self.align = align;
self
}
pub fn with_anchor(mut self, anchor: (Anchor, Anchor)) -> Self {
self.anchor = anchor;
self
}
pub fn with_ratio(mut self, ratio: f32) -> Self {
assert!(ratio > 0.0);
self.ratio = ratio;
self
}
pub fn get_ratio(&self) -> f32 {
self.ratio
}
pub fn set_ratio(&mut self, ratio: f32) {
assert!(ratio > 0.0);
self.ratio = ratio;
}
pub fn add_child(&mut self, index: TdIndex) {
assert!(self.child.is_none());
self.child = Some(index);
}
pub fn get_child(&self) -> Option<TdIndex> {
self.child
}
}
impl Default for AspectRatio {
fn default() -> Self {
Self::new()
}
}
impl UiNode for AspectRatio {
fn get_align(&self) -> (Alignment, Alignment) {
self.align
}
fn get_align_mut(&mut self) -> (&mut Alignment, &mut Alignment) {
(&mut self.align.0, &mut self.align.1)
}
fn calculate_min_size(&self, tree: &UiTree) -> (f32, f32) {
if let Some(child) = self.child {
let child_min = tree.get_cache(child).expect("Child not in cache").min_size;
if child_min.0 == 0.0 || child_min.1 == 0.0 {
return (0.0, 0.0);
}
let child_ratio = child_min.0 / child_min.1;
if child_ratio > self.ratio {
(child_min.0, child_min.0 / self.ratio)
} else {
(child_min.1 * self.ratio, child_min.1)
}
} else {
(0.0, 0.0)
}
}
fn calculate_rects(&self, cache: &NodeCache, tree: &UiTree) -> Vec<Rect> {
if let Some(child) = self.child {
let child_min = tree.get_cache(child).expect("Child not in cache").min_size;
if child_min.0 == 0.0 || child_min.1 == 0.0 {
return vec![Rect::new(cache.rect.x, cache.rect.y, 0.0, 0.0)];
}
let child_ratio = child_min.0 / child_min.1;
let (w, h) = if child_ratio > self.ratio {
(child_min.0, child_min.0 / self.ratio)
} else {
(child_min.1 * self.ratio, child_min.1)
};
let w = w.max(child_min.0);
let h = h.max(child_min.1);
let shrunk = cache.rect.anchor(self.anchor, (w, h));
let space = shrunk.align(self.align, child_min);
vec![space]
} else {
vec![]
}
}
fn get_children(&self) -> Vec<TdIndex> {
self.child.into_iter().collect()
}
}
pub struct HSplit {
pub spacing: f32,
percent: f32,
align: (Alignment, Alignment),
left: Option<TdIndex>,
right: Option<TdIndex>,
}
impl HSplit {
pub fn new() -> Self {
Self {
spacing: 0.0,
percent: 0.5,
align: (Alignment::Begin, Alignment::Begin),
left: None,
right: None,
}
}
pub fn with_children(mut self, left: TdIndex, right: TdIndex) -> Self {
assert!(self.left.is_none() && self.right.is_none());
self.left = Some(left);
self.right = Some(right);
self
}
pub fn with_child(mut self, index: TdIndex) -> Self {
self.add_child(index);
self
}
pub fn with_align(mut self, align: (Alignment, Alignment)) -> Self {
self.align = align;
self
}
pub fn with_spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing;
self
}
pub fn with_percent(mut self, percent: f32) -> Self {
assert!(matches!(percent, 0.0..=1.0));
self.percent = percent;
self
}
pub fn get_percent(&self) -> f32 {
self.percent
}
pub fn set_percent(&mut self, percent: f32) {
assert!(matches!(percent, 0.0..=1.0));
self.percent = percent;
}
pub fn get_left_index(&self) -> TdIndex {
self.left.expect("Left slot not set")
}
pub fn get_right_index(&self) -> TdIndex {
self.right.expect("Right slot not set")
}
pub fn add_child(&mut self, index: TdIndex) {
if self.left.is_none() {
self.left = Some(index);
} else if self.right.is_none() {
self.right = Some(index);
}
}
}
impl Default for HSplit {
fn default() -> Self {
Self::new()
}
}
impl UiNode for HSplit {
fn get_align(&self) -> (Alignment, Alignment) {
self.align
}
fn get_align_mut(&mut self) -> (&mut Alignment, &mut Alignment) {
(&mut self.align.0, &mut self.align.1)
}
fn calculate_min_size(&self, tree: &UiTree) -> (f32, f32) {
if let Some((left, right)) = self.left.zip(self.right) {
let left_min = tree
.get_cache(left)
.expect("Left child not in cache")
.min_size;
let right_min = tree
.get_cache(right)
.expect("Right child not in cache")
.min_size;
(
left_min.0 + right_min.0 + self.spacing,
left_min.1.max(right_min.1),
)
} else {
(0.0, 0.0)
}
}
fn calculate_rects(&self, cache: &NodeCache, tree: &UiTree) -> Vec<Rect> {
if let Some((left, right)) = self.left.zip(self.right) {
let left_min = tree
.get_cache(left)
.expect("Left child not in cache")
.min_size;
let right_min = tree
.get_cache(right)
.expect("Right child not in cache")
.min_size;
let div_left = (cache.rect.w - self.spacing) * self.percent;
if div_left < left_min.0 {
let div_right = cache.rect.w - left_min.0 - self.spacing;
let x_right = cache.rect.x + left_min.0 + self.spacing;
let left_space = Rect::new(cache.rect.x, cache.rect.y, left_min.0, cache.rect.h)
.align(self.align, left_min);
let right_space = Rect::new(x_right, cache.rect.y, div_right, cache.rect.h)
.align(self.align, right_min);
return vec![left_space, right_space];
}
let div_right = cache.rect.w - div_left - self.spacing;
if div_right < right_min.0 {
let div_left = cache.rect.w - right_min.0 - self.spacing;
let x_right = cache.rect.x + div_left + self.spacing;
let left_space = Rect::new(cache.rect.x, cache.rect.y, div_left, cache.rect.h)
.align(self.align, left_min);
let right_space = Rect::new(x_right, cache.rect.y, right_min.0, cache.rect.h)
.align(self.align, right_min);
return vec![left_space, right_space];
}
let x_right = cache.rect.x + div_left + self.spacing;
let left_space = Rect::new(cache.rect.x, cache.rect.y, div_left, cache.rect.h)
.align(self.align, left_min);
let right_space = Rect::new(x_right, cache.rect.y, div_right, cache.rect.h)
.align(self.align, right_min);
vec![left_space, right_space]
} else {
vec![]
}
}
fn get_children(&self) -> Vec<TdIndex> {
if let Some((left, right)) = self.left.zip(self.right) {
vec![left, right]
} else {
vec![]
}
}
}
pub struct VSplit {
pub spacing: f32,
percent: f32,
align: (Alignment, Alignment),
top: Option<TdIndex>,
bot: Option<TdIndex>,
}
impl VSplit {
pub fn new() -> Self {
Self {
spacing: 0.0,
percent: 0.5,
align: (Alignment::Begin, Alignment::Begin),
top: None,
bot: None,
}
}
pub fn with_children(mut self, top: TdIndex, bottom: TdIndex) -> Self {
assert!(self.top.is_none() && self.bot.is_none());
self.top = Some(top);
self.bot = Some(bottom);
self
}
pub fn with_child(mut self, index: TdIndex) -> Self {
self.add_child(index);
self
}
pub fn with_align(mut self, align: (Alignment, Alignment)) -> Self {
self.align = align;
self
}
pub fn with_spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing;
self
}
pub fn with_percent(mut self, percent: f32) -> Self {
assert!(matches!(percent, 0.0..=1.0));
self.percent = percent;
self
}
pub fn get_percent(&self) -> f32 {
self.percent
}
pub fn set_percent(&mut self, percent: f32) {
assert!(matches!(percent, 0.0..=1.0));
self.percent = percent;
}
pub fn get_top_index(&self) -> TdIndex {
self.top.expect("Top slot not set")
}
pub fn get_bottom_index(&self) -> TdIndex {
self.bot.expect("Bottom slot not set")
}
pub fn add_child(&mut self, index: TdIndex) {
if self.top.is_none() {
self.top = Some(index);
} else if self.bot.is_none() {
self.bot = Some(index);
} else {
panic!("Cannot add child when both children are bound");
}
}
}
impl Default for VSplit {
fn default() -> Self {
Self::new()
}
}
impl UiNode for VSplit {
fn get_align(&self) -> (Alignment, Alignment) {
self.align
}
fn get_align_mut(&mut self) -> (&mut Alignment, &mut Alignment) {
(&mut self.align.0, &mut self.align.1)
}
fn calculate_min_size(&self, tree: &UiTree) -> (f32, f32) {
if let Some((top, bot)) = self.top.zip(self.bot) {
let top_min = tree
.get_cache(top)
.expect("Top child not in cache")
.min_size;
let bot_min = tree
.get_cache(bot)
.expect("Bottom child not in cache")
.min_size;
(
top_min.0.max(bot_min.0),
top_min.1 + bot_min.1 + self.spacing,
)
} else {
(0.0, 0.0)
}
}
fn calculate_rects(&self, cache: &NodeCache, tree: &UiTree) -> Vec<Rect> {
if let Some((top, bot)) = self.top.zip(self.bot) {
let top_min = tree
.get_cache(top)
.expect("Top child not in cache")
.min_size;
let bot_min = tree
.get_cache(bot)
.expect("Bottom child not in cache")
.min_size;
let div_top = (cache.rect.h - self.spacing) * self.percent;
if div_top < top_min.1 {
let div_bot = cache.rect.h - top_min.1 - self.spacing;
let y_bot = cache.rect.y + top_min.1 + self.spacing;
let top_space = Rect::new(cache.rect.x, cache.rect.y, cache.rect.w, top_min.1)
.align(self.align, top_min);
let bot_space = Rect::new(cache.rect.x, y_bot, cache.rect.w, div_bot)
.align(self.align, bot_min);
return vec![top_space, bot_space];
}
let div_bot = cache.rect.h - div_top - self.spacing;
if div_bot < bot_min.1 {
let div_top = cache.rect.h - bot_min.1 - self.spacing;
let y_bot = cache.rect.y + div_top + self.spacing;
let top_space = Rect::new(cache.rect.x, cache.rect.y, cache.rect.w, div_top)
.align(self.align, top_min);
let bot_space = Rect::new(cache.rect.x, y_bot, cache.rect.w, bot_min.1)
.align(self.align, bot_min);
return vec![top_space, bot_space];
}
let y_bot = cache.rect.y + div_top + self.spacing;
let top_space = Rect::new(cache.rect.x, cache.rect.y, cache.rect.w, div_top)
.align(self.align, top_min);
let bot_space =
Rect::new(cache.rect.x, y_bot, cache.rect.w, div_bot).align(self.align, bot_min);
vec![top_space, bot_space]
} else {
vec![]
}
}
fn get_children(&self) -> Vec<TdIndex> {
if let Some((top, bot)) = self.top.zip(self.bot) {
vec![top, bot]
} else {
vec![]
}
}
}
pub struct Percent {
pub strict: bool,
percent: (f32, f32),
pub anchor: (Anchor, Anchor),
align: (Alignment, Alignment),
child: Option<TdIndex>,
}
impl Percent {
pub fn new() -> Self {
Self {
strict: false,
percent: (1.0, 1.0),
anchor: (Anchor::Begin, Anchor::Begin),
align: (Alignment::Begin, Alignment::Begin),
child: None,
}
}
pub fn with_child(mut self, index: TdIndex) -> Self {
assert!(self.child.is_none());
self.child = Some(index);
self
}
pub fn with_align(mut self, align: (Alignment, Alignment)) -> Self {
self.align = align;
self
}
pub fn with_anchor(mut self, anchor: (Anchor, Anchor)) -> Self {
self.anchor = anchor;
self
}
pub fn with_percent(mut self, percent: (f32, f32)) -> Self {
assert!(matches!(percent, (0.0..=1.0, 0.0..=1.0)));
if self.strict {
assert!(
percent.0 > 0.0 && percent.1 > 0.0,
"Percent must be greater than 0 when strict is enabled"
);
}
self.percent = percent;
self
}
pub fn with_strict(mut self, strict: bool) -> Self {
if strict {
assert!(
self.percent.0 > 0.0 && self.percent.1 > 0.0,
"Percent must be greater than 0 when strict is enabled"
);
}
self.strict = strict;
self
}
pub fn set_percent(&mut self, percent: (f32, f32)) {
assert!(matches!(percent, (0.0..=1.0, 0.0..=1.0)));
if self.strict {
assert!(
percent.0 > 0.0 && percent.1 > 0.0,
"Percent must be greater than 0 when strict is enabled"
);
}
self.percent = percent;
}
pub fn set_strict(&mut self, strict: bool) {
if strict {
assert!(
self.percent.0 > 0.0 && self.percent.1 > 0.0,
"Percent must be greater than 0 when strict is enabled"
);
}
self.strict = strict;
}
pub fn get_percent(&self) -> (f32, f32) {
self.percent
}
pub fn add_child(&mut self, index: TdIndex) {
assert!(self.child.is_none());
self.child = Some(index);
}
pub fn get_child(&self) -> Option<TdIndex> {
self.child
}
}
impl Default for Percent {
fn default() -> Self {
Self::new()
}
}
impl UiNode for Percent {
fn get_align(&self) -> (Alignment, Alignment) {
self.align
}
fn get_align_mut(&mut self) -> (&mut Alignment, &mut Alignment) {
(&mut self.align.0, &mut self.align.1)
}
fn calculate_min_size(&self, tree: &UiTree) -> (f32, f32) {
if let Some(child) = self.child {
let child_min = tree.get_cache(child).expect("Child not in cache").min_size;
if self.strict {
(child_min.0 / self.percent.0, child_min.1 / self.percent.1)
} else {
child_min
}
} else {
(0.0, 0.0)
}
}
fn calculate_rects(&self, cache: &NodeCache, tree: &UiTree) -> Vec<Rect> {
if let Some(child) = self.child {
let child_min = tree.get_cache(child).expect("Child not in cache").min_size;
let w = child_min.0.max(cache.rect.w * self.percent.0);
let h = child_min.1.max(cache.rect.h * self.percent.1);
let shrunk = cache.rect.anchor(self.anchor, (w, h));
let space = shrunk.align(self.align, child_min);
vec![space]
} else {
vec![]
}
}
fn get_children(&self) -> Vec<TdIndex> {
if let Some(child) = self.child {
vec![child]
} else {
vec![]
}
}
}