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
This commit is contained in:
FantasyPvP
2023-11-23 20:29:51 +00:00
parent 461c9d9c6a
commit 467a42a5fa
14 changed files with 352 additions and 128 deletions
+2 -2
View File
@@ -27,7 +27,7 @@ impl Application for GameOfLife {
}
async fn run(&mut self, args: Vec<String>) -> 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(())
}
}
+3 -3
View File
@@ -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('*'));
}
+63 -47
View File
@@ -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<CommandHandler> = 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<String>,
}
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();
}
}
}
+2 -2
View File
@@ -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(())
}
}
+2 -2
View File
@@ -28,7 +28,7 @@ impl Application for TetrisEngine {
}
async fn run(&mut self, args: Vec<String>) -> 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(())
}
}
+11 -4
View File
@@ -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<Frame, RenderError>;
}
/// 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<Frame, RenderError>; // this can also be implemented in a way that inserts characters
fn delete_char(&self) -> Result<Frame, RenderError>;
fn move_cursor(&self, direction: bool) -> Result<(), RenderError>; // true = right, false = left
fn clear(&self) -> Result<Frame, RenderError>;
}
+53
View File
@@ -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<char>,
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<Frame, RenderError> {
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)
}
}
+93 -38
View File
@@ -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<Box<dyn CgComponent>>,
pub struct CgContainer<'a> {
pub elements: Vec<Box<&'a dyn CgComponent>>,
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<Frame, RenderError> {
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::<String>();
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::<String>();
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<CgIndicatorWidget>,
pub fields: Vec<CgIndicatorWidget>,
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<Frame, RenderError> {
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);
}
}
+2 -1
View File
@@ -1,3 +1,4 @@
pub mod cg_core;
pub(crate) mod cg_widgets;
pub mod cg_widgets;
pub mod cg_inputs;