From 84aaa90e6670ed567a487e61df92b2a17bb34af4 Mon Sep 17 00:00:00 2001 From: FantasyPvP <80643031+FantasyPvP@users.noreply.github.com> Date: Wed, 22 Nov 2023 21:07:00 +0000 Subject: [PATCH] new gui module text box implementation starting to work on the new UI library, got labels and text boxes working as well as containers for widgets. planning a big refactor --- src/system/kernel/render.rs | 79 +++--- src/system/std/frame.rs | 5 +- src/user/bin/shell.rs | 58 ++++- src/user/bin/tetris.rs | 2 +- src/user/lib/gui_v2/mod.rs | 2 - .../{gui_v2/widgets.rs => libgui/cg_core.rs} | 153 ++++++------ src/user/lib/libgui/cg_widgets.rs | 234 ++++++++++++++++++ src/user/lib/libgui/mod.rs | 5 +- .../libgui_core.rs | 0 .../libgui_elements.rs | 0 src/user/lib/libgui_old_archive/mod.rs | 2 + src/user/lib/mod.rs | 4 +- 12 files changed, 421 insertions(+), 123 deletions(-) delete mode 100644 src/user/lib/gui_v2/mod.rs rename src/user/lib/{gui_v2/widgets.rs => libgui/cg_core.rs} (51%) create mode 100644 src/user/lib/libgui/cg_widgets.rs rename src/user/lib/{libgui => libgui_old_archive}/libgui_core.rs (100%) rename src/user/lib/{libgui => libgui_old_archive}/libgui_elements.rs (100%) create mode 100644 src/user/lib/libgui_old_archive/mod.rs diff --git a/src/system/kernel/render.rs b/src/system/kernel/render.rs index 49cc7b8..67d562e 100644 --- a/src/system/kernel/render.rs +++ b/src/system/kernel/render.rs @@ -6,6 +6,7 @@ use volatile::Volatile; use alloc::borrow::ToOwned; use alloc::vec; use alloc::vec::Vec; +use crate::serial_println; use crate::std::io::Screen; #[allow(dead_code)] @@ -54,13 +55,19 @@ impl ScreenChar { colour: ColorCode::new(Color::White, Color::Black), } } - pub fn white(character: u8) -> ScreenChar { + pub fn white(mut character: u8) -> ScreenChar { + if let Some(c) = special_char(character as char) { + character = c; + } ScreenChar { character, colour: ColorCode::new(Color::White, Color::Black), } } - pub fn new(character: u8, colour: ColorCode) -> ScreenChar { + pub fn new(mut character: u8, colour: ColorCode) -> ScreenChar { + if let Some(c) = special_char(character as char) { + character = c + } ScreenChar { character, colour, @@ -101,9 +108,10 @@ impl Renderer { pub fn render_frame(&mut self, frame: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT]) { // renders the given frame to the app buffer let mut processed_frame: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT] = [[ScreenChar::null(); BUFFER_WIDTH]; BUFFER_HEIGHT]; + for (i, row) in frame.iter().enumerate() { for (j, col) in row.iter().enumerate() { - processed_frame[i][j] = match self.special_char(col.character as char) { + processed_frame[i][j] = match special_char(col.character as char) { Some(c) => ScreenChar::new(c as u8, col.colour), None => *col, }; @@ -153,7 +161,7 @@ impl Renderer { pub fn write_string(&mut self, string: &str, col: Option) { if self.application_mode { return; }; for ch in string.chars() { - match self.special_char(ch) { + match special_char(ch) { Some(c) => self.write_byte(c, col), None => match ch as u8 { 0x20..=0xff | b'\n' => self.write_byte(ch as u8, col), @@ -288,36 +296,43 @@ impl Renderer { } self.set_cursor_position(BUFFER_HEIGHT - 1, self.col_pos); } - - fn special_char(&self, ch: char) -> Option { - let res: u8 = match ch { - '│' => 179, - '─' => 196, - '┴' => 193, - '┤' => 180, - '═' => 205, - '║' => 186, - '╗' => 187, - '╝' => 188, - '╚' => 200, - '╔' => 201, - '»' => 175, - '┐' => 191, - '└' => 192, - '┘' => 217, - '┌' => 218, - '┼' => 197, - '░' => 176, - '▓' => 178, - '«' => 174, - _ => { - return None; - } - }; - Some(res) - } } +pub fn special_char(ch: char) -> Option { + let res: u8 = match ch { + '│' => 179, + '─' => 196, + '┴' => 193, + '┤' => 180, + '═' => 205, + '║' => 186, + '╗' => 187, + '╝' => 188, + '╚' => 200, + '╔' => 201, + '»' => 175, + '┐' => 191, + '└' => 192, + '┘' => 217, + '┌' => 218, + '┼' => 197, + '░' => 176, + '▓' => 178, + '«' => 174, + _ => { + return None; + } + }; + Some(res) +} + + + + + + + + impl fmt::Write for Renderer { fn write_str(&mut self, string: &str) -> fmt::Result { self.write_string(string, None); diff --git a/src/system/std/frame.rs b/src/system/std/frame.rs index 12ae3e8..ba46e53 100644 --- a/src/system/std/frame.rs +++ b/src/system/std/frame.rs @@ -12,7 +12,10 @@ use spin::Mutex; /// /// nothing will appear on the screen until the frame is actually rendered by /// the render_frame method on the renderer -/// + +pub use crate::system::kernel::render::special_char; + + pub type Frame = [ [ ScreenChar; BUFFER_WIDTH ]; BUFFER_HEIGHT]; #[derive(Clone)] diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index a81e40c..48c1490 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -13,8 +13,12 @@ use crate::std::io::{Color, write, Screen, Stdin, Serial}; use crate::std::random::Random; use crate::user::bin::gigachad_detector::GigachadDetector; use crate::user::bin::grapher::Grapher; -use crate::user::lib::gui_v2; -use crate::user::lib::gui_v2::widgets::GuiComponent; +use crate::user::lib::libgui; +use crate::user::lib::libgui::{ + cg_core::{Frame, Position, Dimensions, CgComponent}, + cg_widgets::{CgTextBox, CgContainer}, +}; +use crate::user::lib::libgui::cg_widgets::CgLabel; lazy_static! { pub static ref CMD: Mutex = Mutex::new(CommandHandler::new()); @@ -181,23 +185,55 @@ async fn exec() -> Result<(), Error> { timer(); } "test_features" => { - std::io::Screen::application_mode(); - let mut container = gui_v2::widgets::Container::new( - gui_v2::widgets::Position::new(2, 2), - gui_v2::widgets::Dimensions::new(10, 10), + 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, 2), + Dimensions::new(40, 12), true, ); - let res = container.render().unwrap().render(); - let mut elem = ColouredElement { - frame: res as Vec>, - dimensions: (container.dimensions.x as u8, container.dimensions.y as u8), + let textbox2 = 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(10, 5), + Dimensions::new(40, 12), + true, + ); + + + let mut label = CgLabel::new( + String::from("i'd just like to interject for a moment"), + Position::new(5, 16), + 15, + ); + + let mut container = CgContainer::new( + Position::new(2, 2), + Dimensions::new(65, 20), + true, + ); + + container.elements.push(Box::new(textbox)); + container.elements.push(Box::new(textbox2)); + container.elements.push(Box::new(label)); + + let res = if let Ok(frame) = container.render() { + frame + } else { + return Err(Error::CommandFailed("failed to render frame".to_string())) }; - elem.render((2, 2)).unwrap(); + let mut elem = ColouredElement::generate( + res.render_screen_char(), + (res.dimensions().x as u8, res.dimensions().y as u8), + ); + elem.render((0, 0)).unwrap(); + + //std::io::Screen::terminal_mode(); } _ => { diff --git a/src/user/bin/tetris.rs b/src/user/bin/tetris.rs index 6f62c3f..5e34923 100644 --- a/src/user/bin/tetris.rs +++ b/src/user/bin/tetris.rs @@ -8,7 +8,7 @@ use crate::{serial_print, serial_println}; use crate::std::application::{Application, Error}; use crate::std::io::Screen; use crate::user::lib::coords::{Direction, Position, PositionReal}; -use crate::user::lib::libgui::libgui_core::Pos; +use crate::user::lib::libgui_old_archive::libgui_core::Pos; pub(crate) struct TetrisEngine { diff --git a/src/user/lib/gui_v2/mod.rs b/src/user/lib/gui_v2/mod.rs deleted file mode 100644 index f8e41a3..0000000 --- a/src/user/lib/gui_v2/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod widgets; - diff --git a/src/user/lib/gui_v2/widgets.rs b/src/user/lib/libgui/cg_core.rs similarity index 51% rename from src/user/lib/gui_v2/widgets.rs rename to src/user/lib/libgui/cg_core.rs index ebfd847..0d8e962 100644 --- a/src/user/lib/gui_v2/widgets.rs +++ b/src/user/lib/libgui/cg_core.rs @@ -3,11 +3,13 @@ use alloc::string::String; use alloc::vec; use alloc::vec::Vec; use core::slice::from_mut; -use crate::kernel::render::ScreenChar; -use crate::printerr; -use crate::user::lib::gui_v2::widgets::XorY::Both; +use crate::kernel::render::{ColorCode, ScreenChar}; +use crate::{printerr, serial_println}; +use crate::std::frame::special_char; +use crate::std::io::Color; +use crate::user::lib::libgui::cg_core::XorY::Both; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub struct Position { pub x: usize, pub y: usize, @@ -52,12 +54,50 @@ pub enum GuiError { } pub type Dimensions = Position; -pub type ColouredChar = ScreenChar; -#[derive(Clone)] +#[derive(Clone, Copy, Debug)] +pub struct ColouredChar { + pub character: char, + pub colour: ColorCode, +} + +impl ColouredChar { + pub fn new(character: char, colour: ColorCode) -> ColouredChar { + ColouredChar { + character, + colour, + } + } + pub fn white(character: char) -> ColouredChar { + ColouredChar { + character, + colour: ColorCode::new(Color::White, Color::Black), + } + } + pub fn null() -> ColouredChar { + ColouredChar { + character: ' ', + colour: ColorCode::new(Color::White, Color::Black), + } + } + pub fn as_screen_char(&self) -> ScreenChar { + ScreenChar { + character: { + if let Some(c) = special_char(self.character) { + c + } else { + self.character as u8 + } + }, + colour: self.colour, + } + } +} + +#[derive(Clone, Debug)] pub struct Frame { - position: Position, - dimensions: Dimensions, + pub position: Position, + pub dimensions: Dimensions, frame: Vec>, } @@ -66,12 +106,20 @@ impl Frame { Ok(Frame { position, dimensions, - frame: vec![vec![ScreenChar::null(); dimensions.x]; dimensions.y], + frame: vec![vec![ColouredChar::null(); dimensions.x]; dimensions.y], }) } pub fn render(&self) -> Vec> { self.frame.clone() } + + pub fn render_screen_char(&self) -> Vec> { + self.frame.clone().into_iter().map(|row| { + row.into_iter().map(|char| { + char.as_screen_char() + }).collect::>() + }).collect::>>() + } pub fn position(&self) -> Position { self.position } @@ -82,11 +130,27 @@ impl Frame { self.frame[position.y][position.x] = char } pub fn render_element(&mut self, other: &Frame) { + serial_println!("frame:\n{}", + other.frame.iter().map(|x| { + x.iter().map(|y| { + y.character + }).collect::() + }).collect::>().join("\n") + ); + for (i, row) in other.frame.iter().enumerate() { for (j, chr) in row.iter().enumerate() { - self.frame[i + self.position.y][j + self.position.x] = *chr + self.frame[i + other.position.y][j + other.position.x] = *chr } } + + serial_println!("self:\n{}", + self.frame.iter().map(|x| { + x.iter().map(|y| { + y.character + }).collect::() + }).collect::>().join("\n") + ); } pub fn render_bounds_check(&self, element: &Frame, should_panic: bool) -> Result<(), XorY> { @@ -127,73 +191,18 @@ impl Frame { } } +pub trait CgOutline { + fn render_outline(&self, frame: &mut Frame); +} -pub trait GuiComponent { + +pub trait CgComponent { fn render(&self) -> Result; } -pub struct Container { - pub frame: Vec>, - pub elements: Vec>, - pub position: Position, - pub dimensions: Dimensions, - pub outlined: bool, -} - -impl Container { - - pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> Container { - Container { - frame: vec![vec![ScreenChar::null(); dimensions.x]; dimensions.y], - elements: Vec::new(), - position, - dimensions, - outlined, - } - } - - fn render_outline(&self, frame: &mut Frame) { - // draws the sides of the container - for i in 0..frame.dimensions.x { - frame.set_pos(Position::new(i, 0), ColouredChar::white('│' as u8)); - frame.set_pos(Position::new(i, frame.dimensions.y - 1), ColouredChar::white('│' as u8)); - } - - // draws the top and bottom of the container - for i in 0..frame.dimensions.y { - frame.set_pos(Position::new(0, i), ColouredChar::white('─' as u8)); - frame.set_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::white('─' as u8)); - } - - // draws the corners of the container - frame.set_pos(Position::new(0, 0), ColouredChar::white('┌' as u8)); - frame.set_pos(Position::new(self.dimensions.x - 1, 0), ColouredChar::white('┐' as u8)); - frame.set_pos(Position::new(0, self.dimensions.y - 1), ColouredChar::white('└' as u8)); - frame.set_pos(Position::new(self.dimensions.x - 1, self.dimensions.y - 1), ColouredChar::white('┘' as u8)); - } -} - - -impl GuiComponent for Container { - fn render(&self) -> Result { - let mut result = Frame::new(self.position, self.dimensions)?; - - for widget in &self.elements { - let frame = widget.render()?; - match result.render_bounds_check(&frame, true) { // TODO: this needs to be set to false for production - Ok(()) => result.render_element(&frame), - Err(e) => return Err(GuiError::OutOfBounds(e)), - } - } - - if self.outlined { - self.render_outline(&mut result); - } - - Ok(result) - } -} + + diff --git a/src/user/lib/libgui/cg_widgets.rs b/src/user/lib/libgui/cg_widgets.rs new file mode 100644 index 0000000..276ef7b --- /dev/null +++ b/src/user/lib/libgui/cg_widgets.rs @@ -0,0 +1,234 @@ +use alloc::{ + boxed::Box, + string::String, + vec, + vec::Vec, +}; +use crate::serial_println; +use super::cg_core::{ + Position, Dimensions, ColouredChar, CgComponent, CgOutline, Frame, GuiError, +}; + + + + +pub struct CgContainer { + pub elements: Vec>, + pub position: Position, + pub dimensions: Dimensions, + pub outlined: bool, +} + +impl CgContainer { + pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> CgContainer { + CgContainer { + elements: Vec::new(), + position, + dimensions, + outlined, + } + } +} + +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.set_pos(Position::new(i, 0), ColouredChar::white('─')); + frame.set_pos(Position::new(i, frame.dimensions.y - 1), ColouredChar::white('─')); + } + + // draws the top and bottom of the container + for i in 0..frame.dimensions.y { + frame.set_pos(Position::new(0, i), ColouredChar::white('│')); + frame.set_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::white('│')); + } + + // draws the corners of the container + frame.set_pos(Position::new(0, 0), ColouredChar::white('┌')); + frame.set_pos(Position::new(self.dimensions.x - 1, 0), ColouredChar::white('┐')); + frame.set_pos(Position::new(0, self.dimensions.y - 1), ColouredChar::white('└')); + frame.set_pos(Position::new(self.dimensions.x - 1, self.dimensions.y - 1), ColouredChar::white('┘')); + } +} + +impl CgComponent for CgContainer { + fn render(&self) -> Result { + let mut result = Frame::new(self.position, self.dimensions)?; + + for widget in &self.elements { + let frame = widget.render()?; + match result.render_bounds_check(&frame, true) { // TODO: this needs to be set to false for production + Ok(()) => result.render_element(&frame), + Err(e) => return Err(GuiError::OutOfBounds(e)), + } + } + + if self.outlined { + self.render_outline(&mut result); + } + + Ok(result) + } +} + + +pub struct CgTextBox { + title: String, + content: String, + pub position: Position, + pub dimensions: Dimensions, + outlined: bool, +} + +impl CgTextBox { + pub fn new(title: String, content: String, position: Position, dimensions: Dimensions, outlined: bool) -> CgTextBox { + CgTextBox { title, content, position, dimensions, outlined } + } +} + +impl CgComponent for CgTextBox { + fn render(&self) -> Result { + let mut result = Frame::new(self.position, self.dimensions)?; + + if self.outlined { + self.render_outline(&mut result); + } + + 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 + result.set_pos(Position::new(i + 1, 0), ColouredChar::white('.')); + } else if i + 2 >= self.dimensions.x - 2 { + result.set_pos(Position::new(i + 1, 0), ColouredChar::white('.')); + break; + + } + result.set_pos(Position::new(i + 2, 0), ColouredChar::white(c)); + } + + let (mut x, mut y) = (1,1); + + for c in self.content.chars() { + if x == self.dimensions.x - 1 { + x = 1; + y += 1; + if c == ' ' { + continue; + } + } + if y == self.dimensions.y - 1 { + if c != ' ' { + (2..5).for_each(|z| { + result.set_pos(Position::new(self.dimensions.x - z, self.dimensions.y -1), ColouredChar::white('.')); + }) + } + break; + } + + result.set_pos(Position::new(x, y), ColouredChar::white(c)); + x += 1; + }; + + Ok(result) + } +} + +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.set_pos(Position::new(i, 0), ColouredChar::white('─')); + frame.set_pos(Position::new(i, frame.dimensions.y - 1), ColouredChar::white('─')); + } + + // draws the top and bottom of the container + for i in 0..frame.dimensions.y { + frame.set_pos(Position::new(0, i), ColouredChar::white('│')); + frame.set_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::white('│')); + } + + // draws the corners of the container + frame.set_pos(Position::new(0, 0), ColouredChar::white('┌')); + frame.set_pos(Position::new(self.dimensions.x - 1, 0), ColouredChar::white('┐')); + frame.set_pos(Position::new(0, self.dimensions.y - 1), ColouredChar::white('└')); + frame.set_pos(Position::new(self.dimensions.x - 1, self.dimensions.y - 1), ColouredChar::white('┘')); + } +} + + + + + +pub struct CgLabel { + content: String, + position: Position, + dimensions: Dimensions, +} + +impl CgLabel { + pub fn new(content: String, position: Position, width: usize) -> CgLabel { + CgLabel { + content, + position: Position::new(position.x, position.y), + dimensions: Dimensions::new(width, 1), + } + } +} + +impl CgComponent for CgLabel { + fn render(&self) -> Result { + let mut result = Frame::new(self.position, self.dimensions)?; + + let shortened_string = self.content.chars().take(self.dimensions.x).collect::(); + + for (i, c) in shortened_string.chars().enumerate() { + result.set_pos(Position::new(i, 0), ColouredChar::white(c)); + }; + + serial_println!("{:?}", result); + + Ok(result) + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/user/lib/libgui/mod.rs b/src/user/lib/libgui/mod.rs index 6bbad83..d6607d8 100644 --- a/src/user/lib/libgui/mod.rs +++ b/src/user/lib/libgui/mod.rs @@ -1,2 +1,3 @@ -pub mod libgui_core; -pub mod libgui_elements; +pub mod cg_core; +pub(crate) mod cg_widgets; + diff --git a/src/user/lib/libgui/libgui_core.rs b/src/user/lib/libgui_old_archive/libgui_core.rs similarity index 100% rename from src/user/lib/libgui/libgui_core.rs rename to src/user/lib/libgui_old_archive/libgui_core.rs diff --git a/src/user/lib/libgui/libgui_elements.rs b/src/user/lib/libgui_old_archive/libgui_elements.rs similarity index 100% rename from src/user/lib/libgui/libgui_elements.rs rename to src/user/lib/libgui_old_archive/libgui_elements.rs diff --git a/src/user/lib/libgui_old_archive/mod.rs b/src/user/lib/libgui_old_archive/mod.rs new file mode 100644 index 0000000..6bbad83 --- /dev/null +++ b/src/user/lib/libgui_old_archive/mod.rs @@ -0,0 +1,2 @@ +pub mod libgui_core; +pub mod libgui_elements; diff --git a/src/user/lib/mod.rs b/src/user/lib/mod.rs index 5703c35..6dbd4ee 100644 --- a/src/user/lib/mod.rs +++ b/src/user/lib/mod.rs @@ -1,3 +1,3 @@ -pub mod libgui; +pub mod libgui_old_archive; pub mod coords; -pub mod gui_v2; +pub mod libgui;