From c70ecf5fbef7755ca61b2ec78107d25299aaaaef Mon Sep 17 00:00:00 2001 From: emilis Date: Sat, 14 Jan 2023 23:07:07 +0000 Subject: [PATCH] removed debug!, made subframes work --- rustfmt.toml | 1 + src/display/body.rs | 175 ++++++++++++++++++++++++++++--------------- src/display/debug.rs | 15 ---- src/display/frame.rs | 134 ++++++++++++++++++++++++--------- src/display/mod.rs | 92 +++++++++++++++-------- src/display/theme.rs | 20 ++++- 6 files changed, 292 insertions(+), 145 deletions(-) create mode 100644 rustfmt.toml delete mode 100644 src/display/debug.rs diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..0c3b24b --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 70 \ No newline at end of file diff --git a/src/display/body.rs b/src/display/body.rs index 90c7f50..80a4438 100644 --- a/src/display/body.rs +++ b/src/display/body.rs @@ -2,25 +2,48 @@ use std::io::{Stdout, Write}; use termion::{clear, cursor, raw::RawTerminal}; -use super::{Environment, Event, Page}; +use super::{ + frame::Frame, theme::ColorSet, Environment, Event, Page, +}; type Result = std::result::Result; #[derive(Debug, Clone)] pub enum Body { Echo, - // Signin(SigninPage), + Signin(SigninPage), } impl Body { - fn echo(env: Environment, screen: &mut RawTerminal, event: Event) -> Result<()> { + fn echo( + env: Environment, + screen: &mut RawTerminal, + event: Event, + ) -> Result<()> { let event = format!("{}", event); write!( 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, + ) -> Result<()> { + write!( + screen, + "{hide}{clear}{fr}{cursor}", clear = clear::All, - frame = env.frame.frame_str(env.theme.primary()), - content = env.frame.writeln(&format!("Event: {}", event), 0, 0), + hide = cursor::Hide, + fr = env.frame.frame_str(env.theme.primary()), + cursor = env.frame.goto(0, 0), )?; screen.flush()?; Ok(()) @@ -35,71 +58,103 @@ impl Page for Body { event: Event, ) -> Result<()> { 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)?, }; Ok(()) } - fn init(&self, env: Environment, screen: &mut RawTerminal) -> Result<()> { - write!( - screen, - "{hide}{clear}{fr}{cursor}", - hide = cursor::Hide, - fr = env.frame.frame_str(env.theme.primary()), - cursor = env.frame.goto(0, 0), - clear = clear::All - )?; - screen.flush()?; - Ok(()) + fn init( + &mut self, + env: Environment, + screen: &mut RawTerminal, + ) -> Result<()> { + match self { + Body::Echo => self.echo_init(env, screen), + Body::Signin(signin) => signin.init(env, screen), + } } } -// #[derive(Debug, Clone, Default)] -// struct SigninPage { -// hostname: String, -// username: String, -// cursor: SigninCursorLocation, -// } +#[derive(Debug, Clone, Default)] +pub struct SigninPage { + hostname: String, + username: String, + cursor: SigninCursorLocation, + cursor_position: u16, + frame: Option, +} -// #[derive(Debug, Clone)] -// enum SigninCursorLocation { -// Hostname, -// Username, -// Next, -// } +#[derive(Debug, Clone)] +enum SigninCursorLocation { + Hostname, + Username, + Next, +} -// impl Default for SigninCursorLocation { -// fn default() -> Self { -// Self::Hostname -// } -// } +impl Default for SigninCursorLocation { + fn default() -> Self { + Self::Hostname + } +} -// impl SigninPage { -// fn frame_string() -> String { -// format!("") -// } -// } +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, -// event: Event, -// ) -> Result<()> { -// Ok(()) -// } +impl Page for SigninPage { + fn receive_event( + &mut self, + env: Environment, + screen: &mut RawTerminal, + 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 => {} + // + _ => {} + }, + } -// fn init(&self, env: Environment, screen: &mut RawTerminal) -> Result<()> { -// write!( -// screen, -// "{frame}{hide_cursor}", -// frame = env.frame.frame_str(&env.theme, env.theme.primary()), -// hide_cursor = cursor::Hide, -// )?; -// screen.flush()?; + Ok(()) + } -// Ok(()) -// } -// } + fn init( + &mut self, + env: Environment, + screen: &mut RawTerminal, + ) -> Result<()> { + self.frame = Some(env.frame.sub( + env.theme.colors.subwin, + super::frame::FrameSize::ByPercent(80, 80), + 2, + )); + write!( + screen, + "{theme}{clear}{frame}{subframe}{hide_cursor}{content}", + theme = env.theme.frame(), + clear = clear::All, + frame = env.frame.frame_str(env.theme.primary()), + subframe = self + .frame + .unwrap() + .frame_str(ColorSet::default().to_string()), + hide_cursor = cursor::Hide, + content = self.draw(), + )?; + screen.flush()?; + + Ok(()) + } +} diff --git a/src/display/debug.rs b/src/display/debug.rs deleted file mode 100644 index 40c0520..0000000 --- a/src/display/debug.rs +++ /dev/null @@ -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; diff --git a/src/display/frame.rs b/src/display/frame.rs index a62fae3..012b2e5 100644 --- a/src/display/frame.rs +++ b/src/display/frame.rs @@ -1,11 +1,9 @@ -use termion::{clear, cursor}; +use termion::{clear, cursor, raw::RawTerminal}; const ESTIMATED_FRAME_BIT_SIZE: usize = 11; -use std::io::Write; +use std::io::{Stdout, Write}; -use crate::display::debug::debug; - -use super::theme::Theme; +use super::theme::{ColorSet, Theme}; const FRAME_CHAR_HORIZONTAL: char = ' '; const FRAME_CHAR_VERTICAL: char = ' '; @@ -21,15 +19,32 @@ pub struct Frame { start: (u16, u16), end: (u16, u16), width: u16, - theme: Theme, + theme: ColorSet, } 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)] pub fn goto(&self, x: u16, y: u16) -> String { let cg = cursor::Goto( - (self.width + self.start.0 + x).min(self.end.0 - self.width), - (self.width + self.start.1 + y).min(self.end.1 - self.width), + self.width.max( + (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() } @@ -43,38 +58,73 @@ impl Frame { .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 { let words = s.split('\n').collect::>(); let mut out_words = Vec::with_capacity(words.len()); for i in 0..words.len() { out_words.push(format!( "{ret}{str}", - str = words[i], + str = self.write_clear_to_end(words[i]), ret = self.goto(x, y + i as u16), )); } 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::>(); + 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)] pub fn size(&self) -> (u16, u16) { (self.end.0 - self.start.0, self.end.1 - self.start.1) } #[inline(always)] - pub fn from_terminal_size(theme: Theme, width: u16) -> Result { + pub fn from_terminal_size( + theme: ColorSet, + width: u16, + ) -> Result { let term = termion::terminal_size()?; // 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)) } #[inline(always)] fn from_bottom_right( - (pos_h, pos_w): (u16, u16), - (term_h, term_w): (u16, u16), + (pos_w, pos_h): (u16, u16), + (term_w, term_h): (u16, u16), width: u16, - theme: Theme, + theme: ColorSet, ) -> Self { Self { start: (1.max(term_w - pos_w), 1.max(term_h - pos_h)), @@ -90,32 +140,34 @@ impl Frame { } self.goto_internal(0, 0); 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 frame_theme = self.theme.frame(); - let body_clear = body_theme.clone() + &clear::CurrentLine.to_string(); - frame.push(frame_theme.clone()); + let frame_theme = self.theme.to_string(); + let body_clear = + 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 { - 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!( - "{left}{body_clear}{frame_theme}{char}{right}{char}", + "{left}{body_clear}{frame_theme}{left}{char}{right}{char}", body_clear = &body_clear, frame_theme = &frame_theme, - left = cursor::Goto(self.start.0, y), - right = cursor::Goto(self.start.0 + self.end.0 - self.width, y), - char = FRAME_CHAR_VERTICAL.to_string().repeat(self.width as usize), + left = self.goto_internal(0, y), + right = self.goto_internal(w_len - self.width, y), + char = FRAME_CHAR_VERTICAL + .to_string() + .repeat(self.width as usize), )); } - for y in self.end.1 - self.width - 1..self.end.1 { - frame.push(make_line(self.start.1 + y)); + for y in h_len - self.width..h_len { + frame.push(self.write_frame_line(y, w_len)); } - frame.push(make_line(self.end.1)); - frame.push(self.goto(1, 1)); + frame.push(self.goto(0, 0)); frame.push(body_theme); frame.concat() } @@ -123,22 +175,32 @@ impl Frame { impl FrameSize { #[inline(always)] - fn abs_size(&self, theme: Theme, width: u16) -> Frame { - let (term_height, term_width) = - termion::terminal_size().expect("could not get terminal size"); + fn abs_size( + &self, + (term_width, term_height): (u16, u16), + theme: ColorSet, + width: u16, + ) -> Frame { 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) => { // term_height = 100% // x = h% // // term_height * h / 100 = x ( - term_height * (*h).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, + ) } } diff --git a/src/display/mod.rs b/src/display/mod.rs index 6e41fde..50e3ab0 100644 --- a/src/display/mod.rs +++ b/src/display/mod.rs @@ -8,47 +8,74 @@ use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::sync::Mutex; use tokio::time::Instant; -use self::body::Body; +use self::body::{Body, SigninPage}; use self::frame::Frame; use self::theme::{Color, Theme}; pub mod body; -pub mod debug; pub mod frame; pub mod theme; type Result = std::result::Result; pub struct Screen { screen: Mutex>, - events_ch: Receiver, + events_ch: Receiver, body: Body, last_interrupt: Option, theme: Theme, } #[derive(Debug)] -pub enum Event { +enum KeyEvent { Key(Key), Interrupt, } -impl Display for Event { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Event::Key(key) => write!(f, "{}", format!("{:#?}", key).replace('\n', "\r\n")), - Event::Interrupt => write!(f, "Interrupt"), +#[derive(Debug)] +pub enum Event { + Key(Key), +} + +impl From 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 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 for KeyEvent { fn from(value: Key) -> Self { - Event::Key(value) + KeyEvent::Key(value) } } pub trait Page { - fn init(&self, env: Environment, screen: &mut RawTerminal) -> Result<()>; + fn init( + &mut self, + env: Environment, + screen: &mut RawTerminal, + ) -> Result<()>; fn receive_event( &mut self, env: Environment, @@ -65,11 +92,7 @@ pub struct Environment { impl Into for Environment { fn into(self) -> String { - format!( - "{frame}{theme}", - frame = self.frame.frame_str(self.theme.primary()), - theme = self.theme.primary(), - ) + self.frame.frame_str(self.theme.primary()) } } @@ -80,7 +103,8 @@ impl Environment { .unwrap_or(1); Self { 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 { pub fn new(theme: Theme) -> Result { let screen = Mutex::new(io::stdout().into_raw_mode()?); - let (send, recv) = mpsc::channel::(16); + let (send, recv) = mpsc::channel::(16); tokio::spawn(async { Screen::input_loop(send).await }); - // let body = Body::Signin(SigninPage::default()); - let body = Body::Echo; + let body = Body::Signin(SigninPage::default()); + // let body = Body::Echo; Ok(Self { screen, body, @@ -115,16 +139,22 @@ impl Screen { while let Some(ev) = self.events_ch.recv().await { let mut scr = self.screen.lock().await; - if let Event::Interrupt = ev { - if let Some(last) = self.last_interrupt { - if last.elapsed().as_millis() < 500 { - self.events_ch.close(); - break; + match ev { + KeyEvent::Interrupt => { + if let Some(last) = self.last_interrupt { + if last.elapsed().as_millis() < 500 { + self.events_ch.close(); + break; + } } + self.last_interrupt = Some(Instant::now()); } - self.last_interrupt = Some(Instant::now()); + KeyEvent::Key(key) => self.body.receive_event( + env, + &mut scr, + Event::Key(key), + )?, } - self.body.receive_event(env, &mut scr, ev)?; } // Cleanup @@ -142,13 +172,13 @@ impl Screen { Ok(()) } - async fn input_loop(snd: Sender) { + async fn input_loop(snd: Sender) { let input = io::stdin(); for key in input.keys() { let event = match key.unwrap() { Key::Ctrl(sub) => match sub { - 'c' => Event::Interrupt, - key => Event::Key(Key::Ctrl(key)), + 'c' => KeyEvent::Interrupt, + key => KeyEvent::Key(Key::Ctrl(key)), }, key => key.into(), }; diff --git a/src/display/theme.rs b/src/display/theme.rs index 1f6378b..562f3f0 100644 --- a/src/display/theme.rs +++ b/src/display/theme.rs @@ -13,6 +13,15 @@ pub struct ColorSet { pub bg: Color, } +impl Default for ColorSet { + fn default() -> Self { + Self { + fg: "#ffffff".into(), + bg: "#000000".into(), + } + } +} + impl ToString for ColorSet { #[inline(always)] fn to_string(&self) -> String { @@ -24,6 +33,7 @@ impl ToString for ColorSet { pub struct Colors { pub primary: ColorSet, pub frame: ColorSet, + pub subwin: ColorSet, } impl Theme {} @@ -54,9 +64,12 @@ impl From<&str> for Color { i = 1; } Self(color::Rgb( - u8::from_str_radix(&value[i..i + 2], 16).expect("red 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"), + u8::from_str_radix(&value[i..i + 2], 16) + .expect("red 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(), bg: "#330033".into(), }, + subwin: ColorSet{ fg: "#ffe6ff".into(), bg: "#110011".into() }, }, } }