Plan -> Instruction logic and test
This commit is contained in:
parent
dd1fe448a7
commit
4a3c5ac492
|
@ -1 +1,3 @@
|
||||||
target/
|
target/
|
||||||
|
# openbsd core dump
|
||||||
|
*.core
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::theme::{Color, ColorSet};
|
use crate::{
|
||||||
|
theme::{Color, ColorSet},
|
||||||
|
token::Token,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Eq, Debug, Clone)]
|
#[derive(Eq, Debug, Clone)]
|
||||||
pub enum Component {
|
pub enum Component {
|
||||||
X(usize),
|
X(usize),
|
||||||
String(String),
|
String(String),
|
||||||
Clear(Clear),
|
|
||||||
Fg(Color),
|
Fg(Color),
|
||||||
Bg(Color),
|
Bg(Color),
|
||||||
}
|
}
|
||||||
|
@ -20,10 +22,6 @@ impl PartialEq for Component {
|
||||||
Self::String(other_s) => s == other_s,
|
Self::String(other_s) => s == other_s,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
Self::Clear(clr) => match other {
|
|
||||||
Self::Clear(other_clr) => clr == other_clr,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
Self::Fg(c) => match other {
|
Self::Fg(c) => match other {
|
||||||
Self::Fg(other_c) => c == other_c,
|
Self::Fg(other_c) => c == other_c,
|
||||||
_ => false,
|
_ => false,
|
||||||
|
@ -36,8 +34,326 @@ impl PartialEq for Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Copy, Eq)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Clear {
|
pub struct Line {
|
||||||
Line,
|
color: ColorSet,
|
||||||
Screen,
|
components: Vec<Component>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub enum SectionWidth {
|
||||||
|
Full,
|
||||||
|
Third,
|
||||||
|
TwoThirds,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SectionWidth {
|
||||||
|
pub fn abs_size(&self, max_width: usize) -> usize {
|
||||||
|
match self {
|
||||||
|
SectionWidth::Full => max_width,
|
||||||
|
SectionWidth::Third => max_width / 3,
|
||||||
|
SectionWidth::TwoThirds => (max_width / 3) * 2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq)]
|
||||||
|
pub struct Widget {
|
||||||
|
want_width: SectionWidth,
|
||||||
|
per_line: Vec<Token>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Widget {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.want_width == other.want_width
|
||||||
|
&& self.per_line.len() == other.per_line.len()
|
||||||
|
&& (&self.per_line)
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.all(|(i, token)| token.eq(&other.per_line[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget {
|
||||||
|
pub fn new(
|
||||||
|
width: SectionWidth,
|
||||||
|
tokens_per_line: Vec<Token>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
want_width: width,
|
||||||
|
per_line: tokens_per_line,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_line(&self, line: usize) -> Option<&Token> {
|
||||||
|
self.per_line.get(line - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq)]
|
||||||
|
pub enum Instruction {
|
||||||
|
FixedHeight(Box<Instruction>, usize, Vec<Widget>),
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Instruction {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match self {
|
||||||
|
Instruction::FixedHeight(next, size, widgets) => {
|
||||||
|
match other {
|
||||||
|
Instruction::FixedHeight(
|
||||||
|
other_next,
|
||||||
|
other_size,
|
||||||
|
other_widgets,
|
||||||
|
) => {
|
||||||
|
if size == other_size
|
||||||
|
&& next.eq(other_next)
|
||||||
|
&& widgets.len() == other_widgets.len()
|
||||||
|
{
|
||||||
|
widgets.into_iter().enumerate().all(
|
||||||
|
|(i, widget)| {
|
||||||
|
widget.eq(&other_widgets[i])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::End => match other {
|
||||||
|
Instruction::End => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Instruction {
|
||||||
|
pub fn make(
|
||||||
|
self,
|
||||||
|
(term_width, term_height): (u16, u16),
|
||||||
|
) -> String {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start() -> Self {
|
||||||
|
Self::End
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fixed(self, height: usize, widgets: Vec<Widget>) -> Self {
|
||||||
|
Self::FixedHeight(Box::new(self), height, widgets)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Plan {
|
||||||
|
FixedHeight(Box<Plan>, usize, Vec<Widget>),
|
||||||
|
Fill(Box<Plan>, Vec<Widget>),
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plan {
|
||||||
|
pub fn start() -> Self {
|
||||||
|
Self::End
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fit_check(widgets: &Vec<Widget>) {
|
||||||
|
if widgets
|
||||||
|
.into_iter()
|
||||||
|
.map(|wd| wd.want_width.abs_size(100))
|
||||||
|
.sum::<usize>()
|
||||||
|
>= 100
|
||||||
|
{
|
||||||
|
panic!("widgets do not fit screen")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_instruction_set(self, max_height: usize) -> Instruction {
|
||||||
|
let fill_height = {
|
||||||
|
let (reserved_lines, fill_count) = self.count();
|
||||||
|
(max_height - reserved_lines) / 1.max(fill_count)
|
||||||
|
};
|
||||||
|
self.to_instruction_fixed_fill_height(fill_height)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_instruction_fixed_fill_height(
|
||||||
|
self,
|
||||||
|
fill_height: usize,
|
||||||
|
) -> Instruction {
|
||||||
|
match self {
|
||||||
|
Plan::FixedHeight(next, height, widgets) => {
|
||||||
|
Instruction::FixedHeight(
|
||||||
|
Box::new(next.to_instruction_fixed_fill_height(
|
||||||
|
fill_height,
|
||||||
|
)),
|
||||||
|
height,
|
||||||
|
widgets,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Plan::Fill(next, widgets) => {
|
||||||
|
Instruction::FixedHeight(
|
||||||
|
Box::new(next.to_instruction_fixed_fill_height(
|
||||||
|
fill_height,
|
||||||
|
)),
|
||||||
|
fill_height,
|
||||||
|
widgets,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Plan::End => Instruction::End,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// counts how many lines are fixed height, those lines
|
||||||
|
// are reserved, and the rest may be split between fills.
|
||||||
|
// the return value is (reserved_lines, fill_count)
|
||||||
|
// so, fill_height should be reserved_lines/fill_count
|
||||||
|
fn count(&self) -> (usize, usize) {
|
||||||
|
match self {
|
||||||
|
Plan::FixedHeight(next, h, _) => {
|
||||||
|
let next = next.count();
|
||||||
|
(next.0 + h, next.1)
|
||||||
|
}
|
||||||
|
Plan::Fill(next, _) => {
|
||||||
|
let next = next.count();
|
||||||
|
(next.0, next.1 + 1)
|
||||||
|
}
|
||||||
|
Plan::End => (0, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line(self, widgets: Vec<Widget>) -> Self {
|
||||||
|
self.fixed(1, widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fixed(self, height: usize, widgets: Vec<Widget>) -> Self {
|
||||||
|
Self::fit_check(&widgets);
|
||||||
|
Self::FixedHeight(Box::new(self), height, widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill(self, widgets: Vec<Widget>) -> Self {
|
||||||
|
Self::fit_check(&widgets);
|
||||||
|
Self::Fill(Box::new(self), widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn make_line(&self, term_width: u16, )
|
||||||
|
|
||||||
|
// pub fn make(
|
||||||
|
// &self,
|
||||||
|
// (term_width, term_height): (u16, u16),
|
||||||
|
// ) -> String {
|
||||||
|
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_plan_to_instructions() {
|
||||||
|
const HEIGHT: usize = 40;
|
||||||
|
let (widget_1, widget_2, widget_3) = (
|
||||||
|
Widget::new(
|
||||||
|
SectionWidth::Third,
|
||||||
|
vec![Token::text("hello")],
|
||||||
|
),
|
||||||
|
Widget::new(
|
||||||
|
SectionWidth::Third,
|
||||||
|
vec![Token::text("hello")
|
||||||
|
.pad_char('*', 16)
|
||||||
|
.bg(Color::RED)],
|
||||||
|
),
|
||||||
|
Widget::new(
|
||||||
|
SectionWidth::Third,
|
||||||
|
vec![Token::text("hello").limited(16).padded(20)],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
("end -> end", Plan::start(), Instruction::End),
|
||||||
|
(
|
||||||
|
"fill entire screen",
|
||||||
|
Plan::start().fill(vec![]),
|
||||||
|
Instruction::start().fixed(HEIGHT, vec![]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"5 | fill | 5 -> 5 | HEIGHT - 5 - 5 | 5",
|
||||||
|
Plan::start()
|
||||||
|
.fixed(5, vec![widget_1.clone()])
|
||||||
|
.fill(vec![widget_3.clone()])
|
||||||
|
.fixed(5, vec![widget_2.clone()]),
|
||||||
|
Instruction::start()
|
||||||
|
.fixed(5, vec![widget_1.clone()])
|
||||||
|
.fixed(HEIGHT - 5 - 5, vec![widget_3.clone()])
|
||||||
|
.fixed(5, vec![widget_2.clone()]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"5 | 5 | fill -> 5 | 5 | HEIGHT - 5 - 5",
|
||||||
|
Plan::start()
|
||||||
|
.fixed(5, vec![widget_3.clone()])
|
||||||
|
.fixed(5, vec![widget_2.clone()])
|
||||||
|
.fill(vec![widget_1.clone()]),
|
||||||
|
Instruction::start()
|
||||||
|
.fixed(5, vec![widget_3.clone()])
|
||||||
|
.fixed(5, vec![widget_2.clone()])
|
||||||
|
.fixed(HEIGHT - 5 - 5, vec![widget_1.clone()]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fill -> HEIGHT",
|
||||||
|
Plan::start().fill(vec![widget_1.clone()]),
|
||||||
|
Instruction::start()
|
||||||
|
.fixed(HEIGHT, vec![widget_1.clone()]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"
|
||||||
|
5 | fill | 5 | fill | 5
|
||||||
|
->
|
||||||
|
5 | (HEIGHT - 15) / 2 | 5 | (HEIGHT - 15) / 2 | 5\n",
|
||||||
|
Plan::start()
|
||||||
|
.fixed(
|
||||||
|
5,
|
||||||
|
vec![widget_1.clone(), widget_2.clone()],
|
||||||
|
)
|
||||||
|
.fill(vec![widget_1.clone(), widget_3.clone()])
|
||||||
|
.fixed(5, vec![])
|
||||||
|
.fill(vec![widget_2.clone()])
|
||||||
|
.fixed(
|
||||||
|
5,
|
||||||
|
vec![widget_3.clone(), widget_2.clone()],
|
||||||
|
),
|
||||||
|
Instruction::start()
|
||||||
|
.fixed(
|
||||||
|
5,
|
||||||
|
vec![widget_1.clone(), widget_2.clone()],
|
||||||
|
)
|
||||||
|
.fixed(
|
||||||
|
(HEIGHT - 15) / 2,
|
||||||
|
vec![widget_1.clone(), widget_3.clone()],
|
||||||
|
)
|
||||||
|
.fixed(5, vec![])
|
||||||
|
.fixed((HEIGHT - 15) / 2, vec![widget_2.clone()])
|
||||||
|
.fixed(
|
||||||
|
5,
|
||||||
|
vec![widget_3.clone(), widget_2.clone()],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.for_each(|(name, plan, expected)| {
|
||||||
|
eprintln!("running test <{}>", &name);
|
||||||
|
let actual = plan.to_instruction_set(HEIGHT);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
expected == actual,
|
||||||
|
"<{}>: not equal!
|
||||||
|
expected:\n{:#?}
|
||||||
|
actual:\n{:#?}",
|
||||||
|
&name,
|
||||||
|
expected,
|
||||||
|
actual,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{component::Component, theme::Color};
|
use crate::{component::Component, theme::Color};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
Centered(Box<Token>),
|
Centered(Box<Token>),
|
||||||
Limited(Box<Token>, usize),
|
Limited(Box<Token>, usize),
|
||||||
|
@ -236,9 +236,8 @@ mod tests {
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.for_each(|test| {
|
.for_each(|(name, token, expected)| {
|
||||||
let (name, token, expected) = test;
|
eprintln!("running test <{}>", &name);
|
||||||
eprintln!("token: {:#?}", &token);
|
|
||||||
let actual = token.with_width(WIDTH);
|
let actual = token.with_width(WIDTH);
|
||||||
assert!(
|
assert!(
|
||||||
expected.len() == actual.len(),
|
expected.len() == actual.len(),
|
||||||
|
|
Loading…
Reference in New Issue