still dark; refactor tui engine input/output

This commit is contained in:
🪞👃🪞 2024-12-31 20:01:04 +01:00
parent 675d376100
commit 73432a220a
3 changed files with 271 additions and 265 deletions

View file

@ -1,3 +1,6 @@
mod tui_output; pub use self::tui_output::*;
mod tui_input; pub use self::tui_input::*;
use crate::*;
use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
use std::io::{stdout, Stdout};
@ -159,268 +162,3 @@ impl<T: Content<Tui> + Handle<Tui> + Sized + 'static> TuiRun<T> for Arc<RwLock<T
}
}
#[derive(Debug, Clone)]
pub struct TuiInput {
pub(crate) exited: Arc<AtomicBool>,
pub(crate) event: crossterm::event::Event,
}
impl Input<Tui> for TuiInput {
type Event = crossterm::event::Event;
fn event (&self) -> &crossterm::event::Event {
&self.event
}
fn is_done (&self) -> bool {
self.exited.fetch_and(true, Relaxed)
}
fn done (&self) {
self.exited.store(true, Relaxed);
}
}
#[macro_export] macro_rules! key_pat {
(Ctrl-Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
(Ctrl-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL) };
(Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::ALT) };
(Shift-$code:pat) => { key_event_pat!($code, KeyModifiers::SHIFT) };
($code:pat) => { crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}) };
}
#[macro_export] macro_rules! key_event_pat {
($code:pat) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
($code:pat, $modifiers: pat) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: $modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
}
#[macro_export] macro_rules! kexp {
(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) };
(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
($code:ident) => { key_event_expr!($code) };
($code:expr) => { key_event_expr!($code) };
}
#[macro_export] macro_rules! key_expr {
(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
($code:ident) => { key_event_expr!($code) };
}
#[macro_export] macro_rules! key_event_expr {
($code:expr, $modifiers: expr) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: $modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
($code:expr) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
}
pub struct TuiOutput {
pub buffer: Buffer,
pub area: [u16;4]
}
impl Output<Tui> for TuiOutput {
#[inline] fn area (&self) -> [u16;4] { self.area }
#[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area }
#[inline] fn place (&mut self, area: [u16;4], content: &impl Content<Tui>) {
let last = self.area().xywh().clone();
//panic!("a {last:?} {area:?} {:?}", self.area);
*self.area_mut() = area.xywh().clone();
//panic!("b {last:?} {area:?} {:?}", self.area);
content.render(self);
//panic!("c {last:?} {area:?} {:?}", self.area);
*self.area_mut() = last;
//panic!("placed");
}
}
impl TuiOutput {
pub fn buffer_update (&mut self, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
buffer_update(&mut self.buffer, area, callback);
}
pub fn fill_bold (&mut self, area: [u16;4], on: bool) {
if on {
self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD))
} else {
self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD))
}
}
pub fn fill_bg (&mut self, area: [u16;4], color: Color) {
self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);})
}
pub fn fill_fg (&mut self, area: [u16;4], color: Color) {
self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);})
}
pub fn fill_ul (&mut self, area: [u16;4], color: Color) {
self.buffer_update(area, &|cell,_,_|{
cell.modifier = ratatui::prelude::Modifier::UNDERLINED;
cell.underline_color = color;
})
}
pub fn fill_char (&mut self, area: [u16;4], c: char) {
self.buffer_update(area, &|cell,_,_|{cell.set_char(c);})
}
pub fn make_dim (&mut self) {
for cell in self.buffer.content.iter_mut() {
cell.bg = ratatui::style::Color::Rgb(30,30,30);
cell.fg = ratatui::style::Color::Rgb(100,100,100);
cell.modifier = ratatui::style::Modifier::DIM;
}
}
pub fn blit (
&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>
) {
let text = text.as_ref();
let buf = &mut self.buffer;
if x < buf.area.width && y < buf.area.height {
buf.set_string(x, y, text, style.unwrap_or(Style::default()));
}
}
#[inline]
pub fn with_rect (&mut self, area: [u16;4]) -> &mut Self {
self.area = area;
self
}
}
//impl Area<u16> for Rect {
//fn x (&self) -> u16 { self.x }
//fn y (&self) -> u16 { self.y }
//fn w (&self) -> u16 { self.width }
//fn h (&self) -> u16 { self.height }
//}
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
match (lower, upper) {
(true, true) => Some('█'),
(true, false) => Some('▄'),
(false, true) => Some('▀'),
_ => None
}
}
//impl<T: Content<Tui>> Render<Tui> for T {}
impl Content<Tui> for &str {
fn area (&self, to: [u16;4]) -> [u16;4] {
[to[0], to[1], self.chars().count() as u16, 1]
}
fn render (&self, to: &mut TuiOutput) {
to.blit(self, to.area.x(), to.area.y(), None)
}
}
impl Content<Tui> for String {
fn area (&self, to: [u16;4]) -> [u16;4] {
[to[0], to[1], self.chars().count() as u16, 1]
}
fn render (&self, to: &mut TuiOutput) {
to.blit(self, to.area.x(), to.area.y(), None)
}
}
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);
}
}
}
}
/// Define a key
pub const fn key (code: KeyCode) -> KeyEvent {
let modifiers = KeyModifiers::NONE;
let kind = KeyEventKind::Press;
let state = KeyEventState::NONE;
KeyEvent { code, modifiers, kind, state }
}
/// Add Ctrl modifier to key
pub const fn ctrl (key: KeyEvent) -> KeyEvent {
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key }
}
/// Add Alt modifier to key
pub const fn alt (key: KeyEvent) -> KeyEvent {
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key }
}
/// Add Shift modifier to key
pub const fn shift (key: KeyEvent) -> KeyEvent {
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key }
}
/*
/// Define a keymap
#[macro_export] macro_rules! keymap {
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => {
&[
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),*
] as &'static [KeyBinding<$T>]
}
}
*/
/*
impl TuiInput {
// TODO remove
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
match self.event() {
TuiEvent::Input(Key(event)) => {
for (code, modifiers, _, _, command) in keymap.iter() {
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
return command(state)
}
}
},
_ => {}
};
Ok(false)
}
}
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
pub type KeyBinding<T> = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>);
pub type KeyMap<T> = [KeyBinding<T>];
*/

150
engine/src/tui/tui_input.rs Normal file
View file

@ -0,0 +1,150 @@
use crate::{*, tui::*};
#[derive(Debug, Clone)]
pub struct TuiInput {
pub(crate) exited: Arc<AtomicBool>,
pub(crate) event: crossterm::event::Event,
}
impl Input<Tui> for TuiInput {
type Event = crossterm::event::Event;
fn event (&self) -> &crossterm::event::Event {
&self.event
}
fn is_done (&self) -> bool {
self.exited.fetch_and(true, Relaxed)
}
fn done (&self) {
self.exited.store(true, Relaxed);
}
}
#[macro_export] macro_rules! key_pat {
(Ctrl-Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
(Ctrl-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL) };
(Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::ALT) };
(Shift-$code:pat) => { key_event_pat!($code, KeyModifiers::SHIFT) };
($code:pat) => { crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
}) };
}
#[macro_export] macro_rules! key_event_pat {
($code:pat) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
($code:pat, $modifiers: pat) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: $modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
}
#[macro_export] macro_rules! kexp {
(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) };
(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
($code:ident) => { key_event_expr!($code) };
($code:expr) => { key_event_expr!($code) };
}
#[macro_export] macro_rules! key_expr {
(Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) };
(Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) };
(Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) };
(Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) };
($code:ident) => { key_event_expr!($code) };
}
#[macro_export] macro_rules! key_event_expr {
($code:expr, $modifiers: expr) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: $modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
($code:expr) => {
crossterm::event::Event::Key(KeyEvent {
code: $code,
modifiers: KeyModifiers::NONE,
kind: KeyEventKind::Press,
state: KeyEventState::NONE
})
};
}
/// Define a key
pub const fn key (code: KeyCode) -> KeyEvent {
let modifiers = KeyModifiers::NONE;
let kind = KeyEventKind::Press;
let state = KeyEventState::NONE;
KeyEvent { code, modifiers, kind, state }
}
/// Add Ctrl modifier to key
pub const fn ctrl (key: KeyEvent) -> KeyEvent {
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key }
}
/// Add Alt modifier to key
pub const fn alt (key: KeyEvent) -> KeyEvent {
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key }
}
/// Add Shift modifier to key
pub const fn shift (key: KeyEvent) -> KeyEvent {
KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key }
}
/*
/// Define a keymap
#[macro_export] macro_rules! keymap {
($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => {
&[
$((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),*
] as &'static [KeyBinding<$T>]
}
}
*/
/*
impl TuiInput {
// TODO remove
pub fn handle_keymap <T> (&self, state: &mut T, keymap: &KeyMap<T>) -> Usually<bool> {
match self.event() {
TuiEvent::Input(Key(event)) => {
for (code, modifiers, _, _, command) in keymap.iter() {
if *code == event.code && modifiers.bits() == event.modifiers.bits() {
return command(state)
}
}
},
_ => {}
};
Ok(false)
}
}
pub type KeyHandler<T> = &'static dyn Fn(&mut T)->Usually<bool>;
pub type KeyBinding<T> = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler<T>);
pub type KeyMap<T> = [KeyBinding<T>];
*/

View file

@ -0,0 +1,118 @@
use crate::{*, tui::*};
pub struct TuiOutput {
pub buffer: Buffer,
pub area: [u16;4]
}
impl Output<Tui> for TuiOutput {
#[inline] fn area (&self) -> [u16;4] { self.area }
#[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area }
#[inline] fn place (&mut self, area: [u16;4], content: &impl Content<Tui>) {
let last = self.area().xywh().clone();
//panic!("a {last:?} {area:?} {:?}", self.area);
*self.area_mut() = area.xywh().clone();
//panic!("b {last:?} {area:?} {:?}", self.area);
content.render(self);
//panic!("c {last:?} {area:?} {:?}", self.area);
*self.area_mut() = last;
//panic!("placed");
}
}
impl TuiOutput {
pub fn buffer_update (&mut self, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
buffer_update(&mut self.buffer, area, callback);
}
pub fn fill_bold (&mut self, area: [u16;4], on: bool) {
if on {
self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD))
} else {
self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD))
}
}
pub fn fill_bg (&mut self, area: [u16;4], color: Color) {
self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);})
}
pub fn fill_fg (&mut self, area: [u16;4], color: Color) {
self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);})
}
pub fn fill_ul (&mut self, area: [u16;4], color: Color) {
self.buffer_update(area, &|cell,_,_|{
cell.modifier = ratatui::prelude::Modifier::UNDERLINED;
cell.underline_color = color;
})
}
pub fn fill_char (&mut self, area: [u16;4], c: char) {
self.buffer_update(area, &|cell,_,_|{cell.set_char(c);})
}
pub fn make_dim (&mut self) {
for cell in self.buffer.content.iter_mut() {
cell.bg = ratatui::style::Color::Rgb(30,30,30);
cell.fg = ratatui::style::Color::Rgb(100,100,100);
cell.modifier = ratatui::style::Modifier::DIM;
}
}
pub fn blit (
&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>
) {
let text = text.as_ref();
let buf = &mut self.buffer;
if x < buf.area.width && y < buf.area.height {
buf.set_string(x, y, text, style.unwrap_or(Style::default()));
}
}
#[inline]
pub fn with_rect (&mut self, area: [u16;4]) -> &mut Self {
self.area = area;
self
}
}
//impl Area<u16> for Rect {
//fn x (&self) -> u16 { self.x }
//fn y (&self) -> u16 { self.y }
//fn w (&self) -> u16 { self.width }
//fn h (&self) -> u16 { self.height }
//}
pub fn half_block (lower: bool, upper: bool) -> Option<char> {
match (lower, upper) {
(true, true) => Some('█'),
(true, false) => Some('▄'),
(false, true) => Some('▀'),
_ => None
}
}
//impl<T: Content<Tui>> Render<Tui> for T {}
impl Content<Tui> for &str {
fn area (&self, to: [u16;4]) -> [u16;4] {
[to[0], to[1], self.chars().count() as u16, 1]
}
fn render (&self, to: &mut TuiOutput) {
to.blit(self, to.area.x(), to.area.y(), None)
}
}
impl Content<Tui> for String {
fn area (&self, to: [u16;4]) -> [u16;4] {
[to[0], to[1], self.chars().count() as u16, 1]
}
fn render (&self, to: &mut TuiOutput) {
to.blit(self, to.area.x(), to.area.y(), None)
}
}
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);
}
}
}
}