mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-08 04:36:45 +01:00
231 lines
6.8 KiB
Rust
231 lines
6.8 KiB
Rust
//! Rendering of application to display.
|
|
|
|
use crate::*;
|
|
pub(crate) use ratatui::prelude::CrosstermBackend;
|
|
pub(crate) use ratatui::style::Style;
|
|
pub(crate) use ratatui::layout::Rect;
|
|
pub(crate) use ratatui::buffer::{Buffer, Cell};
|
|
|
|
/// Main thread render loop
|
|
pub fn render_thread (
|
|
exited: &Arc<AtomicBool>,
|
|
device: &Arc<RwLock<impl Render + 'static>>
|
|
) -> Usually<JoinHandle<()>> {
|
|
let exited = exited.clone();
|
|
let device = device.clone();
|
|
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
|
|
let sleep = Duration::from_millis(20);
|
|
Ok(spawn(move || loop {
|
|
|
|
if let Ok(device) = device.try_read() {
|
|
terminal.draw(|frame|{
|
|
let area = frame.size();
|
|
let buffer = frame.buffer_mut();
|
|
device
|
|
.render(buffer, area)
|
|
.expect("Failed to render content");
|
|
})
|
|
.expect("Failed to render frame");
|
|
}
|
|
|
|
if exited.fetch_and(true, Ordering::Relaxed) {
|
|
break
|
|
}
|
|
std::thread::sleep(sleep);
|
|
}))
|
|
}
|
|
|
|
/// Trait for things that render to the display.
|
|
pub trait Render: Send + Sync {
|
|
// Render something to an area of the buffer.
|
|
// Returns area used by component.
|
|
// This is insufficient but for the most basic dynamic layout algorithms.
|
|
fn render (&self, _b: &mut Buffer, _a: Rect) -> Usually<Rect> {
|
|
Ok(Rect { x: 0, y: 0, width: 0, height: 0 })
|
|
}
|
|
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
|
|
Collected::Box(Box::new(self))
|
|
}
|
|
}
|
|
|
|
/// Implement the `Render` trait.
|
|
#[macro_export] macro_rules! render {
|
|
($T:ty) => {
|
|
impl Render for $T {}
|
|
};
|
|
($T:ty |$self:ident, $buf:ident, $area:ident|$block:expr) => {
|
|
impl Render for $T {
|
|
fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually<Rect> {
|
|
$block
|
|
}
|
|
}
|
|
};
|
|
($T:ty = $render:path) => {
|
|
impl Render for $T {
|
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
$render(self, buf, area)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Render for () {
|
|
fn render (&self, _: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
Ok(Rect { x: a.x, y: a.y, width: 0, height: 0 })
|
|
}
|
|
}
|
|
|
|
impl<T: Render> Render for &T {
|
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
(*self).render(buf, area)
|
|
}
|
|
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
|
|
Collected::Ref(self)
|
|
}
|
|
}
|
|
|
|
impl<T: Render> Render for &mut T {
|
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
(**self).render(buf, area)
|
|
}
|
|
fn into_collected <'a> (self) -> Collected<'a> where Self: Sized + 'a {
|
|
Collected::Ref(self)
|
|
}
|
|
}
|
|
|
|
impl<T: Render> Render for Option<T> {
|
|
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
match self {
|
|
Some(widget) => widget.render(b, a),
|
|
None => ().render(b, a),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Render for Box<dyn Render + 'a> {
|
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
(**self).render(buf, area)
|
|
}
|
|
fn into_collected <'b> (self) -> Collected<'b> where Self: Sized + 'b {
|
|
Collected::Box(self)
|
|
}
|
|
}
|
|
|
|
//impl<'a, T: Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> Render for T {
|
|
//fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
//(*self)(b, a)
|
|
//}
|
|
//}
|
|
|
|
impl<'a> Render for Box<dyn Fn(&mut Buffer, Rect) -> Usually<Rect> + Send + Sync + 'a> {
|
|
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
(*self)(b, a)
|
|
}
|
|
}
|
|
|
|
impl<T: Render> Render for Arc<T> {
|
|
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
self.as_ref().render(b, a)
|
|
}
|
|
}
|
|
|
|
impl<T: Render> Render for Mutex<T> {
|
|
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
self.lock().unwrap().render(b, a)
|
|
}
|
|
}
|
|
|
|
impl<T: Render + Sync> Render for RwLock<T> {
|
|
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
|
self.read().unwrap().render(b, a)
|
|
}
|
|
}
|
|
|
|
pub fn center_box (area: Rect, w: u16, h: u16) -> Rect {
|
|
let width = w.min(area.width * 3 / 5);
|
|
let height = h.min(area.width * 3 / 5);
|
|
let x = area.x + (area.width - width) / 2;
|
|
let y = area.y + (area.height - height) / 2;
|
|
Rect { x, y, width, height }
|
|
}
|
|
|
|
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
|
|
match (lower, upper) {
|
|
(true, true) => Some('█'),
|
|
(true, false) => Some('▄'),
|
|
(false, true) => Some('▀'),
|
|
_ => None
|
|
}
|
|
}
|
|
|
|
pub trait Blit {
|
|
// Render something to X, Y coordinates in a buffer, ignoring width/height.
|
|
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>) -> Usually<Rect>;
|
|
}
|
|
|
|
impl<T: AsRef<str>> Blit for T {
|
|
fn blit (&self, buf: &mut Buffer, x: u16, y: u16, style: Option<Style>) -> Usually<Rect> {
|
|
if x < buf.area.width && y < buf.area.height {
|
|
buf.set_string(x, y, self.as_ref(), style.unwrap_or(Style::default()));
|
|
}
|
|
Ok(Rect { x, y, width: self.as_ref().len() as u16, height: 1 })
|
|
}
|
|
}
|
|
|
|
//impl WidgetRef for &dyn Render {
|
|
//fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
|
//Render::render(*self, buf, area).expect("Failed to render device.");
|
|
//}
|
|
//}
|
|
|
|
//impl WidgetRef for dyn Render {
|
|
//fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
|
//Render::render(self, buf, area).expect("Failed to render device.");
|
|
//}
|
|
//}
|
|
|
|
#[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 struct If<'a>(pub bool, pub &'a (dyn Render + Sync));
|
|
|
|
impl<'a> Render for If<'a> {
|
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
match self.0 {
|
|
true => self.1 as &dyn Render,
|
|
false => &() as &dyn Render
|
|
}.render(buf, area)
|
|
}
|
|
}
|
|
|
|
pub struct IfElse<'a>(pub bool, pub &'a (dyn Render + Sync), pub &'a (dyn Render + Sync));
|
|
|
|
impl<'a> Render for IfElse<'a> {
|
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
match self.0 {
|
|
true => self.1 as &dyn Render,
|
|
false => &() as &dyn Render
|
|
}.render(buf, area)
|
|
}
|
|
}
|