diff --git a/src/terminal/components/mod.rs b/src/terminal/components/mod.rs new file mode 100644 index 0000000..3bd63cf --- /dev/null +++ b/src/terminal/components/mod.rs @@ -0,0 +1,2 @@ +pub mod statusbar; +pub mod welcome; \ No newline at end of file diff --git a/src/terminal/components/statusbar.rs b/src/terminal/components/statusbar.rs new file mode 100644 index 0000000..14ff133 --- /dev/null +++ b/src/terminal/components/statusbar.rs @@ -0,0 +1,55 @@ +use crossterm::style::Stylize; +use crate::core::editor::Editor; +use crate::terminal::screen::{Coords, Screen}; +use crate::terminal::utils; + +pub fn draw(screen: &mut Screen, editor: &Editor) -> Result<(), ()> { + // Calculate where to draw the status bar + let status_height = screen.size.height - 2; + + // Get the editor logo from the config + let editor_logo = &utils::with_spaces(editor.config.logo) as &str; + // Get the current mode into a string + let mode_string = &utils::with_spaces(editor.mode.as_str()) as &str; + // Get the current open file name + let file_name = &utils::with_spaces(editor.buffer.name) as &str; + + // Calculate the total length of all the status bar components + let total_length = editor_logo.len() + mode_string.len() + file_name.len() + 1; + + // If the screen isn't wide enough, panic as we can't draw the status bar + if screen.size.width < total_length { + Err(()) + + } else { + // Write the editor logo + screen.write_at( + editor_logo.yellow().bold().reverse().to_string(), + Coords::from(0, status_height), + ); + + // Calculate where to write the current mode + let x = editor_logo.len() - 1; + // Write the current mode + screen.write_at( + mode_string.green().bold().reverse().to_string(), + Coords::from(x, status_height), + ); + // Calculate where to write the file name + let x = x + mode_string.len(); + // Write the current file name + screen.write_at( + file_name.magenta().bold().reverse().to_string(), + Coords::from(x, status_height), + ); + + // Draw the rest of the status bar + let x = x + file_name.len(); + screen.write_at( + " ".repeat(screen.size.width - x).reverse().to_string(), + Coords::from(x, status_height), + ); + + Ok(()) + } +} \ No newline at end of file diff --git a/src/terminal/components/welcome.rs b/src/terminal/components/welcome.rs new file mode 100644 index 0000000..ac88800 --- /dev/null +++ b/src/terminal/components/welcome.rs @@ -0,0 +1,39 @@ +use crossterm::style::Stylize; +use crate::core::editor::Editor; +use crate::terminal::screen::{Coords, Screen}; +use crate::terminal::utils; + +pub fn draw(screen: &mut Screen, editor: &Editor) { + // The welcome message + let title = format!("{} {}", editor.config.logo, editor.config.friendly_name); + let message: [&str; 5] = [ + "Hackable text editor for nerds", + "", + "Type :help to open the README.md document", + "Type :o to open a file and edit", + "Type :q! or to quit lambda", + ]; + + // If the screen is big enough, we can draw + if screen.size.width > utils::longest_element_in_vec(message.to_vec()) && screen.size.height > message.len() + 4 { + // The starting y position in the centre of the screen + let mut y = (screen.size.height / 2) - (message.len() / 2) - 2; + + // Calculate where to place the title + let x = utils::calc_centred_x(screen.size.width, title.len()); + + // Write the title to the screen + screen.write_at(title.yellow().to_string(), Coords::from(x, y)); + + for line in message { + // Each line has different width so requires a different x position to center it + let x = utils::calc_centred_x(screen.size.width, line.len()) ; + + // For each line we move downwards so increment y + y += 1; + + // Write the line to the screen at position (x, y) + screen.write_at(line.to_string(), Coords::from(x, y)); + } + } +} diff --git a/src/terminal/mod.rs b/src/terminal/mod.rs index a865f33..14a8ff8 100644 --- a/src/terminal/mod.rs +++ b/src/terminal/mod.rs @@ -1,2 +1,4 @@ pub mod screen; pub mod tui; +pub mod utils; +mod components; \ No newline at end of file diff --git a/src/terminal/tui.rs b/src/terminal/tui.rs index a939730..721bcda 100644 --- a/src/terminal/tui.rs +++ b/src/terminal/tui.rs @@ -1,115 +1,19 @@ use crate::core::editor::Editor; -use crate::terminal::screen::{Coords, Screen}; -use crossterm::style::Stylize; +use crate::terminal::screen::Screen; +use crate::terminal::components; use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers}; -// Utils -fn with_spaces(text: &str) -> String { - format!(" {} ", text) -} -fn calc_x(screen_width: usize, item_length: usize) -> usize { - (screen_width / 2) - (item_length / 2) -} -fn longest_element_in_array(elements: Vec<&str>) -> usize { - elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len() -} - -pub fn draw_status(screen: &mut Screen, editor: &Editor) -> Result<(), ()> { - // Calculate where to draw the status bar - let status_height = screen.size.height - 2; - - // Get the editor logo from the config - let editor_logo = &with_spaces(editor.config.logo) as &str; - // Get the current mode into a string - let mode_string = &with_spaces(editor.mode.as_str()) as &str; - // Get the current open file name - let file_name = &with_spaces(editor.buffer.name) as &str; - - // Calculate the total length of all the status bar components - let total_length = editor_logo.len() + mode_string.len() + file_name.len() + 1; - - // If the screen isn't wide enough, panic as we can't draw the status bar - if screen.size.width < total_length { - Err(()) - - } else { - // Write the editor logo - screen.write_at( - editor_logo.yellow().bold().reverse().to_string(), - Coords::from(0, status_height), - ); - - // Calculate where to write the current mode - let x = editor_logo.len() - 1; - // Write the current mode - screen.write_at( - mode_string.green().bold().reverse().to_string(), - Coords::from(x, status_height), - ); - // Calculate where to write the file name - let x = x + mode_string.len(); - // Write the current file name - screen.write_at( - file_name.magenta().bold().reverse().to_string(), - Coords::from(x, status_height), - ); - - // Draw the rest of the status bar - let x = x + file_name.len(); - screen.write_at( - " ".repeat(screen.size.width - x).reverse().to_string(), - Coords::from(x, status_height), - ); - - Ok(()) - } -} - -pub fn draw_welcome(screen: &mut Screen, editor: &Editor) { - // The welcome message - let title = format!("{} {}", editor.config.logo, editor.config.friendly_name); - let message: [&str; 5] = [ - "Hackable text editor for nerds", - "", - "Type :help to open the README.md document", - "Type :o to open a file and edit", - "Type :q! or to quit lambda", - ]; - - // If the screen is big enough, we can draw - if screen.size.width > longest_element_in_array(message.to_vec()) && screen.size.height > message.len() + 4 { - // The starting y position in the centre of the screen - let mut y = (screen.size.height / 2) - (message.len() / 2) - 2; - - // Calculate where to place the title - let x = calc_x(screen.size.width, title.len()); - - // Write the title to the screen - screen.write_at(title.yellow().to_string(), Coords::from(x, y)); - - for line in message { - // Each line has different width so requires a different x position to center it - let x = calc_x(screen.size.width, line.len()) ; - - // For each line we move downwards so increment y - y += 1; - - // Write the line to the screen at position (x, y) - screen.write_at(line.to_string(), Coords::from(x, y)); - } - } -} - pub fn start(screen: &mut Screen, editor: Editor) { + // Main screen loop, runs until the program exits loop { // Refresh the screen screen.refresh().unwrap(); // Draw the welcome message - draw_welcome(screen, &editor); + components::welcome::draw(screen, &editor); // Draw the status bar - draw_status(screen, &editor).unwrap(); + components::statusbar::draw(screen, &editor).unwrap(); // Check for any key presses match read().unwrap() { diff --git a/src/terminal/utils.rs b/src/terminal/utils.rs new file mode 100644 index 0000000..d6721f5 --- /dev/null +++ b/src/terminal/utils.rs @@ -0,0 +1,14 @@ +// Surrounds a &str with spaces +pub fn with_spaces(text: &str) -> String { + format!(" {} ", text) +} + +// Calculates the starting x coordinate for centred text +pub fn calc_centred_x(screen_width: usize, item_length: usize) -> usize { + (screen_width / 2) - (item_length / 2) +} + +// Returns the longest element in a vector +pub fn longest_element_in_vec(elements: Vec<&str>) -> usize { + elements.iter().max_by_key(|x: &&&str| x.len()).unwrap().len() +}