338 lines
12 KiB
Rust
338 lines
12 KiB
Rust
use std::{cell::RefCell, rc::Rc};
|
|
|
|
use crate::{
|
|
container::Container,
|
|
prelude::{Callable, Pos, Runes, Size, ViewContext},
|
|
view::View,
|
|
};
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub enum StackAlignment {
|
|
#[default]
|
|
Left,
|
|
Right,
|
|
Center,
|
|
Top,
|
|
Bottom,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum StackDirection {
|
|
Vertical,
|
|
Horizontal,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Stack {
|
|
pub(crate) direction: StackDirection,
|
|
pub(crate) container: Rc<RefCell<Container>>,
|
|
pub(crate) view: View,
|
|
pub(crate) position: Pos,
|
|
pub(crate) alignment: StackAlignment,
|
|
}
|
|
|
|
impl Stack {
|
|
pub fn alignment(&mut self, alignment: StackAlignment) {
|
|
self.alignment = alignment;
|
|
}
|
|
|
|
pub fn component<F, Args, S>(&mut self, size: S, f: F)
|
|
where
|
|
F: crate::prelude::Callable<Args>,
|
|
Args: crate::prelude::FromContainer,
|
|
S: Into<Size>,
|
|
{
|
|
let size = size.into();
|
|
|
|
let pos = match self.direction {
|
|
StackDirection::Vertical => {
|
|
if size.width != self.view.size().width {
|
|
match self.alignment {
|
|
StackAlignment::Left | StackAlignment::Top | StackAlignment::Bottom => {
|
|
self.position
|
|
}
|
|
StackAlignment::Right => Pos::new(
|
|
self.position.x + self.view.size().width - size.width,
|
|
self.position.y,
|
|
),
|
|
StackAlignment::Center => {
|
|
let view_width = self.view.size().width as f32;
|
|
let diff = view_width - size.width as f32;
|
|
Pos::new(
|
|
self.position.x + (diff / 2.0).floor() as usize,
|
|
self.position.y,
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
self.position
|
|
}
|
|
}
|
|
StackDirection::Horizontal => {
|
|
if size.height != self.view.size().height {
|
|
match self.alignment {
|
|
StackAlignment::Top | StackAlignment::Left | StackAlignment::Right => {
|
|
self.position
|
|
}
|
|
StackAlignment::Bottom => Pos::new(
|
|
self.position.x,
|
|
self.position.y + self.view.size().height - size.height,
|
|
),
|
|
StackAlignment::Center => {
|
|
let view_height = self.view.size().height as f32;
|
|
let diff = view_height - size.height as f32;
|
|
Pos::new(
|
|
self.position.x,
|
|
self.position.y + (diff / 2.0).floor() as usize,
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
self.position
|
|
}
|
|
}
|
|
};
|
|
|
|
let mut context = ViewContext::new(self.container.clone(), size);
|
|
f.call(&mut context, Args::from_container(&self.container.borrow()));
|
|
self.view.apply(pos, &context.view);
|
|
self.position += match self.direction {
|
|
StackDirection::Vertical => Pos::new(0, size.height),
|
|
StackDirection::Horizontal => Pos::new(size.width, 0),
|
|
};
|
|
}
|
|
|
|
/// Insert a set a runes, such as a string, into the stack.
|
|
pub fn insert<R: Into<Runes>>(&mut self, value: R) {
|
|
let runes: Runes = value.into();
|
|
let size = Size::new(runes.len(), 1);
|
|
|
|
let pos = match self.direction {
|
|
StackDirection::Vertical => {
|
|
if size.width != self.view.size().width {
|
|
match self.alignment {
|
|
StackAlignment::Left | StackAlignment::Top | StackAlignment::Bottom => {
|
|
self.position
|
|
}
|
|
|
|
StackAlignment::Right => Pos::new(
|
|
self.position.x + self.view.size().width - size.width,
|
|
self.position.y,
|
|
),
|
|
StackAlignment::Center => {
|
|
let view_width = self.view.size().width as f32;
|
|
let diff = view_width - size.width as f32;
|
|
Pos::new(
|
|
self.position.x + (diff / 2.0).floor() as usize,
|
|
self.position.y,
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
self.position
|
|
}
|
|
}
|
|
StackDirection::Horizontal => {
|
|
if size.height != self.view.size().height {
|
|
match self.alignment {
|
|
StackAlignment::Top | StackAlignment::Left | StackAlignment::Right => {
|
|
self.position
|
|
}
|
|
StackAlignment::Bottom => Pos::new(
|
|
self.position.x,
|
|
self.position.y + self.view.size().height - size.height,
|
|
),
|
|
StackAlignment::Center => {
|
|
let view_height = self.view.size().height as f32;
|
|
let diff = view_height - size.height as f32;
|
|
Pos::new(
|
|
self.position.x,
|
|
self.position.y + (diff / 2.0).floor() as usize,
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
self.position
|
|
}
|
|
}
|
|
};
|
|
|
|
self.view.insert(pos, runes);
|
|
self.position += match self.direction {
|
|
StackDirection::Vertical => Pos::new(0, 1),
|
|
StackDirection::Horizontal => Pos::new(size.width, 0),
|
|
};
|
|
}
|
|
}
|
|
|
|
impl Callable<()> for Stack {
|
|
fn call(&self, ctx: &mut ViewContext, _args: ()) {
|
|
ctx.apply((0, 0), &self.view);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::prelude::{StackAlignment, ViewContext};
|
|
|
|
#[test]
|
|
fn test_vertical_insert() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.vertical_stack((10, 2));
|
|
stack.insert("one");
|
|
stack.insert("two");
|
|
assert_eq!(
|
|
stack.view.render_text(),
|
|
"one\0\0\0\0\0\0\0\ntwo\0\0\0\0\0\0\0\n".to_string()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_horizontal_insert() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.horizontal_stack((10, 1));
|
|
stack.insert("one");
|
|
stack.insert("two");
|
|
assert_eq!(stack.view.render_text(), "onetwo\0\0\0\0\n".to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn test_component() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.horizontal_stack((10, 2));
|
|
stack.component((10, 2), |ctx: &mut ViewContext| {
|
|
ctx.insert((3, 1), "one");
|
|
});
|
|
assert_eq!(
|
|
stack.view.render_text(),
|
|
"\0\0\0\0\0\0\0\0\0\0\n\0\0\0one\0\0\0\0\n".to_string()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_align_left() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.vertical_stack((10, 2));
|
|
stack.component((5, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "one");
|
|
});
|
|
stack.insert("two");
|
|
assert_eq!(
|
|
stack.view.render_text(),
|
|
"one\0\0\0\0\0\0\0\ntwo\0\0\0\0\0\0\0\n".to_string()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_align_right() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.vertical_stack((10, 3));
|
|
stack.alignment = StackAlignment::Right;
|
|
stack.insert("one");
|
|
stack.component((5, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "one");
|
|
});
|
|
stack.component((10, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "two");
|
|
});
|
|
|
|
let res = "\0\0\0\0\0\0\0one\n\0\0\0\0\0one\0\0\ntwo\0\0\0\0\0\0\0\n".to_string();
|
|
|
|
crate::tests::print_render_text(&stack.view.render_text());
|
|
println!("---");
|
|
crate::tests::print_render_text(&res);
|
|
|
|
assert_eq!(stack.view.render_text(), res);
|
|
}
|
|
|
|
#[test]
|
|
fn test_align_center_v() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.vertical_stack((10, 3));
|
|
stack.alignment = StackAlignment::Center;
|
|
stack.insert("one");
|
|
stack.component((5, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "one");
|
|
});
|
|
stack.component((10, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "two");
|
|
});
|
|
|
|
let res = "\0\0\0one\0\0\0\0\n\0\0one\0\0\0\0\0\ntwo\0\0\0\0\0\0\0\n".to_string();
|
|
|
|
crate::tests::print_render_text(&stack.view.render_text());
|
|
println!("---");
|
|
crate::tests::print_render_text(&res);
|
|
|
|
assert_eq!(stack.view.render_text(), res);
|
|
}
|
|
|
|
#[test]
|
|
fn test_align_top() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.horizontal_stack((9, 6));
|
|
stack.component((3, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "one");
|
|
});
|
|
stack.component((3, 3), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 1), "two");
|
|
});
|
|
stack.insert("one");
|
|
|
|
let res = "one\0\0\0one\n\0\0\0two\0\0\0\n\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n"
|
|
.to_string();
|
|
|
|
crate::tests::print_render_text(&stack.view.render_text());
|
|
println!("---");
|
|
crate::tests::print_render_text(&res);
|
|
|
|
assert_eq!(stack.view.render_text(), res.to_string());
|
|
}
|
|
|
|
#[test]
|
|
fn test_align_bottom() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.horizontal_stack((9, 6));
|
|
stack.alignment(StackAlignment::Bottom);
|
|
stack.component((3, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "one");
|
|
});
|
|
stack.component((3, 3), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 1), "two");
|
|
});
|
|
stack.insert("one");
|
|
|
|
let res = "\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n\0\0\0two\0\0\0\none\0\0\0one\n"
|
|
.to_string();
|
|
|
|
crate::tests::print_render_text(&stack.view.render_text());
|
|
println!("---");
|
|
crate::tests::print_render_text(&res);
|
|
|
|
assert_eq!(stack.view.render_text(), res);
|
|
}
|
|
|
|
#[test]
|
|
fn test_align_center_h() {
|
|
let ctx = crate::context::tests::context_fixture();
|
|
let mut stack = ctx.horizontal_stack((9, 6));
|
|
stack.alignment(StackAlignment::Center);
|
|
stack.component((3, 1), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 0), "one");
|
|
});
|
|
stack.component((3, 3), |ctx: &mut ViewContext| {
|
|
ctx.insert((0, 1), "two");
|
|
});
|
|
stack.insert("one");
|
|
|
|
let res = "\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\nonetwoone\n\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n\0\0\0\0\0\0\0\0\0\n"
|
|
.to_string();
|
|
|
|
crate::tests::print_render_text(&stack.view.render_text());
|
|
println!("---");
|
|
crate::tests::print_render_text(&res);
|
|
|
|
assert_eq!(stack.view.render_text(), res);
|
|
}
|
|
}
|