finished body/frame refactoring
This commit is contained in:
		
							parent
							
								
									99808f3d90
								
							
						
					
					
						commit
						ac9e337e73
					
				|  | @ -3,7 +3,7 @@ use std::io::{Stdout, Write}; | |||
| use termion::{clear, cursor, raw::RawTerminal}; | ||||
| 
 | ||||
| use super::{ | ||||
|     compose::{Component, Components}, | ||||
|     compose::{Component, Components, HorizontalAlignment, Text}, | ||||
|     frame::Frame, | ||||
|     theme::ColorSet, | ||||
|     Environment, Event, Page, | ||||
|  | @ -23,16 +23,14 @@ impl Body { | |||
|         screen: &mut RawTerminal<Stdout>, | ||||
|         event: Event, | ||||
|     ) -> Result<()> { | ||||
|         let event = format!("{}", event); | ||||
|         write!( | ||||
|             screen, | ||||
|             "{theme}{content}", | ||||
|             theme = env.theme.primary(), | ||||
|             content = env | ||||
|                 .frame | ||||
|                 .writeln(&format!("Event: {}", event), 0, 0) | ||||
|                 .concat_for(&env.frame), | ||||
|         )?; | ||||
|         let echo_comps = | ||||
|             Components::break_into(format!("Event: {}", event)) | ||||
|                 .nextline_after_text( | ||||
|                     0, | ||||
|                     HorizontalAlignment::Left, | ||||
|                     &env.frame, | ||||
|                 ); | ||||
|         write!(screen, "{}", env.frame.make(echo_comps))?; | ||||
|         screen.flush()?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | @ -112,43 +110,76 @@ impl SigninPage { | |||
|         const HOSTNAME_Y: u16 = 3; | ||||
|         const OK_Y: u16 = 5; | ||||
|         let frame = self.frame.unwrap(); | ||||
|         let fr_width = frame.size().0; | ||||
|         let total_width = fr_width / 3; | ||||
|         let (w, _) = frame.inner_size(); | ||||
|         let exp_w = w / 3; | ||||
| 
 | ||||
|         todo!() | ||||
|         // frame
 | ||||
|         //     .write_centered("login kk", HEADER_Y)
 | ||||
|         //     .into_iter()
 | ||||
|         //     .chain(
 | ||||
|         //         frame
 | ||||
|         //             .write_centered(
 | ||||
|         //                 &self.field_str(
 | ||||
|         //                     highlight,
 | ||||
|         //                     total_width,
 | ||||
|         //                     "hostname",
 | ||||
|         //                     "",
 | ||||
|         //                 ),
 | ||||
|         //                 HOSTNAME_Y,
 | ||||
|         //             )
 | ||||
|         //             .into_iter(),
 | ||||
|         //     )
 | ||||
|         //     .chain(vec![Component::Theme(normal)].into_iter())
 | ||||
|         //     .chain(frame.write_centered("[ok]", OK_Y).into_iter())
 | ||||
|         //     .collect::<Vec<Component>>()
 | ||||
|         //     .into()
 | ||||
|         vec![ | ||||
|             frame | ||||
|                 .prefix_centered_goto( | ||||
|                     vec![Component::String(Text::Normal( | ||||
|                         "login kk".into(), | ||||
|                     ))] | ||||
|                     .into(), | ||||
|                     HEADER_Y, | ||||
|                 ) | ||||
|                 .0, | ||||
|             frame | ||||
|                 .prefix_centered_goto( | ||||
|                     self.field( | ||||
|                         highlight, | ||||
|                         normal, | ||||
|                         exp_w, | ||||
|                         "hostname", | ||||
|                         &self.hostname, | ||||
|                     ), | ||||
|                     HOSTNAME_Y, | ||||
|                 ) | ||||
|                 .0, | ||||
|             frame | ||||
|                 .prefix_centered_goto( | ||||
|                     self.field( | ||||
|                         highlight, | ||||
|                         normal, | ||||
|                         exp_w, | ||||
|                         "username", | ||||
|                         &self.username, | ||||
|                     ), | ||||
|                     OK_Y, | ||||
|                 ) | ||||
|                 .0, | ||||
|         ] | ||||
|         .into_iter() | ||||
|         .flatten() | ||||
|         .collect::<Vec<Component>>() | ||||
|         .into() | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     fn field_str( | ||||
|     fn field<C>( | ||||
|         &self, | ||||
|         color: ColorSet, | ||||
|         width: u16, | ||||
|         field: &str, | ||||
|         field_content: &str, | ||||
|     ) -> String { | ||||
|         let unpadded = format!("{field}: {field_content}",); | ||||
|         let len = field.len() + field_content.len() + 2; | ||||
|         unpadded + &"_".repeat(width as usize - len) | ||||
|         normal: ColorSet, | ||||
|         exp_width: u16, | ||||
|         field: C, | ||||
|         field_content: C, | ||||
|     ) -> Components | ||||
|     where | ||||
|         C: Into<String>, | ||||
|     { | ||||
|         let (field, field_content) = | ||||
|             (field.into(), field_content.into()); | ||||
|         let budget_for_content = exp_width - field.len() as u16 - 3; | ||||
|         vec![ | ||||
|             Component::String((field + &": ").into()), | ||||
|             Component::Theme(color), | ||||
|             Component::String(Text::LimitedOrPadded( | ||||
|                 field_content, | ||||
|                 '_', | ||||
|                 budget_for_content, | ||||
|             )), | ||||
|             Component::Theme(normal), | ||||
|         ] | ||||
|         .into() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -197,11 +228,10 @@ impl Page for SigninPage { | |||
|                 .unwrap() | ||||
|                 .frame_str(env.theme.colors.subwin), | ||||
|             hide_cursor = cursor::Hide, | ||||
|             content = "", | ||||
|             // content = self.draw(
 | ||||
|             //     env.theme.colors.highlight,
 | ||||
|             //     env.theme.colors.subwin
 | ||||
|             // ),
 | ||||
|             content = self.frame.unwrap().make(self.draw( | ||||
|                 env.theme.colors.highlight, | ||||
|                 env.theme.colors.subwin | ||||
|             )) | ||||
|         )?; | ||||
|         screen.flush()?; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,14 +1,121 @@ | |||
| use std::{ | ||||
|     iter::Map, | ||||
|     ops::{Deref, Range}, | ||||
|     vec::IntoIter, | ||||
| }; | ||||
| 
 | ||||
| use super::{frame::Frame, theme::ColorSet}; | ||||
| use std::ops::Deref; | ||||
| 
 | ||||
| const ELLIPSES: char = '…'; | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| pub enum Text { | ||||
|     // Unrestricted
 | ||||
|     Normal(String), | ||||
|     // To length
 | ||||
|     Limited(String, u16), | ||||
|     // Limit or pad to length
 | ||||
|     LimitedOrPadded(String, char, u16), | ||||
|     PaddedBothSides(u16, String, u16), | ||||
| } | ||||
| 
 | ||||
| impl From<String> for Text { | ||||
|     fn from(value: String) -> Self { | ||||
|         Self::Normal(value) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Text { | ||||
|     #[inline] | ||||
|     pub fn limit(self, limit: u16) -> Text { | ||||
|         match self { | ||||
|             Self::Normal(s) => Self::Limited(s, limit), | ||||
|             Self::Limited(s, _) => Self::Limited(s, limit), | ||||
|             Self::LimitedOrPadded(s, c, _) => { | ||||
|                 Self::LimitedOrPadded(s, c, limit) | ||||
|             } | ||||
|             Self::PaddedBothSides(left, s, right) => todo!(), | ||||
|         } | ||||
|     } | ||||
|     #[inline] | ||||
|     pub fn len(&self) -> u16 { | ||||
|         match self { | ||||
|             Text::Normal(s) => s.len() as u16, | ||||
|             Text::Limited(s, max) => (*max).min(s.len() as u16), | ||||
|             Text::LimitedOrPadded(_, _, len) => *len, | ||||
|             Text::PaddedBothSides(left, s, right) => { | ||||
|                 left + s.len() as u16 + right | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     #[inline] | ||||
|     fn conditional_truncate(s: String, len: u16) -> String { | ||||
|         let mut s = s; | ||||
|         if s.len() > len as usize { | ||||
|             s.truncate(len as usize - 1); | ||||
|             s.push(ELLIPSES); | ||||
|         } | ||||
|         s | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     pub fn make(self, frame: &Frame) -> String { | ||||
|         match self { | ||||
|             Text::Normal(s) => s, | ||||
|             Text::Limited(s, len) => { | ||||
|                 Self::conditional_truncate(s, len) | ||||
|             } | ||||
|             Text::LimitedOrPadded(s, pad, len) => { | ||||
|                 let s = Self::conditional_truncate(s, len); | ||||
|                 let sln = s.len(); | ||||
|                 s + &pad.to_string().repeat(len as usize - sln) | ||||
|             } | ||||
|             Text::PaddedBothSides(left, s, right) => { | ||||
|                 format!( | ||||
|                     "{}{}{}", | ||||
|                     " ".repeat(left as usize), | ||||
|                     s, | ||||
|                     " ".repeat(right as usize) | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Copy)] | ||||
| pub enum HorizontalAlignment { | ||||
|     Left, | ||||
|     Right, | ||||
|     Center, | ||||
| } | ||||
| 
 | ||||
| impl HorizontalAlignment { | ||||
|     pub fn align( | ||||
|         &self, | ||||
|         s: Text, | ||||
|         frame: &Frame, | ||||
|         y: u16, | ||||
|     ) -> Components { | ||||
|         let (width, _) = frame.size(); | ||||
|         match self { | ||||
|             Self::Left => { | ||||
|                 vec![Component::Goto(0, y), Component::String(s)] | ||||
|             } | ||||
|             Self::Right => { | ||||
|                 vec![ | ||||
|                     Component::Goto(s.len() as u16 - width, y), | ||||
|                     Component::String(s), | ||||
|                 ] | ||||
|             } | ||||
|             Self::Center => { | ||||
|                 vec![ | ||||
|                     Component::Goto((width - s.len()) / 2, y), | ||||
|                     Component::String(s), | ||||
|                 ] | ||||
|             } | ||||
|         } | ||||
|         .into() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| pub enum Component { | ||||
|     String(String), | ||||
|     String(Text), | ||||
|     Goto(u16, u16), | ||||
|     Theme(ColorSet), | ||||
|     Padding(u16), | ||||
|  | @ -19,9 +126,9 @@ pub enum Component { | |||
| } | ||||
| 
 | ||||
| impl Component { | ||||
|     pub fn prepare_for(self, frame: &Frame) -> String { | ||||
|     pub fn make(self, frame: &Frame) -> String { | ||||
|         match self { | ||||
|             Component::String(s) => s, | ||||
|             Component::String(s) => s.make(frame), | ||||
|             Component::Goto(x, y) => frame.goto(x, y), | ||||
|             Component::Theme(c) => c.to_string(), | ||||
|             Component::Clear => termion::clear::All.to_string(), | ||||
|  | @ -58,27 +165,48 @@ impl From<Component> for Components { | |||
| } | ||||
| 
 | ||||
| impl Components { | ||||
|     // Consumes itself, returns a set of new components
 | ||||
|     // with gotos for the following line, starting at y (not y+1)
 | ||||
|     // if there's a goto, the goto is used as the last y.
 | ||||
|     // also applies alignment, relative to the given frame
 | ||||
|     pub fn nextline_after_text( | ||||
|         self, | ||||
|         y: u16, | ||||
|         align: HorizontalAlignment, | ||||
|         frame: &Frame, | ||||
|     ) -> Self { | ||||
|         let mut last_y = y; | ||||
|         let xxx = self.0.into_iter().map(|comp| match comp { | ||||
|             Component::String(s) => { | ||||
|                 last_y += 1; | ||||
|                 align.align(s, frame, last_y).0 | ||||
|             } | ||||
|             Component::Goto(_, y) => { | ||||
|                 last_y = y; | ||||
|                 vec![comp] | ||||
|             } | ||||
|             _ => vec![comp], | ||||
|         }); | ||||
|         todo!() | ||||
|     } | ||||
| 
 | ||||
|     pub fn break_into(s: String) -> Components { | ||||
|         s.split('\n') | ||||
|             .map(|pt| Component::String(Text::Normal(pt.into()))) | ||||
|             .collect::<Vec<Component>>() | ||||
|             .into() | ||||
|     } | ||||
| 
 | ||||
|     pub fn str_len(&self) -> u16 { | ||||
|         (&self.0) | ||||
|             .into_iter() | ||||
|             .map(|comp| { | ||||
|                 if let Component::String(s) = comp { | ||||
|                     s.len() as u16 | ||||
|                     s.len() | ||||
|                 } else { | ||||
|                     0 | ||||
|                 } | ||||
|             }) | ||||
|             .sum() | ||||
|     } | ||||
| 
 | ||||
|     pub fn concat_for(self, frame: &Frame) -> String { | ||||
|         self.0 | ||||
|             .into_iter() | ||||
|             .map(|component| component.prepare_for(frame)) | ||||
|             .collect::<String>() | ||||
|     } | ||||
| 
 | ||||
|     pub fn push(&mut self, comp: Component) { | ||||
|         self.0.push(comp) | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,15 +1,12 @@ | |||
| use termion::{clear, cursor, raw::RawTerminal}; | ||||
| use termion::cursor; | ||||
| 
 | ||||
| const ESTIMATED_FRAME_BIT_SIZE: usize = 11; | ||||
| use std::{ | ||||
|     io::{Stdout, Write}, | ||||
|     slice::Iter, | ||||
|     vec::IntoIter, | ||||
| }; | ||||
| 
 | ||||
| use crate::display::compose::Text; | ||||
| 
 | ||||
| use super::{ | ||||
|     compose::{Component, Components}, | ||||
|     theme::{ColorSet, Theme}, | ||||
|     theme::ColorSet, | ||||
| }; | ||||
| 
 | ||||
| const FRAME_CHAR_HORIZONTAL: char = ' '; | ||||
|  | @ -60,6 +57,20 @@ impl Frame { | |||
|         .into() | ||||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|     pub fn prefix_centered_goto( | ||||
|         &self, | ||||
|         comp: Components, | ||||
|         line_num: u16, | ||||
|     ) -> Components { | ||||
|         let x = (self.inner_size().0 - comp.str_len()) / 2; | ||||
|         vec![Component::Goto(x, line_num)] | ||||
|             .into_iter() | ||||
|             .chain(comp.0.into_iter()) | ||||
|             .collect::<Vec<Component>>() | ||||
|             .into() | ||||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|     fn goto_internal(&self, x: u16, y: u16) -> Component { | ||||
|         Component::Internal( | ||||
|  | @ -73,7 +84,7 @@ impl Frame { | |||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|     fn write_centered_clear(&self, s: &str) -> Components { | ||||
|     fn write_centered_clear(&self, s: &str) -> Component { | ||||
|         let width = self.size().0; | ||||
|         let limit = width - self.border * 2; | ||||
|         let s = if s.len() > limit as usize { | ||||
|  | @ -83,12 +94,11 @@ impl Frame { | |||
|         }; | ||||
|         let len = s.len() as u16; | ||||
|         let base_size = ((width - len) / 2) - self.border; | ||||
|         vec![ | ||||
|             Component::Padding(base_size + ((width - len) % 2)), | ||||
|             Component::String(s.into()), | ||||
|             Component::Padding(base_size), | ||||
|         ] | ||||
|         .into() | ||||
|         Component::String(super::compose::Text::PaddedBothSides( | ||||
|             base_size + ((width - len) % 2), | ||||
|             s.into(), | ||||
|             base_size, | ||||
|         )) | ||||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|  | @ -100,58 +110,39 @@ impl Frame { | |||
|         .into() | ||||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|     fn write_clear_to_end(&self, s: &str) -> Components { | ||||
|         let limit = (self.size().0 - self.border * 2) as usize; | ||||
|         let s = if s.len() > limit { &s[..limit] } else { s }; | ||||
|     #[inline] | ||||
|     pub fn to_end(&self, s: &str, x: u16, y: u16) -> Components { | ||||
|         // FIXME: handle the fucking newlines grrrr i hate this
 | ||||
|         let width = self.inner_size().0; | ||||
|         vec![ | ||||
|             Component::String(s.into()), | ||||
|             Component::Padding( | ||||
|                 self.size().0 - s.len() as u16 - (self.border * 2), | ||||
|             ), | ||||
|             Component::Goto(x, y), | ||||
|             Component::String(Text::LimitedOrPadded( | ||||
|                 s.into(), | ||||
|                 ' ', | ||||
|                 width - x, | ||||
|             )), | ||||
|         ] | ||||
|         .into() | ||||
|     } | ||||
| 
 | ||||
|     pub fn writeln(&self, s: &str, x: u16, y: u16) -> Components { | ||||
|         let mut index: u16 = 0; | ||||
|         s.split('\n') | ||||
|             .flat_map(|wrd| { | ||||
|                 index += 1; | ||||
|                 vec![ | ||||
|                     vec![Component::Goto(x, y + index - 1)], | ||||
|                     self.write_clear_to_end(wrd).0, | ||||
|                 ] | ||||
|             }) | ||||
|             .flatten() | ||||
|             .collect::<Vec<Component>>() | ||||
|             .into() | ||||
|     } | ||||
| 
 | ||||
|     pub fn write_centered(&self, s: &str, y: u16) -> Components { | ||||
|         let mut index: u16 = 0; | ||||
|         s.split('\n') | ||||
|             .flat_map(|wrd| { | ||||
|                 index += 1; | ||||
|                 vec![ | ||||
|                     vec![Component::Goto(0, y + index - 1)], | ||||
|                     self.write_centered_clear(wrd).0, | ||||
|                 ] | ||||
|             }) | ||||
|             .flatten() | ||||
|             .collect::<Vec<Component>>() | ||||
|             .into() | ||||
|     pub fn make(&self, comp: Components) -> String { | ||||
|         comp.0.into_iter().map(|c| c.make(self)).collect() | ||||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|     pub fn size(&self) -> (u16, u16) { | ||||
|         eprintln!( | ||||
|             "end.0: {}, start.0: {}, end.1: {}, start.1: {}", | ||||
|             self.end.0, self.start.0, self.end.1, self.start.1 | ||||
|         ); | ||||
|         (self.end.0 - self.start.0, self.end.1 - self.start.1) | ||||
|     } | ||||
| 
 | ||||
|     // Size within the borders
 | ||||
|     #[inline(always)] | ||||
|     pub fn inner_size(&self) -> (u16, u16) { | ||||
|         ( | ||||
|             self.end.0 - self.start.0 - self.border * 2, | ||||
|             self.end.1 - self.start.1 - self.border * 2, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|     pub fn from_terminal_size( | ||||
|         theme: ColorSet, | ||||
|  | @ -225,8 +216,9 @@ impl Frame { | |||
|         frame | ||||
|     } | ||||
| 
 | ||||
|     #[inline(always)] | ||||
|     pub fn frame_str(&self, body_theme: ColorSet) -> String { | ||||
|         self.compose(body_theme).concat_for(self) | ||||
|         self.make(self.compose(body_theme)) | ||||
|     } | ||||
| 
 | ||||
|     // compose generates a collection of components for drawing
 | ||||
|  | @ -240,13 +232,10 @@ impl Frame { | |||
|         let (w_len, h_len) = self.size(); | ||||
|         let frame_theme = Component::Internal(self.theme.to_string()); | ||||
| 
 | ||||
|         let body_clear = Component::Internal( | ||||
|             Into::<Components>::into(vec![ | ||||
|                 body_theme.clone(), | ||||
|                 Component::Padding(w_len), | ||||
|             ]) | ||||
|             .concat_for(self), | ||||
|         ); | ||||
|         let body_clear = | ||||
|             Component::Internal(self.make(Into::<Components>::into( | ||||
|                 vec![body_theme.clone(), Component::Padding(w_len)], | ||||
|             ))); | ||||
| 
 | ||||
|         let border = | ||||
|             Component::Repeated(FRAME_CHAR_VERTICAL, self.border); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue