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
This commit is contained in:
FantasyPvP
2023-11-27 19:02:32 +00:00
parent 417833fc41
commit f48eb133b3
5 changed files with 256 additions and 71 deletions
+41 -16
View File
@@ -3,6 +3,7 @@ use alloc::{format, vec};
use alloc::vec::Vec; use alloc::vec::Vec;
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::sync::Arc; use alloc::sync::Arc;
use core::any::Any;
use async_trait::async_trait; use async_trait::async_trait;
use spin::Mutex; use spin::Mutex;
use crate::{println, serial_println}; use crate::{println, serial_println};
@@ -17,18 +18,20 @@ use crate::user::lib::libgui::{
cg_widgets::CgContainer, cg_widgets::CgContainer,
cg_inputs::CgLineEdit, cg_inputs::CgLineEdit,
}; };
use crate::user::lib::libgui::cg_core::CgTextEdit; use crate::user::lib::libgui::cg_core::{CgTextEdit, Widget};
use super::calc; use super::calc;
const OFFSET_X: i64 = 39; const OFFSET_X: i64 = 39;
const OFFSET_Y: i64 = 10; const OFFSET_Y: i64 = 10;
#[derive(Clone)]
pub struct Grapher { pub struct Grapher {
points: Vec<PointF64>, points: Vec<PointF64>,
frame: Frame, frame: Frame,
} }
#[derive(Clone, Debug)]
struct PointF64 { struct PointF64 {
x: f64, x: f64,
y: f64, y: f64,
@@ -71,35 +74,40 @@ impl Application for Grapher {
return Ok(()); return Ok(());
} }
else { 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), Position::new(1, 23),
78, 78,
String::from("function >") String::from("function >")
); )));
container.insert("grapher", Widget::insert(self.clone()));
let mut commandresult = String::new(); let mut commandresult = String::new();
while let c = Stdin::keystroke().await { while let c = Stdin::keystroke().await {
let mut container = CgContainer::new(
Position::new(0, 0), let mut entry_widget = container.elements.get("entry_box").unwrap();
Dimensions::new(80, 25), let mut entry = entry_widget.fetch::<CgLineEdit>().unwrap();
true,
);
match c { match c {
KeyStroke::Char('\n') => { KeyStroke::Char('\n') => {
commandresult = entry_box.text.iter().collect(); commandresult = entry.text.iter().collect();
entry_box.clear(); entry.clear();
}, },
KeyStroke::Char(Stdin::BACKSPACE) => { KeyStroke::Char(Stdin::BACKSPACE) => {
entry_box.backspace() entry.backspace()
}, },
KeyStroke::Char('`') => { KeyStroke::Char('`') => {
break; break;
} }
KeyStroke::Char(c) => entry_box.write_char(c), KeyStroke::Char(c) => entry.write_char(c),
KeyStroke::Left => entry_box.move_cursor(false), KeyStroke::Left => entry.move_cursor(false),
KeyStroke::Right => entry_box.move_cursor(true), KeyStroke::Right => entry.move_cursor(true),
KeyStroke::Alt => break, KeyStroke::Alt => break,
_ => {} _ => {}
} }
@@ -107,13 +115,27 @@ impl Application for Grapher {
if commandresult.len() > 0 { if commandresult.len() > 0 {
self.reset_frame(); self.reset_frame();
self.graph_equation(commandresult.clone()); self.graph_equation(commandresult.clone());
let self_widget = container.elements.get("grapher").unwrap();
self_widget.update(self.clone());
commandresult.clear(); commandresult.clear();
} }
container.insert(Box::new(self)); serial_println!("{:?}", entry.text);
container.insert(Box::new(&entry_box)); entry_widget.update(entry);
if let Ok(frame) = container.render() { if let Ok(frame) = container.render() {
let self_widget = container.elements.get("grapher").unwrap();
let self_clone = self_widget.fetch::<Grapher>().unwrap();
serial_println!("{:?}", self_clone.points);
let entry = container.elements.get("entry_box").unwrap();
let entry_clone = entry.fetch::<CgLineEdit>().unwrap();
serial_println!("{:?}", entry_clone.text);
frame.write_to_screen().map_err(|_| Error::ApplicationError(String::from("failed to write to screen")))?; 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<Frame, RenderError> { fn render(&self) -> Result<Frame, RenderError> {
Ok(self.frame.clone()) Ok(self.frame.clone())
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
+58 -33
View File
@@ -16,6 +16,7 @@ use crate::user::lib::libgui::{
cg_widgets::{CgTextBox, CgContainer, CgIndicatorBar, CgIndicatorWidget, CgLabel, CgStatusBar}, cg_widgets::{CgTextBox, CgContainer, CgIndicatorBar, CgIndicatorWidget, CgLabel, CgStatusBar},
cg_inputs::CgLineEdit, cg_inputs::CgLineEdit,
}; };
use crate::user::lib::libgui::cg_core::Widget;
lazy_static! { lazy_static! {
pub static ref CMD: Mutex<CommandHandler> = Mutex::new(CommandHandler::new()); pub static ref CMD: Mutex<CommandHandler> = Mutex::new(CommandHandler::new());
@@ -268,6 +269,8 @@ async fn setup_ui(input: impl Fn(KeyStroke) -> (KeyStroke, bool)) {
40, 40,
); );
test_new_datastore().await;
let mut statusbar = CgStatusBar::new(Position::new(0, 0), Dimensions::new(80, 1)); let mut statusbar = CgStatusBar::new(Position::new(0, 0), Dimensions::new(80, 1));
let mut textedit = CgLineEdit::new( let mut textedit = CgLineEdit::new(
@@ -278,41 +281,65 @@ async fn setup_ui(input: impl Fn(KeyStroke) -> (KeyStroke, bool)) {
let mut commandresult = String::new(); let mut commandresult = String::new();
while let (c, false) = input(Stdin::keystroke().await) { // while let (c, false) = input(Stdin::keystroke().await) {
let mut container = CgContainer::new( // let mut container = CgContainer::new(
Position::new(0, 0), // Position::new(0, 0),
Dimensions::new(80, 25), // Dimensions::new(80, 25),
false, // 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 { async fn test_new_datastore() {
KeyStroke::Char('\n') => { let container = Widget::insert(CgContainer::new(
commandresult = textedit.text.iter().collect(); Position::new(0, 0),
textedit.clear(); Dimensions::new(80, 25),
}, true,
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 textbox = Widget::insert(CgTextBox::new(
let string = commandresult.clone(); String::from("test textbox"),
textbox.content = string; String::from("dam"),
} Position::new(2, 5),
Dimensions::new(40, 12),
true,
));
container.insert(Box::new(&textbox)); let mut c = textbox.fetch::<CgTextBox>().unwrap();
container.insert(Box::new(&statusbar)); c.content = String::from("dam2");
container.insert(Box::new(&textedit)); textbox.update(c);
if let Ok(frame) = container.render() { let c = textbox.fetch::<CgTextBox>().unwrap();
frame.write_to_screen().unwrap();
} serial_println!("{}", c.content);
}
} }
@@ -333,8 +360,6 @@ async fn setup_ui(input: impl Fn(KeyStroke) -> (KeyStroke, bool)) {
+121 -8
View File
@@ -1,12 +1,17 @@
use alloc::boxed::Box; use hashbrown::HashMap;
use alloc::string::String; use spin::{Mutex, MutexGuard};
use alloc::vec;
use alloc::vec::Vec;
use core::slice::from_mut;
use crate::{printerr, serial_println}; use crate::{printerr, serial_println};
use crate::std::frame::{ColouredChar, Dimensions, Position, special_char, Frame, RenderError, ColorCode}; 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 /// implement this trait if you require the widget to be able to have an outline
pub trait CgOutline: CgComponent { 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 /// 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. /// that can be rendered to the screen.
pub trait CgComponent { pub trait CgComponent: Any {
fn render(&self) -> Result<Frame, RenderError>; fn render(&self) -> Result<Frame, RenderError>;
fn as_any(&self) -> &dyn Any;
} }
/// trait for components that can have editable text, such as search boxes, command palettes, 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 { pub trait CgTextEdit: CgComponent {
fn write_char(&mut self, c: char); // this can also be implemented in a way that inserts characters 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 move_cursor(&mut self, direction: bool); // true = right, false = left
fn clear(&mut self); fn clear(&mut self);
} }
static ID_COUNTER: Mutex<usize> = Mutex::new(0);
lazy_static!(
static ref GUITREE: Mutex<DataStore> = 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<T: 'static + Send + Sync + Clone + CgComponent>(&self) -> Option<T> {
GUITREE.lock().fetch(self)
}
pub fn insert<T: 'static + Send + Sync + Clone + CgComponent>(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<T: 'static + Send + Sync + Clone + CgComponent>(&self, item: T) {
GUITREE.lock().insert(self, item);
}
pub fn render(&self) -> Result<Frame, RenderError> {
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<HashMap<usize, Arc<Mutex<dyn CgComponent + Send + Sync>>>>,
id_counter: Mutex<usize>,
}
impl DataStore {
fn new() -> Self {
DataStore {
items: Mutex::new(HashMap::new()),
id_counter: Mutex::new(0),
}
}
fn insert<T: 'static + CgComponent + Send + Sync + Clone>(&self, id: &Widget, item: T) {
let mut items = self.items.lock();
items.insert(id.id, Arc::new(Mutex::new(item)));
}
fn fetch<T: 'static + Send + Sync + Clone>(&self, id: &Widget) -> Option<T> 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::<T>().cloned()
})
}
fn frame(&self, id: &Widget) -> Option<Result<Frame, RenderError>> {
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);
}
}
+4
View File
@@ -1,5 +1,6 @@
use alloc::string::String; use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::any::Any;
use crate::std::frame::{ColouredChar, Dimensions, Frame, Position, RenderError}; use crate::std::frame::{ColouredChar, Dimensions, Frame, Position, RenderError};
use crate::user::lib::libgui::cg_core::{CgComponent, CgTextEdit}; use crate::user::lib::libgui::cg_core::{CgComponent, CgTextEdit};
@@ -56,6 +57,9 @@ impl CgComponent for CgLineEdit {
Ok(frame) Ok(frame)
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
impl CgTextEdit for CgLineEdit { impl CgTextEdit for CgLineEdit {
+32 -14
View File
@@ -1,36 +1,37 @@
use alloc::{boxed::Box, format, string::String, vec, vec::Vec}; use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
use alloc::fmt::format; use alloc::fmt::format;
use alloc::string::ToString; use alloc::string::ToString;
use core::any::Any;
use core::cmp::{max, min}; use core::cmp::{max, min};
use hashbrown::HashMap;
use crate::serial_println; use crate::serial_println;
use super::cg_core::{ use super::cg_core::{CgComponent, CgOutline, Widget};
CgComponent, CgOutline
};
use crate::std::frame::{ColouredChar, Dimensions, Position, Frame, RenderError, ColorCode}; use crate::std::frame::{ColouredChar, Dimensions, Position, Frame, RenderError, ColorCode};
use crate::std::io::Color; use crate::std::io::Color;
pub struct CgContainer<'a> { #[derive(Debug, Clone)]
pub elements: Vec<Box<&'a dyn CgComponent>>, pub struct CgContainer {
pub elements: HashMap<&'static str, Widget>,
pub position: Position, pub position: Position,
pub dimensions: Dimensions, pub dimensions: Dimensions,
pub outlined: bool, pub outlined: bool,
} }
impl<'a> CgContainer<'a> { impl CgContainer {
pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> CgContainer<'a> { pub fn new(position: Position, dimensions: Dimensions, outlined: bool) -> CgContainer {
CgContainer { CgContainer {
elements: Vec::new(), elements: HashMap::new(),
position, position,
dimensions, dimensions,
outlined, outlined,
} }
} }
pub fn insert(&mut self, element: Box<&'a dyn CgComponent>) { pub fn insert(&mut self, name: &'static str, element: Widget) {
self.elements.push(element); self.elements.insert(name,element);
} }
} }
impl CgOutline for CgContainer<'_> { impl CgOutline for CgContainer {
fn render_outline(&self, frame: &mut Frame) { fn render_outline(&self, frame: &mut Frame) {
// draws the sides of the container // draws the sides of the container
for i in 0..frame.dimensions.x { 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<Frame, RenderError> { fn render(&self) -> Result<Frame, RenderError> {
let mut result = Frame::new(self.position, self.dimensions)?; let mut result = Frame::new(self.position, self.dimensions)?;
for widget in &self.elements { 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 match result.render_bounds_check(&frame, true) { // TODO: this needs to be set to false for production
Ok(()) => result.place_child_element(&frame), Ok(()) => result.place_child_element(&frame),
Err(e) => return Err(e), Err(e) => return Err(e),
@@ -70,6 +71,9 @@ impl CgComponent for CgContainer<'_> {
Ok(result) Ok(result)
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -150,7 +154,9 @@ impl CgComponent for CgTextBox {
Ok(result) Ok(result)
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
@@ -210,6 +216,9 @@ impl CgComponent for CgLabel {
}; };
Ok(result) Ok(result)
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -259,6 +268,9 @@ impl CgComponent for CgIndicatorWidget {
Ok(result) Ok(result)
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -301,6 +313,9 @@ impl CgComponent for CgIndicatorBar {
Ok(result) Ok(result)
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -333,6 +348,9 @@ impl CgComponent for CgStatusBar {
Ok(frame) Ok(frame)
} }
fn as_any(&self) -> &dyn Any {
self
}
} }
impl CgStatusBar { impl CgStatusBar {