From 140ac0ab32b7bd25aa3aa86e7b3299107d25609d Mon Sep 17 00:00:00 2001 From: FantasyPvP <80643031+FantasyPvP@users.noreply.github.com> Date: Fri, 24 Nov 2023 00:40:27 +0000 Subject: [PATCH] updates updated grapher to use new widtgets system (not working yet but close) made a basic app using the widgets system implemented CgTextEdit for CgLineEdit (now fully working, but there may be bugs so i'll address them tomorrow) --- src/system/kernel/render.rs | 6 + src/system/kernel/tasks/keyboard.rs | 48 ++--- src/system/std/frame.rs | 23 +-- src/system/std/io.rs | 8 +- src/user/bin/crystal_rpg/init.rs | 5 +- src/user/bin/gameoflife.rs | 6 +- src/user/bin/grapher.rs | 137 ++++++++++--- src/user/bin/shell.rs | 87 ++++++--- src/user/bin/snake.rs | 10 +- src/user/bin/snake_old.rs | 290 ---------------------------- src/user/lib/libgui/cg_core.rs | 10 +- src/user/lib/libgui/cg_inputs.rs | 53 +++-- src/user/lib/libgui/cg_widgets.rs | 24 ++- 13 files changed, 288 insertions(+), 419 deletions(-) delete mode 100644 src/user/bin/snake_old.rs diff --git a/src/system/kernel/render.rs b/src/system/kernel/render.rs index e0f59de..5c8b1e0 100644 --- a/src/system/kernel/render.rs +++ b/src/system/kernel/render.rs @@ -261,10 +261,16 @@ impl Renderer { if self.application_mode { return; }; // only in terminal mode self.term_buffer.push([ScreenChar::null(); BUFFER_WIDTH]); self.col_pos = 0; + if self.term_buffer.len() > 100 { + self.term_buffer.remove(0); + } } fn internal_lastline(&mut self) { // goes back to previous line and shifts all lines down if self.application_mode { return; }; + if self.term_buffer.len() <= 25 { + self.term_buffer.insert(0, [ScreenChar::null(); BUFFER_WIDTH]); + } self.term_buffer.pop(); self.col_pos = BUFFER_WIDTH; } diff --git a/src/system/kernel/tasks/keyboard.rs b/src/system/kernel/tasks/keyboard.rs index 34917e5..71bb711 100644 --- a/src/system/kernel/tasks/keyboard.rs +++ b/src/system/kernel/tasks/keyboard.rs @@ -5,7 +5,7 @@ use x86_64::instructions::interrupts; use conquer_once::spin::OnceCell; use crossbeam_queue::ArrayQueue; -use crate::println; +use crate::{println, serial_println}; use core::{pin::Pin, task::{Poll, Context}}; use futures_util::stream::Stream; @@ -15,8 +15,6 @@ use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1, Ke 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(); @@ -36,6 +34,7 @@ enum CharOrKeystroke { Keystroke(KeyCode), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum KeyStroke { Char(char), Ctrl, @@ -46,6 +45,9 @@ pub enum KeyStroke { RShift, Meta, RMeta, + Backspace, + Left, + Right, None, } @@ -60,6 +62,9 @@ impl KeyStroke { KeyCode::ShiftRight => KeyStroke::RShift, KeyCode::WindowsLeft => KeyStroke::Meta, KeyCode::WindowsRight => KeyStroke::RMeta, + KeyCode::Backspace => KeyStroke::Backspace, + KeyCode::ArrowLeft => KeyStroke::Left, + KeyCode::ArrowRight => KeyStroke::Right, _ => KeyStroke::None, } } @@ -81,20 +86,12 @@ impl KeyboardHandler { if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { if let Some(key) = self.keyboard.process_keyevent(key_event) { match key { - DecodedKey::Unicode(character) => { - if character == b'\x08' as char { // checks if the character is a backspace - interrupts::without_interrupts(|| { - RENDERER.lock().backspace(); // runs the backspace function of the vga buffer to remove the last character - }); - return None; - } else { - return Some(KeyStroke::Char(character)); - } - }, + DecodedKey::Unicode(character) => return Some(KeyStroke::Char(character)), DecodedKey::RawKey(key) => { - print!("{:?}", key) - match key { - KeyCode::NOn + print!("{:?}", key); + match KeyStroke::from_keycode(key) { + KeyStroke::None => (), + k => return Some(k) } }, } @@ -108,11 +105,8 @@ impl KeyboardHandler { loop { match self.get_keystroke_inner().await { Some(c) => match c { - CharOrKeystroke::Char(c) => return KeyStroke::Char(c), - CharOrKeystroke::Keystroke(c) => match KeyStroke::from_keycode(c) { - KeyStroke::None => (), - key => return key - } + KeyStroke::None => (), + c => return c }, None => () } @@ -157,8 +151,16 @@ impl KeyboardHandler { None => { val.pop(); continue; }, }; - if let CharOrKeystroke::Char(c) = character { - print!("{}", character); + if let KeyStroke::Char(c) = character { + if c == '\x08' { + val.pop(); + interrupts::without_interrupts(|| { + RENDERER.lock().backspace(); // runs the backspace function of the vga buffer to remove the last character + }); + continue; + } + + print!("{}", c); let (c, execute): (char, bool) = match c { '\n' => (c, true), _ => (c, false), diff --git a/src/system/std/frame.rs b/src/system/std/frame.rs index b7da2d5..610986a 100644 --- a/src/system/std/frame.rs +++ b/src/system/std/frame.rs @@ -96,8 +96,7 @@ impl Frame { for line in elemstr.split("\n") { element.frame.push( - line - .chars() + line.chars() .map(|c| ColouredChar::new(c)) .collect::>() ); @@ -105,7 +104,7 @@ impl Frame { for row in element.clone().frame { let n = row.len(); - if n > element.dimensions.x as usize { + if n > element.dimensions.x { element.dimensions.x = n; } } @@ -116,21 +115,19 @@ impl Frame { self.frame.clone() } - pub fn render_to_screen(&self) -> Result<(), RenderError> { + pub fn write_to_screen(&self) -> Result<(), RenderError> { let mut frame: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT] = [[ScreenChar::null(); BUFFER_WIDTH]; BUFFER_HEIGHT]; for (i, row) in self.frame.iter().enumerate() { for (j, col) in row.iter().enumerate() { - //println!("{} {} {}", i, j, col); - frame[i + self.position.y as usize][j + self.position.x as usize] = col.as_screen_char(); + frame[i + self.position.y][j + self.position.x] = col.as_screen_char(); }; } RENDERER.lock().render_frame(frame); Ok(()) } - pub fn position(&self) -> Position { + pub fn get_position(&self) -> Position { self.position } - pub fn set_position(&mut self, position: Position) { self.position = position } @@ -140,7 +137,7 @@ impl Frame { pub fn write(&mut self, position: Position, char: ColouredChar) { self.frame[position.y][position.x] = char } - pub fn render_element(&mut self, other: &Frame) { + pub fn place_child_element(&mut self, other: &Frame) { for (i, row) in other.frame.iter().enumerate() { for (j, chr) in row.iter().enumerate() { self.frame[i + other.position.y][j + other.position.x] = *chr @@ -152,20 +149,20 @@ impl Frame { let (mut x, mut y) = (false, false); - if element.dimensions().x + element.position().x > self.dimensions.x { + if element.dimensions().x + element.get_position().x > self.dimensions.x { if should_panic { panic!( "Element is to large to be rendered {} {}", - element.dimensions().x + element.position().x, + element.dimensions().x + element.get_position().x, self.dimensions.x )} else { x = true; } } - if element.dimensions().y + element.position().y > self.dimensions.y { + if element.dimensions().y + element.get_position().y > self.dimensions.y { if should_panic { panic!( "Element is to large to be rendered {} {}", - element.dimensions().y + element.position().y, + element.dimensions().y + element.get_position().y, self.dimensions.y )} else { y = true; diff --git a/src/system/std/io.rs b/src/system/std/io.rs index 27f1f0f..e0025ea 100644 --- a/src/system/std/io.rs +++ b/src/system/std/io.rs @@ -1,7 +1,8 @@ use crate::{ kernel::render::{RENDERER, self}, - kernel::tasks::keyboard::{KEYBOARD, KeyStroke}, + kernel::tasks::keyboard::{KEYBOARD}, }; +pub use crate::kernel::tasks::keyboard::KeyStroke; use alloc::string::String; use alloc::vec::Vec; @@ -17,6 +18,7 @@ use crate::std::frame::RenderError; pub struct Stdin {} impl Stdin { + pub const BACKSPACE: char = b'\x08' as char; /// 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; @@ -24,13 +26,13 @@ impl Stdin { } /// waits for a keystroke | blocking - pub async fn keystroke() -> char { + pub async fn keystroke() -> KeyStroke { let chr = KEYBOARD.lock().get_keystroke().await; chr } /// gets the next keystroke if any is present | non blocking - pub fn try_keystroke() -> Option { + pub fn try_keystroke() -> Option { let chr = KEYBOARD.lock().try_keystroke(); chr } diff --git a/src/user/bin/crystal_rpg/init.rs b/src/user/bin/crystal_rpg/init.rs index a18d414..56ca7f6 100644 --- a/src/user/bin/crystal_rpg/init.rs +++ b/src/user/bin/crystal_rpg/init.rs @@ -17,6 +17,7 @@ use crate::{ Error, }, }; +use crate::std::io::{KeyStroke, Stdin}; pub struct GameLoop; @@ -98,7 +99,9 @@ impl Application for GameLoop { loop { - println!("{}", io::Stdin::keystroke().await) + if let KeyStroke::Char(c) = Stdin::keystroke().await { + println!("{}", c) + } } Ok(()) diff --git a/src/user/bin/gameoflife.rs b/src/user/bin/gameoflife.rs index bb7b10a..f03ff2f 100644 --- a/src/user/bin/gameoflife.rs +++ b/src/user/bin/gameoflife.rs @@ -8,7 +8,7 @@ use async_trait::async_trait; use crate::kernel::render::{Color, ColorCode}; use crate::{println, serial_println}; use crate::std::frame::{ColouredChar, Frame, Position, Dimensions, RenderError}; -use crate::std::io::{Screen, Stdin}; +use crate::std::io::{KeyStroke, Screen, Stdin}; use crate::std::time::wait; use crate::user::bin::snake::Game; @@ -75,7 +75,7 @@ impl GameOfLife { self.render().map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; match Stdin::try_keystroke() { - Some('x') => break 'mainloop, + Some(KeyStroke::Char('x')) => break 'mainloop, _ => {}, } @@ -115,7 +115,7 @@ impl GameOfLife { } fn render(&self) -> Result<(), RenderError> { - self.frame.render_to_screen()?; + self.frame.write_to_screen()?; Ok(()) } } diff --git a/src/user/bin/grapher.rs b/src/user/bin/grapher.rs index 1f8201a..6d64af1 100644 --- a/src/user/bin/grapher.rs +++ b/src/user/bin/grapher.rs @@ -4,13 +4,18 @@ use alloc::vec::Vec; use alloc::boxed::Box; use async_trait::async_trait; use crate::{println, serial_println}; +use crate::kernel::render::RenderError; use crate::shell::command_handler; use crate::std::application::{Application, Error}; use crate::std::frame::{self, Frame, Position, Dimensions, ColouredChar}; -use crate::std::io::{Screen, Stdin}; +use crate::std::io::{KeyStroke, Screen, Stdin}; +use crate::user::lib::libgui::cg_core::{CgComponent, CgTextEdit}; +use crate::user::lib::libgui::cg_inputs::CgLineEdit; +use crate::user::lib::libgui::cg_widgets::CgContainer; +use super::calc; -const OFFSET_X: i64 = 40; -const OFFSET_Y: i64 = 12; +const OFFSET_X: i64 = 39; +const OFFSET_Y: i64 = 10; pub struct Grapher { points: Vec, @@ -32,15 +37,86 @@ impl Application for Grapher { fn new() -> Self { Self { points: Vec::new(), - frame: Frame::new(Position::new(0, 0), Dimensions::new(80, 25)).unwrap() + frame: Frame::new(Position::new(0, 0), Dimensions::new(78, 22)).unwrap() } } async fn run(&mut self, args: Vec) -> Result<(), Error> { - let mut equation: String = args.into_iter().collect(); - use super::calc; + Screen::Application.set_mode().map_err(|_| Error::ApplicationError(String::from("failed to set application mode")))?; + + if args.len() > 0 { + let equation: String = args.into_iter().collect(); + self.graph_equation(equation); + + if let Ok(frame) = self.render() { + frame.write_to_screen().map_err(|_| Error::ApplicationError(String::from("failed to write to screen")))?; + } + + loop { + match Stdin::keystroke().await { + KeyStroke::Char('x') => break, + _ => continue, + } + } + + Screen::Terminal.set_mode().map_err(|_| Error::ApplicationError(String::from("failed to set terminal mode")))?; + return Ok(()); + } + else { + let mut entry_box = CgLineEdit::new( + Position::new(1, 23), + 78, + String::from("function >") + ); + + let mut commandresult = String::new(); + + while let c = Stdin::keystroke().await { + let mut container = CgContainer::new( + Position::new(0, 0), + Dimensions::new(80, 25), + false, + ); + + match c { + KeyStroke::Char('\n') => { + commandresult = entry_box.text.iter().collect(); + entry_box.clear(); + }, + KeyStroke::Char(Stdin::BACKSPACE) => { + serial_println!("backspace"); + entry_box.backspace() + }, + KeyStroke::Char(c) => entry_box.write_char(c), + KeyStroke::Left => entry_box.move_cursor(false), + KeyStroke::Right => entry_box.move_cursor(true), + KeyStroke::Alt => break, + _ => {} + } + + if commandresult.len() > 0 { + let equation = commandresult.chars().take(40).collect(); + self.graph_equation(equation); + commandresult.clear(); + } + + container.insert(Box::new(self)); + container.insert(Box::new(&entry_box)); + + if let Ok(frame) = container.render() { + frame.write_to_screen().map_err(|_| Error::ApplicationError(String::from("failed to write to screen")))?; + } + } + } + + Ok(()) + } +} + +impl Grapher { + + fn graph_equation(&mut self, equation: String) { let cal = calc::Calculator::new(); - for x in -4000..4000 { let x = x as f64 / 100.0; @@ -48,8 +124,7 @@ impl Application for Grapher { if c == 'x' { format!("({})", x) } else { c.to_string() } }).collect::(); - let fx = cal.calculate(new_eq).map_err(|_| Error::ApplicationError(String::from("failed to calculate"))); - + let fx = cal.calculate(new_eq); if let Ok(y) = fx { self.render_point(PointF64 { x, @@ -57,45 +132,45 @@ impl Application for Grapher { }) } }; - - Screen::Application.set_mode().map_err(|_| Error::ApplicationError(String::from("failed to set application mode")))?; - self.display(); - - loop { - match Stdin::keystroke().await { - 'x' => break, - _ => continue, - } - } - - Screen::Terminal.set_mode().map_err(|_| Error::ApplicationError(String::from("failed to set terminal mode")))?; - Ok(()) } -} -impl Grapher { fn render_point(&mut self, point: PointF64) { - let point = PointI64 { x: point.x as i64, y: point.y as i64, }; - if point.x < -40 || point.x >= 40 || point.y < -12 || point.y >= 12 { + if point.x < -39 || point.x >= 39 || point.y < -10 || point.y >= 12 { return; } let offset_x = point.x + OFFSET_X; let offset_y = point.y + OFFSET_Y; - // serial_println!("{} {}", 24-offset_y as usize, offset_x as usize); - - self.frame.write(Position::new(offset_x as usize, 24-offset_y as usize), ColouredChar::new('*')); + self.frame.write(Position::new(offset_x as usize, 22-offset_y as usize), ColouredChar::new('*')); } fn display(&mut self) { - self.frame.render_to_screen().unwrap(); + self.frame.write_to_screen().unwrap(); } -} \ No newline at end of file +} + +impl CgComponent for Grapher { + fn render(&self) -> Result { + Ok(self.frame.clone()) + } +} + + + + + + + + + + + + diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index 70bb652..2b97b50 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -9,7 +9,7 @@ use vga::writers::{GraphicsWriter, PrimitiveDrawing}; use crate::{print, printerr, println, serial_println, std, std::application::{Application, Error}, user::bin::*}; use crate::kernel::render::ColorCode; use crate::std::frame::{Dimensions, Position}; -use crate::std::io::{Color, write, Screen, Stdin, Serial}; +use crate::std::io::{Color, write, Screen, Stdin, Serial, KeyStroke}; use crate::std::random::Random; use crate::user::bin::gigachad_detector::GigachadDetector; use crate::user::bin::grapher::Grapher; @@ -17,6 +17,7 @@ use crate::user::lib::libgui::{ cg_core::{CgComponent}, cg_widgets::{CgTextBox, CgContainer}, }; +use crate::user::lib::libgui::cg_core::CgTextEdit; use crate::user::lib::libgui::cg_inputs::CgLineEdit; use crate::user::lib::libgui::cg_widgets::{CgIndicatorBar, CgIndicatorWidget, CgLabel, CgStatusBar}; @@ -190,8 +191,8 @@ async fn exec() -> Result<(), Error> { "test_features" => { Screen::Application.set_mode().unwrap(); - new_eventloop(|c| match c { - 'x' => ('x', true), + setup_ui(|c| match c { + KeyStroke::Char('x') => (c, true), _ => (c, false), }).await; @@ -259,7 +260,7 @@ struct CmdHistory { history: Vec, } -async fn new_eventloop(input: impl Fn(char) -> (char, bool)) { +async fn setup_ui(input: impl Fn(KeyStroke) -> (KeyStroke, bool)) { let textbox = CgTextBox::new( String::from("i'd just like to interject for a moment"), @@ -269,42 +270,80 @@ async fn new_eventloop(input: impl Fn(char) -> (char, bool)) { true, ); + let mut label = CgLabel::new( + String::from("test label"), + Position::new(1, 1), + 40, + ); + let mut statusbar = CgStatusBar::new(Position::new(0, 0), Dimensions::new(80, 1)); let mut textedit = CgLineEdit::new( - Position::new(0, 1), + Position::new(0, 20), 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)); + let mut commandresult = String::new(); 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)); + match c { + KeyStroke::Char('\n') => { + commandresult = textedit.text.iter().collect(); + textedit.clear(); + }, + KeyStroke::Char(Stdin::BACKSPACE) => { + serial_println!("backspace"); + textedit.backspace() + }, + KeyStroke::Char(c) => textedit.write_char(c), + KeyStroke::Left => textedit.move_cursor(false), + KeyStroke::Right => textedit.move_cursor(true), + _ => {} + } + + if commandresult.len() > 0 { + let string = commandresult.chars().take(40).collect(); + label.set_text(string); + } + + container.insert(Box::new(&textbox)); + container.insert(Box::new(&statusbar)); + container.insert(Box::new(&textedit)); + container.insert(Box::new(&label)); + if let Ok(frame) = container.render() { - frame.render_to_screen().unwrap(); + frame.write_to_screen().unwrap(); } } +} + + + + + + + + + + + + + + + + + + + + + + + -} \ No newline at end of file diff --git a/src/user/bin/snake.rs b/src/user/bin/snake.rs index b65ab9d..d0cf987 100644 --- a/src/user/bin/snake.rs +++ b/src/user/bin/snake.rs @@ -4,7 +4,7 @@ use alloc::borrow::ToOwned; use core::arch::x86_64::_mm_test_all_ones; use core::cell::RefCell; use async_trait::async_trait; -use crate::std::io::{Color, Screen, Stdin}; +use crate::std::io::{Color, KeyStroke, Screen, Stdin}; use crate::std::time; use crate::kernel::tasks::keyboard::KEYBOARD; use crossbeam_queue::SegQueue; @@ -138,7 +138,7 @@ impl Game { // loop triggers when game is lost loop { match Stdin::keystroke().await { - 'x' => break 'gameloop, + KeyStroke::Char('x') => break 'gameloop, _ => continue, } } @@ -199,7 +199,7 @@ impl Game { } }); - frame.render_to_screen()?; + frame.write_to_screen()?; Ok(()) } @@ -221,7 +221,7 @@ impl Game { frame[12] = Game::centre_text(80, String::from(format!("ur score was {}", self.score))).chars().map(|c| ColouredChar::coloured(c, ColorCode::new(Color::LightGreen, Color::Black))).collect(); frame[14] = Game::centre_text(80, String::from("L bozo")).chars().map(|c| ColouredChar::coloured(c, ColorCode::new(Color::Red, Color::Black))).collect(); - frame.render_to_screen()?; + frame.write_to_screen()?; Ok(()) } } @@ -272,7 +272,7 @@ impl Snake { if self.ai_controlled { self.dir = PathFinder::decide(&self.head, tails, points_of_interest); } else { - if let Some(c) = Stdin::try_keystroke() { + if let Some(KeyStroke::Char(c)) = Stdin::try_keystroke() { self.dir = match c { 'w' => Direction::Degrees0, 'a' => Direction::Degrees270, diff --git a/src/user/bin/snake_old.rs b/src/user/bin/snake_old.rs deleted file mode 100644 index 037481e..0000000 --- a/src/user/bin/snake_old.rs +++ /dev/null @@ -1,290 +0,0 @@ -use alloc::string::String; -use alloc::{format, vec, vec::Vec, boxed::Box}; -use async_trait::async_trait; -use crate::std::io::{Color, Screen, Stdin}; -use crate::std::time; -use crate::kernel::tasks::keyboard::KEYBOARD; -use crossbeam_queue::SegQueue; -use crate::kernel::render::{ColorCode, ScreenChar}; -use crate::std::application::{Application, Error}; -use crate::std::random::Random; -use crate::system::std::frame::ColouredElement; - -#[derive(Clone, Debug, PartialEq)] -struct Point { - x: i8, - y: i8, -} - -#[derive(Clone, Debug, PartialEq)] -struct Position { - x: i8, - y: i8, - dir: Direction, -} - -#[derive(Clone, Debug, PartialEq)] -enum Direction { - Up, - Down, - Left, - Right, -} - -enum Status { - Scored, - Lost, - Exited, - None, -} - -pub struct Game { - snake: SegQueue, - head: Point, - poi: Point, - mv: char, - score: u8, - hardmode: bool, -} - - -snake.rs -#[async_trait] -impl Application for Game { - fn new() -> Self { - Self { - snake: SegQueue::new(), - head: Point { x: 5, y: 5 }, - poi: Point { x: 0, y: 0 }, - mv: ' ', - score: 0, - hardmode: false, - } - } - - async fn run(&mut self, _: Vec) -> Result<(), Error> { - Screen::application_mode(); - let clone = self.clone_snake(); - - // render the initial state of the screen. - self.render(clone).map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; - - - (5..=7).for_each(|x| { - self.snake.push(Point { x, y: 5 }); - }); - self.head = Point { x: 7, y: 5 }; - self.new_poi(); - - self.gameloop().await?; - - Screen::terminal_mode(); - Ok(()) - } -} - -impl Game { - - async fn gameloop(&mut self) -> Result<(), Error> { // main gameloop - 'gameloop: loop { - - time::wait(0.1); - - if let Some(c) = Stdin::try_keystroke() { - self.mv = c; - } - - //self.mv = Stdin::keystroke().await; - - match self.mv { - 'w' => self.head.y -= 1, - 'a' => self.head.x -= 1, - 's' => self.head.y += 1, - 'd' => self.head.x += 1, - 'x' => break, - _ => continue, - } - - self.snake.push(Point { x: self.head.x, y: self.head.y }); // new head added - - if self.head == self.poi { - self.new_poi(); - self.score += 1 - } else { - self.snake.pop().unwrap(); // tail removed if score does not increase - } - - if self.lose_condition() { - self.render_end_screen().map_err(|_| Error::ApplicationError(String::from("failed to render game over screen")))?; - while let chr = KEYBOARD.lock().get_keystroke().await { - match chr { - 'x' => break 'gameloop, - _ => continue, - } - } - } - - let clone = self.clone_snake(); - self.render(clone).map_err(|_| Error::ApplicationError(String::from("failed to render game screen")))?; - }; - Ok(()) - } - - fn new_poi(&mut self) { - self.poi = Point { x: Random::int(3, 76) as i8, y: Random::int(3, 21) as i8 } - } - - fn render(&mut self, snake: Vec) -> Result<(), ()> { - let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; - snake.into_iter().for_each(|p| { - frame[p.y as usize][p.x as usize] = ScreenChar::new('@' as u8, ColorCode::new(Color::Cyan, Color::Black)); - }); - - frame[self.poi.y as usize][self.poi.x as usize] = ScreenChar::new('o' as u8, ColorCode::new(Color::Red, Color::Black)); - let literal = format!("snake go brr score: {}", self.score); - let msg = Game::centre_text(80, literal); - frame[1] = msg.chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::LightGreen, Color::Black))).collect(); - - let mut elem = ColouredElement::generate(frame, (80, 25)); - elem.render((0,0)) - } - - fn lose_condition(&mut self) -> bool { - let cloned = self.clone_snake(); - let snake_overlaps = (1..cloned.len()).any(|i| cloned[i..].contains(&cloned[i - 1])); // checks if any part of the snake overlaps itself - let out_of_bounds = cloned.iter().filter(|p| p.x < 0 || p.y < 0 || p.x > 79 || p.y > 24).count() > 0; // checks if the snake goes out of bounds - - snake_overlaps || out_of_bounds - } - - fn centre_text(dims: usize, text: String) -> String { // centres text in a string of whitespace of a given length - let max_pad = dims / 2; - let mut msg = String::new(); - msg.push_str(" ".repeat(max_pad - round_up(text.len() as f64 / 2.0)).as_str()); - msg.push_str(text.as_str()); - msg.push_str(" ".repeat(max_pad - round_down(text.len() as f64 / 2.0 + 0.51)).as_str()); - msg - } - - fn render_end_screen(&mut self) -> Result<(), ()> { - let mut frame = vec![vec![ScreenChar::null(); 80]; 25]; - - frame[10] = Game::centre_text(80, String::from("u lost")).chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::Red, Color::Black))).collect(); - frame[12] = Game::centre_text(80, String::from(format!("ur score was {}", self.score))).chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::LightGreen, Color::Black))).collect(); - frame[14] = Game::centre_text(80, String::from("L bozo")).chars().map(|c| ScreenChar::new(c as u8, ColorCode::new(Color::Red, Color::Black))).collect(); - - - let mut elem = ColouredElement::generate(frame, (80, 25)); - elem.render((0,0)) - } - - fn clone_snake(&mut self) -> Vec { - let mut cloned= Vec::new(); - let mut snake = SegQueue::new(); - while !self.snake.is_empty() { - let item = self.snake.pop().unwrap(); - cloned.push(item.clone()); - snake.push(item); - } - self.snake = snake; - cloned - } -} - - - - - - - - - - - - - -struct Snake { - ai_controlled: bool, - head: Point, - tail: Vec, - dir: Direction, -} - -impl Snake { - fn ai(id: usize) -> Self { - Self { - ai_controlled: true, - head: Point { x: 1 + id as i8 * 2, y: 1 }, - tail: Vec::new(), - dir: Direction::Up - } - } - fn player(id: usize) -> Self { - Self { - ai_controlled: false, - head: Point { x: 1 + id as i8 * 2, y: 1 }, - tail: Vec::new(), - dir: Direction::Up, - } - } - - fn next(&mut self, points_of_interest: &Vec, tails: &Vec) -> Status { // returns (lose_condition, scored) - - // uses pathing algorithm if ai else keyboard input if human - if self.ai_controlled { - self.dir = self.decide_dir(); - } else { - if let Some(c) = Stdin::try_keystroke() { - self.dir = match c { - 'w' => Direction::Up, - 'a' => Direction::Left, - 's' => Direction::Down, - 'd' => Direction::Right, - 'x' => return Status::Exited, - _ => self.dir.clone(), - } - } - } - - self.tail.push(self.head.clone()); - - match self.dir { - Direction::Up => self.head.y -= 1, - Direction::Down => self.head.y += 1, - Direction::Left => self.head.x -= 1, - Direction::Right => self.head.x += 1, - } - - if self.lose_condition(tails) { - return Status::Lost; - } - - if points_of_interest.contains(&self.head) { - Status::Scored - } else { - self.tail.remove(0); - Status::None - } - } - - fn decide_dir(&mut self) -> Direction { - unimplemented!() // implement a basic pathfinding or random movement algorithm - } - - fn lose_condition(&mut self, tails: &Vec) -> bool { // where tails includes the tail of every other snake - let p = self.head.clone(); - - let snake_overlaps = tails.contains(&self.head); // checks if any part of the snake overlaps itself - let out_of_bounds = p.x < 0 || p.y < 0 || p.x > 79 || p.y > 24; // checks if the snake goes out of bounds - - snake_overlaps || out_of_bounds - } -} - - -fn round_up(n: f64) -> usize { - (n + 0.99) as usize -} -fn round_down(n: f64) -> usize { - n as usize -} diff --git a/src/user/lib/libgui/cg_core.rs b/src/user/lib/libgui/cg_core.rs index 2386391..595c08b 100644 --- a/src/user/lib/libgui/cg_core.rs +++ b/src/user/lib/libgui/cg_core.rs @@ -18,12 +18,12 @@ 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. +/// trait for components that can have editable text, such as search boxes, command palettes, 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; + fn write_char(&mut self, c: char); // this can also be implemented in a way that inserts characters + fn backspace(&mut self); + fn move_cursor(&mut self, direction: bool); // true = right, false = left + fn clear(&mut self); } diff --git a/src/user/lib/libgui/cg_inputs.rs b/src/user/lib/libgui/cg_inputs.rs index 9549eb9..157a8a7 100644 --- a/src/user/lib/libgui/cg_inputs.rs +++ b/src/user/lib/libgui/cg_inputs.rs @@ -3,6 +3,7 @@ use alloc::vec::Vec; use crate::std::frame::{ColouredChar, Dimensions, Frame, Position, RenderError}; use crate::user::lib::libgui::cg_core::{CgComponent, CgTextEdit}; +#[derive(Debug, Clone)] pub struct CgLineEdit { pub position: Position, pub dimensions: Dimensions, @@ -26,28 +27,56 @@ impl CgLineEdit { impl CgComponent for CgLineEdit { fn render(&self) -> Result { let mut frame = Frame::new(self.position, self.dimensions)?; - - let mut ptr = 0; + let mut idx = 0; for c in self.prompt.chars() { - if ptr >= self.dimensions.x { + if idx >= self.dimensions.x { break; } - frame.write(Position::new(ptr, 0), ColouredChar::new(c)); - ptr += 1 + frame.write(Position::new(idx, 0), ColouredChar::new(c)); + idx += 1 } - ptr += 1; // create a space between the prompt and the text + idx += 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 + if idx + self.text.len() > self.dimensions.x { + frame.write(Position::new(idx, 0), ColouredChar::new('[')); + frame.write(Position::new(idx + 1, 0), ColouredChar::new('.')); + frame.write(Position::new(idx + 2, 0), ColouredChar::new('.')); + frame.write(Position::new(idx + 3, 0), ColouredChar::new('.')); + frame.write(Position::new(idx + 4, 0), ColouredChar::new(']')); + idx += 5 } + + self.text.iter().rev().take(self.dimensions.x - idx).rev().for_each(|c| { + frame.write(Position::new(idx, 0), ColouredChar::new(*c)); + idx += 1 + }); + Ok(frame) } } +impl CgTextEdit for CgLineEdit { + fn write_char(&mut self, c: char) { + self.text.insert(self.ptr, c); + self.ptr += 1; + } + fn backspace(&mut self) { + if self.ptr >= 0 { + self.ptr -= 1; + self.text.remove(self.ptr); + } + } + fn move_cursor(&mut self, direction: bool) { + match direction { + true => if self.ptr < self.text.len() { self.ptr += 1; }, + false => if self.ptr > 0 { self.ptr -= 1; }, + } + } + fn clear(&mut self) { + self.text.clear(); + self.ptr = 0 + } +} diff --git a/src/user/lib/libgui/cg_widgets.rs b/src/user/lib/libgui/cg_widgets.rs index 0c75b15..acb4057 100644 --- a/src/user/lib/libgui/cg_widgets.rs +++ b/src/user/lib/libgui/cg_widgets.rs @@ -10,7 +10,6 @@ use super::cg_core::{ use crate::std::frame::{ColouredChar, Dimensions, Position, Frame}; use crate::std::io::Color; - pub struct CgContainer<'a> { pub elements: Vec>, pub position: Position, @@ -27,6 +26,9 @@ impl<'a> CgContainer<'a> { outlined, } } + pub fn insert(&mut self, element: Box<&'a dyn CgComponent>) { + self.elements.push(element); + } } impl CgOutline for CgContainer<'_> { @@ -58,7 +60,7 @@ impl CgComponent for CgContainer<'_> { 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), + Ok(()) => result.place_child_element(&frame), Err(e) => return Err(e), } } @@ -71,7 +73,7 @@ impl CgComponent for CgContainer<'_> { } } - +#[derive(Debug, Clone)] pub struct CgTextBox { title: String, content: String, @@ -178,7 +180,7 @@ impl CgOutline for CgTextBox { - +#[derive(Debug, Clone)] pub struct CgLabel { content: String, position: Position, @@ -193,6 +195,9 @@ impl CgLabel { dimensions: Dimensions::new(width, 1), } } + pub fn set_text(&mut self, text: String) { + self.content = text; + } } impl CgComponent for CgLabel { @@ -208,6 +213,7 @@ impl CgComponent for CgLabel { } } +#[derive(Debug, Clone)] pub struct CgIndicatorWidget { content: String, colour: ColorCode, @@ -256,6 +262,7 @@ impl CgComponent for CgIndicatorWidget { } } +#[derive(Debug, Clone)] pub struct CgIndicatorBar { pub fields: Vec, position: Position, @@ -288,7 +295,7 @@ impl CgComponent for CgIndicatorBar { width_idx += widget.len(); match result.render_bounds_check(&frame, true) { - Ok(()) => result.render_element(&frame), + Ok(()) => result.place_child_element(&frame), Err(e) => return Err(e), } } @@ -297,6 +304,7 @@ impl CgComponent for CgIndicatorBar { } } +#[derive(Debug, Clone)] pub struct CgStatusBar { position: Position, dimensions: Dimensions, @@ -321,10 +329,8 @@ impl CgComponent for CgStatusBar { 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); + frame.place_child_element(&window_title); + frame.place_child_element(&screen_mode); Ok(frame) }