cleanup Engine API and generalize Inset/Outset

This commit is contained in:
🪞👃🪞 2024-09-13 21:27:40 +03:00
parent 4e0eb0c335
commit 0737769232
6 changed files with 118 additions and 91 deletions

View file

@ -14,19 +14,22 @@ pub trait Engine: Send + Sync + Sized {
/// Unit of distance.
type Unit: Number;
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug;
type Size: Size<Self::Unit> + From<[Self::Unit;2]> + Debug;
type HandleInput;
type Handled;
// FIXME
fn area (&self) -> Self::Area;
// FIXME
fn with_area (&mut self, x: Self::Unit, y: Self::Unit, w: Self::Unit, h: Self::Unit)
-> &mut Self;
// FIXME
fn render_in (
#[inline] fn area (&self) -> Self::Area;
#[inline] fn area_mut (&mut self) -> &mut Self::Area;
#[inline] fn render_in (
&mut self, area: Self::Area, widget: &impl Widget<Engine = Self>
) -> Perhaps<Self::Area>;
) -> Perhaps<Self::Area> {
let last = self.area();
*self.area_mut() = area;
let next = widget.render(self)?;
*self.area_mut() = last;
Ok(next)
}
}
pub trait Widget: Send + Sync {
@ -600,6 +603,42 @@ impl<N: Number, T: Widget> Outset<N, T> {
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Inset<E::Unit, T> {
type Engine = E;
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
match *self {
Self::X(x, ref inner) => Shrink::X(x + x, inner as &dyn Widget<Engine = E>),
Self::Y(y, ref inner) => Shrink::Y(y + y, inner as &dyn Widget<Engine = E>),
Self::XY(x, y, ref inner) => Shrink::XY(x + x, y + y, inner as &dyn Widget<Engine = E>),
}.layout(to)
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
match *self {
Self::X(x, ref inner) => Plus::X(x, inner as &dyn Widget<Engine = E>),
Self::Y(y, ref inner) => Plus::Y(y, inner as &dyn Widget<Engine = E>),
Self::XY(x, y, ref inner) => Plus::XY(x, y, inner as &dyn Widget<Engine = E>),
}.render(to)
}
}
impl<E: Engine, T: Widget<Engine = E>> Widget for Outset<E::Unit, T> {
type Engine = E;
fn layout (&self, to: E::Area) -> Perhaps<E::Area> {
match *self {
Self::X(x, ref inner) => Grow::X(x + x, inner as &dyn Widget<Engine = E>),
Self::Y(y, ref inner) => Grow::Y(y + y, inner as &dyn Widget<Engine = E>),
Self::XY(x, y, ref inner) => Grow::XY(x + x, y + y, inner as &dyn Widget<Engine = E>),
}.layout(to)
}
fn render (&self, to: &mut E) -> Perhaps<E::Area> {
match *self {
Self::X(x, ref inner) => Plus::X(x, inner as &dyn Widget<Engine = E>),
Self::Y(y, ref inner) => Plus::Y(y, inner as &dyn Widget<Engine = E>),
Self::XY(x, y, ref inner) => Plus::XY(x, y, inner as &dyn Widget<Engine = E>),
}.render(to)
}
}
/// Move origin point of drawing area
pub enum Plus<N: Number, T: Widget> {
/// Move origin to the right

View file

@ -1,18 +1,18 @@
use crate::*;
pub trait Point<N: Number> {
pub trait Size<N: Number> {
fn x (&self) -> N;
fn y (&self) -> N;
fn w (&self) -> N { self.x() }
fn h (&self) -> N { self.y() }
}
impl<N: Number> Point<N> for (N, N) {
impl<N: Number> Size<N> for (N, N) {
fn x (&self) -> N { self.0 }
fn y (&self) -> N { self.1 }
}
impl<N: Number> Point<N> for [N;2] {
impl<N: Number> Size<N> for [N;2] {
fn x (&self) -> N { self[0] }
fn y (&self) -> N { self[1] }
}
@ -22,8 +22,11 @@ pub trait Area<N: Number>: Copy {
fn y (&self) -> N;
fn w (&self) -> N;
fn h (&self) -> N;
fn x2 (&self) -> N { self.x() + self.w() - 1.into() }
fn y2 (&self) -> N { self.y() + self.h() - 1.into() }
fn x2 (&self) -> N { self.x() + self.w() }
fn y2 (&self) -> N { self.y() + self.h() }
fn wh (&self) -> [N;2] {
[self.w(), self.h()]
}
fn xywh (&self) -> [N;4] {
[self.x(), self.y(), self.w(), self.h()]
}
@ -37,6 +40,9 @@ pub trait Area<N: Number>: Copy {
Ok(self)
}
}
fn clip (&self, wh: impl Size<N>) -> [N;4] {
[self.x(), self.y(), wh.w(), wh.h()]
}
}
impl<N: Number> Area<N> for (N, N, N, N) {

View file

@ -1,32 +1,66 @@
use crate::*;
struct TestEngine([u16;4], Vec<Vec<char>>);
impl Engine for TestEngine {
type Unit = u16;
type Size = [Self::Unit;2];
type Area = [Self::Unit;4];
type HandleInput = Self;
type Handled = bool;
fn exited (&self) -> bool {
true
}
fn area (&self) -> Self::Area {
self.0
}
fn area_mut (&mut self) -> &mut Self::Area {
&mut self.0
}
}
#[derive(Copy, Clone)]
struct TestArea(u16, u16);
impl Widget for TestArea {
type Engine = Tui;
type Engine = TestEngine;
fn layout (&self, to: [u16;4]) -> Perhaps<[u16;4]> {
Ok(Some([to[0], to[1], self.0, self.1]))
}
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
self.layout(to.area())
fn render (&self, to: &mut Self::Engine) -> Perhaps<[u16;4]> {
if let Some(layout) = self.layout(to.area())? {
for y in layout.y()..layout.y()+layout.h()-1 {
for x in layout.x()..layout.x()+layout.w()-1 {
to.1[y as usize][x as usize] = '*';
}
}
Ok(Some(layout))
} else {
Ok(None)
}
}
}
#[test]
fn test_0 () -> Usually<()> {
let area: [u16;4] = [0, 0, 10, 10];
let test = TestArea(4, 4);
assert_eq!(test.layout(area)?,
Some([0, 0, 4, 4]));
assert_eq!(Outset::X(1, test).layout(area)?,
Some([0, 0, 6, 4]));
assert_eq!(Align::X(test).layout(area)?,
Some([3, 0, 4, 4]));
assert_eq!(Align::X(Outset::X(1, test)).layout(area)?,
Some([2, 0, 6, 4]));
assert_eq!(Outset::X(1, Align::X(test)).layout(area)?,
Some([2, 0, 6, 4]));
fn test_plus_minus () -> Usually<()> {
let area = [0, 0, 10, 10];
let engine = TestEngine(area, vec![vec![' ';10];10]);
let test = TestArea(4, 4);
assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
assert_eq!(Plus::X(1, test).layout(area)?, Some([1, 0, 4, 4]));
Ok(())
}
#[test]
fn test_outset_align () -> Usually<()> {
let area = [0, 0, 10, 10];
let engine = TestEngine(area, vec![vec![' ';10];10]);
let test = TestArea(4, 4);
assert_eq!(test.layout(area)?, Some([0, 0, 4, 4]));
assert_eq!(Outset::X(1, test).layout(area)?, Some([0, 0, 6, 4]));
assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4]));
assert_eq!(Align::X(Outset::X(1, test)).layout(area)?, Some([2, 0, 6, 4]));
assert_eq!(Outset::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4]));
Ok(())
}

View file

@ -22,6 +22,7 @@ pub struct Tui {
impl Engine for Tui {
type Unit = u16;
type Size = [Self::Unit;2];
type Area = [Self::Unit;4];
type HandleInput = Self;
type Handled = bool;
@ -45,24 +46,11 @@ impl Engine for Tui {
self.backend.show_cursor()?;
disable_raw_mode().map_err(Into::into)
}
// FIXME
fn area (&self) -> Self::Area {
#[inline] fn area (&self) -> Self::Area {
self.area
}
#[inline]
fn with_area (&mut self, x: u16, y: u16, w: u16, h: u16) -> &mut Self {
self.with_rect([x, y, w, h]);
self
}
#[inline]
fn render_in (
&mut self, area: [u16;4], widget: &impl Widget<Engine = Self>
) -> Perhaps<[u16;4]> {
let last = self.area;
self.area = area;
let next = widget.render(self)?;
self.area = last;
Ok(next)
#[inline] fn area_mut (&mut self) -> &mut Self::Area {
&mut self.area
}
}
impl Tui {
@ -183,13 +171,6 @@ impl Tui {
Ok(Some([x, y, text.len() as u16, 1]))
}
#[inline]
pub fn alter_area (
&mut self, alter: impl Fn([u16;4])->[u16;4]
) -> &mut Self {
let [x, y, w, h] = alter(self.area.xywh());
self.with_area(x, y, w, h)
}
#[inline]
pub fn with_rect (&mut self, area: [u16;4]) -> &mut Self {
self.area = area;
self
@ -425,41 +406,6 @@ where
}
}
impl<T: Widget<Engine = Tui>> Content for Inset<u16, T> {
type Engine = Tui;
fn content (&self) -> impl Widget<Engine = Tui> {
match self {
Self::X(x, inner) => Plus::X(
*x, Shrink::X(*x + *x, Align::X(inner as &dyn Widget<Engine = Tui>))
),
Self::Y(y, inner) => Plus::Y(
*y, Shrink::X(*y + *y, Align::Y(inner as &dyn Widget<Engine = Tui>))
),
Self::XY(x, y, inner) => Plus::XY(
*x, *y, Shrink::XY(*x, *y, Align::Center(inner as &dyn Widget<Engine = Tui>))
),
}
}
}
impl<T: Widget<Engine = Tui>> Widget for Outset<u16, T> {
type Engine = Tui;
fn layout (&self, to: [u16;4]) -> Perhaps<[u16;4]> {
match *self {
Self::X(x, ref inner) => Grow::X(x + x, inner as &dyn Widget<Engine = Tui>),
Self::Y(y, ref inner) => Grow::Y(y + y, inner as &dyn Widget<Engine = Tui>),
Self::XY(x, y, ref inner) => Grow::XY(x + x, y + y, inner as &dyn Widget<Engine = Tui>),
}.layout(to)
}
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
match *self {
Self::X(x, ref inner) => Plus::X(x, inner as &dyn Widget<Engine = Tui>),
Self::Y(y, ref inner) => Plus::Y(y, inner as &dyn Widget<Engine = Tui>),
Self::XY(x, y, ref inner) => Plus::XY(x, y, inner as &dyn Widget<Engine = Tui>),
}.render(to)
}
}
pub struct Border<S: BorderStyle>(pub S);
impl<S: BorderStyle> Widget for Border<S> {

View file

@ -407,9 +407,11 @@ impl<'a> Widget for RowSeparators<'a> {
break
}
for x in area.x()..area.x2().saturating_sub(2) {
let cell = to.buffer().get_mut(x, y);
cell.modifier = Modifier::UNDERLINED;
cell.underline_color = Nord::SEPARATOR;
if x < to.buffer().area.x && y < to.buffer().area.y {
let cell = to.buffer().get_mut(x, y);
cell.modifier = Modifier::UNDERLINED;
cell.underline_color = Nord::SEPARATOR;
}
}
}
Ok(Some(area))

View file

@ -412,7 +412,7 @@ impl Sequencer<Tui> {
add(&SequenceLoopRange)?;
add(&SequenceNoteRange)?;
Ok(())
}).render(to.with_area(area.x(), area.y(), 10, area.h()))?;
}).render(to.with_rect([area.x(), area.y(), 10, area.h()]))?;
let area = [area.x() + 10, area.y(), area.w().saturating_sub(10), area.h().min(66)];
Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(area))?;
let area = [area.x() + 1, area.y(), area.w().saturating_sub(1), area.h()];