- got text boxes fully working

- this includes text wrapping not cutting words in half (can be disabled using a method on the text box)
- refactored frame.rs, cg_core.rs and cg_widgets.rs to avoid code reuse and duplication
- created a simplified unified interface for rendering frames to the screen using the Frame struct provided by frame.rs instead of Element, FrameGen, etc.
- moved all widgets from cg_core.rs to cg_widgets.rs
- the label widget now works
- also added CgIndicatorBar and CgIndicatorWidget widgets to eventually make a working status bar
- refactored all applications in the system to use the new api to render to the screen
This commit is contained in:
FantasyPvP
2023-11-23 00:29:04 +00:00
parent 0ac21cd0b1
commit 461c9d9c6a
11 changed files with 488 additions and 501 deletions
+3 -186
View File
@@ -3,201 +3,18 @@ use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use core::slice::from_mut;
use crate::kernel::render::{ColorCode, ScreenChar};
use crate::kernel::render::{ColorCode, RenderError, 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, Debug)]
pub struct Position {
pub x: usize,
pub y: usize,
}
impl Position {
pub fn new(x: usize, y: usize) -> Position {
Position { x, y }
}
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum XorY {
X,
Y,
Both,
None,
}
use crate::std::frame::{ColouredChar, Dimensions, Position, special_char, Frame};
impl XorY {
pub fn setx(&mut self) {
if self == &XorY::None {
*self = XorY::X;
} else if self == &XorY::Y {
*self = XorY::Both;
}
}
pub fn sety(&mut self) {
if self == &XorY::None {
*self = XorY::Y;
} else if self == &XorY::X {
*self = XorY::Both;
}
}
}
#[derive(Debug)]
pub enum GuiError {
OutOfBounds(XorY)
}
pub type Dimensions = Position;
#[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 {
pub position: Position,
pub dimensions: Dimensions,
frame: Vec<Vec<ColouredChar>>,
}
impl Frame {
pub fn new(position: Position, dimensions: Dimensions) -> Result<Frame, GuiError> {
Ok(Frame {
position,
dimensions,
frame: vec![vec![ColouredChar::null(); dimensions.x]; dimensions.y],
})
}
pub fn render(&self) -> Vec<Vec<ColouredChar>> {
self.frame.clone()
}
pub fn render_screen_char(&self) -> Vec<Vec<ScreenChar>> {
self.frame.clone().into_iter().map(|row| {
row.into_iter().map(|char| {
char.as_screen_char()
}).collect::<Vec<ScreenChar>>()
}).collect::<Vec<Vec<ScreenChar>>>()
}
pub fn position(&self) -> Position {
self.position
}
pub fn dimensions(&self) -> Dimensions {
self.dimensions
}
pub fn set_pos(&mut self, position: Position, char: ColouredChar) {
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::<String>()
}).collect::<Vec<String>>().join("\n")
);
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
}
}
serial_println!("self:\n{}",
self.frame.iter().map(|x| {
x.iter().map(|y| {
y.character
}).collect::<String>()
}).collect::<Vec<String>>().join("\n")
);
}
pub fn render_bounds_check(&self, element: &Frame, should_panic: bool) -> Result<(), XorY> {
use XorY::{X, Y};
let mut res = XorY::None;
if element.dimensions().x + element.position().x > self.dimensions.x {
if should_panic {
panic!(
"Element is to large to be rendered {} {}",
element.dimensions().x + element.position().x,
self.dimensions.x
)
} else {
res.setx();
}
}
if element.dimensions().y + element.position().y > self.dimensions.y {
if should_panic {
panic!(
"Element is to large to be rendered {} {}",
element.dimensions().y + element.position().y,
self.dimensions.y
)
} else {
res.sety();
}
}
if res != XorY::None {
Err(res)
} else {
Ok(())
}
}
}
pub trait CgOutline {
fn render_outline(&self, frame: &mut Frame);
}
pub trait CgComponent {
fn render(&self) -> Result<Frame, GuiError>;
fn render(&self) -> Result<Frame, RenderError>;
}
+163 -67
View File
@@ -1,15 +1,12 @@
use alloc::{
boxed::Box,
string::String,
vec,
vec::Vec,
};
use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
use alloc::fmt::format;
use crate::kernel::render::{ColorCode, RenderError};
use crate::serial_println;
use super::cg_core::{
Position, Dimensions, ColouredChar, CgComponent, CgOutline, Frame, GuiError,
CgComponent, CgOutline
};
use crate::std::frame::{ColouredChar, Dimensions, Position, Frame};
use crate::std::io::Color;
pub struct CgContainer {
@@ -34,33 +31,33 @@ 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('─'));
frame.write_pos(Position::new(i, 0), ColouredChar::new('─'));
frame.write_pos(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.set_pos(Position::new(0, i), ColouredChar::white('│'));
frame.set_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::white('│'));
frame.write_pos(Position::new(0, i), ColouredChar::new('│'));
frame.write_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::new('│'));
}
// 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('┘'));
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('┘'));
}
}
impl CgComponent for CgContainer {
fn render(&self) -> Result<Frame, GuiError> {
fn render(&self) -> Result<Frame, RenderError> {
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)),
Err(e) => return Err(e),
}
}
@@ -79,80 +76,100 @@ pub struct CgTextBox {
pub position: Position,
pub dimensions: Dimensions,
outlined: bool,
wrap_words: bool // if false then will not wrap until the end of a word if possible
}
impl CgTextBox {
pub fn new(title: String, content: String, position: Position, dimensions: Dimensions, outlined: bool) -> CgTextBox {
CgTextBox { title, content, position, dimensions, outlined }
CgTextBox { title, content, position, dimensions, outlined, wrap_words: false }
}
fn render_title(&self, frame: &mut Frame) {
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('.'));
} else if i + 2 >= self.dimensions.x - 2 {
frame.write_pos(Position::new(i + 1, 0), ColouredChar::new('.'));
break;
}
frame.write_pos(Position::new(i + 2, 0), ColouredChar::new(c));
}
}
pub fn wrap_words(&mut self, wrap: bool) {
self.wrap_words = wrap;
}
}
impl CgComponent for CgTextBox {
fn render(&self) -> Result<Frame, GuiError> {
fn render(&self) -> Result<Frame, RenderError> {
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;
self.render_title(&mut result);
let (mut x, mut y) = (1, 1);
for word in self.content.split(' ') {
if self.wrap_words {
if word.len() > self.dimensions.x - 2 - x {
if word.len() <= self.dimensions.x - 2 {
x = 1;
y += 1;
}
}
}
result.set_pos(Position::new(i + 2, 0), ColouredChar::white(c));
for c in format!("{} ", word).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.write_pos(Position::new(self.dimensions.x - z, self.dimensions.y - 1), ColouredChar::new('.'));
})
}
break;
}
result.write_pos(Position::new(x, y), ColouredChar::new(c));
x += 1;
};
}
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('─'));
frame.write_pos(Position::new(i, 0), ColouredChar::new('─'));
frame.write_pos(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.set_pos(Position::new(0, i), ColouredChar::white('│'));
frame.set_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::white('│'));
frame.write_pos(Position::new(0, i), ColouredChar::new('│'));
frame.write_pos(Position::new(frame.dimensions.x - 1, i), ColouredChar::new('│'));
}
// 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('┘'));
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('┘'));
}
}
@@ -177,13 +194,13 @@ impl CgLabel {
}
impl CgComponent for CgLabel {
fn render(&self) -> Result<Frame, GuiError> {
fn render(&self) -> Result<Frame, RenderError> {
let mut result = Frame::new(self.position, self.dimensions)?;
let shortened_string = self.content.chars().take(self.dimensions.x).collect::<String>();
for (i, c) in shortened_string.chars().enumerate() {
result.set_pos(Position::new(i, 0), ColouredChar::white(c));
result.write_pos(Position::new(i, 0), ColouredChar::new(c));
};
serial_println!("{:?}", result);
@@ -192,6 +209,90 @@ impl CgComponent for CgLabel {
}
}
pub struct CgIndicatorWidget {
content: String,
colour: ColorCode,
visible: bool,
max_width: usize,
}
impl CgIndicatorWidget {
pub fn new(content: String, max_width: usize) -> CgIndicatorWidget {
CgIndicatorWidget {
content,
visible: true,
colour: ColorCode::new(Color::White, Color::Black),
max_width
}
}
pub fn set_colour(&mut self, colour: ColorCode) {
self.colour = colour;
}
fn visible(&mut self, visible: bool) {
self.visible = visible;
}
fn len(&self) -> usize {
self.max_width
}
}
impl CgComponent for CgIndicatorWidget {
fn render(&self) -> Result<Frame, RenderError> {
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 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));
};
Ok(result)
}
}
pub struct CgIndicatorBar {
pub(crate) fields: Vec<CgIndicatorWidget>,
position: Position,
dimensions: Dimensions,
}
impl CgIndicatorBar {
pub fn new(position: Position, width: usize) -> CgIndicatorBar {
CgIndicatorBar {
fields: Vec::new(),
position: Position::new(position.x, position.y),
dimensions: Dimensions::new(width, 1),
}
}
fn add_field(&mut self, field: CgIndicatorWidget) {
self.fields.push(field);
}
}
impl CgComponent for CgIndicatorBar {
fn render(&self) -> Result<Frame, RenderError> {
let mut result = Frame::new(self.position, self.dimensions)?;
let mut width_idx = 0;
for widget in &self.fields {
let mut frame = widget.render()?;
frame.set_position(Position::new(width_idx, 0));
width_idx += widget.len();
match result.render_bounds_check(&frame, true) {
Ok(()) => result.render_element(&frame),
Err(e) => return Err(e),
}
}
Ok(result)
}
}
@@ -221,11 +322,6 @@ impl CgComponent for CgLabel {
+1 -1
View File
@@ -1,3 +1,3 @@
pub mod libgui_old_archive;
// pub mod libgui_old_archive;
pub mod coords;
pub mod libgui;