From 467a42a5faddf36e9438a30fcdf81aafa4562135 Mon Sep 17 00:00:00 2001 From: FantasyPvP <80643031+FantasyPvP@users.noreply.github.com> Date: Thu, 23 Nov 2023 20:29:51 +0000 Subject: [PATCH] continued to work on new UI library - implemented CgStatusBar widget which is a specific version of the CgIndicatorBar widget with predefined fields - std::io::Screen is now an enum that makes switching between display modes more intuitive - created a basic CgLineEdit implementation that allows for a user to type in a character and have it re-render that widget - other more minor changes like fixes for existing apps to work with new features --- src/lib.rs | 1 + src/system/kernel/render.rs | 20 ++++- src/system/kernel/tasks/keyboard.rs | 93 ++++++++++++++++---- src/system/std/frame.rs | 2 +- src/system/std/io.rs | 34 ++++++-- src/user/bin/gameoflife.rs | 4 +- src/user/bin/grapher.rs | 6 +- src/user/bin/shell.rs | 110 +++++++++++++---------- src/user/bin/snake.rs | 4 +- src/user/bin/tetris.rs | 4 +- src/user/lib/libgui/cg_core.rs | 15 +++- src/user/lib/libgui/cg_inputs.rs | 53 +++++++++++ src/user/lib/libgui/cg_widgets.rs | 131 ++++++++++++++++++++-------- src/user/lib/libgui/mod.rs | 3 +- 14 files changed, 352 insertions(+), 128 deletions(-) create mode 100644 src/user/lib/libgui/cg_inputs.rs diff --git a/src/lib.rs b/src/lib.rs index 81b2d56..7ef80a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ #![feature(abi_x86_interrupt)] #![feature(alloc_error_handler)] #![feature(async_fn_in_trait)] +#![feature(async_closure)] #![feature(global_asm)] use core::panic::PanicInfo; diff --git a/src/system/kernel/render.rs b/src/system/kernel/render.rs index a2e080f..e0f59de 100644 --- a/src/system/kernel/render.rs +++ b/src/system/kernel/render.rs @@ -38,7 +38,7 @@ pub struct ColorCode(u8); impl ColorCode { pub fn new(foreground: Color, background: Color) -> ColorCode { - ColorCode((background as u8) << 5 | (foreground as u8)) + ColorCode((background as u8) << 4 | (foreground as u8)) } } @@ -207,11 +207,23 @@ impl Renderer { self.temp_colour = None; } + pub fn cursor_position(&mut self, x: u8, y: u8) -> Result<(), RenderError> { + // check that x and y are within bounds + if x >= 80 || x < 0 || y >= 25 || y < 0 { + return Err(RenderError::OutOfBounds( + x >= 80 || x < 0, + y >= 25 || y < 0 + )) + } + self.internal_set_cursor_position(x, y); + Ok(()) + } + // INTERNAL API ONLY - fn set_cursor_position(&mut self, row: usize, col: usize) { + fn internal_set_cursor_position(&mut self, x: u8, y: u8) { use x86_64::instructions::port::Port; - let cursor_position: u16 = (row as u16) * 80 + (col as u16); + let cursor_position: u16 = (y as u16) * 80 + (x as u16); unsafe {// Write the high byte of the cursor position to register 14 let mut control_port = Port::::new(0x3D4); @@ -302,7 +314,7 @@ impl Renderer { } } } - self.set_cursor_position(BUFFER_HEIGHT - 1, self.col_pos); + self.internal_set_cursor_position(self.col_pos as u8, BUFFER_HEIGHT as u8 - 1); } } diff --git a/src/system/kernel/tasks/keyboard.rs b/src/system/kernel/tasks/keyboard.rs index 6e93945..34917e5 100644 --- a/src/system/kernel/tasks/keyboard.rs +++ b/src/system/kernel/tasks/keyboard.rs @@ -11,10 +11,12 @@ use core::{pin::Pin, task::{Poll, Context}}; use futures_util::stream::Stream; use futures_util::task::AtomicWaker; use futures_util::stream::StreamExt; -use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; +use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1, KeyCode}; use crate::print; use crate::kernel::render::RENDERER; use alloc::{string::String}; +use core::ascii::Char; +use crate::kernel::tasks::keyboard::CharOrKeystroke::Char; static WAKER: AtomicWaker = AtomicWaker::new(); static SCANCODE_QUEUE: OnceCell> = OnceCell::uninit(); @@ -29,6 +31,42 @@ pub struct KeyboardHandler { keyboard: Keyboard, } +enum CharOrKeystroke { + Char(char), + Keystroke(KeyCode), +} + +pub enum KeyStroke { + Char(char), + Ctrl, + RCtrl, + Alt, + RAlt, + Shift, + RShift, + Meta, + RMeta, + None, +} + +impl KeyStroke { + pub fn from_keycode(key: KeyCode) -> KeyStroke { + match key { + KeyCode::ControlLeft => KeyStroke::Ctrl, + KeyCode::ControlRight => KeyStroke::RCtrl, + KeyCode::AltLeft => KeyStroke::Alt, + KeyCode::AltRight => KeyStroke::RAlt, + KeyCode::ShiftLeft => KeyStroke::Shift, + KeyCode::ShiftRight => KeyStroke::RShift, + KeyCode::WindowsLeft => KeyStroke::Meta, + KeyCode::WindowsRight => KeyStroke::RMeta, + _ => KeyStroke::None, + } + } +} + + + impl KeyboardHandler { pub fn new() -> KeyboardHandler { KeyboardHandler { @@ -37,7 +75,7 @@ impl KeyboardHandler { } } - pub async fn get_keystroke_inner(&mut self) -> Option { + pub async fn get_keystroke_inner(&mut self) -> Option { loop { if let Some(scancode) = self.scancodes.next().await { if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { @@ -50,10 +88,15 @@ impl KeyboardHandler { }); return None; } else { - return Some(character); + return Some(KeyStroke::Char(character)); + } + }, + DecodedKey::RawKey(key) => { + print!("{:?}", key) + match key { + KeyCode::NOn } }, - DecodedKey::RawKey(key) => { print!("{:?}", key) }, } } } @@ -61,16 +104,22 @@ impl KeyboardHandler { } } - pub async fn get_keystroke(&mut self) -> char { + pub async fn get_keystroke(&mut self) -> KeyStroke { loop { match self.get_keystroke_inner().await { - Some(c) => return c, + Some(c) => match c { + CharOrKeystroke::Char(c) => return KeyStroke::Char(c), + CharOrKeystroke::Keystroke(c) => match KeyStroke::from_keycode(c) { + KeyStroke::None => (), + key => return key + } + }, None => () } } } - pub fn try_keystroke(&mut self) -> Option { + pub fn try_keystroke(&mut self) -> Option { if let Some(scancode) = self.scancodes.try_next() { if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { if let Some(key) = self.keyboard.process_keyevent(key_event) { @@ -82,10 +131,16 @@ impl KeyboardHandler { }); return None; } else { - return Some(character); + return Some(KeyStroke::Char(character)); + } + }, + DecodedKey::RawKey(key) => { + print!("{:?}", key); + match KeyStroke::from_keycode(key) { + KeyStroke::None => (), + key => return Some(key) } }, - DecodedKey::RawKey(key) => { print!("{:?}", key) }, } } } @@ -101,15 +156,19 @@ impl KeyboardHandler { Some(c) => { c }, None => { val.pop(); continue; }, }; - print!("{}", character); - let (character, execute): (char, bool) = match character { - '\n' => (character, true), - _ => (character, false), - }; - val.push(character); - if execute { - return val; + + if let CharOrKeystroke::Char(c) = character { + print!("{}", character); + let (c, execute): (char, bool) = match c { + '\n' => (c, true), + _ => (c, false), + }; + val.push(c); + if execute { + return val; + } } + } } diff --git a/src/system/std/frame.rs b/src/system/std/frame.rs index 7cfd4a5..b7da2d5 100644 --- a/src/system/std/frame.rs +++ b/src/system/std/frame.rs @@ -137,7 +137,7 @@ impl Frame { pub fn dimensions(&self) -> Dimensions { self.dimensions } - pub fn write_pos(&mut self, position: Position, char: ColouredChar) { + pub fn write(&mut self, position: Position, char: ColouredChar) { self.frame[position.y][position.x] = char } pub fn render_element(&mut self, other: &Frame) { diff --git a/src/system/std/io.rs b/src/system/std/io.rs index d3d4ba5..27f1f0f 100644 --- a/src/system/std/io.rs +++ b/src/system/std/io.rs @@ -1,6 +1,6 @@ use crate::{ kernel::render::{RENDERER, self}, - kernel::tasks::keyboard::KEYBOARD, + kernel::tasks::keyboard::{KEYBOARD, KeyStroke}, }; use alloc::string::String; @@ -12,19 +12,24 @@ use crate::kernel::serial::serial_reply; use lazy_static::lazy_static; use spin::Mutex; +use crate::kernel::render::Renderer; +use crate::std::frame::RenderError; pub struct Stdin {} impl Stdin { + /// waits for the user to type in a string and press enter | blocking pub async fn readline() -> String { let string = KEYBOARD.lock().get_string().await; string } + /// waits for a keystroke | blocking pub async fn keystroke() -> char { let chr = KEYBOARD.lock().get_keystroke().await; chr } + /// gets the next keystroke if any is present | non blocking pub fn try_keystroke() -> Option { let chr = KEYBOARD.lock().try_keystroke(); chr @@ -39,15 +44,30 @@ impl Serial { } } -pub struct Screen {} +/// enum with a terminal and application mode +pub enum Screen { + Terminal, + Application, +} impl Screen { - pub fn terminal_mode() { - RENDERER.lock().terminal_mode().unwrap(); + /// mode can be set for the kernel using this method + pub fn set_mode(&self) -> Result<(), RenderError> { + match self { + Screen::Terminal => RENDERER.lock().terminal_mode(), + Screen::Application => RENDERER.lock().application_mode(), + } } - pub fn application_mode() { - RENDERER.lock().application_mode().unwrap(); + + /// returns the current display mode + pub fn get_mode() -> Screen { + match RENDERER.lock().mode_is_app() { + true => Screen::Application, + false => Screen::Terminal, + } } - pub fn switch() { + + /// switches between modes + pub fn switch(&self) { if RENDERER.lock().mode_is_app() == true { RENDERER.lock().terminal_mode().unwrap(); } else { diff --git a/src/user/bin/gameoflife.rs b/src/user/bin/gameoflife.rs index a7338cb..bb7b10a 100644 --- a/src/user/bin/gameoflife.rs +++ b/src/user/bin/gameoflife.rs @@ -27,7 +27,7 @@ impl Application for GameOfLife { } async fn run(&mut self, args: Vec) -> Result<(), Error> { // setup: - Screen::application_mode(); + Screen::Application.set_mode(); let xoffset = 38; let yoffset = 5; @@ -58,7 +58,7 @@ impl Application for GameOfLife { self.mainloop()?; - Screen::terminal_mode(); + Screen::Terminal.set_mode(); Ok(()) } } diff --git a/src/user/bin/grapher.rs b/src/user/bin/grapher.rs index 9af1204..1f8201a 100644 --- a/src/user/bin/grapher.rs +++ b/src/user/bin/grapher.rs @@ -58,7 +58,7 @@ impl Application for Grapher { } }; - Screen::application_mode(); + Screen::Application.set_mode().map_err(|_| Error::ApplicationError(String::from("failed to set application mode")))?; self.display(); loop { @@ -68,7 +68,7 @@ impl Application for Grapher { } } - Screen::terminal_mode(); + Screen::Terminal.set_mode().map_err(|_| Error::ApplicationError(String::from("failed to set terminal mode")))?; Ok(()) } } @@ -91,7 +91,7 @@ impl Grapher { // serial_println!("{} {}", 24-offset_y as usize, offset_x as usize); - self.frame.write_pos(Position::new(offset_x as usize, 24-offset_y as usize), ColouredChar::new('*')); + self.frame.write(Position::new(offset_x as usize, 24-offset_y as usize), ColouredChar::new('*')); } diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index 7b96782..70bb652 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -3,6 +3,7 @@ use lazy_static::lazy_static; use spin::Mutex; use alloc::{boxed::Box, string::{String, ToString}, vec, vec::Vec}; +use core::future::Future; use vga::writers::{GraphicsWriter, PrimitiveDrawing}; use crate::{print, printerr, println, serial_println, std, std::application::{Application, Error}, user::bin::*}; @@ -16,7 +17,8 @@ use crate::user::lib::libgui::{ cg_core::{CgComponent}, cg_widgets::{CgTextBox, CgContainer}, }; -use crate::user::lib::libgui::cg_widgets::{CgIndicatorBar, CgIndicatorWidget, CgLabel}; +use crate::user::lib::libgui::cg_inputs::CgLineEdit; +use crate::user::lib::libgui::cg_widgets::{CgIndicatorBar, CgIndicatorWidget, CgLabel, CgStatusBar}; lazy_static! { pub static ref CMD: Mutex = Mutex::new(CommandHandler::new()); @@ -176,60 +178,24 @@ async fn exec() -> Result<(), Error> { //interrupts::without_interrupts(|| {}); } "switch" => { - Screen::switch(); + match Screen::get_mode() { + Screen::Terminal => Screen::Application.set_mode().unwrap(), + Screen::Application => Screen::Terminal.set_mode().unwrap(), + }; } "time" => { use crate::std::time::timer; timer(); } "test_features" => { - std::io::Screen::application_mode(); + Screen::Application.set_mode().unwrap(); - let textbox = CgTextBox::new( - String::from("i'd just like to interject for a moment"), - String::from("I'd just like to interject for a moment. What you're refering to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called Linux, and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called Linux distributions are really distributions of GNU/Linux!"), - Position::new(2, 5), - Dimensions::new(40, 12), - true, - ); + new_eventloop(|c| match c { + 'x' => ('x', true), + _ => (c, false), + }).await; - let mut indicatorbar = CgIndicatorBar::new( - Position::new(0, 0), - 80 - ); - - let mut w1 = CgIndicatorWidget::new( - String::from("test"), - 5 - ); - - let mut w2 = CgIndicatorWidget::new( - String::from("widget 2"), - 20 - ); - w2.set_colour(ColorCode::new(Color::Cyan, Color::Black)); - - indicatorbar.fields.push(w1); - indicatorbar.fields.push(w2); - - let mut container = CgContainer::new( - Position::new(0, 0), - Dimensions::new(80, 25), - false, - ); - - container.elements.push(Box::new(textbox)); - container.elements.push(Box::new(indicatorbar)); - - if let Ok(frame) = container.render() { - frame.render_to_screen().unwrap(); - } else { - return Err(Error::CommandFailed("failed to render frame".to_string())) - }; - - - - //std::io::Screen::terminal_mode(); + Screen::Terminal.set_mode().unwrap() } _ => { @@ -292,3 +258,53 @@ impl CommandHandler { struct CmdHistory { history: Vec, } + +async fn new_eventloop(input: impl Fn(char) -> (char, bool)) { + + let textbox = CgTextBox::new( + String::from("i'd just like to interject for a moment"), + String::from("I'd just like to interject for a moment. What you're refering to as Linux, is in fact, GNU/Linux, or as I've recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX. Many computer users run a modified version of the GNU system every day, without realizing it. Through a peculiar turn of events, the version of GNU which is widely used today is often called Linux, and many of its users are not aware that it is basically the GNU system, developed by the GNU Project. There really is a Linux, and these people are using it, but it is just a part of the system they use. Linux is the kernel: the program in the system that allocates the machine's resources to the other programs that you run. The kernel is an essential part of an operating system, but useless by itself; it can only function in the context of a complete operating system. Linux is normally used in combination with the GNU operating system: the whole system is basically GNU with Linux added, or GNU/Linux. All the so-called Linux distributions are really distributions of GNU/Linux!"), + Position::new(2, 5), + Dimensions::new(40, 12), + true, + ); + + let mut statusbar = CgStatusBar::new(Position::new(0, 0), Dimensions::new(80, 1)); + + let mut textedit = CgLineEdit::new( + Position::new(0, 1), + 80, + String::from("enter text here >"), + ); + textedit.text = "hello".chars().collect(); + + let mut container = CgContainer::new( + Position::new(0, 0), + Dimensions::new(80, 25), + false, + ); + + container.elements.push(Box::new(&textbox)); + container.elements.push(Box::new(&statusbar)); + container.elements.push(Box::new(&textedit)); + + while let (c, false) = input(Stdin::keystroke().await) { + + + textedit.text.push(c); + + let mut container = CgContainer::new( + Position::new(0, 0), + Dimensions::new(80, 25), + false, + ); + + container.elements.push(Box::new(&textbox)); + container.elements.push(Box::new(&statusbar)); + container.elements.push(Box::new(&textedit)); + if let Ok(frame) = container.render() { + frame.render_to_screen().unwrap(); + } + } + +} \ No newline at end of file diff --git a/src/user/bin/snake.rs b/src/user/bin/snake.rs index 08bc5a5..b65ab9d 100644 --- a/src/user/bin/snake.rs +++ b/src/user/bin/snake.rs @@ -83,13 +83,13 @@ impl Application for Game { self.prepare(); // switch OS to application mode - Screen::application_mode(); + Screen::Application.set_mode(); // render the initial state of the screen. self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; // run the game self.gameloop().await?; // return to the terminal - Screen::terminal_mode(); + Screen::Terminal.set_mode(); Ok(()) } } diff --git a/src/user/bin/tetris.rs b/src/user/bin/tetris.rs index ede05dd..10d1932 100644 --- a/src/user/bin/tetris.rs +++ b/src/user/bin/tetris.rs @@ -28,7 +28,7 @@ impl Application for TetrisEngine { } async fn run(&mut self, args: Vec) -> Result<(), Error> { // setup: - Screen::application_mode(); + Screen::Application.set_mode(); let piece_type = PieceType::OPiece; let mut piece = TetrisPiece::new(piece_type); @@ -38,7 +38,7 @@ impl Application for TetrisEngine { serial_println!("{:?}", piece.get_positions()); - Screen::terminal_mode(); + Screen::Terminal.set_mode(); Ok(()) } } diff --git a/src/user/lib/libgui/cg_core.rs b/src/user/lib/libgui/cg_core.rs index d6747e0..2386391 100644 --- a/src/user/lib/libgui/cg_core.rs +++ b/src/user/lib/libgui/cg_core.rs @@ -7,17 +7,24 @@ use crate::kernel::render::{ColorCode, RenderError, ScreenChar}; use crate::{printerr, serial_println}; use crate::std::frame::{ColouredChar, Dimensions, Position, special_char, Frame}; - - -pub trait CgOutline { +/// implement this trait if you require the widget to be able to have an outline +pub trait CgOutline: CgComponent { fn render_outline(&self, frame: &mut Frame); } +/// generic components for the user interface that defined a render method. this should be implemented for all types +/// that can be rendered to the screen. pub trait CgComponent { fn render(&self) -> Result; } - +/// trait for components that can have editable text, such as search boxes, command palletes, terminals, text inputs etc. +pub trait CgTextEdit: CgComponent { + fn write_char(&self) -> Result; // this can also be implemented in a way that inserts characters + fn delete_char(&self) -> Result; + fn move_cursor(&self, direction: bool) -> Result<(), RenderError>; // true = right, false = left + fn clear(&self) -> Result; +} diff --git a/src/user/lib/libgui/cg_inputs.rs b/src/user/lib/libgui/cg_inputs.rs new file mode 100644 index 0000000..9549eb9 --- /dev/null +++ b/src/user/lib/libgui/cg_inputs.rs @@ -0,0 +1,53 @@ +use alloc::string::String; +use alloc::vec::Vec; +use crate::std::frame::{ColouredChar, Dimensions, Frame, Position, RenderError}; +use crate::user::lib::libgui::cg_core::{CgComponent, CgTextEdit}; + +pub struct CgLineEdit { + pub position: Position, + pub dimensions: Dimensions, + pub prompt: String, + pub text: Vec, + pub ptr: usize, // cursor position +} + +impl CgLineEdit { + pub fn new(position: Position, width: usize, prompt: String) -> CgLineEdit { + CgLineEdit { + position, + dimensions: Dimensions::new(width, 1), + prompt: prompt, + text: Vec::new(), + ptr: 0 + } + } +} + +impl CgComponent for CgLineEdit { + fn render(&self) -> Result { + let mut frame = Frame::new(self.position, self.dimensions)?; + + let mut ptr = 0; + + for c in self.prompt.chars() { + if ptr >= self.dimensions.x { + break; + } + frame.write(Position::new(ptr, 0), ColouredChar::new(c)); + ptr += 1 + } + + ptr += 1; // create a space between the prompt and the text + + for c in self.text.iter() { + if ptr >= self.dimensions.x { + break; + } + frame.write(Position::new(ptr, 0), ColouredChar::new(*c)); + ptr += 1 + } + + Ok(frame) + } +} + diff --git a/src/user/lib/libgui/cg_widgets.rs b/src/user/lib/libgui/cg_widgets.rs index 4b56a78..0c75b15 100644 --- a/src/user/lib/libgui/cg_widgets.rs +++ b/src/user/lib/libgui/cg_widgets.rs @@ -1,5 +1,7 @@ use alloc::{boxed::Box, format, string::String, vec, vec::Vec}; use alloc::fmt::format; +use alloc::string::ToString; +use core::cmp::{max, min}; use crate::kernel::render::{ColorCode, RenderError}; use crate::serial_println; use super::cg_core::{ @@ -9,15 +11,15 @@ use crate::std::frame::{ColouredChar, Dimensions, Position, Frame}; use crate::std::io::Color; -pub struct CgContainer { - pub elements: Vec>, +pub struct CgContainer<'a> { + pub elements: Vec>, pub position: Position, pub dimensions: Dimensions, pub outlined: bool, } -impl CgContainer { - pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> CgContainer { +impl<'a> CgContainer<'a> { + pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> CgContainer<'a> { CgContainer { elements: Vec::new(), position, @@ -27,29 +29,29 @@ impl CgContainer { } } -impl CgOutline for CgContainer { +impl CgOutline for CgContainer<'_> { fn render_outline(&self, frame: &mut Frame) { // draws the sides of the container for i in 0..frame.dimensions.x { - frame.write_pos(Position::new(i, 0), ColouredChar::new('─')); - frame.write_pos(Position::new(i, frame.dimensions.y - 1), ColouredChar::new('─')); + frame.write(Position::new(i, 0), ColouredChar::new('─')); + frame.write(Position::new(i, frame.dimensions.y - 1), ColouredChar::new('─')); } // draws the top and bottom of the container for i in 0..frame.dimensions.y { - frame.write_pos(Position::new(0, i), ColouredChar::new('│')); - frame.write_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::new('│')); + frame.write(Position::new(0, i), ColouredChar::new('│')); + frame.write(Position::new(frame.dimensions.x - 1, i), ColouredChar::new('│')); } // draws the corners of the container - frame.write_pos(Position::new(0, 0), ColouredChar::new('┌')); - frame.write_pos(Position::new(self.dimensions.x - 1, 0), ColouredChar::new('┐')); - frame.write_pos(Position::new(0, self.dimensions.y - 1), ColouredChar::new('└')); - frame.write_pos(Position::new(self.dimensions.x - 1, self.dimensions.y - 1), ColouredChar::new('┘')); + frame.write(Position::new(0, 0), ColouredChar::new('┌')); + frame.write(Position::new(self.dimensions.x - 1, 0), ColouredChar::new('┐')); + frame.write(Position::new(0, self.dimensions.y - 1), ColouredChar::new('└')); + frame.write(Position::new(self.dimensions.x - 1, self.dimensions.y - 1), ColouredChar::new('┘')); } } -impl CgComponent for CgContainer { +impl CgComponent for CgContainer<'_> { fn render(&self) -> Result { let mut result = Frame::new(self.position, self.dimensions)?; @@ -88,12 +90,12 @@ impl CgTextBox { let title = self.title.chars(); for (i, c) in title.enumerate() { if i + 2 == self.dimensions.x - 3 { // we dont want to write at the top of the text box - frame.write_pos(Position::new(i + 1, 0), ColouredChar::new('.')); + frame.write(Position::new(i + 1, 0), ColouredChar::new('.')); } else if i + 2 >= self.dimensions.x - 2 { - frame.write_pos(Position::new(i + 1, 0), ColouredChar::new('.')); + frame.write(Position::new(i + 1, 0), ColouredChar::new('.')); break; } - frame.write_pos(Position::new(i + 2, 0), ColouredChar::new(c)); + frame.write(Position::new(i + 2, 0), ColouredChar::new(c)); } } pub fn wrap_words(&mut self, wrap: bool) { @@ -134,13 +136,13 @@ impl CgComponent for CgTextBox { if y == self.dimensions.y - 1 { if c != ' ' { (2..5).for_each(|z| { - result.write_pos(Position::new(self.dimensions.x - z, self.dimensions.y - 1), ColouredChar::new('.')); + result.write(Position::new(self.dimensions.x - z, self.dimensions.y - 1), ColouredChar::new('.')); }) } break; } - result.write_pos(Position::new(x, y), ColouredChar::new(c)); + result.write(Position::new(x, y), ColouredChar::new(c)); x += 1; }; } @@ -155,21 +157,21 @@ impl CgOutline for CgTextBox { fn render_outline(&self, frame: &mut Frame) { // draws the sides of the container for i in 0..frame.dimensions.x { - frame.write_pos(Position::new(i, 0), ColouredChar::new('─')); - frame.write_pos(Position::new(i, frame.dimensions.y - 1), ColouredChar::new('─')); + frame.write(Position::new(i, 0), ColouredChar::new('─')); + frame.write(Position::new(i, frame.dimensions.y - 1), ColouredChar::new('─')); } // draws the top and bottom of the container for i in 0..frame.dimensions.y { - frame.write_pos(Position::new(0, i), ColouredChar::new('│')); - frame.write_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::new('│')); + frame.write(Position::new(0, i), ColouredChar::new('│')); + frame.write(Position::new(frame.dimensions.x - 1, i), ColouredChar::new('│')); } // draws the corners of the container - frame.write_pos(Position::new(0, 0), ColouredChar::new('┌')); - frame.write_pos(Position::new(self.dimensions.x - 1, 0), ColouredChar::new('┐')); - frame.write_pos(Position::new(0, self.dimensions.y - 1), ColouredChar::new('└')); - frame.write_pos(Position::new(self.dimensions.x - 1, self.dimensions.y - 1), ColouredChar::new('┘')); + frame.write(Position::new(0, 0), ColouredChar::new('┌')); + frame.write(Position::new(self.dimensions.x - 1, 0), ColouredChar::new('┐')); + frame.write(Position::new(0, self.dimensions.y - 1), ColouredChar::new('└')); + frame.write(Position::new(self.dimensions.x - 1, self.dimensions.y - 1), ColouredChar::new('┘')); } } @@ -200,11 +202,8 @@ impl CgComponent for CgLabel { let shortened_string = self.content.chars().take(self.dimensions.x).collect::(); for (i, c) in shortened_string.chars().enumerate() { - result.write_pos(Position::new(i, 0), ColouredChar::new(c)); + result.write(Position::new(i, 0), ColouredChar::new(c)); }; - - serial_println!("{:?}", result); - Ok(result) } } @@ -224,7 +223,6 @@ impl CgIndicatorWidget { max_width } } - pub fn set_colour(&mut self, colour: ColorCode) { self.colour = colour; } @@ -234,6 +232,12 @@ impl CgIndicatorWidget { fn len(&self) -> usize { self.max_width } + fn text(&self) -> &str { + &self.content + } + fn set_text(&mut self, content: String) { + self.content = content; + } } impl CgComponent for CgIndicatorWidget { @@ -241,11 +245,11 @@ impl CgComponent for CgIndicatorWidget { if !self.visible { return Ok(Frame::new(Position::new(0, 0), Dimensions::new(0, 0))?); } - let mut result = Frame::new(Position::new(0, 0), Dimensions::new(self.max_width, 1))?; + let mut result = Frame::new(Position::new(0, 0), Dimensions::new(min(self.max_width, self.content.len()), 1))?; let shortened_string = self.content.chars().take(self.max_width).collect::(); for (i, c) in shortened_string.chars().enumerate() { - result.write_pos(Position::new(i, 0), ColouredChar::coloured(c, self.colour)); + result.write(Position::new(i, 0), ColouredChar::coloured(c, self.colour)); }; Ok(result) @@ -253,7 +257,7 @@ impl CgComponent for CgIndicatorWidget { } pub struct CgIndicatorBar { - pub(crate) fields: Vec, + pub fields: Vec, position: Position, dimensions: Dimensions, } @@ -293,9 +297,60 @@ impl CgComponent for CgIndicatorBar { } } - - - +pub struct CgStatusBar { + position: Position, + dimensions: Dimensions, + + window_title: CgIndicatorWidget, + screen_mode: CgIndicatorWidget, +} + +impl CgComponent for CgStatusBar { + fn render(&self) -> Result { + let mut frame = Frame::new(self.position, self.dimensions)?; + + (0..80).for_each(|x| frame[0][x] = ColouredChar::coloured(' ', ColorCode::new(Color::Black, Color::DarkGray))); + + // render window title centred + let mut window_title = self.window_title.render()?; + let width = window_title.dimensions().x; + window_title.set_position(Position::new((self.dimensions.x - width) / 2, 0)); + + // render screen mode right + let mut screen_mode = self.screen_mode.render()?; + let width = screen_mode.dimensions().x; + screen_mode.set_position(Position::new(self.dimensions.x - width, 0)); + + frame.render_element(&window_title); + frame.render_element(&screen_mode); + + serial_println!("{:?}", frame); + + Ok(frame) + } +} + +impl CgStatusBar { + pub fn new(position: Position, dimensions: Dimensions) -> CgStatusBar { + let mut widget = CgStatusBar { + position, + dimensions, + window_title: CgIndicatorWidget::new("feature test".to_string(), 20), + screen_mode: CgIndicatorWidget::new("Application".to_string(), 20), + }; + widget.window_title.set_colour(ColorCode::new(Color::Cyan, Color::DarkGray)); + widget.screen_mode.set_colour(ColorCode::new(Color::Yellow, Color::DarkGray)); + widget + } + + pub fn set_window_title(&mut self, title: String) { + self.window_title.set_text(title); + } + + pub fn set_screen_mode(&mut self, mode: String) { + self.screen_mode.set_text(mode); + } +} diff --git a/src/user/lib/libgui/mod.rs b/src/user/lib/libgui/mod.rs index d6607d8..333a3bc 100644 --- a/src/user/lib/libgui/mod.rs +++ b/src/user/lib/libgui/mod.rs @@ -1,3 +1,4 @@ pub mod cg_core; -pub(crate) mod cg_widgets; +pub mod cg_widgets; +pub mod cg_inputs;