ui components working
This commit is contained in:
parent
07e489e825
commit
e050cb568c
@ -146,7 +146,7 @@ impl Screen {
|
|||||||
execute!(stdout(), Print(text)).unwrap();
|
execute!(stdout(), Print(text)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_at(&mut self, text: String, position: Coords) {
|
pub fn write_at(&mut self, text: &String, position: Coords) {
|
||||||
// Writes a line at a set of coordinates
|
// Writes a line at a set of coordinates
|
||||||
self.cursor.move_to(position);
|
self.cursor.move_to(position);
|
||||||
Screen::write(&text);
|
Screen::write(&text);
|
||||||
|
@ -1,131 +0,0 @@
|
|||||||
use crate::core::editor::Editor;
|
|
||||||
use crate::terminal::screen::{Coords, Screen};
|
|
||||||
use crossterm::style::Stylize;
|
|
||||||
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 <file> to open a file and edit",
|
|
||||||
"Type :q! or <C-c> 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) {
|
|
||||||
loop {
|
|
||||||
// Refresh the screen
|
|
||||||
screen.refresh().unwrap();
|
|
||||||
|
|
||||||
// Draw the welcome message
|
|
||||||
draw_welcome(screen, &editor);
|
|
||||||
|
|
||||||
// Draw the status bar
|
|
||||||
draw_status(screen, &editor).unwrap();
|
|
||||||
|
|
||||||
// Check for any key presses
|
|
||||||
match read().unwrap() {
|
|
||||||
Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Char('q'),
|
|
||||||
modifiers: KeyModifiers::CONTROL,
|
|
||||||
..
|
|
||||||
}) => break,
|
|
||||||
Event::Key(KeyEvent {
|
|
||||||
code: KeyCode::Char('c'),
|
|
||||||
modifiers: KeyModifiers::CONTROL,
|
|
||||||
..
|
|
||||||
}) => break,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.exit();
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
use crate::tui::ui::Component;
|
|
||||||
use crate::tui::utils::with_spaces;
|
use crate::tui::utils::with_spaces;
|
||||||
|
use crate::tui::ui::Component;
|
||||||
use crate::core::editor::Editor;
|
use crate::core::editor::Editor;
|
||||||
use crate::terminal::screen::{Coords, Screen};
|
use crate::terminal::screen::{Coords, Screen};
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
@ -42,7 +42,7 @@ impl<'a> Component for StatusBar<'a> {
|
|||||||
} else {
|
} else {
|
||||||
// Write the editor logo
|
// Write the editor logo
|
||||||
screen.write_at(
|
screen.write_at(
|
||||||
editor_logo.yellow().bold().reverse().to_string(),
|
&editor_logo.yellow().bold().reverse().to_string(),
|
||||||
Coords::from(0, status_height),
|
Coords::from(0, status_height),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -50,21 +50,21 @@ impl<'a> Component for StatusBar<'a> {
|
|||||||
let x = editor_logo.len() - 1;
|
let x = editor_logo.len() - 1;
|
||||||
// Write the current mode
|
// Write the current mode
|
||||||
screen.write_at(
|
screen.write_at(
|
||||||
mode_string.green().bold().reverse().to_string(),
|
&mode_string.green().bold().reverse().to_string(),
|
||||||
Coords::from(x, status_height),
|
Coords::from(x, status_height),
|
||||||
);
|
);
|
||||||
// Calculate where to write the file name
|
// Calculate where to write the file name
|
||||||
let x = x + mode_string.len();
|
let x = x + mode_string.len();
|
||||||
// Write the current file name
|
// Write the current file name
|
||||||
screen.write_at(
|
screen.write_at(
|
||||||
file_name.magenta().bold().reverse().to_string(),
|
&file_name.magenta().bold().reverse().to_string(),
|
||||||
Coords::from(x, status_height),
|
Coords::from(x, status_height),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Draw the rest of the status bar
|
// Draw the rest of the status bar
|
||||||
let x = x + file_name.len();
|
let x = x + file_name.len();
|
||||||
screen.write_at(
|
screen.write_at(
|
||||||
" ".repeat(screen.size.width - x).reverse().to_string(),
|
&" ".repeat(screen.size.width - x).reverse().to_string(),
|
||||||
Coords::from(x, status_height),
|
Coords::from(x, status_height),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
use crate::tui::utils;
|
use crate::tui::utils;
|
||||||
|
use crate::tui::ui::Component;
|
||||||
use crate::core::editor::Editor;
|
use crate::core::editor::Editor;
|
||||||
use crate::terminal::screen::{Coords, Screen};
|
use crate::terminal::screen::{Coords, Screen};
|
||||||
use crossterm::style::Stylize;
|
use crossterm::style::Stylize;
|
||||||
|
|
||||||
pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
|
pub struct WelcomeMessage<'a> {
|
||||||
// The welcome message
|
title: String,
|
||||||
|
message: [&'a str; 5],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> WelcomeMessage<'a> {
|
||||||
|
pub fn new(editor: &'a Editor<'a>) -> Self {
|
||||||
let title = format!("{} {}", editor.config.logo, editor.config.friendly_name);
|
let title = format!("{} {}", editor.config.logo, editor.config.friendly_name);
|
||||||
|
|
||||||
|
// The welcome message
|
||||||
let message: [&str; 5] = [
|
let message: [&str; 5] = [
|
||||||
"Hackable text editor for nerds",
|
"Hackable text editor for nerds",
|
||||||
"",
|
"",
|
||||||
@ -13,19 +21,27 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
|
|||||||
"Type :o <file> to open a file and edit",
|
"Type :o <file> to open a file and edit",
|
||||||
"Type :q! or <C-c> to quit lambda",
|
"Type :q! or <C-c> to quit lambda",
|
||||||
];
|
];
|
||||||
|
Self {
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Component for WelcomeMessage<'a> {
|
||||||
|
fn draw(&self, screen: &mut Screen, _editor: &Editor) -> Result<(), ()> {
|
||||||
// If the screen is big enough, we can draw
|
// 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 {
|
if screen.size.width > utils::longest_element_in_vec(self.message.to_vec()) && screen.size.height > self.message.len() + 4 {
|
||||||
// The starting y position in the centre of the screen
|
// The starting y position in the centre of the screen
|
||||||
let mut y = (screen.size.height / 2) - (message.len() / 2) - 2;
|
let mut y = (screen.size.height / 2) - (self.message.len() / 2) - 2;
|
||||||
|
|
||||||
// Calculate where to place the title
|
// Calculate where to place the title
|
||||||
let x = utils::calc_centered_x(screen.size.width, title.len());
|
let x = utils::calc_centered_x(screen.size.width, self.title.len());
|
||||||
|
|
||||||
// Write the title to the screen
|
// Write the title to the screen
|
||||||
screen.write_at(title.yellow().to_string(), Coords::from(x, y));
|
screen.write_at(&self.title.clone().yellow().to_string(), Coords::from(x, y));
|
||||||
|
|
||||||
for line in message {
|
for line in &self.message {
|
||||||
// Each line has different width so requires a different x position to center it
|
// Each line has different width so requires a different x position to center it
|
||||||
let x = utils::calc_centered_x(screen.size.width, line.len()) ;
|
let x = utils::calc_centered_x(screen.size.width, line.len()) ;
|
||||||
|
|
||||||
@ -33,8 +49,13 @@ pub fn draw_welcome(screen: &mut Screen, editor: &Editor) {
|
|||||||
y += 1;
|
y += 1;
|
||||||
|
|
||||||
// Write the line to the screen at position (x, y)
|
// Write the line to the screen at position (x, y)
|
||||||
screen.write_at(line.to_string(), Coords::from(x, y));
|
screen.write_at(&line.to_string(), Coords::from(x, y));
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,25 +7,44 @@ pub trait Component {
|
|||||||
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()>;
|
fn draw(&self, screen: &mut Screen, editor: &Editor) -> Result<(), ()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Components {
|
struct Components<'a> {
|
||||||
bottom: Vec<Box<dyn Component>>,
|
bottom: Vec<Box<dyn Component + 'a>>,
|
||||||
|
centre: Vec<Box<dyn Component + 'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ui {
|
pub struct Ui<'a> {
|
||||||
components: Components,
|
components: Components<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl<'a> Ui<'a> {
|
||||||
pub fn new<'a>(editor: &'a Editor<'a>) -> Self {
|
pub fn new(editor: &'a Editor<'a>) -> Self {
|
||||||
let status_bar = components::statusbar::StatusBar::new(editor);
|
let status_bar = components::statusbar::StatusBar::new(editor);
|
||||||
|
let welcome_msg = components::welcome::WelcomeMessage::new(editor);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
components: Components { bottom: vec![Box::new(status_bar)]},
|
components: Components {
|
||||||
|
bottom: vec![Box::new(status_bar)],
|
||||||
|
centre: vec![Box::new(welcome_msg)],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(&self) {
|
pub fn draw(&self, screen: &mut Screen, editor: &Editor) {
|
||||||
|
// Dereference the box
|
||||||
|
let _ = &*self.components.bottom;
|
||||||
|
|
||||||
|
// Draw all components on the bottom
|
||||||
|
for component in &self.components.bottom {
|
||||||
|
component.draw(screen, &editor).unwrap();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dereference the box
|
||||||
|
let _ = &*self.components.centre;
|
||||||
|
|
||||||
|
// Draw all components in the center
|
||||||
|
for component in &self.components.centre {
|
||||||
|
component.draw(screen, &editor).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(screen: &mut Screen, editor: Editor) {
|
pub fn run(screen: &mut Screen, editor: Editor) {
|
||||||
@ -37,7 +56,7 @@ impl Ui {
|
|||||||
let components = Ui::new(&editor);
|
let components = Ui::new(&editor);
|
||||||
|
|
||||||
// Draw all UI elements
|
// Draw all UI elements
|
||||||
components.draw();
|
components.draw(screen, &editor);
|
||||||
|
|
||||||
// Check for any key presses
|
// Check for any key presses
|
||||||
match read().unwrap() {
|
match read().unwrap() {
|
||||||
|
Loading…
Reference in New Issue
Block a user