mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 04:36:45 +01:00
refactor: unify tui
This commit is contained in:
parent
6378b554e2
commit
ef8f87aaa0
5 changed files with 536 additions and 542 deletions
|
|
@ -11,8 +11,6 @@ use crossterm::terminal::{
|
||||||
enable_raw_mode, disable_raw_mode
|
enable_raw_mode, disable_raw_mode
|
||||||
};
|
};
|
||||||
|
|
||||||
submod! { tui_border tui_buffer tui_colors tui_layout }
|
|
||||||
|
|
||||||
pub struct Tui {
|
pub struct Tui {
|
||||||
exited: Arc<AtomicBool>,
|
exited: Arc<AtomicBool>,
|
||||||
buffer: usize,
|
buffer: usize,
|
||||||
|
|
@ -224,3 +222,539 @@ pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct BigBuffer {
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub content: Vec<Cell>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BigBuffer {
|
||||||
|
pub fn new (width: usize, height: usize) -> Self {
|
||||||
|
Self { width, height, content: vec![Cell::default(); width*height] }
|
||||||
|
}
|
||||||
|
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
||||||
|
let i = self.index_of(x, y);
|
||||||
|
self.content.get(i)
|
||||||
|
}
|
||||||
|
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
||||||
|
let i = self.index_of(x, y);
|
||||||
|
self.content.get_mut(i)
|
||||||
|
}
|
||||||
|
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
||||||
|
y * self.width + x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
|
||||||
|
for row in 0..area.h() {
|
||||||
|
let y = area.y() + row;
|
||||||
|
for col in 0..area.w() {
|
||||||
|
let x = area.x() + col;
|
||||||
|
if x < buf.area.width && y < buf.area.height {
|
||||||
|
callback(buf.get_mut(x, y), col, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for &str {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||||
|
let [x, y, ..] = area;
|
||||||
|
// TODO: line breaks
|
||||||
|
Ok(Some([x, y, self.len() as u16, 1]))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
let area = self.layout(to.area())?.unwrap();
|
||||||
|
to.blit(&self, area.x(), area.y(), None)?;
|
||||||
|
Ok(Some(area))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Styled<T: Widget<Engine = Tui>>(pub Option<Style>, pub T);
|
||||||
|
|
||||||
|
impl Widget for Styled<&str> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||||
|
Ok(Some([area.x(), area.y(), self.1.len() as u16, 1]))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
let area = self.layout(to.area())?.unwrap();
|
||||||
|
Ok(Some([area.x(), area.y(), self.1.len() as u16, 1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FillBg(pub Color);
|
||||||
|
|
||||||
|
impl Widget for FillBg {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
to.fill_bg(to.area(), self.0);
|
||||||
|
Ok(Some(to.area))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const COLOR_BG0: Color = Color::Rgb(30, 33, 36);
|
||||||
|
pub const COLOR_BG1: Color = Color::Rgb(41, 46, 57);
|
||||||
|
pub const COLOR_BG2: Color = Color::Rgb(46, 52, 64);
|
||||||
|
pub const COLOR_BG3: Color = Color::Rgb(59, 66, 82);
|
||||||
|
pub const COLOR_BG4: Color = Color::Rgb(67, 76, 94);
|
||||||
|
pub const COLOR_BG5: Color = Color::Rgb(76, 86, 106);
|
||||||
|
|
||||||
|
pub trait Theme {
|
||||||
|
const BG0: Color;
|
||||||
|
const BG1: Color;
|
||||||
|
const BG2: Color;
|
||||||
|
const BG3: Color;
|
||||||
|
const BG4: Color;
|
||||||
|
const RED: Color;
|
||||||
|
const YELLOW: Color;
|
||||||
|
const GREEN: Color;
|
||||||
|
|
||||||
|
const PLAYING: Color;
|
||||||
|
const SEPARATOR: Color;
|
||||||
|
|
||||||
|
fn bg_hier (focused: bool, entered: bool) -> Color {
|
||||||
|
if focused && entered {
|
||||||
|
Self::BG3
|
||||||
|
} else if focused {
|
||||||
|
Self::BG2
|
||||||
|
} else {
|
||||||
|
Self::BG1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bg_hi (focused: bool, entered: bool) -> Color {
|
||||||
|
if focused && entered {
|
||||||
|
Self::BG2
|
||||||
|
} else if focused {
|
||||||
|
Self::BG1
|
||||||
|
} else {
|
||||||
|
Self::BG0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bg_lo (focused: bool, entered: bool) -> Color {
|
||||||
|
if focused && entered {
|
||||||
|
Self::BG1
|
||||||
|
} else if focused {
|
||||||
|
Self::BG0
|
||||||
|
} else {
|
||||||
|
Color::Reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn style_hi (focused: bool, highlight: bool) -> Style {
|
||||||
|
if highlight && focused {
|
||||||
|
Style::default().yellow().not_dim()
|
||||||
|
} else if highlight {
|
||||||
|
Style::default().yellow().dim()
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Nord;
|
||||||
|
|
||||||
|
impl Theme for Nord {
|
||||||
|
const BG0: Color = Color::Rgb(41, 46, 57);
|
||||||
|
const BG1: Color = Color::Rgb(46, 52, 64);
|
||||||
|
const BG2: Color = Color::Rgb(59, 66, 82);
|
||||||
|
const BG3: Color = Color::Rgb(67, 76, 94);
|
||||||
|
const BG4: Color = Color::Rgb(76, 86, 106);
|
||||||
|
const RED: Color = Color::Rgb(191, 97, 106);
|
||||||
|
const YELLOW: Color = Color::Rgb(235, 203, 139);
|
||||||
|
const GREEN: Color = Color::Rgb(163, 190, 140);
|
||||||
|
|
||||||
|
const PLAYING: Color = Color::Rgb(60, 100, 50);
|
||||||
|
const SEPARATOR: Color = Color::Rgb(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const GRAY: Style = Style {
|
||||||
|
fg: Some(Color::Gray),
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::empty(),
|
||||||
|
sub_modifier: Modifier::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GRAY_NOT_DIM: Style = Style {
|
||||||
|
fg: Some(Color::Gray),
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::empty(),
|
||||||
|
sub_modifier: Modifier::DIM,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DIM: Style = Style {
|
||||||
|
fg: None,
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::DIM,
|
||||||
|
sub_modifier: Modifier::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GRAY_DIM: Style = Style {
|
||||||
|
fg: Some(Color::Gray),
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::DIM,
|
||||||
|
sub_modifier: Modifier::empty(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const WHITE_NOT_DIM_BOLD: Style = Style {
|
||||||
|
fg: Some(Color::White),
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::BOLD,
|
||||||
|
sub_modifier: Modifier::DIM,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GRAY_NOT_DIM_BOLD: Style = Style {
|
||||||
|
fg: Some(Color::Gray),
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::BOLD,
|
||||||
|
sub_modifier: Modifier::DIM,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NOT_DIM: Style = Style {
|
||||||
|
fg: None,
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::empty(),
|
||||||
|
sub_modifier: Modifier::DIM,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NOT_DIM_GREEN: Style = Style {
|
||||||
|
fg: Some(Color::Rgb(96, 255, 32)),
|
||||||
|
bg: Some(COLOR_BG1),
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::empty(),
|
||||||
|
sub_modifier: Modifier::DIM,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const NOT_DIM_BOLD: Style = Style {
|
||||||
|
fg: None,
|
||||||
|
bg: None,
|
||||||
|
underline_color: None,
|
||||||
|
add_modifier: Modifier::BOLD,
|
||||||
|
sub_modifier: Modifier::DIM,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl<F> Widget for Split<Tui, F>
|
||||||
|
where
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>)->Usually<()>)->Usually<()>
|
||||||
|
{
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, to: [u16;4]) -> Perhaps<[u16;4]> {
|
||||||
|
let mut w = 0;
|
||||||
|
let mut h = 0;
|
||||||
|
match self.1 {
|
||||||
|
Direction::Down => {
|
||||||
|
(self.0)(&mut |component| {
|
||||||
|
if h >= to.h() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
if let Some([_, _, width, height]) = Offset::Y(
|
||||||
|
h, component as &dyn Widget<Engine = Tui>
|
||||||
|
).layout(to)? {
|
||||||
|
h += height;
|
||||||
|
w = w.max(width)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
Direction::Right => {
|
||||||
|
(self.0)(&mut |component| {
|
||||||
|
if w >= to.w() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
if let Some([_, _, width, height]) = Offset::X(
|
||||||
|
w, component as &dyn Widget<Engine = Tui>
|
||||||
|
).layout(to)? {
|
||||||
|
w += width;
|
||||||
|
h = h.max(height)
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
};
|
||||||
|
Ok(Some([to.x(), to.y(), w, h]))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
let area = to.area();
|
||||||
|
let mut w = 0;
|
||||||
|
let mut h = 0;
|
||||||
|
match self.1 {
|
||||||
|
Direction::Down => {
|
||||||
|
(self.0)(&mut |component| {
|
||||||
|
if h >= area.h() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
if let Some([_, _, width, height]) = Offset::Y(
|
||||||
|
h, component as &dyn Widget<Engine = Tui>
|
||||||
|
).render(to)? {
|
||||||
|
h += height;
|
||||||
|
w = w.max(width)
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
Direction::Right => {
|
||||||
|
(self.0)(&mut |component| {
|
||||||
|
if w >= area.w() {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
if let Some([_, _, width, height]) = Offset::X(
|
||||||
|
w, component as &dyn Widget<Engine = Tui>
|
||||||
|
).render(to)? {
|
||||||
|
w += width;
|
||||||
|
h = h.max(height)
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
},
|
||||||
|
_ => todo!()
|
||||||
|
};
|
||||||
|
Ok(Some([area.x(), area.y(), w, h]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> Widget for Layers<Tui, F>
|
||||||
|
where
|
||||||
|
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>)->Usually<()>)->Usually<()>
|
||||||
|
{
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||||
|
let [x, y, ..] = area;
|
||||||
|
let mut w = 0;
|
||||||
|
let mut h = 0;
|
||||||
|
(self.0)(&mut |layer| {
|
||||||
|
if let Some(layer_area) = layer.layout(area)? {
|
||||||
|
w = w.max(layer_area.w());
|
||||||
|
h = h.max(layer_area.h());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
Ok(Some([x, y, w, h]))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
self.layout(to.area())?
|
||||||
|
.map(|area|(self.0)(&mut |layer| {
|
||||||
|
to.render_in(area, &layer)?;
|
||||||
|
Ok(())
|
||||||
|
}).map(|_|area))
|
||||||
|
.transpose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Widget<Engine = Tui>> Widget for Align<T> {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, outer_area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||||
|
Ok(self.inner().layout(outer_area)?.map(|inner_area|match self {
|
||||||
|
Self::Center(_) => {
|
||||||
|
let [_, _, w, h] = inner_area.xywh();
|
||||||
|
let offset_x = (outer_area.w() / 2).saturating_sub(w / 2);
|
||||||
|
let offset_y = (outer_area.h() / 2).saturating_sub(h / 2);
|
||||||
|
let result = [outer_area.x() + offset_x, outer_area.y() + offset_y, w, h];
|
||||||
|
result
|
||||||
|
},
|
||||||
|
Self::NW(_) => { todo!() },
|
||||||
|
Self::N(_) => { todo!() },
|
||||||
|
Self::NE(_) => { todo!() },
|
||||||
|
Self::W(_) => { todo!() },
|
||||||
|
Self::E(_) => { todo!() },
|
||||||
|
Self::SW(_) => { todo!() },
|
||||||
|
Self::S(_) => { todo!() },
|
||||||
|
Self::SE(_) => { todo!() },
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
self.layout(to.area())?
|
||||||
|
.map(|area|to.render_in(area, self.inner()))
|
||||||
|
.transpose()
|
||||||
|
.map(|x|x.flatten())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BorderStyle {
|
||||||
|
const NW: &'static str = "";
|
||||||
|
const N: &'static str = "";
|
||||||
|
const NE: &'static str = "";
|
||||||
|
const E: &'static str = "";
|
||||||
|
const SE: &'static str = "";
|
||||||
|
const S: &'static str = "";
|
||||||
|
const SW: &'static str = "";
|
||||||
|
const W: &'static str = "";
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw <'a> (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
self.draw_horizontal(to, None)?;
|
||||||
|
self.draw_vertical(to, None)?;
|
||||||
|
self.draw_corners(to, None)?;
|
||||||
|
Ok(Some(to.area))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw_horizontal (&self, to: &mut Tui, style: Option<Style>) -> Usually<[u16;4]> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_horizontal());
|
||||||
|
let [x, x2, y, y2] = area.lrtb();
|
||||||
|
for x in x..x2.saturating_sub(1) {
|
||||||
|
self.draw_north(to, x, y, style)?;
|
||||||
|
self.draw_south(to, x, y2.saturating_sub(1), style)?;
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn draw_north (&self, to: &mut Tui, x: u16, y: u16, style: Option<Style>) -> Perhaps<[u16;4]> {
|
||||||
|
to.blit(&Self::N, x, y, style)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn draw_south (&self, to: &mut Tui, x: u16, y: u16, style: Option<Style>) -> Perhaps<[u16;4]> {
|
||||||
|
to.blit(&Self::S, x, y, style)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw_vertical (&self, to: &mut Tui, style: Option<Style>) -> Usually<[u16;4]> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_vertical());
|
||||||
|
let [x, x2, y, y2] = area.lrtb();
|
||||||
|
for y in y..y2.saturating_sub(1) {
|
||||||
|
to.blit(&Self::W, x, y, style)?;
|
||||||
|
to.blit(&Self::E, x2.saturating_sub(1), y, style)?;
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn draw_corners (&self, to: &mut Tui, style: Option<Style>) -> Usually<[u16;4]> {
|
||||||
|
let area = to.area();
|
||||||
|
let style = style.or_else(||self.style_corners());
|
||||||
|
let [x, y, width, height] = area.xywh();
|
||||||
|
if width > 0 && height > 0 {
|
||||||
|
to.blit(&Self::NW, x, y, style)?;
|
||||||
|
to.blit(&Self::NE, x + width - 1, y, style)?;
|
||||||
|
to.blit(&Self::SW, x, y + height - 1, style)?;
|
||||||
|
to.blit(&Self::SE, x + width - 1, y + height - 1, style)?;
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn style_horizontal (&self) -> Option<Style> {
|
||||||
|
self.style()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn style_vertical (&self) -> Option<Style> {
|
||||||
|
self.style()
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn style_corners (&self) -> Option<Style> {
|
||||||
|
self.style()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! border {
|
||||||
|
($($T:ty {
|
||||||
|
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
||||||
|
$($x:tt)*
|
||||||
|
}),+) => {$(
|
||||||
|
impl BorderStyle for $T {
|
||||||
|
const NW: &'static str = $nw;
|
||||||
|
const N: &'static str = $n;
|
||||||
|
const NE: &'static str = $ne;
|
||||||
|
const W: &'static str = $w;
|
||||||
|
const E: &'static str = $e;
|
||||||
|
const SW: &'static str = $sw;
|
||||||
|
const S: &'static str = $s;
|
||||||
|
const SE: &'static str = $se;
|
||||||
|
$($x)*
|
||||||
|
}
|
||||||
|
impl Widget for $T {
|
||||||
|
type Engine = Tui;
|
||||||
|
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
||||||
|
Ok(Some(area))
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
||||||
|
self.draw(to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)+}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Lozenge(pub Style);
|
||||||
|
pub struct LozengeV(pub Style);
|
||||||
|
pub struct LozengeDotted(pub Style);
|
||||||
|
pub struct Quarter(pub Style);
|
||||||
|
pub struct QuarterV(pub Style);
|
||||||
|
pub struct Chamfer(pub Style);
|
||||||
|
pub struct Corners(pub Style);
|
||||||
|
|
||||||
|
border! {
|
||||||
|
Lozenge {
|
||||||
|
"╭" "─" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"╰" "─" "╯"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LozengeV {
|
||||||
|
"╭" "" "╮"
|
||||||
|
"│" "│"
|
||||||
|
"╰" "" "╯"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
LozengeDotted {
|
||||||
|
"╭" "┅" "╮"
|
||||||
|
"┇" "┇"
|
||||||
|
"╰" "┅" "╯"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Quarter {
|
||||||
|
"▎" "▔" "🮇"
|
||||||
|
"▎" "🮇"
|
||||||
|
"▎" "▁" "🮇"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
QuarterV {
|
||||||
|
"▎" "" "🮇"
|
||||||
|
"▎" "🮇"
|
||||||
|
"▎" "" "🮇"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Chamfer {
|
||||||
|
"🭂" "▔" "🭍"
|
||||||
|
"▎" "🮇"
|
||||||
|
"🭓" "▁" "🭞"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Corners {
|
||||||
|
"🬆" "" "🬊" // 🬴 🬸
|
||||||
|
"" ""
|
||||||
|
"🬱" "" "🬵"
|
||||||
|
fn style (&self) -> Option<Style> {
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub trait BorderStyle {
|
|
||||||
const NW: &'static str = "";
|
|
||||||
const N: &'static str = "";
|
|
||||||
const NE: &'static str = "";
|
|
||||||
const E: &'static str = "";
|
|
||||||
const SE: &'static str = "";
|
|
||||||
const S: &'static str = "";
|
|
||||||
const SW: &'static str = "";
|
|
||||||
const W: &'static str = "";
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn draw <'a> (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
self.draw_horizontal(to, None)?;
|
|
||||||
self.draw_vertical(to, None)?;
|
|
||||||
self.draw_corners(to, None)?;
|
|
||||||
Ok(Some(to.area))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn draw_horizontal (&self, to: &mut Tui, style: Option<Style>) -> Usually<[u16;4]> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_horizontal());
|
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
|
||||||
for x in x..x2.saturating_sub(1) {
|
|
||||||
self.draw_north(to, x, y, style)?;
|
|
||||||
self.draw_south(to, x, y2.saturating_sub(1), style)?;
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn draw_north (&self, to: &mut Tui, x: u16, y: u16, style: Option<Style>) -> Perhaps<[u16;4]> {
|
|
||||||
to.blit(&Self::N, x, y, style)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn draw_south (&self, to: &mut Tui, x: u16, y: u16, style: Option<Style>) -> Perhaps<[u16;4]> {
|
|
||||||
to.blit(&Self::S, x, y, style)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn draw_vertical (&self, to: &mut Tui, style: Option<Style>) -> Usually<[u16;4]> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_vertical());
|
|
||||||
let [x, x2, y, y2] = area.lrtb();
|
|
||||||
for y in y..y2.saturating_sub(1) {
|
|
||||||
to.blit(&Self::W, x, y, style)?;
|
|
||||||
to.blit(&Self::E, x2.saturating_sub(1), y, style)?;
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn draw_corners (&self, to: &mut Tui, style: Option<Style>) -> Usually<[u16;4]> {
|
|
||||||
let area = to.area();
|
|
||||||
let style = style.or_else(||self.style_corners());
|
|
||||||
let [x, y, width, height] = area.xywh();
|
|
||||||
if width > 0 && height > 0 {
|
|
||||||
to.blit(&Self::NW, x, y, style)?;
|
|
||||||
to.blit(&Self::NE, x + width - 1, y, style)?;
|
|
||||||
to.blit(&Self::SW, x, y + height - 1, style)?;
|
|
||||||
to.blit(&Self::SE, x + width - 1, y + height - 1, style)?;
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn style_horizontal (&self) -> Option<Style> {
|
|
||||||
self.style()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn style_vertical (&self) -> Option<Style> {
|
|
||||||
self.style()
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
fn style_corners (&self) -> Option<Style> {
|
|
||||||
self.style()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! border {
|
|
||||||
($($T:ty {
|
|
||||||
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
|
|
||||||
$($x:tt)*
|
|
||||||
}),+) => {$(
|
|
||||||
impl BorderStyle for $T {
|
|
||||||
const NW: &'static str = $nw;
|
|
||||||
const N: &'static str = $n;
|
|
||||||
const NE: &'static str = $ne;
|
|
||||||
const W: &'static str = $w;
|
|
||||||
const E: &'static str = $e;
|
|
||||||
const SW: &'static str = $sw;
|
|
||||||
const S: &'static str = $s;
|
|
||||||
const SE: &'static str = $se;
|
|
||||||
$($x)*
|
|
||||||
}
|
|
||||||
impl Widget for $T {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
|
||||||
Ok(Some(area))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
self.draw(to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)+}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Lozenge(pub Style);
|
|
||||||
pub struct LozengeV(pub Style);
|
|
||||||
pub struct LozengeDotted(pub Style);
|
|
||||||
pub struct Quarter(pub Style);
|
|
||||||
pub struct QuarterV(pub Style);
|
|
||||||
pub struct Chamfer(pub Style);
|
|
||||||
pub struct Corners(pub Style);
|
|
||||||
|
|
||||||
border! {
|
|
||||||
Lozenge {
|
|
||||||
"╭" "─" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "─" "╯"
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LozengeV {
|
|
||||||
"╭" "" "╮"
|
|
||||||
"│" "│"
|
|
||||||
"╰" "" "╯"
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LozengeDotted {
|
|
||||||
"╭" "┅" "╮"
|
|
||||||
"┇" "┇"
|
|
||||||
"╰" "┅" "╯"
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Quarter {
|
|
||||||
"▎" "▔" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "▁" "🮇"
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
QuarterV {
|
|
||||||
"▎" "" "🮇"
|
|
||||||
"▎" "🮇"
|
|
||||||
"▎" "" "🮇"
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Chamfer {
|
|
||||||
"🭂" "▔" "🭍"
|
|
||||||
"▎" "🮇"
|
|
||||||
"🭓" "▁" "🭞"
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Corners {
|
|
||||||
"🬆" "" "🬊" // 🬴 🬸
|
|
||||||
"" ""
|
|
||||||
"🬱" "" "🬵"
|
|
||||||
fn style (&self) -> Option<Style> {
|
|
||||||
Some(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct BigBuffer {
|
|
||||||
pub width: usize,
|
|
||||||
pub height: usize,
|
|
||||||
pub content: Vec<Cell>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BigBuffer {
|
|
||||||
pub fn new (width: usize, height: usize) -> Self {
|
|
||||||
Self { width, height, content: vec![Cell::default(); width*height] }
|
|
||||||
}
|
|
||||||
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
|
|
||||||
let i = self.index_of(x, y);
|
|
||||||
self.content.get(i)
|
|
||||||
}
|
|
||||||
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
|
|
||||||
let i = self.index_of(x, y);
|
|
||||||
self.content.get_mut(i)
|
|
||||||
}
|
|
||||||
pub fn index_of (&self, x: usize, y: usize) -> usize {
|
|
||||||
y * self.width + x
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
|
|
||||||
for row in 0..area.h() {
|
|
||||||
let y = area.y() + row;
|
|
||||||
for col in 0..area.w() {
|
|
||||||
let x = area.x() + col;
|
|
||||||
if x < buf.area.width && y < buf.area.height {
|
|
||||||
callback(buf.get_mut(x, y), col, row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl Widget for &str {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
|
||||||
let [x, y, ..] = area;
|
|
||||||
// TODO: line breaks
|
|
||||||
Ok(Some([x, y, self.len() as u16, 1]))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
let area = self.layout(to.area())?.unwrap();
|
|
||||||
to.blit(&self, area.x(), area.y(), None)?;
|
|
||||||
Ok(Some(area))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Styled<T: Widget<Engine = Tui>>(pub Option<Style>, pub T);
|
|
||||||
|
|
||||||
impl Widget for Styled<&str> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
|
||||||
Ok(Some([area.x(), area.y(), self.1.len() as u16, 1]))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
let area = self.layout(to.area())?.unwrap();
|
|
||||||
Ok(Some([area.x(), area.y(), self.1.len() as u16, 1]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FillBg(pub Color);
|
|
||||||
|
|
||||||
impl Widget for FillBg {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
to.fill_bg(to.area(), self.0);
|
|
||||||
Ok(Some(to.area))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const COLOR_BG0: Color = Color::Rgb(30, 33, 36);
|
|
||||||
pub const COLOR_BG1: Color = Color::Rgb(41, 46, 57);
|
|
||||||
pub const COLOR_BG2: Color = Color::Rgb(46, 52, 64);
|
|
||||||
pub const COLOR_BG3: Color = Color::Rgb(59, 66, 82);
|
|
||||||
pub const COLOR_BG4: Color = Color::Rgb(67, 76, 94);
|
|
||||||
pub const COLOR_BG5: Color = Color::Rgb(76, 86, 106);
|
|
||||||
|
|
||||||
pub trait Theme {
|
|
||||||
const BG0: Color;
|
|
||||||
const BG1: Color;
|
|
||||||
const BG2: Color;
|
|
||||||
const BG3: Color;
|
|
||||||
const BG4: Color;
|
|
||||||
const RED: Color;
|
|
||||||
const YELLOW: Color;
|
|
||||||
const GREEN: Color;
|
|
||||||
|
|
||||||
const PLAYING: Color;
|
|
||||||
const SEPARATOR: Color;
|
|
||||||
|
|
||||||
fn bg_hier (focused: bool, entered: bool) -> Color {
|
|
||||||
if focused && entered {
|
|
||||||
Self::BG3
|
|
||||||
} else if focused {
|
|
||||||
Self::BG2
|
|
||||||
} else {
|
|
||||||
Self::BG1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bg_hi (focused: bool, entered: bool) -> Color {
|
|
||||||
if focused && entered {
|
|
||||||
Self::BG2
|
|
||||||
} else if focused {
|
|
||||||
Self::BG1
|
|
||||||
} else {
|
|
||||||
Self::BG0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bg_lo (focused: bool, entered: bool) -> Color {
|
|
||||||
if focused && entered {
|
|
||||||
Self::BG1
|
|
||||||
} else if focused {
|
|
||||||
Self::BG0
|
|
||||||
} else {
|
|
||||||
Color::Reset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn style_hi (focused: bool, highlight: bool) -> Style {
|
|
||||||
if highlight && focused {
|
|
||||||
Style::default().yellow().not_dim()
|
|
||||||
} else if highlight {
|
|
||||||
Style::default().yellow().dim()
|
|
||||||
} else {
|
|
||||||
Style::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Nord;
|
|
||||||
|
|
||||||
impl Theme for Nord {
|
|
||||||
const BG0: Color = Color::Rgb(41, 46, 57);
|
|
||||||
const BG1: Color = Color::Rgb(46, 52, 64);
|
|
||||||
const BG2: Color = Color::Rgb(59, 66, 82);
|
|
||||||
const BG3: Color = Color::Rgb(67, 76, 94);
|
|
||||||
const BG4: Color = Color::Rgb(76, 86, 106);
|
|
||||||
const RED: Color = Color::Rgb(191, 97, 106);
|
|
||||||
const YELLOW: Color = Color::Rgb(235, 203, 139);
|
|
||||||
const GREEN: Color = Color::Rgb(163, 190, 140);
|
|
||||||
|
|
||||||
const PLAYING: Color = Color::Rgb(60, 100, 50);
|
|
||||||
const SEPARATOR: Color = Color::Rgb(0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const GRAY: Style = Style {
|
|
||||||
fg: Some(Color::Gray),
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::empty(),
|
|
||||||
sub_modifier: Modifier::empty(),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const GRAY_NOT_DIM: Style = Style {
|
|
||||||
fg: Some(Color::Gray),
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::empty(),
|
|
||||||
sub_modifier: Modifier::DIM,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DIM: Style = Style {
|
|
||||||
fg: None,
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::DIM,
|
|
||||||
sub_modifier: Modifier::empty(),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const GRAY_DIM: Style = Style {
|
|
||||||
fg: Some(Color::Gray),
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::DIM,
|
|
||||||
sub_modifier: Modifier::empty(),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const WHITE_NOT_DIM_BOLD: Style = Style {
|
|
||||||
fg: Some(Color::White),
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::BOLD,
|
|
||||||
sub_modifier: Modifier::DIM,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const GRAY_NOT_DIM_BOLD: Style = Style {
|
|
||||||
fg: Some(Color::Gray),
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::BOLD,
|
|
||||||
sub_modifier: Modifier::DIM,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const NOT_DIM: Style = Style {
|
|
||||||
fg: None,
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::empty(),
|
|
||||||
sub_modifier: Modifier::DIM,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const NOT_DIM_GREEN: Style = Style {
|
|
||||||
fg: Some(Color::Rgb(96, 255, 32)),
|
|
||||||
bg: Some(COLOR_BG1),
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::empty(),
|
|
||||||
sub_modifier: Modifier::DIM,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const NOT_DIM_BOLD: Style = Style {
|
|
||||||
fg: None,
|
|
||||||
bg: None,
|
|
||||||
underline_color: None,
|
|
||||||
add_modifier: Modifier::BOLD,
|
|
||||||
sub_modifier: Modifier::DIM,
|
|
||||||
};
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl<F> Widget for Split<Tui, F>
|
|
||||||
where
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>)->Usually<()>)->Usually<()>
|
|
||||||
{
|
|
||||||
type Engine = Tui;
|
|
||||||
fn layout (&self, to: [u16;4]) -> Perhaps<[u16;4]> {
|
|
||||||
let mut w = 0;
|
|
||||||
let mut h = 0;
|
|
||||||
match self.1 {
|
|
||||||
Direction::Down => {
|
|
||||||
(self.0)(&mut |component| {
|
|
||||||
if h >= to.h() {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
if let Some([_, _, width, height]) = Offset::Y(
|
|
||||||
h, component as &dyn Widget<Engine = Tui>
|
|
||||||
).layout(to)? {
|
|
||||||
h += height;
|
|
||||||
w = w.max(width)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
Direction::Right => {
|
|
||||||
(self.0)(&mut |component| {
|
|
||||||
if w >= to.w() {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
if let Some([_, _, width, height]) = Offset::X(
|
|
||||||
w, component as &dyn Widget<Engine = Tui>
|
|
||||||
).layout(to)? {
|
|
||||||
w += width;
|
|
||||||
h = h.max(height)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
_ => todo!()
|
|
||||||
};
|
|
||||||
Ok(Some([to.x(), to.y(), w, h]))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
let area = to.area();
|
|
||||||
let mut w = 0;
|
|
||||||
let mut h = 0;
|
|
||||||
match self.1 {
|
|
||||||
Direction::Down => {
|
|
||||||
(self.0)(&mut |component| {
|
|
||||||
if h >= area.h() {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
if let Some([_, _, width, height]) = Offset::Y(
|
|
||||||
h, component as &dyn Widget<Engine = Tui>
|
|
||||||
).render(to)? {
|
|
||||||
h += height;
|
|
||||||
w = w.max(width)
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
Direction::Right => {
|
|
||||||
(self.0)(&mut |component| {
|
|
||||||
if w >= area.w() {
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
if let Some([_, _, width, height]) = Offset::X(
|
|
||||||
w, component as &dyn Widget<Engine = Tui>
|
|
||||||
).render(to)? {
|
|
||||||
w += width;
|
|
||||||
h = h.max(height)
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
},
|
|
||||||
_ => todo!()
|
|
||||||
};
|
|
||||||
Ok(Some([area.x(), area.y(), w, h]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F> Widget for Layers<Tui, F>
|
|
||||||
where
|
|
||||||
F: Send + Sync + Fn(&mut dyn FnMut(&dyn Widget<Engine = Tui>)->Usually<()>)->Usually<()>
|
|
||||||
{
|
|
||||||
type Engine = Tui;
|
|
||||||
fn layout (&self, area: [u16;4]) -> Perhaps<[u16;4]> {
|
|
||||||
let [x, y, ..] = area;
|
|
||||||
let mut w = 0;
|
|
||||||
let mut h = 0;
|
|
||||||
(self.0)(&mut |layer| {
|
|
||||||
if let Some(layer_area) = layer.layout(area)? {
|
|
||||||
w = w.max(layer_area.w());
|
|
||||||
h = h.max(layer_area.h());
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
Ok(Some([x, y, w, h]))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
self.layout(to.area())?
|
|
||||||
.map(|area|(self.0)(&mut |layer| {
|
|
||||||
to.render_in(area, &layer)?;
|
|
||||||
Ok(())
|
|
||||||
}).map(|_|area))
|
|
||||||
.transpose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Widget<Engine = Tui>> Widget for Align<T> {
|
|
||||||
type Engine = Tui;
|
|
||||||
fn layout (&self, outer_area: [u16;4]) -> Perhaps<[u16;4]> {
|
|
||||||
Ok(self.inner().layout(outer_area)?.map(|inner_area|match self {
|
|
||||||
Self::Center(_) => {
|
|
||||||
let [_, _, w, h] = inner_area.xywh();
|
|
||||||
let offset_x = (outer_area.w() / 2).saturating_sub(w / 2);
|
|
||||||
let offset_y = (outer_area.h() / 2).saturating_sub(h / 2);
|
|
||||||
let result = [outer_area.x() + offset_x, outer_area.y() + offset_y, w, h];
|
|
||||||
result
|
|
||||||
},
|
|
||||||
Self::NW(_) => { todo!() },
|
|
||||||
Self::N(_) => { todo!() },
|
|
||||||
Self::NE(_) => { todo!() },
|
|
||||||
Self::W(_) => { todo!() },
|
|
||||||
Self::E(_) => { todo!() },
|
|
||||||
Self::SW(_) => { todo!() },
|
|
||||||
Self::S(_) => { todo!() },
|
|
||||||
Self::SE(_) => { todo!() },
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
fn render (&self, to: &mut Tui) -> Perhaps<[u16;4]> {
|
|
||||||
self.layout(to.area())?
|
|
||||||
.map(|area|to.render_in(area, self.inner()))
|
|
||||||
.transpose()
|
|
||||||
.map(|x|x.flatten())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue