removed debug!, made subframes work

This commit is contained in:
emilis 2023-01-14 23:07:07 +00:00
parent 9f7be00735
commit c70ecf5fbe
6 changed files with 292 additions and 145 deletions

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
max_width = 70

View File

@ -2,25 +2,48 @@ use std::io::{Stdout, Write};
use termion::{clear, cursor, raw::RawTerminal}; use termion::{clear, cursor, raw::RawTerminal};
use super::{Environment, Event, Page}; use super::{
frame::Frame, theme::ColorSet, Environment, Event, Page,
};
type Result<T> = std::result::Result<T, anyhow::Error>; type Result<T> = std::result::Result<T, anyhow::Error>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Body { pub enum Body {
Echo, Echo,
// Signin(SigninPage), Signin(SigninPage),
} }
impl Body { impl Body {
fn echo(env: Environment, screen: &mut RawTerminal<Stdout>, event: Event) -> Result<()> { fn echo(
env: Environment,
screen: &mut RawTerminal<Stdout>,
event: Event,
) -> Result<()> {
let event = format!("{}", event); let event = format!("{}", event);
write!( write!(
screen, screen,
"{clear}{frame}{content}", "{theme}{content}",
theme = env.theme.primary(),
content =
env.frame.writeln(&format!("Event: {}", event), 0, 0),
)?;
screen.flush()?;
Ok(())
}
fn echo_init(
&self,
env: Environment,
screen: &mut RawTerminal<Stdout>,
) -> Result<()> {
write!(
screen,
"{hide}{clear}{fr}{cursor}",
clear = clear::All, clear = clear::All,
frame = env.frame.frame_str(env.theme.primary()), hide = cursor::Hide,
content = env.frame.writeln(&format!("Event: {}", event), 0, 0), fr = env.frame.frame_str(env.theme.primary()),
cursor = env.frame.goto(0, 0),
)?; )?;
screen.flush()?; screen.flush()?;
Ok(()) Ok(())
@ -35,71 +58,103 @@ impl Page for Body {
event: Event, event: Event,
) -> Result<()> { ) -> Result<()> {
match self { match self {
// Body::Signin(b) => b.receive_event(env, screen, event)?, Body::Signin(b) => b.receive_event(env, screen, event)?,
Body::Echo => Body::echo(env, screen, event)?, Body::Echo => Body::echo(env, screen, event)?,
}; };
Ok(()) Ok(())
} }
fn init(&self, env: Environment, screen: &mut RawTerminal<Stdout>) -> Result<()> { fn init(
&mut self,
env: Environment,
screen: &mut RawTerminal<Stdout>,
) -> Result<()> {
match self {
Body::Echo => self.echo_init(env, screen),
Body::Signin(signin) => signin.init(env, screen),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct SigninPage {
hostname: String,
username: String,
cursor: SigninCursorLocation,
cursor_position: u16,
frame: Option<Frame>,
}
#[derive(Debug, Clone)]
enum SigninCursorLocation {
Hostname,
Username,
Next,
}
impl Default for SigninCursorLocation {
fn default() -> Self {
Self::Hostname
}
}
impl SigninPage {
#[inline]
fn draw(&self) -> String {
let frame = self.frame.unwrap();
frame.write_centered("login kk", 1)
}
}
impl Page for SigninPage {
fn receive_event(
&mut self,
env: Environment,
screen: &mut RawTerminal<Stdout>,
event: Event,
) -> Result<()> {
match event {
Event::Key(key) => match key {
termion::event::Key::Char(_) => {}
termion::event::Key::Up => {}
termion::event::Key::Down => {}
termion::event::Key::Backspace => {}
termion::event::Key::Delete => {}
termion::event::Key::Left => {}
termion::event::Key::Right => {}
//
_ => {}
},
}
Ok(())
}
fn init(
&mut self,
env: Environment,
screen: &mut RawTerminal<Stdout>,
) -> Result<()> {
self.frame = Some(env.frame.sub(
env.theme.colors.subwin,
super::frame::FrameSize::ByPercent(80, 80),
2,
));
write!( write!(
screen, screen,
"{hide}{clear}{fr}{cursor}", "{theme}{clear}{frame}{subframe}{hide_cursor}{content}",
hide = cursor::Hide, theme = env.theme.frame(),
fr = env.frame.frame_str(env.theme.primary()), clear = clear::All,
cursor = env.frame.goto(0, 0), frame = env.frame.frame_str(env.theme.primary()),
clear = clear::All subframe = self
.frame
.unwrap()
.frame_str(ColorSet::default().to_string()),
hide_cursor = cursor::Hide,
content = self.draw(),
)?; )?;
screen.flush()?; screen.flush()?;
Ok(()) Ok(())
} }
} }
// #[derive(Debug, Clone, Default)]
// struct SigninPage {
// hostname: String,
// username: String,
// cursor: SigninCursorLocation,
// }
// #[derive(Debug, Clone)]
// enum SigninCursorLocation {
// Hostname,
// Username,
// Next,
// }
// impl Default for SigninCursorLocation {
// fn default() -> Self {
// Self::Hostname
// }
// }
// impl SigninPage {
// fn frame_string() -> String {
// format!("")
// }
// }
// impl Page for SigninPage {
// fn receive_event(
// &mut self,
// env: Environment,
// screen: &mut RawTerminal<Stdout>,
// event: Event,
// ) -> Result<()> {
// Ok(())
// }
// fn init(&self, env: Environment, screen: &mut RawTerminal<Stdout>) -> Result<()> {
// write!(
// screen,
// "{frame}{hide_cursor}",
// frame = env.frame.frame_str(&env.theme, env.theme.primary()),
// hide_cursor = cursor::Hide,
// )?;
// screen.flush()?;
// Ok(())
// }
// }

View File

@ -1,15 +0,0 @@
pub fn debug_append(s: String) {
std::process::Command::new("fish")
.arg("-c")
.arg(format!("echo \"{}\" >> debug.log", s))
.output()
.unwrap();
}
macro_rules! debug {
($fmt_string:expr, $($arg:tt)*) => {
$crate::display::debug::debug_append(
format!($fmt_string, $($arg)*)
);
};
}
pub(crate) use debug;

View File

@ -1,11 +1,9 @@
use termion::{clear, cursor}; use termion::{clear, cursor, raw::RawTerminal};
const ESTIMATED_FRAME_BIT_SIZE: usize = 11; const ESTIMATED_FRAME_BIT_SIZE: usize = 11;
use std::io::Write; use std::io::{Stdout, Write};
use crate::display::debug::debug; use super::theme::{ColorSet, Theme};
use super::theme::Theme;
const FRAME_CHAR_HORIZONTAL: char = ' '; const FRAME_CHAR_HORIZONTAL: char = ' ';
const FRAME_CHAR_VERTICAL: char = ' '; const FRAME_CHAR_VERTICAL: char = ' ';
@ -21,15 +19,32 @@ pub struct Frame {
start: (u16, u16), start: (u16, u16),
end: (u16, u16), end: (u16, u16),
width: u16, width: u16,
theme: Theme, theme: ColorSet,
} }
impl Frame { impl Frame {
#[inline(always)]
pub fn sub(
&self,
theme: ColorSet,
size: FrameSize,
width: u16,
) -> Frame {
let (p_width, p_height) = self.size();
size.abs_size((p_width - 1, p_height - 1), theme, width)
}
#[inline(always)] #[inline(always)]
pub fn goto(&self, x: u16, y: u16) -> String { pub fn goto(&self, x: u16, y: u16) -> String {
let cg = cursor::Goto( let cg = cursor::Goto(
(self.width + self.start.0 + x).min(self.end.0 - self.width), self.width.max(
(self.width + self.start.1 + y).min(self.end.1 - self.width), (self.width + self.start.0 + x)
.min(self.end.0 - self.width),
),
self.width.max(
(self.width + self.start.1 + y)
.min(self.end.1 - self.width),
),
); );
cg.into() cg.into()
} }
@ -43,38 +58,73 @@ impl Frame {
.into() .into()
} }
#[inline(always)]
fn write_clear_to_end(&self, s: &str) -> String {
let clear_length = self.size().0 as usize
- s.len()
- (self.width * 2) as usize;
format!("{}{}", s, " ".repeat(clear_length))
}
pub fn writeln(&self, s: &str, x: u16, y: u16) -> String { pub fn writeln(&self, s: &str, x: u16, y: u16) -> String {
let words = s.split('\n').collect::<Vec<&str>>(); let words = s.split('\n').collect::<Vec<&str>>();
let mut out_words = Vec::with_capacity(words.len()); let mut out_words = Vec::with_capacity(words.len());
for i in 0..words.len() { for i in 0..words.len() {
out_words.push(format!( out_words.push(format!(
"{ret}{str}", "{ret}{str}",
str = words[i], str = self.write_clear_to_end(words[i]),
ret = self.goto(x, y + i as u16), ret = self.goto(x, y + i as u16),
)); ));
} }
out_words.concat() out_words.concat()
} }
#[inline(always)]
fn write_frame_line(&self, y: u16, length: u16) -> String {
self.goto_internal(0, y)
+ &FRAME_CHAR_HORIZONTAL
.to_string()
.repeat(length as usize)
}
pub fn write_centered(&self, s: &str, y: u16) -> String {
let (width, _) = self.size();
let words = s.split('\n').collect::<Vec<&str>>();
let mut out_words = Vec::with_capacity(words.len());
for i in 0..words.len() {
let word = words[i];
let x = width - (word.len() as u16).min(width);
out_words.push(format!(
"{ret}{str}",
str = word,
ret = self.goto(x / 2, y + i as u16),
));
}
out_words.concat()
}
#[inline(always)] #[inline(always)]
pub fn size(&self) -> (u16, u16) { pub fn size(&self) -> (u16, u16) {
(self.end.0 - self.start.0, self.end.1 - self.start.1) (self.end.0 - self.start.0, self.end.1 - self.start.1)
} }
#[inline(always)] #[inline(always)]
pub fn from_terminal_size(theme: Theme, width: u16) -> Result<Self, anyhow::Error> { pub fn from_terminal_size(
theme: ColorSet,
width: u16,
) -> Result<Self, anyhow::Error> {
let term = termion::terminal_size()?; let term = termion::terminal_size()?;
// Swap term dimensions to use x/y // Swap term dimensions to use x/y
let term = (term.1, term.0); // let term = (term.1, term.0);
Ok(Self::from_bottom_right(term, term, width, theme)) Ok(Self::from_bottom_right(term, term, width, theme))
} }
#[inline(always)] #[inline(always)]
fn from_bottom_right( fn from_bottom_right(
(pos_h, pos_w): (u16, u16), (pos_w, pos_h): (u16, u16),
(term_h, term_w): (u16, u16), (term_w, term_h): (u16, u16),
width: u16, width: u16,
theme: Theme, theme: ColorSet,
) -> Self { ) -> Self {
Self { Self {
start: (1.max(term_w - pos_w), 1.max(term_h - pos_h)), start: (1.max(term_w - pos_w), 1.max(term_h - pos_h)),
@ -90,32 +140,34 @@ impl Frame {
} }
self.goto_internal(0, 0); self.goto_internal(0, 0);
let (w_len, h_len) = self.size(); let (w_len, h_len) = self.size();
let width_str = FRAME_CHAR_HORIZONTAL.to_string().repeat(w_len as usize + 1); let width_str = FRAME_CHAR_HORIZONTAL
.to_string()
.repeat(w_len as usize + 1);
let mut frame = Vec::with_capacity(h_len as usize + 4); let mut frame = Vec::with_capacity(h_len as usize + 4);
let frame_theme = self.theme.frame(); let frame_theme = self.theme.to_string();
let body_clear = body_theme.clone() + &clear::CurrentLine.to_string(); let body_clear =
frame.push(frame_theme.clone()); body_theme.clone() + &" ".repeat(w_len as usize);
frame.push(self.theme.to_string());
let make_line =
|y: u16| format!("{left}{}", width_str, left = cursor::Goto(self.start.0, y));
for y in 0..self.width { for y in 0..self.width {
frame.push(make_line(self.start.1 + y)); frame.push(self.write_frame_line(y, w_len));
} }
for y in self.width + self.start.1..self.end.1 - self.width { for y in self.width..h_len - self.width {
frame.push(format!( frame.push(format!(
"{left}{body_clear}{frame_theme}{char}{right}{char}", "{left}{body_clear}{frame_theme}{left}{char}{right}{char}",
body_clear = &body_clear, body_clear = &body_clear,
frame_theme = &frame_theme, frame_theme = &frame_theme,
left = cursor::Goto(self.start.0, y), left = self.goto_internal(0, y),
right = cursor::Goto(self.start.0 + self.end.0 - self.width, y), right = self.goto_internal(w_len - self.width, y),
char = FRAME_CHAR_VERTICAL.to_string().repeat(self.width as usize), char = FRAME_CHAR_VERTICAL
.to_string()
.repeat(self.width as usize),
)); ));
} }
for y in self.end.1 - self.width - 1..self.end.1 { for y in h_len - self.width..h_len {
frame.push(make_line(self.start.1 + y)); frame.push(self.write_frame_line(y, w_len));
} }
frame.push(make_line(self.end.1)); frame.push(self.goto(0, 0));
frame.push(self.goto(1, 1));
frame.push(body_theme); frame.push(body_theme);
frame.concat() frame.concat()
} }
@ -123,22 +175,32 @@ impl Frame {
impl FrameSize { impl FrameSize {
#[inline(always)] #[inline(always)]
fn abs_size(&self, theme: Theme, width: u16) -> Frame { fn abs_size(
let (term_height, term_width) = &self,
termion::terminal_size().expect("could not get terminal size"); (term_width, term_height): (u16, u16),
theme: ColorSet,
width: u16,
) -> Frame {
let pos = match self { let pos = match self {
FrameSize::ByAbsolute(h, w) => ((*h).min(term_height), (*w).min(term_width)), FrameSize::ByAbsolute(h, w) => {
((*w).min(term_width), (*h).min(term_height))
}
FrameSize::ByPercent(h, w) => { FrameSize::ByPercent(h, w) => {
// term_height = 100% // term_height = 100%
// x = h% // x = h%
// //
// term_height * h / 100 = x // term_height * h / 100 = x
( (
term_height * (*h).min(100) / 100,
term_width * (*w).min(100) / 100, term_width * (*w).min(100) / 100,
term_height * (*h).min(100) / 100,
) )
} }
}; };
Frame::from_bottom_right(pos, (term_height, term_width), width, theme) Frame::from_bottom_right(
pos,
(term_width, term_height),
width,
theme,
)
} }
} }

View File

@ -8,47 +8,74 @@ use tokio::sync::mpsc::{self, Receiver, Sender};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tokio::time::Instant; use tokio::time::Instant;
use self::body::Body; use self::body::{Body, SigninPage};
use self::frame::Frame; use self::frame::Frame;
use self::theme::{Color, Theme}; use self::theme::{Color, Theme};
pub mod body; pub mod body;
pub mod debug;
pub mod frame; pub mod frame;
pub mod theme; pub mod theme;
type Result<T> = std::result::Result<T, anyhow::Error>; type Result<T> = std::result::Result<T, anyhow::Error>;
pub struct Screen { pub struct Screen {
screen: Mutex<RawTerminal<Stdout>>, screen: Mutex<RawTerminal<Stdout>>,
events_ch: Receiver<Event>, events_ch: Receiver<KeyEvent>,
body: Body, body: Body,
last_interrupt: Option<Instant>, last_interrupt: Option<Instant>,
theme: Theme, theme: Theme,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum Event { enum KeyEvent {
Key(Key), Key(Key),
Interrupt, Interrupt,
} }
impl Display for Event { #[derive(Debug)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { pub enum Event {
match self { Key(Key),
Event::Key(key) => write!(f, "{}", format!("{:#?}", key).replace('\n', "\r\n")), }
Event::Interrupt => write!(f, "Interrupt"),
impl From<KeyEvent> for Event {
fn from(value: KeyEvent) -> Self {
match value {
KeyEvent::Key(key) => Event::Key(key),
_ => panic!(
"FIXME: this event should be handled outside of children"
),
} }
} }
} }
impl From<Key> for Event { impl Display for Event {
fn fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match self {
Event::Key(key) => {
write!(
f,
"{}",
format!("{:#?}", key)
)
}
}
}
}
impl From<Key> for KeyEvent {
fn from(value: Key) -> Self { fn from(value: Key) -> Self {
Event::Key(value) KeyEvent::Key(value)
} }
} }
pub trait Page { pub trait Page {
fn init(&self, env: Environment, screen: &mut RawTerminal<Stdout>) -> Result<()>; fn init(
&mut self,
env: Environment,
screen: &mut RawTerminal<Stdout>,
) -> Result<()>;
fn receive_event( fn receive_event(
&mut self, &mut self,
env: Environment, env: Environment,
@ -65,11 +92,7 @@ pub struct Environment {
impl Into<String> for Environment { impl Into<String> for Environment {
fn into(self) -> String { fn into(self) -> String {
format!( self.frame.frame_str(self.theme.primary())
"{frame}{theme}",
frame = self.frame.frame_str(self.theme.primary()),
theme = self.theme.primary(),
)
} }
} }
@ -80,7 +103,8 @@ impl Environment {
.unwrap_or(1); .unwrap_or(1);
Self { Self {
theme, theme,
frame: Frame::from_terminal_size(theme, w).expect("could not get terminal size"), frame: Frame::from_terminal_size(theme.colors.frame, w)
.expect("could not get terminal size"),
} }
} }
@ -93,10 +117,10 @@ impl Environment {
impl Screen { impl Screen {
pub fn new(theme: Theme) -> Result<Self> { pub fn new(theme: Theme) -> Result<Self> {
let screen = Mutex::new(io::stdout().into_raw_mode()?); let screen = Mutex::new(io::stdout().into_raw_mode()?);
let (send, recv) = mpsc::channel::<Event>(16); let (send, recv) = mpsc::channel::<KeyEvent>(16);
tokio::spawn(async { Screen::input_loop(send).await }); tokio::spawn(async { Screen::input_loop(send).await });
// let body = Body::Signin(SigninPage::default()); let body = Body::Signin(SigninPage::default());
let body = Body::Echo; // let body = Body::Echo;
Ok(Self { Ok(Self {
screen, screen,
body, body,
@ -115,7 +139,8 @@ impl Screen {
while let Some(ev) = self.events_ch.recv().await { while let Some(ev) = self.events_ch.recv().await {
let mut scr = self.screen.lock().await; let mut scr = self.screen.lock().await;
if let Event::Interrupt = ev { match ev {
KeyEvent::Interrupt => {
if let Some(last) = self.last_interrupt { if let Some(last) = self.last_interrupt {
if last.elapsed().as_millis() < 500 { if last.elapsed().as_millis() < 500 {
self.events_ch.close(); self.events_ch.close();
@ -124,7 +149,12 @@ impl Screen {
} }
self.last_interrupt = Some(Instant::now()); self.last_interrupt = Some(Instant::now());
} }
self.body.receive_event(env, &mut scr, ev)?; KeyEvent::Key(key) => self.body.receive_event(
env,
&mut scr,
Event::Key(key),
)?,
}
} }
// Cleanup // Cleanup
@ -142,13 +172,13 @@ impl Screen {
Ok(()) Ok(())
} }
async fn input_loop(snd: Sender<Event>) { async fn input_loop(snd: Sender<KeyEvent>) {
let input = io::stdin(); let input = io::stdin();
for key in input.keys() { for key in input.keys() {
let event = match key.unwrap() { let event = match key.unwrap() {
Key::Ctrl(sub) => match sub { Key::Ctrl(sub) => match sub {
'c' => Event::Interrupt, 'c' => KeyEvent::Interrupt,
key => Event::Key(Key::Ctrl(key)), key => KeyEvent::Key(Key::Ctrl(key)),
}, },
key => key.into(), key => key.into(),
}; };

View File

@ -13,6 +13,15 @@ pub struct ColorSet {
pub bg: Color, pub bg: Color,
} }
impl Default for ColorSet {
fn default() -> Self {
Self {
fg: "#ffffff".into(),
bg: "#000000".into(),
}
}
}
impl ToString for ColorSet { impl ToString for ColorSet {
#[inline(always)] #[inline(always)]
fn to_string(&self) -> String { fn to_string(&self) -> String {
@ -24,6 +33,7 @@ impl ToString for ColorSet {
pub struct Colors { pub struct Colors {
pub primary: ColorSet, pub primary: ColorSet,
pub frame: ColorSet, pub frame: ColorSet,
pub subwin: ColorSet,
} }
impl Theme {} impl Theme {}
@ -54,9 +64,12 @@ impl From<&str> for Color {
i = 1; i = 1;
} }
Self(color::Rgb( Self(color::Rgb(
u8::from_str_radix(&value[i..i + 2], 16).expect("red hex"), u8::from_str_radix(&value[i..i + 2], 16)
u8::from_str_radix(&value[i + 2..i + 4], 16).expect("green hex"), .expect("red hex"),
u8::from_str_radix(&value[i + 4..i + 6], 16).expect("blue hex"), u8::from_str_radix(&value[i + 2..i + 4], 16)
.expect("green hex"),
u8::from_str_radix(&value[i + 4..i + 6], 16)
.expect("blue hex"),
)) ))
} }
} }
@ -73,6 +86,7 @@ impl Default for Theme {
fg: "#ffe6ff".into(), fg: "#ffe6ff".into(),
bg: "#330033".into(), bg: "#330033".into(),
}, },
subwin: ColorSet{ fg: "#ffe6ff".into(), bg: "#110011".into() },
}, },
} }
} }