layout (no-draw) mode for tui

This commit is contained in:
facile pop culture reference 2026-08-01 21:25:29 +03:00
parent a27dacff93
commit 64c83b5460
4 changed files with 167 additions and 105 deletions

View file

@ -1325,21 +1325,45 @@ macro_rules! eval_xy (
crossterm::event::read,
};
impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
impl Wide<u16> for Tui { fn w (&self) -> u16 { self.1.2 } }
impl Tall<u16> for Tui { fn h (&self) -> u16 { self.1.3 } }
//impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
//impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
//impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
impl AsRef<XYWH<u16>> for Tui {
fn as_ref (&self) -> &XYWH<u16> {
match self {
Self::Draw(_, area) => area,
Self::Layout(area) => area
}
}
}
impl Wide<u16> for Tui {
fn w (&self) -> u16 { self.as_ref().2 }
}
impl Tall<u16> for Tui {
fn h (&self) -> u16 { self.as_ref().3 }
}
impl Xy<u16> for Tui {
fn x (&self) -> u16 { self.as_ref().0 }
fn y (&self) -> u16 { self.as_ref().1 }
}
impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } }
impl Xy<u16> for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } }
/// Terminal output.
pub struct Tui(
/// Ratatui buffer; area is screen size
pub Buffer,
/// Current draw area
pub XYWH<u16>
);
pub enum Tui {
Draw (
/// Ratatui buffer; area is screen size
Buffer,
/// Current draw area
XYWH<u16>
),
Layout (
XYWH<u16>
)
}
impl Tui {
pub fn setup_panic () {
@ -1402,14 +1426,16 @@ macro_rules! eval_xy (
disable_raw_mode().map_err(Into::into)
}
pub fn new (width: u16, height: u16) -> Self {
Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
Self::Draw(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
}
pub fn resize <W: Write> (&mut self, back: &mut CrosstermBackend<W>, width: u16, height: u16) {
let size = Rect { x: 0, y: 0, width, height };
if self.0.area != size {
back.clear_region(ClearType::All).unwrap();
self.0.resize(size);
self.0.reset();
if let Self::Draw(buffer, ..) = self {
if buffer.area != size {
back.clear_region(ClearType::All).unwrap();
buffer.resize(size);
buffer.reset();
}
}
}
pub fn redraw <'b, W: Write> (
@ -1417,20 +1443,25 @@ macro_rules! eval_xy (
back: &mut CrosstermBackend<W>,
mut next: &'b mut Self
) {
let updates = self.0.diff(&next.0);
back.draw(updates.into_iter()).expect("failed to render");
Backend::flush(back).expect("failed to flush output new");
std::mem::swap(self, &mut next);
next.0.reset();
if let Self::Draw(prev, ..) = self && let Self::Draw(next, ..) = next {
let updates = prev.diff(&next);
back.draw(updates.into_iter()).expect("failed to render");
Backend::flush(back).expect("failed to flush output new");
std::mem::swap(prev, next);
next.reset();
}
}
pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
for row in 0..self.h() {
let y = self.y() + row;
for col in 0..self.w() {
let x = self.x() + col;
if x < self.0.area.width && y < self.0.area.height {
if let Some(cell) = self.0.cell_mut(Position { x, y }) {
callback(cell, col, row);
let XYWH(x0, y0, w, h) = self.area();
if let Self::Draw(buffer, ..) = self {
for row in 0..h {
let y = y0 + row;
for col in 0..w {
let x = x0 + col;
if x < buffer.area.width && y < buffer.area.height {
if let Some(cell) = buffer.cell_mut(Position { x, y }) {
callback(cell, col, row);
}
}
}
}
@ -1438,17 +1469,21 @@ macro_rules! eval_xy (
self.xywh()
}
pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
let text = text.as_ref();
let style = style.unwrap_or(Style::default());
if x < self.0.area.width && y < self.0.area.height {
self.0.set_string(x, y, text, style);
if let Self::Draw(buffer, ..) = self {
let text = text.as_ref();
let style = style.unwrap_or(Style::default());
if x < buffer.area.width && y < buffer.area.height {
buffer.set_string(x, y, text, style);
}
}
}
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
for cell in self.0.content.iter_mut() {
cell.fg = fg;
cell.bg = bg;
cell.modifier = modifier;
if let Self::Draw(buffer, ..) = self {
for cell in buffer.content.iter_mut() {
cell.fg = fg;
cell.bg = bg;
cell.modifier = modifier;
}
}
}
/// Spawn the TUI output thread which writes colored characters to the terminal.
@ -1488,7 +1523,7 @@ macro_rules! eval_xy (
prev.redraw(&mut backend, &mut next);
}
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
prev.set_string(0, 0, &timer, Style::default());
prev.blit(&timer, 0, 0, Some(Style::default()));
})?)
}
/// Draw TUI content or its error message.
@ -1647,25 +1682,35 @@ macro_rules! eval_xy (
if string_width > max_width {
break
}
if let Some(cell) = self.0.cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character);
} else {
break
if let Self::Draw(buffer, ..) = self {
if let Some(cell) = buffer.cell_mut(ratatui::prelude::Position { x, y }) {
cell.set_char(character);
} else {
break
}
}
}
Ok(Some(XYWH(x0, y, string_width, 1)))
}
/// Get mutable reference to current clipping area
fn area_mut (&mut self) -> &mut XYWH<u16> {
match self {
Self::Draw(_, area) => area,
Self::Layout(area) => area,
}
}
}
impl Screen for Tui {
type Unit = u16;
/// Render drawable in subarea specified by `area`
fn show (&mut self, content: impl Draw<Self>) -> Perhaps<XYWH<u16>> {
let previous_area = self.1;
Ok(if let Some(area) = content.layout(self.1)? {
self.1 = area;
let previous_area = self.area();
Ok(if let Some(area) = content.layout(self.area())? {
*self.area_mut() = area;
if let Some(result_area) = content.draw(self)? {
self.1 = previous_area;
*self.area_mut() = previous_area;
Some(result_area)
} else {
None
@ -1677,7 +1722,10 @@ macro_rules! eval_xy (
/// Get current clipping area
fn area (&self) -> XYWH<Self::Unit> {
self.1
match self {
Self::Draw(_, area) => *area,
Self::Layout(area) => *area,
}
}
fn clip <T> (
@ -1685,12 +1733,12 @@ macro_rules! eval_xy (
area: impl Into<Option<XYWH<u16>>>,
draw: impl FnOnce(&mut Self)->T
) -> T {
let prev = self.1;
let prev = self.area();
if let Some(area) = area.into() {
self.1 = area.into();
*self.area_mut() = area.into();
}
let result = draw(self);
self.1 = prev;
*self.area_mut() = prev;
result
}
}
@ -1776,8 +1824,10 @@ macro_rules! eval_xy (
break
}
let pos = Position { x: x + width - 1, y };
if let Some(cell) = to.0.cell_mut(pos) {
cell.set_char(c);
if let Tui::Draw(buffer, _) = to {
if let Some(cell) = buffer.cell_mut(pos) {
cell.set_char(c);
}
}
width += c.width().unwrap_or(0) as u16;
}