From f48eb133b37c66a16819c9b7410c29b7630c0e6a Mon Sep 17 00:00:00 2001 From: FantasyPvP <80643031+FantasyPvP@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:02:32 +0000 Subject: [PATCH] we frikin did it bois gui library works frrr implementation: - containers now store a Widget type. - every widget has a unique id - there is a data store that maps the id to the internally stored gui component. - the compoenents can be fetched from the datastore using their Widget type - the datastore uses a hashmap so fetching shouldnt have any real performance penalty. - widgets can be fetched, inserted, updated and rendered from the datastore - the render method can be called directly from the Widget type without having to know the underlying type that it references --- src/user/bin/grapher.rs | 57 +++++++++---- src/user/bin/shell.rs | 91 +++++++++++++-------- src/user/lib/libgui/cg_core.rs | 129 ++++++++++++++++++++++++++++-- src/user/lib/libgui/cg_inputs.rs | 4 + src/user/lib/libgui/cg_widgets.rs | 46 +++++++---- 5 files changed, 256 insertions(+), 71 deletions(-) diff --git a/src/user/bin/grapher.rs b/src/user/bin/grapher.rs index 3050f04..6c63454 100644 --- a/src/user/bin/grapher.rs +++ b/src/user/bin/grapher.rs @@ -3,6 +3,7 @@ use alloc::{format, vec}; use alloc::vec::Vec; use alloc::boxed::Box; use alloc::sync::Arc; +use core::any::Any; use async_trait::async_trait; use spin::Mutex; use crate::{println, serial_println}; @@ -17,18 +18,20 @@ use crate::user::lib::libgui::{ cg_widgets::CgContainer, cg_inputs::CgLineEdit, }; -use crate::user::lib::libgui::cg_core::CgTextEdit; +use crate::user::lib::libgui::cg_core::{CgTextEdit, Widget}; use super::calc; const OFFSET_X: i64 = 39; const OFFSET_Y: i64 = 10; +#[derive(Clone)] pub struct Grapher { points: Vec, frame: Frame, } +#[derive(Clone, Debug)] struct PointF64 { x: f64, y: f64, @@ -71,35 +74,40 @@ impl Application for Grapher { return Ok(()); } else { - let mut entry_box = CgLineEdit::new( + let mut container = CgContainer::new( + Position::new(0, 0), + Dimensions::new(80, 25), + true, + ); + + container.insert("entry_box", Widget::insert(CgLineEdit::new( Position::new(1, 23), 78, String::from("function >") - ); + ))); + container.insert("grapher", Widget::insert(self.clone())); let mut commandresult = String::new(); while let c = Stdin::keystroke().await { - let mut container = CgContainer::new( - Position::new(0, 0), - Dimensions::new(80, 25), - true, - ); + + let mut entry_widget = container.elements.get("entry_box").unwrap(); + let mut entry = entry_widget.fetch::().unwrap(); match c { KeyStroke::Char('\n') => { - commandresult = entry_box.text.iter().collect(); - entry_box.clear(); + commandresult = entry.text.iter().collect(); + entry.clear(); }, KeyStroke::Char(Stdin::BACKSPACE) => { - entry_box.backspace() + entry.backspace() }, KeyStroke::Char('`') => { break; } - KeyStroke::Char(c) => entry_box.write_char(c), - KeyStroke::Left => entry_box.move_cursor(false), - KeyStroke::Right => entry_box.move_cursor(true), + KeyStroke::Char(c) => entry.write_char(c), + KeyStroke::Left => entry.move_cursor(false), + KeyStroke::Right => entry.move_cursor(true), KeyStroke::Alt => break, _ => {} } @@ -107,13 +115,27 @@ impl Application for Grapher { if commandresult.len() > 0 { self.reset_frame(); self.graph_equation(commandresult.clone()); + + let self_widget = container.elements.get("grapher").unwrap(); + self_widget.update(self.clone()); + commandresult.clear(); } - container.insert(Box::new(self)); - container.insert(Box::new(&entry_box)); + serial_println!("{:?}", entry.text); + entry_widget.update(entry); if let Ok(frame) = container.render() { + + let self_widget = container.elements.get("grapher").unwrap(); + let self_clone = self_widget.fetch::().unwrap(); + serial_println!("{:?}", self_clone.points); + + let entry = container.elements.get("entry_box").unwrap(); + let entry_clone = entry.fetch::().unwrap(); + serial_println!("{:?}", entry_clone.text); + + frame.write_to_screen().map_err(|_| Error::ApplicationError(String::from("failed to write to screen")))?; } } @@ -171,6 +193,9 @@ impl CgComponent for Grapher { fn render(&self) -> Result { Ok(self.frame.clone()) } + fn as_any(&self) -> &dyn Any { + self + } } diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs index 4998a8e..6b30fb5 100644 --- a/src/user/bin/shell.rs +++ b/src/user/bin/shell.rs @@ -16,6 +16,7 @@ use crate::user::lib::libgui::{ cg_widgets::{CgTextBox, CgContainer, CgIndicatorBar, CgIndicatorWidget, CgLabel, CgStatusBar}, cg_inputs::CgLineEdit, }; +use crate::user::lib::libgui::cg_core::Widget; lazy_static! { pub static ref CMD: Mutex = Mutex::new(CommandHandler::new()); @@ -268,6 +269,8 @@ async fn setup_ui(input: impl Fn(KeyStroke) -> (KeyStroke, bool)) { 40, ); + test_new_datastore().await; + let mut statusbar = CgStatusBar::new(Position::new(0, 0), Dimensions::new(80, 1)); let mut textedit = CgLineEdit::new( @@ -278,41 +281,65 @@ async fn setup_ui(input: impl Fn(KeyStroke) -> (KeyStroke, bool)) { let mut commandresult = String::new(); - while let (c, false) = input(Stdin::keystroke().await) { - let mut container = CgContainer::new( - Position::new(0, 0), - Dimensions::new(80, 25), - false, - ); + // while let (c, false) = input(Stdin::keystroke().await) { + // let mut container = CgContainer::new( + // Position::new(0, 0), + // Dimensions::new(80, 25), + // false, + // ); + // + // 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.clone(); + // textbox.content = string; + // } + // + // container.insert(Box::new(textbox.clone())); + // container.insert(Box::new(statusbar.clone())); + // container.insert(Box::new(textedit.clone())); + // + // if let Ok(frame) = container.render() { + // frame.write_to_screen().unwrap(); + // } + // } +} - 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), - _ => {} - } +async fn test_new_datastore() { + let container = Widget::insert(CgContainer::new( + Position::new(0, 0), + Dimensions::new(80, 25), + true, + )); - if commandresult.len() > 0 { - let string = commandresult.clone(); - textbox.content = string; - } + let textbox = Widget::insert(CgTextBox::new( + String::from("test textbox"), + String::from("dam"), + Position::new(2, 5), + Dimensions::new(40, 12), + true, + )); - container.insert(Box::new(&textbox)); - container.insert(Box::new(&statusbar)); - container.insert(Box::new(&textedit)); + let mut c = textbox.fetch::().unwrap(); + c.content = String::from("dam2"); + textbox.update(c); - if let Ok(frame) = container.render() { - frame.write_to_screen().unwrap(); - } - } + let c = textbox.fetch::().unwrap(); + + serial_println!("{}", c.content); } @@ -333,8 +360,6 @@ async fn setup_ui(input: impl Fn(KeyStroke) -> (KeyStroke, bool)) { - - diff --git a/src/user/lib/libgui/cg_core.rs b/src/user/lib/libgui/cg_core.rs index 6d17bb8..78a86c9 100644 --- a/src/user/lib/libgui/cg_core.rs +++ b/src/user/lib/libgui/cg_core.rs @@ -1,12 +1,17 @@ -use alloc::boxed::Box; -use alloc::string::String; -use alloc::vec; -use alloc::vec::Vec; -use core::slice::from_mut; +use hashbrown::HashMap; +use spin::{Mutex, MutexGuard}; use crate::{printerr, serial_println}; use crate::std::frame::{ColouredChar, Dimensions, Position, special_char, Frame, RenderError, ColorCode}; -use crate::user::lib::libgui::cg_inputs::CgLineEdit; -use crate::user::lib::libgui::cg_widgets::{CgContainer, CgTextBox, CgIndicatorBar, CgIndicatorWidget, CgLabel, CgStatusBar}; + +use alloc::{ + boxed::Box, + sync::Arc, + vec::Vec, + vec, + string::String, +}; +use core::any::Any; +use lazy_static::lazy_static; /// implement this trait if you require the widget to be able to have an outline pub trait CgOutline: CgComponent { @@ -15,10 +20,13 @@ pub trait CgOutline: CgComponent { /// 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 { +pub trait CgComponent: Any { fn render(&self) -> Result; + + fn as_any(&self) -> &dyn Any; } + /// 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(&mut self, c: char); // this can also be implemented in a way that inserts characters @@ -26,3 +34,108 @@ pub trait CgTextEdit: CgComponent { fn move_cursor(&mut self, direction: bool); // true = right, false = left fn clear(&mut self); } + + + +static ID_COUNTER: Mutex = Mutex::new(0); + +lazy_static!( + static ref GUITREE: Mutex = Mutex::new(DataStore::new()); +); + + + +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct Widget { + id: usize, +} +impl Widget { + fn new() -> Self { + let mut id_counter = ID_COUNTER.lock(); + let id = Widget { + id: *id_counter + }; + *id_counter += 1; + id + } + + pub fn fetch(&self) -> Option { + GUITREE.lock().fetch(self) + } + + pub fn insert(item: T) -> Self { + let mut id_counter = ID_COUNTER.lock(); + let id = Widget { id: *id_counter }; + *id_counter += 1; + GUITREE.lock().insert(&id, item); + id + } + + pub fn update(&self, item: T) { + GUITREE.lock().insert(self, item); + } + + pub fn render(&self) -> Result { + if let Some(frame) = GUITREE.lock().frame(self) { + frame + } else { + panic!("CRITICAL: Widget not found in tree"); + } + } +} + +impl Drop for Widget { + fn drop(&mut self) { + GUITREE.lock().remove(self); + } +} + + + +struct DataStore { + items: Mutex>>>, + id_counter: Mutex, +} + +impl DataStore { + fn new() -> Self { + DataStore { + items: Mutex::new(HashMap::new()), + id_counter: Mutex::new(0), + } + } + + fn insert(&self, id: &Widget, item: T) { + let mut items = self.items.lock(); + items.insert(id.id, Arc::new(Mutex::new(item))); + } + + fn fetch(&self, id: &Widget) -> Option where T: Any + Send + Sync { + let id = id.id; + + let items = self.items.lock(); + items.get(&id).and_then(|arc| { + let any_mutex = arc.lock(); + let any_ref = any_mutex.as_any(); + any_ref.downcast_ref::().cloned() + }) + } + + fn frame(&self, id: &Widget) -> Option> { + let items = self.items.lock(); + items.get(&id.id).and_then(|arc| { + let item = arc.lock(); + Some(item.render()) + }) + } + + fn remove(&self, id: &Widget) { + let mut items = self.items.lock(); + items.remove(&id.id); + } +} + + + + + diff --git a/src/user/lib/libgui/cg_inputs.rs b/src/user/lib/libgui/cg_inputs.rs index d3b20cd..18dc4c1 100644 --- a/src/user/lib/libgui/cg_inputs.rs +++ b/src/user/lib/libgui/cg_inputs.rs @@ -1,5 +1,6 @@ use alloc::string::String; use alloc::vec::Vec; +use core::any::Any; use crate::std::frame::{ColouredChar, Dimensions, Frame, Position, RenderError}; use crate::user::lib::libgui::cg_core::{CgComponent, CgTextEdit}; @@ -56,6 +57,9 @@ impl CgComponent for CgLineEdit { Ok(frame) } + fn as_any(&self) -> &dyn Any { + self + } } impl CgTextEdit for CgLineEdit { diff --git a/src/user/lib/libgui/cg_widgets.rs b/src/user/lib/libgui/cg_widgets.rs index eba760a..8d4e841 100644 --- a/src/user/lib/libgui/cg_widgets.rs +++ b/src/user/lib/libgui/cg_widgets.rs @@ -1,36 +1,37 @@ use alloc::{boxed::Box, format, string::String, vec, vec::Vec}; use alloc::fmt::format; use alloc::string::ToString; +use core::any::Any; use core::cmp::{max, min}; +use hashbrown::HashMap; use crate::serial_println; -use super::cg_core::{ - CgComponent, CgOutline -}; +use super::cg_core::{CgComponent, CgOutline, Widget}; use crate::std::frame::{ColouredChar, Dimensions, Position, Frame, RenderError, ColorCode}; use crate::std::io::Color; -pub struct CgContainer<'a> { - pub elements: Vec>, +#[derive(Debug, Clone)] +pub struct CgContainer { + pub elements: HashMap<&'static str, Widget>, pub position: Position, pub dimensions: Dimensions, pub outlined: bool, } -impl<'a> CgContainer<'a> { - pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> CgContainer<'a> { +impl CgContainer { + pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> CgContainer { CgContainer { - elements: Vec::new(), + elements: HashMap::new(), position, dimensions, outlined, } } - pub fn insert(&mut self, element: Box<&'a dyn CgComponent>) { - self.elements.push(element); + pub fn insert(&mut self, name: &'static str, element: Widget) { + self.elements.insert(name,element); } } -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 { @@ -52,12 +53,12 @@ impl CgOutline for CgContainer<'_> { } } -impl CgComponent for CgContainer<'_> { +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()?; + let frame = widget.1.render()?; match result.render_bounds_check(&frame, true) { // TODO: this needs to be set to false for production Ok(()) => result.place_child_element(&frame), Err(e) => return Err(e), @@ -70,6 +71,9 @@ impl CgComponent for CgContainer<'_> { Ok(result) } + fn as_any(&self) -> &dyn Any { + self + } } #[derive(Debug, Clone)] @@ -150,7 +154,9 @@ impl CgComponent for CgTextBox { Ok(result) } - + fn as_any(&self) -> &dyn Any { + self + } } @@ -210,6 +216,9 @@ impl CgComponent for CgLabel { }; Ok(result) } + fn as_any(&self) -> &dyn Any { + self + } } #[derive(Debug, Clone)] @@ -259,6 +268,9 @@ impl CgComponent for CgIndicatorWidget { Ok(result) } + fn as_any(&self) -> &dyn Any { + self + } } #[derive(Debug, Clone)] @@ -301,6 +313,9 @@ impl CgComponent for CgIndicatorBar { Ok(result) } + fn as_any(&self) -> &dyn Any { + self + } } #[derive(Debug, Clone)] @@ -333,6 +348,9 @@ impl CgComponent for CgStatusBar { Ok(frame) } + fn as_any(&self) -> &dyn Any { + self + } } impl CgStatusBar {