- Created Dialog widget

- standard information dialog works
TODO:
  implement more complex dialogs where multiple options can be selected
This commit is contained in:
FantasyPvP
2024-03-21 00:12:53 +00:00
parent e80df5100f
commit 6505d219de
8 changed files with 171 additions and 37 deletions
+2 -2
View File
@@ -55,8 +55,8 @@ impl Application for CrystalFetch {
" [ OS » {}
[ BUILD » {}
[ Shell » CrySH
[ Github » https://github.com/FantasyPvP/CrystalOS-Restructured
[ Author » FantasyPvP / ZXQ5", os, version);
[ Github » https://github.com/FantasyPvP/CrystalOS
[ Author » ZXQ5", os, version);
// write to output
let spacer = "\n".repeat(25 - logo_string.lines().count() - 4 - info_string.lines().count());
+17 -13
View File
@@ -10,6 +10,7 @@ use crate::{print, printerr, println, serial_println, std, std::application::{Ap
use crate::std::frame::{Dimensions, Position, ColorCode};
use crate::std::io::{Color, write, Screen, Stdin, Serial, KeyStroke};
use crate::std::random::Random;
use crate::std::time::timer;
use crate::user::bin::gigachad_detector::GigachadDetector;
use crate::user::bin::grapher::Grapher;
use crate::user::lib::libgui::{
@@ -17,8 +18,8 @@ use crate::user::lib::libgui::{
cg_widgets::{CgTextBox, CgContainer, CgIndicatorBar, CgIndicatorWidget, CgLabel, CgStatusBar},
cg_inputs::CgLineEdit,
};
use crate::user::lib::libgui::cg_core::{CgTextInput, Widget};
use crate::user::lib::libgui::cg_widgets::CgDialog;
use crate::user::lib::libgui::cg_core::{CgKeyboardCapture, CgTextInput, Widget};
use crate::user::lib::libgui::cg_widgets::{CgDialog, CgDialogType};
lazy_static! {
pub static ref CMD: Mutex<CommandHandler> = Mutex::new(CommandHandler::new());
@@ -254,17 +255,23 @@ struct CmdHistory {
}
async fn setup_ui() {
let dialog = CgDialog::new(
Dimensions::new(40, 10),
String::from("test dialog"),
String::from("dialog body"),
String::from("[dialog footer]")
let exit = |x: KeyStroke| { match x {
KeyStroke::Char('`') => (KeyStroke::None, Exit::Exit),
_ => (x, Exit::None),
}};
let mut dialog = CgDialog::new(
String::from("i'd just like to interject for a moment"),
String::from("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!"),
// CgDialog::Type::Selection(vec![String::from("Shut Up Nerd"), String::from("Ok Boomer"), String::from("Nice")]),
CgDialogType::Information
);
if let Ok(frame) = dialog.render() {
frame.write_to_screen().unwrap();
}
return;
dialog.keyboard_capture(exit, None).await.unwrap();
serial_println!("idk");
let label= Widget::insert(CgLabel::new(
@@ -276,7 +283,7 @@ async fn setup_ui() {
let textbox = Widget::insert(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!"),
String::from("I'd just like to interject for a moment. What you're referring 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,
@@ -309,10 +316,7 @@ async fn setup_ui() {
}
let exit = |x: KeyStroke| { match x {
KeyStroke::Char('`') => (KeyStroke::None, Exit::Exit),
_ => (x, Exit::None),
}};
let container_copy = container.fetch::<CgContainer>().unwrap();
let entry_ref = container_copy.fetch("textedit").unwrap();
+1 -1
View File
@@ -18,4 +18,4 @@ pub(crate) fn render_outline(frame: &mut Frame, dimensions: Dimensions) {
frame.write(Position::new(dimensions.x - 1, 0), ColouredChar::new('┐'));
frame.write(Position::new(0, dimensions.y - 1), ColouredChar::new('└'));
frame.write(Position::new(dimensions.x - 1, dimensions.y - 1), ColouredChar::new('┘'));
}
}
+146 -20
View File
@@ -3,12 +3,14 @@ use alloc::fmt::format;
use alloc::string::ToString;
use core::any::Any;
use core::cmp::{max, min};
use async_trait::async_trait;
use hashbrown::HashMap;
use crate::serial_println;
use super::cg_core::{CgComponent, CgOutline, Widget};
use crate::std::application::Exit;
use super::cg_core::{CgComponent, CgKeyboardCapture, CgOutline, Widget};
use super::cg_utils::render_outline;
use crate::std::frame::{ColouredChar, Dimensions, Position, Frame, RenderError, ColorCode, BUFFER_WIDTH, BUFFER_HEIGHT};
use crate::std::io::Color;
use crate::std::io::{Color, KeyStroke, Stdin};
#[derive(Debug, Clone)]
pub struct CgContainer {
@@ -74,7 +76,7 @@ impl CgTextBox {
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
if i + 2 == self.dimensions.x - 3 { // we don't want to write at the top of the text box
frame.write(Position::new(i + 1, 0), ColouredChar::new('.'));
} else if i + 2 >= self.dimensions.x - 2 {
frame.write(Position::new(i + 1, 0), ColouredChar::new('.'));
@@ -102,7 +104,7 @@ impl CgComponent for CgTextBox {
for word in self.content.split(' ') {
if self.wrap_words {
if word.len() > self.dimensions.x - 2 - x {
if word.len() + 1 > 1 + self.dimensions.x - 2 - x {
if word.len() <= self.dimensions.x - 2 {
x = 1;
y += 1;
@@ -345,46 +347,150 @@ impl CgStatusBar {
}
}
enum CgDialogType {
pub enum CgDialogType {
Information,
Confirmation,
Selection(Vec<String>),
}
pub struct CgDialog {
dimensions: Dimensions,
width: usize,
title: String,
content: String,
button_text: String,
accepted: bool,
outlined: bool,
dialog_class: CgDialogType,
}
impl CgDialog {
pub(crate) fn new(dimensions: Dimensions, title: String, content: String, button_text: String) -> CgDialog {
pub(crate) fn new(title: String, content: String, class: CgDialogType) -> CgDialog {
CgDialog {
dimensions,
width: 40,
title,
content,
button_text,
accepted: false,
outlined: true,
dialog_class: class,
}
}
}
// TODO: make dialogs responsive.
impl CgComponent for CgDialog {
fn render(&self) -> Result<Frame, RenderError> {
if self.dimensions.x > BUFFER_WIDTH || self.dimensions.y > BUFFER_HEIGHT {
return Err(RenderError::OutOfBounds(self.dimensions.x > BUFFER_WIDTH, self.dimensions.y > BUFFER_HEIGHT));
}
let x_offset = (BUFFER_WIDTH - self.dimensions.x) / 2;
let y_offset = (BUFFER_HEIGHT - self.dimensions.y) / 2;
let mut frame = Frame::new(Position::new(x_offset, y_offset), Dimensions::new(self.dimensions.x, self.dimensions.y))?;
// find the size needed for the dialog buttons
let dialog_button_width = match &self.dialog_class {
CgDialogType::Selection(options) => {
options.iter().fold(0, |sum, x| sum + 5 + x.len()) // [ Option ] for each option
},
CgDialogType::Information => 6, // [ Ok ]
CgDialogType::Confirmation => 22 // [ Confirm ] [ Cancel ]
};
if self.outlined {
render_outline(&mut frame, self.dimensions);
// picks the largest out of the title length, dialog button length and 40 to determine the minimum width of the dialog.
let mut width = max(max(self.title.len(), dialog_button_width), 40);
// we set the base height to 5, assuming the content is none.
let mut height = 5;
// calculate required width and height of textbox based on the size of the content.
while self.content.len() as f32 * 1.25 / width as f32 >= BUFFER_HEIGHT as f32 - 8.0 + 1.0 { // the + 1.0 accounts for decimal values being truncated down, ensuring that the max height of 25 can be reached.
if width < BUFFER_WIDTH - 4 {
width += 1;
} else {
// in the case that the text does not fit within the dialog
// TODO: handle this properly
return Err(RenderError::OutOfBounds(true, true));
}
};
height = (self.content.len() as f32 * 1.25 / (width as f32)) as usize;
// account for borders
width += 4;
height += 8;
// offsets to centre the dialog
let x_offset = (BUFFER_WIDTH - width) / 2;
let y_offset = (BUFFER_HEIGHT - height) / 2;
// now that we know the X and Y offsets, we can start to draw the frame
let mut frame = Frame::new(Position::new(x_offset, y_offset), Dimensions::new(width, height))?;
render_outline(&mut frame, Dimensions::new(width, height));
// render title
let title_offset = (width - self.title.len()) / 2;
let title = CgLabel::new(self.title.clone(), Position::new(title_offset, 2), self.title.len(), true);
frame.place_child_element(&title.render().unwrap());
let (mut x, mut y) = (2, 5); // top left of the text box
for word in self.content.split(' ') {
if word.len() + 1 > 1 + width - 4 - x { // adding a +1 on both sides accounts for the possible negative value at the end of the line, avoiding integer underflow.
if word.len() <= width - 4 {
x = 2;
y += 1;
}
}
for c in format!("{} ", word).chars() {
if x >= width - 3 {
x = 2;
y += 1;
if c == ' ' {
continue;
}
}
if y >= height - 4 {
break;
}
frame.write(Position::new(x, y), ColouredChar::new(c));
x += 1;
};
}
// dialog buttons
match &self.dialog_class {
CgDialogType::Information => {
let button_x_offset = (width - 6) / 2;
"[ Ok ]".chars().enumerate().for_each(|(i, c)| {
frame.write(Position::new(button_x_offset + i, height - 3), ColouredChar {
character: c,
colour: ColorCode::new(Color::Black, Color::White),
});
})
}
CgDialogType::Confirmation => {
let button_x_offset = (width - 22) / 2;
let button_y_offset = height - 3;
"[ Confirm ]".chars().enumerate().for_each(|(i, c)| {
frame.write(Position::new(button_x_offset + i, button_y_offset), ColouredChar {
character: c,
colour: ColorCode::new(Color::Black, Color::White),
});
});
"[ Cancel ]".chars().enumerate().for_each(|(i, c)| {
frame.write(Position::new(button_x_offset + i, button_y_offset + 1), ColouredChar {
character: c,
colour: ColorCode::new(Color::Black, Color::White),
});
});
},
CgDialogType::Selection(options) => {
let button_string = options.iter().map(|option| format!("[ {} ] ", option)).collect::<String>();
let button_x_offset = (width - button_string.len()) / 2;
button_string.chars().enumerate().for_each(|(i, c)| {
frame.write(Position::new(button_x_offset + i, height - 3), ColouredChar {
character: c,
colour: ColorCode::new(Color::Black, Color::White),
});
})
}
};
Ok(frame)
}
@@ -394,8 +500,28 @@ impl CgComponent for CgDialog {
}
}
#[async_trait]
impl CgKeyboardCapture for CgDialog {
async fn keyboard_capture(&mut self, break_condition: fn(KeyStroke) -> (KeyStroke, Exit), app: Option<&Widget>) -> Result<bool, RenderError> {
loop {
let k = break_condition(Stdin::keystroke().await);
serial_println!("captured: {:?}", k.0);
match k {
(KeyStroke::Char('\n'), _) => {
return Ok(true)
},
_ => {}
}
}
}
}
impl CgDialog {
pub type Type = CgDialogType;
fn dynamic_layout(&mut self) {
}
}