Large structural changes
This commit is contained in:
@@ -18,3 +18,4 @@ path = "src/main.rs"
|
||||
crossterm = "0.28.1"
|
||||
regex = "*"
|
||||
lazy_static = "1.4"
|
||||
dyn-clone = "1.0.17"
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
//! # Element Module
|
||||
//!
|
||||
//! This module defines the `Element` trait and associated types for creating,
|
||||
//! configuring, and updating UI elements in OSUI. Elements are the building
|
||||
//! blocks of the TUI, each with properties such as size and position and
|
||||
//! methods for rendering and updating.
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
/// Enum representing the size of an `Element`.
|
||||
///
|
||||
/// - `Default(usize)` - Uses a default size for the element.
|
||||
/// - `Custom(usize)` - Allows specifying a custom size for the element.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ElementSize {
|
||||
Default(usize),
|
||||
Custom(usize),
|
||||
}
|
||||
|
||||
impl ElementSize {
|
||||
/// Retrieves the size as an `usize`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// The size value, either the default or custom size.
|
||||
pub fn get_size(&self) -> usize {
|
||||
match *self {
|
||||
ElementSize::Custom(s) => s,
|
||||
ElementSize::Default(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to set the size of an `Element` if it is currently set to `Default`.
|
||||
///
|
||||
/// If the size is `Custom`, this function will not modify it.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `size` - The size to set for the `Element`.
|
||||
pub fn try_set_size(&mut self, size: usize) {
|
||||
if let ElementSize::Default(_) = *self {
|
||||
*self = ElementSize::Default(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for defining UI elements in OSUI.
|
||||
///
|
||||
/// Elements must implement methods for updating and rendering data.
|
||||
/// This trait supports dynamic dispatch and cloning.
|
||||
pub trait Element: std::fmt::Debug + Send + DynClone {
|
||||
/// Retrieves the data for the `Element`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `ElementData` containing position and size information.
|
||||
fn get_data(&self) -> ElementData;
|
||||
|
||||
/// Updates the data of the `Element` based on the given dimensions.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_width` - The width of the terminal window or parent element.
|
||||
/// * `_height` - The height of the terminal window or parent element.
|
||||
fn update_data(&mut self, _width: usize, _height: usize);
|
||||
|
||||
/// Renders the `Element` as a `String`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_state` - The current state of the element; if one or higher, it indicates the element is active.
|
||||
/// If zero, the element is just being rendered.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `String` representing the rendered output of the `Element`.
|
||||
fn render(&self, _state: usize) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
/// Updates the `Element` based on a `Key` event and the current state.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `_state` - The current state of the element; if one or higher, it indicates the element is active.
|
||||
/// * `_k` - The key input triggering the update.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// An `UpdateResponse` enum indicating the result of the update.
|
||||
fn event(&mut self, _state: usize, _k: crate::key::Key) -> UpdateResponse {
|
||||
UpdateResponse::None
|
||||
}
|
||||
}
|
||||
|
||||
// Enable cloning of `Element` trait objects.
|
||||
dyn_clone::clone_trait_object!(Element);
|
||||
|
||||
/// Struct holding data relevant to an `Element`, including position and size.
|
||||
#[derive(Debug)]
|
||||
pub struct ElementData {
|
||||
/// X coordinate of the `Element`.
|
||||
pub x: usize,
|
||||
/// Y coordinate of the `Element`.
|
||||
pub y: usize,
|
||||
/// Width of the `Element`, which can be default or custom.
|
||||
pub width: ElementSize,
|
||||
/// Height of the `Element`, which can be default or custom.
|
||||
pub height: ElementSize,
|
||||
}
|
||||
|
||||
/// Enum representing the possible responses from an element update.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UpdateResponse {
|
||||
/// Indicates that the update is complete.
|
||||
Done,
|
||||
/// Indicates no response.
|
||||
None,
|
||||
/// Issues a single command.
|
||||
Command(Command),
|
||||
/// Issues a list of commands.
|
||||
CommandList(Vec<Command>),
|
||||
}
|
||||
|
||||
/// Enum defining commands that can be issued by an `Element`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Command {
|
||||
/// Renders the element with the specified state.
|
||||
Render(usize),
|
||||
/// Exits the application.
|
||||
Exit,
|
||||
/// Updates the element with the specified state.
|
||||
Update(usize),
|
||||
/// Pauses execution for the specified duration in milliseconds.
|
||||
Sleep(u64),
|
||||
}
|
||||
+66
-28
@@ -1,51 +1,89 @@
|
||||
use std::io::{self, Read};
|
||||
|
||||
/// Represents different key inputs that can be detected from the terminal.
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub enum KeyKind {
|
||||
pub enum Key {
|
||||
/// Represents the Enter key (carriage return).
|
||||
Enter,
|
||||
/// Represents the Tab key.
|
||||
Tab,
|
||||
/// Represents the Shift+Tab key combination.
|
||||
ShiftTab,
|
||||
/// Represents the Escape key.
|
||||
Escape,
|
||||
/// Represents the Up arrow key.
|
||||
Up,
|
||||
/// Represents the Down arrow key.
|
||||
Down,
|
||||
/// Represents the Left arrow key.
|
||||
Left,
|
||||
/// Represents the Right arrow key.
|
||||
Right,
|
||||
Char(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Key {
|
||||
pub kind: KeyKind,
|
||||
pub raw: String,
|
||||
/// Represents any other character or sequence that does not match a predefined key.
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Key {
|
||||
/// Creates a new `Key` instance based on the input string.
|
||||
///
|
||||
/// This function matches specific strings with known keys (e.g., arrow keys, Enter, Tab)
|
||||
/// and returns the corresponding `Key` variant. If the string does not match any of the
|
||||
/// predefined keys, it returns `Key::Other` with the string content.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `k` - A `String` representing the raw key input.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Key` enum variant corresponding to the input.
|
||||
pub fn new(k: String) -> Key {
|
||||
Key {
|
||||
raw: k.clone(),
|
||||
kind: match k.as_str() {
|
||||
"\r" => KeyKind::Enter,
|
||||
"\t" => KeyKind::Tab,
|
||||
"\x1b[Z" => KeyKind::ShiftTab,
|
||||
"\x1b" => KeyKind::Escape,
|
||||
"\x1b[A" => KeyKind::Up,
|
||||
"\x1b[B" => KeyKind::Down,
|
||||
"\x1b[C" => KeyKind::Right,
|
||||
"\x1b[D" => KeyKind::Left,
|
||||
|
||||
_ => KeyKind::Char(k.clone()),
|
||||
},
|
||||
match k.as_str() {
|
||||
"\r" => Key::Enter,
|
||||
"\t" => Key::Tab,
|
||||
"\x1b[Z" => Key::ShiftTab,
|
||||
"\x1b" => Key::Escape,
|
||||
"\x1b[A" => Key::Up,
|
||||
"\x1b[B" => Key::Down,
|
||||
"\x1b[C" => Key::Right,
|
||||
"\x1b[D" => Key::Left,
|
||||
_ => Key::Other(k),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads a key from standard input and returns it as a `Key` enum variant.
|
||||
///
|
||||
/// This function calls `read_key_raw` to get the raw key input as a `String`,
|
||||
/// then uses `Key::new` to convert it to a `Key` variant.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Key` enum variant corresponding to the input read from standard input.
|
||||
pub fn read_key() -> Key {
|
||||
Key::new(read_key_raw())
|
||||
}
|
||||
|
||||
/// Reads raw key input from standard input as a UTF-8 encoded string.
|
||||
///
|
||||
/// This function attempts to read up to 3 bytes from standard input and
|
||||
/// returns them as a `String`. It uses a buffer size of 3, which is enough
|
||||
/// to capture most common key sequences, including basic escape sequences
|
||||
/// for arrow keys and other special keys.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `String` representing the raw key input read from standard input.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if reading from `stdin` fails or if the bytes
|
||||
/// cannot be converted to a valid UTF-8 `String`.
|
||||
pub fn read_key_raw() -> String {
|
||||
let mut buffer = vec![0; 3];
|
||||
io::stdin().read(&mut buffer).unwrap();
|
||||
Key::new(
|
||||
String::from_utf8(buffer)
|
||||
.unwrap()
|
||||
.trim_matches('\0')
|
||||
.to_string(),
|
||||
)
|
||||
String::from_utf8(buffer)
|
||||
.unwrap()
|
||||
.trim_matches('\0')
|
||||
.to_string()
|
||||
}
|
||||
|
||||
+129
-145
@@ -1,163 +1,147 @@
|
||||
use crossterm::ExecutableCommand;
|
||||
//! # OSUI
|
||||
//!
|
||||
//! A terminal user interface (TUI) library providing customizable components
|
||||
//! to build command-line interfaces in Rust. OSUI enables users to create
|
||||
//! interactive CLI applications with various UI elements and handle keyboard
|
||||
//! input for real-time updates.
|
||||
//!
|
||||
//! ## Example Usage
|
||||
//!
|
||||
//! ```rust
|
||||
//! use osui::{parse_rsx_param, rsx, ui::*};
|
||||
//!
|
||||
//! osui::app::run(&mut rsx! {
|
||||
//! text { "Hello, World!" }
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! ## Modules
|
||||
//! - `element` - Defines base elements for constructing UI components.
|
||||
//! - `key` - Handles keyboard input, providing key event management.
|
||||
//! - `utils` - Utility functions for common TUI tasks such as clearing the screen.
|
||||
//! - `ui` - Contains all user interface components, enabling rich CLI experiences.
|
||||
|
||||
pub mod element;
|
||||
pub mod key;
|
||||
pub mod macros;
|
||||
pub mod ui;
|
||||
pub mod utils;
|
||||
|
||||
pub trait Element: std::fmt::Debug {
|
||||
fn get_child(&mut self) -> Option<&mut Box<dyn Element>>;
|
||||
fn get_data(&self) -> ElementData;
|
||||
fn set_data(&mut self, _: ElementData);
|
||||
fn clear_ticks(&mut self);
|
||||
fn render(&mut self, _tick: usize) -> String {
|
||||
String::new()
|
||||
}
|
||||
fn update(&mut self, _ctx: &mut UpdateContext) {}
|
||||
}
|
||||
pub use element::*;
|
||||
pub use utils::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ElementData {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub style: ui::styles::Style,
|
||||
}
|
||||
pub mod app {
|
||||
//! Application entry point and main event loop for OSUI.
|
||||
//!
|
||||
//! Provides functions to render and update UI elements based on keyboard
|
||||
//! input. Manages cursor visibility, terminal size, and controls UI behavior
|
||||
//! using custom commands such as rendering, updating, or exiting.
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UpdateResponse {
|
||||
Exit,
|
||||
Done,
|
||||
None,
|
||||
}
|
||||
use crate::{
|
||||
clear, create_frame, flush, get_term_size, hide_cursor,
|
||||
key::{read_key, Key},
|
||||
render_to_frame, show_cursor, Command, Element, ElementSize, UpdateResponse,
|
||||
};
|
||||
|
||||
pub struct UpdateContext {
|
||||
pub key: key::Key,
|
||||
pub tick: usize,
|
||||
pub response: UpdateResponse,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
element: Box<dyn Element>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Creates a new screen to render components
|
||||
pub fn new() -> App {
|
||||
App {
|
||||
element: ui::text(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new screen to render components with a pre-existing component
|
||||
pub fn from(elem: Box<dyn Element>) -> App {
|
||||
let mut app = App::new();
|
||||
app.set_component(elem);
|
||||
app
|
||||
}
|
||||
|
||||
/// Sets a component
|
||||
pub fn set_component(&mut self, element: Box<dyn Element>) {
|
||||
let (width, height) = crossterm::terminal::size().unwrap();
|
||||
self.element = element;
|
||||
let mut data = self.element.get_data();
|
||||
data.style.is_active = true;
|
||||
if data.width == 0 {
|
||||
data.width = width as usize;
|
||||
}
|
||||
if data.height == 0 {
|
||||
data.height = height as usize;
|
||||
}
|
||||
self.element.set_data(data);
|
||||
}
|
||||
|
||||
/// Render to the screen
|
||||
fn render(&mut self, tick: usize) {
|
||||
let (width, height) = crossterm::terminal::size().unwrap();
|
||||
let mut data = self.element.get_data();
|
||||
if data.width == 0 {
|
||||
data.width = width as usize;
|
||||
}
|
||||
if data.height == 0 {
|
||||
data.height = height as usize;
|
||||
}
|
||||
self.element.set_data(data);
|
||||
let mut frame: Vec<String> = create_frame!(width as usize, height as usize);
|
||||
utils::render_to_frame(tick, &mut frame, &mut self.element);
|
||||
utils::clear();
|
||||
/// Renders a single frame of the UI to the terminal.
|
||||
///
|
||||
/// Sets up a new frame based on the terminal's current size, updates
|
||||
/// the element dimensions, and renders the UI element to the frame.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `elem` - A mutable reference to a boxed UI element that implements the `Element` trait.
|
||||
/// * `state` - Current state of the element, typically used to track the UI's state in the app loop.
|
||||
fn render(elem: &mut Box<dyn Element>, state: usize) {
|
||||
let (width, height) = get_term_size();
|
||||
elem.update_data(width, height);
|
||||
let mut frame: Vec<String> =
|
||||
create_frame(ElementSize::Custom(width), ElementSize::Custom(height));
|
||||
render_to_frame(state, &mut frame, elem);
|
||||
clear();
|
||||
print!("{}", frame.join(""));
|
||||
utils::flush();
|
||||
let mut data = self.element.get_data();
|
||||
if data.width == width as usize {
|
||||
data.width = 0;
|
||||
}
|
||||
if data.height == height as usize {
|
||||
data.height = 0;
|
||||
}
|
||||
self.element.set_data(data);
|
||||
flush();
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &mut UpdateContext) {
|
||||
self.element.update(ctx);
|
||||
match ctx.response {
|
||||
UpdateResponse::Exit => {
|
||||
crossterm::terminal::disable_raw_mode().unwrap();
|
||||
utils::clear();
|
||||
utils::show_cursor();
|
||||
println!("");
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the screen
|
||||
pub fn run(&mut self) {
|
||||
// Initialize
|
||||
utils::hide_cursor();
|
||||
utils::clear();
|
||||
let mut stdout = std::io::stdout();
|
||||
stdout
|
||||
.execute(crossterm::terminal::EnterAlternateScreen)
|
||||
.unwrap();
|
||||
crossterm::terminal::enable_raw_mode().unwrap();
|
||||
|
||||
// Start the update thread
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || loop {
|
||||
tx.send(key::read_key()).unwrap();
|
||||
});
|
||||
|
||||
// Start the render loop
|
||||
let mut tick: usize = 0;
|
||||
let tick_duration = std::time::Duration::from_millis(1000/30);
|
||||
let mut last_tick = std::time::Instant::now();
|
||||
loop {
|
||||
let now = std::time::Instant::now();
|
||||
let elapsed = now.duration_since(last_tick);
|
||||
|
||||
if elapsed >= tick_duration {
|
||||
last_tick = now;
|
||||
if tick > 99 {
|
||||
tick = 0;
|
||||
}
|
||||
self.render(tick);
|
||||
tick += 1;
|
||||
match rx.try_recv() {
|
||||
Ok(k) => self.update(&mut UpdateContext {
|
||||
key: k,
|
||||
tick,
|
||||
response: UpdateResponse::None,
|
||||
}),
|
||||
Err(std::sync::mpsc::TryRecvError::Empty) => {}
|
||||
Err(std::sync::mpsc::TryRecvError::Disconnected) => {
|
||||
panic!("disconnected")
|
||||
/// Updates the UI element based on keyboard input and issues any commands in response.
|
||||
///
|
||||
/// Processes the result of `Element::update` to handle commands like rendering,
|
||||
/// updating, and exiting. Commands can be a single action or a list of actions.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `elem` - A mutable reference to a boxed UI element.
|
||||
/// * `state` - The current UI state, used for conditional updates.
|
||||
/// * `k` - A `Key` input, typically read from the user’s keyboard input.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `true` if an `Exit` command is issued, signaling the application to terminate.
|
||||
fn update(elem: &mut Box<dyn Element>, state: usize, k: Key) -> bool {
|
||||
match elem.event(state, k.clone()) {
|
||||
UpdateResponse::Command(command) => run_command(command, elem, k.clone()),
|
||||
UpdateResponse::CommandList(commands) => {
|
||||
for command in commands {
|
||||
if run_command(command, elem, k.clone()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
if let Some(remaining) = tick_duration.checked_sub(elapsed) {
|
||||
std::thread::sleep(remaining);
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a command, performing actions such as rendering, updating,
|
||||
/// sleeping, or exiting the application.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `command` - The command to execute, such as rendering or updating the UI.
|
||||
/// * `elem` - A mutable reference to the UI element.
|
||||
/// * `k` - A `Key` input passed along for further processing.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// `true` if the command is `Exit`, ending the application loop.
|
||||
fn run_command(command: Command, elem: &mut Box<dyn Element>, k: Key) -> bool {
|
||||
match command {
|
||||
Command::Render(state) => {
|
||||
render(elem, state);
|
||||
false
|
||||
}
|
||||
Command::Update(state) => update(elem, state, k),
|
||||
Command::Sleep(duration) => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(duration));
|
||||
false
|
||||
}
|
||||
Command::Exit => {
|
||||
show_cursor();
|
||||
crossterm::terminal::disable_raw_mode().unwrap();
|
||||
clear();
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the main event loop for the application.
|
||||
///
|
||||
/// Enables raw mode, hides the cursor, and continuously renders and updates
|
||||
/// the UI based on user input. The loop will break if the `Exit` command is triggered.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `elem` - A mutable reference to the main UI element to be rendered and updated.
|
||||
pub fn run(elem: &mut Box<dyn Element>) {
|
||||
// Initialize terminal settings
|
||||
hide_cursor();
|
||||
crossterm::terminal::enable_raw_mode().unwrap();
|
||||
clear();
|
||||
loop {
|
||||
render(elem, 1);
|
||||
let k = read_key();
|
||||
if update(elem, 1, k) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+170
-123
@@ -1,175 +1,222 @@
|
||||
#[macro_export]
|
||||
macro_rules! __rsx {
|
||||
() => {
|
||||
(String::new(), Vec::new())
|
||||
};
|
||||
//! # Macros Module
|
||||
//!
|
||||
//! This module defines various macros for creating and manipulating OSUI elements.
|
||||
//! The macros provide a clean and concise syntax for defining UI components and
|
||||
//! handling their parameters, commands, and rendering.
|
||||
|
||||
($p:expr;) => {{
|
||||
(String::new(), vec![$p as Box<dyn osui::Element>])
|
||||
}};
|
||||
($p:expr; $($rest:tt)+) => {{
|
||||
let mut comps: Vec<Box<dyn osui::Element>> = osui::__rsx!($($rest)+).1;
|
||||
comps.insert(0, $p as Box<dyn osui::Element>);
|
||||
(String::new(), comps)
|
||||
}};
|
||||
// Props, With components (PC)
|
||||
($tag:path { $($inner:tt)* }) => {{
|
||||
(String::new(), vec![rsx!($tag {$($inner)*})])
|
||||
}};
|
||||
|
||||
($tag:path { $($inner:tt)* } $($rest:tt)+) => {{
|
||||
let mut comps: Vec<Box<dyn osui::Element>> = osui::__rsx!($($rest)+).1;
|
||||
comps.insert(0, rsx!($tag {$($inner)*}) );
|
||||
(String::new(), comps)
|
||||
}};
|
||||
|
||||
($tag:path { $($inner:tt)* }) => {{
|
||||
(String::new(), vec![rsx!($tag {$($inner)*})])
|
||||
}};
|
||||
|
||||
($tag:path { $($inner:tt)* } $($rest:tt)+) => {{
|
||||
let mut comps: Vec<Box<dyn osui::Element>> = osui::__rsx!($($rest)+).1;
|
||||
comps.insert(0, rsx!($tag {$($inner)*}) );
|
||||
(String::new(), comps)
|
||||
}};
|
||||
|
||||
($text:expr) => {{
|
||||
(format!($text), Vec::new())
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
/// Write OSUI Markup Language directly with rust.
|
||||
macro_rules! rsx {
|
||||
( $tag:path { $($k:ident: $v:expr),*; $($inner:tt)* } ) => {{
|
||||
let mut c = $tag();
|
||||
let a = osui::__rsx!($($inner)*);
|
||||
c.text = a.0;
|
||||
c.children = a.1;
|
||||
$(
|
||||
c.$k = $v;
|
||||
)*
|
||||
c as Box<dyn osui::Element>
|
||||
}};
|
||||
|
||||
( $tag:path { $($k:ident: $v:expr),* } ) => {{
|
||||
let mut c = $tag();
|
||||
$(
|
||||
c.$k = $v;
|
||||
)*
|
||||
c as Box<dyn osui::Element>
|
||||
}};
|
||||
|
||||
( $tag:path { $($inner:tt)* } ) => {{
|
||||
let mut c = $tag();
|
||||
let a = osui::__rsx!($($inner)*);
|
||||
c.text = a.0;
|
||||
c.children = a.1;
|
||||
c as Box<dyn osui::Element>
|
||||
}};
|
||||
|
||||
( $p:expr; ) => {{
|
||||
$p as Box<dyn osui::Element>
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
///```create_frame!(width, height)```
|
||||
/// Macro for defining an OSUI `Element`.
|
||||
///
|
||||
/// Create a frame for rendering multiple components
|
||||
macro_rules! create_frame {
|
||||
($width:expr, $height:expr) => {
|
||||
vec![" ".repeat($width); $height]
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
///```component!{
|
||||
/// This macro generates a new element type with specified parameters and default values.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// element! {
|
||||
/// MyElem {}
|
||||
/// defaults {}
|
||||
/// }```
|
||||
/// // functions (render, update)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Create a frame for rendering multiple components
|
||||
/// # Parameters
|
||||
/// - `name`: The name of the element type being defined.
|
||||
/// - `inner`: Additional fields or methods for the element.
|
||||
/// - `defaults`: Default values for the element's properties.
|
||||
/// - `functions`: Functions to implement for the element (e.g., rendering, updating).
|
||||
#[macro_export]
|
||||
macro_rules! element {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$name:ident {
|
||||
$( $inner:tt )*
|
||||
}
|
||||
defaults {$( $defaults:tt )*}
|
||||
$( $functions:tt )*
|
||||
) => {
|
||||
#[derive(Debug)]
|
||||
$(#[$meta])*
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct $name {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub width: ElementSize,
|
||||
pub height: ElementSize,
|
||||
pub children: Vec<Box<dyn Element>>,
|
||||
pub child: usize,
|
||||
pub text: String,
|
||||
pub style: Style,
|
||||
pub tick_line: std::collections::HashMap<usize, String>,
|
||||
$( $inner )*
|
||||
}
|
||||
|
||||
impl Element for $name {
|
||||
fn get_child(&mut self) -> Option<&mut Box<dyn Element>> {
|
||||
self.children.get_mut(self.child)
|
||||
}
|
||||
fn get_data(&self) -> ElementData {
|
||||
ElementData {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
style: self.style.clone(),
|
||||
width: self.width.clone(),
|
||||
height: self.height.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_data(&mut self, data: ElementData) {
|
||||
self.x = data.x;
|
||||
self.y = data.y;
|
||||
self.width = data.width;
|
||||
self.height = data.height;
|
||||
self.style = data.style;
|
||||
}
|
||||
|
||||
fn clear_ticks(&mut self) {
|
||||
self.tick_line.clear();
|
||||
fn update_data(&mut self, width: usize, height: usize) {
|
||||
self.width.try_set_size(width);
|
||||
self.height.try_set_size(height);
|
||||
for child in &mut self.children {
|
||||
child.update_data(width, height);
|
||||
}
|
||||
}
|
||||
$( $functions )*
|
||||
}
|
||||
|
||||
impl $name {
|
||||
/// Creates a new instance of the element with default values.
|
||||
pub fn new() -> $name {
|
||||
$name {
|
||||
children: Vec::new(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
width: ElementSize::Default(0),
|
||||
height: ElementSize::Default(0),
|
||||
child: 0,
|
||||
text: String::new(),
|
||||
style: Style::default(),
|
||||
tick_line: std::collections::HashMap::new(),
|
||||
$( $defaults )*
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_action(&self, tick: usize) -> String {
|
||||
match self.tick_line.get(&tick) {
|
||||
Some(action) => action.clone(),
|
||||
None => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_action(&mut self, tick: usize, action: &str) {
|
||||
if tick > 99 {
|
||||
self.tick_line.insert(tick-100, action.to_string());
|
||||
} else {
|
||||
self.tick_line.insert(tick, action.to_string());
|
||||
}
|
||||
/// Retrieves a mutable reference to the current child element, if any.
|
||||
///
|
||||
/// # Returns
|
||||
/// An `Option` containing a mutable reference to the child `Element` or `None`.
|
||||
pub fn get_child(&mut self) -> Option<&mut Box<dyn Element>> {
|
||||
self.children.get_mut(self.child)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for creating a command response from a list of commands.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// command!(Update(1), Render(2));
|
||||
/// ```
|
||||
///
|
||||
/// # Parameters
|
||||
/// - A list of commands to be included in the `UpdateResponse::CommandList`.
|
||||
#[macro_export]
|
||||
macro_rules! command {
|
||||
($($cmd:expr),*) => {
|
||||
UpdateResponse::CommandList(vec![$($cmd),*])
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for parsing parameters and children for an OSUI element.
|
||||
///
|
||||
/// This macro updates the properties of an element based on provided key-value pairs,
|
||||
/// and adds child elements to the parent element.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `elem`: The element being updated.
|
||||
/// - Various key-value pairs, child elements, and text content.
|
||||
#[macro_export]
|
||||
macro_rules! parse_rsx_param {
|
||||
($elem:expr, ) => {};
|
||||
|
||||
($elem:expr, $($k:ident: $v:expr),*) => {
|
||||
$(
|
||||
$elem.$k = $v;
|
||||
)*
|
||||
};
|
||||
|
||||
($elem:expr, $($k:ident: $v:expr),*; $($rest:tt)*) => {
|
||||
$(
|
||||
$elem.$k = $v;
|
||||
)*
|
||||
osui::parse_rsx_param!($elem, $($rest)*);
|
||||
};
|
||||
|
||||
($elem:expr, {$ielem:expr} $($rest:tt)*) => {
|
||||
$elem.children.push($ielem);
|
||||
osui::parse_rsx_param!($elem, $($rest)*);
|
||||
};
|
||||
|
||||
($elem:expr, $($k:ident: $v:expr),*, $text:expr) => {
|
||||
$(
|
||||
$elem.$k = $v;
|
||||
)*
|
||||
$elem.text = format!($text);
|
||||
};
|
||||
|
||||
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
|
||||
$elem.children.push(osui::rsx_elem!($pelem { $($inner)* }));
|
||||
osui::parse_rsx_param!($elem, $($rest)*)
|
||||
};
|
||||
|
||||
($elem:expr, $text:expr) => {
|
||||
$elem.text = format!($text);
|
||||
};
|
||||
}
|
||||
|
||||
/// Macro for creating an OSUI element with parsed parameters.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// rsx! {
|
||||
/// text { "Hello, World!" }
|
||||
/// div {
|
||||
/// button { y: 2, "click me" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This macro provides a clean way to express OSUI elements, functioning like a div that can
|
||||
/// contain multiple components.
|
||||
#[macro_export]
|
||||
macro_rules! rsx_elem {
|
||||
($elem:path { $($inner:tt)* }) => {{
|
||||
let mut elem = $elem();
|
||||
osui::parse_rsx_param!(elem, $($inner)*);
|
||||
elem as Box<dyn osui::Element>
|
||||
}};
|
||||
}
|
||||
|
||||
/// Macro for defining a structured representation of UI elements in OSUI.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// rsx! {
|
||||
/// text { "Hello, World!" }
|
||||
/// div {
|
||||
/// button { y: 2, "click me" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This macro allows the creation of a root element that can contain other elements.
|
||||
#[macro_export]
|
||||
macro_rules! rsx {
|
||||
($($inner:tt)*) => {{
|
||||
osui::rsx_elem!( osui::ui::div { $($inner)* } )
|
||||
}};
|
||||
}
|
||||
|
||||
/// Macro for defining CSS styles in OSUI.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// css!(Style; color: "red"; background: "white");
|
||||
/// ```
|
||||
///
|
||||
/// This macro creates a new style based on the provided properties, applying default values
|
||||
/// for any unspecified fields.
|
||||
#[macro_export]
|
||||
macro_rules! css {
|
||||
(
|
||||
$style:path;
|
||||
$($inner:tt)*
|
||||
) => {{
|
||||
$style {
|
||||
$($inner)*
|
||||
..Default::default()
|
||||
}
|
||||
}};
|
||||
}
|
||||
+7
-6
@@ -1,10 +1,11 @@
|
||||
use osui::{rsx, ui::*, App};
|
||||
use osui::{rsx, ui::*};
|
||||
|
||||
fn main() {
|
||||
let element = rsx! {
|
||||
button { "Hello, World!" }
|
||||
};
|
||||
osui::app::run(&mut app());
|
||||
}
|
||||
|
||||
let mut app_screen = App::from(element);
|
||||
app_screen.run();
|
||||
fn app() -> Box<dyn osui::Element> {
|
||||
rsx! {
|
||||
text { "Hello, World!" }
|
||||
}
|
||||
}
|
||||
|
||||
+71
-30
@@ -1,49 +1,90 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{element, key::KeyKind, ui::Style, Element, ElementData, UpdateContext};
|
||||
use crate::{
|
||||
command, element, key::Key, render_to_frame, ui::Style, Command, Direction, Element,
|
||||
ElementData, ElementSize, UpdateResponse,
|
||||
};
|
||||
|
||||
element! {
|
||||
/// A text element for displaying static text in the TUI.
|
||||
///
|
||||
/// The `Text` element displays text and does not respond to user interactions.
|
||||
Text {}
|
||||
defaults {}
|
||||
fn render(&mut self, tick: usize) -> String {
|
||||
// self.style.write(&self.text)
|
||||
self.style.write(&format!("{tick}"))
|
||||
fn render(&self, _: usize) -> String {
|
||||
self.text.clone()
|
||||
}
|
||||
}
|
||||
element! {
|
||||
/// A clickable button element.
|
||||
///
|
||||
/// The `Button` element can be clicked, triggering an `on_click` function. Its appearance changes
|
||||
/// based on its interaction state, such as being "clicked".
|
||||
Button {
|
||||
/// A callback function executed when the button is clicked.
|
||||
on_click: fn()
|
||||
}
|
||||
|
||||
defaults {
|
||||
on_click: || {}
|
||||
}
|
||||
|
||||
fn render(&self, state: usize) -> String {
|
||||
let writer = self.style.use_style(&state);
|
||||
if state == 2 {
|
||||
return writer.write_clicked(&self.text);
|
||||
}
|
||||
writer.write(&self.text)
|
||||
}
|
||||
|
||||
fn event(&mut self, _state: usize, k: Key) -> UpdateResponse {
|
||||
if k == Key::Enter {
|
||||
(self.on_click)();
|
||||
return command!(
|
||||
Command::Render(2),
|
||||
Command::Sleep(120)
|
||||
);
|
||||
}
|
||||
UpdateResponse::None
|
||||
}
|
||||
}
|
||||
|
||||
element! {
|
||||
Button {
|
||||
pub binds: HashMap<KeyKind, String>,
|
||||
pub on_click: fn(&mut Button),
|
||||
clicked: bool
|
||||
}
|
||||
defaults {
|
||||
binds: HashMap::from([(KeyKind::Enter, String::from("click"))]),
|
||||
on_click: |_|{},
|
||||
clicked: false,
|
||||
/// A container element that can hold multiple child elements and handle directional key input.
|
||||
///
|
||||
/// The `Div` element serves as a container for other elements, allowing navigation between them
|
||||
/// using directional keys.
|
||||
Div {
|
||||
pub keybinds: HashMap<Key, Direction>
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &mut UpdateContext) {
|
||||
if let Some(v) = self.binds.get(&ctx.key.kind) {
|
||||
if v == "click" {
|
||||
self.clicked = true;
|
||||
self.add_action(ctx.tick+2, "un_click");
|
||||
(self.on_click)(self);
|
||||
defaults {
|
||||
keybinds: HashMap::from([
|
||||
(Key::Up, Direction::Up),
|
||||
(Key::Down, Direction::Down),
|
||||
(Key::Left, Direction::Left),
|
||||
(Key::Right, Direction::Right),
|
||||
])
|
||||
}
|
||||
|
||||
fn render(&self, state: usize) -> String {
|
||||
let mut frame = crate::create_frame(self.width, self.height);
|
||||
for (i, child) in (&self.children).iter().enumerate() {
|
||||
if i==self.child {
|
||||
render_to_frame(state, &mut frame, child);
|
||||
} else {
|
||||
render_to_frame(0, &mut frame, child);
|
||||
}
|
||||
}
|
||||
frame.join("\n")
|
||||
}
|
||||
|
||||
fn render(&mut self, tick: usize) -> String {
|
||||
|
||||
if self.get_action(tick)=="un_click" {
|
||||
self.clicked = false;
|
||||
fn event(&mut self, state: usize, k: Key) -> UpdateResponse {
|
||||
if let Some(direction) = self.keybinds.get(&k) {
|
||||
self.child = crate::closest_component(&self.children, self.child, direction.clone());
|
||||
} else if let Some(child) = self.get_child() {
|
||||
return child.event(state, k);
|
||||
}
|
||||
|
||||
if self.clicked {
|
||||
// return self.style.write(&format!("{tick}"))
|
||||
return self.style.write_clicked(&self.text);
|
||||
}
|
||||
self.style.write(&self.text)
|
||||
UpdateResponse::None
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-10
@@ -1,33 +1,46 @@
|
||||
/// The `ui` module provides user interface components and utilities for building
|
||||
/// a text-based user interface (TUI). It includes predefined styles and elements
|
||||
/// such as text, buttons, and containers.
|
||||
|
||||
pub mod styles;
|
||||
pub use styles::*;
|
||||
pub mod elements;
|
||||
pub use elements::*;
|
||||
|
||||
/// Creates and returns a `Box` containing a new `Text` element.
|
||||
/// Creates a new `Text` element.
|
||||
///
|
||||
/// This function constructs a `Text` element and wraps it in a `Box` to
|
||||
/// manage heap allocation and ownership.
|
||||
/// The `Text` element displays static text in the TUI, suitable for labels or headers.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A boxed `Text` element, initialized and ready to be styled or populated
|
||||
/// with text content.
|
||||
/// A `Box<Text>` containing a new `Text` element instance.
|
||||
pub fn text() -> Box<Text> {
|
||||
Box::new(Text::new())
|
||||
}
|
||||
|
||||
/// Creates and returns a `Box` containing a new `Button` element.
|
||||
/// Creates a new `Button` element with a customized style.
|
||||
///
|
||||
/// This function constructs a `Button` element and wraps it in a `Box` to
|
||||
/// manage heap allocation and ownership. Buttons can be styled and
|
||||
/// configured for interactive functionality.
|
||||
/// The `Button` element responds to user clicks, triggering an action. This function
|
||||
/// customizes the button's clicked background and foreground colors.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A boxed `Button` element, initialized and ready for configuration.
|
||||
/// A `Box<Button>` containing a new `Button` element instance.
|
||||
pub fn button() -> Box<Button> {
|
||||
let mut btn = Button::new();
|
||||
btn.style.clicked_bg = styles::Color::White;
|
||||
btn.style.clicked_fg = styles::Color::Black;
|
||||
Box::new(btn)
|
||||
}
|
||||
|
||||
/// Creates a new `Div` container element.
|
||||
///
|
||||
/// The `Div` element serves as a container for other UI elements, enabling navigation
|
||||
/// and grouping of child elements. It supports directional navigation using key bindings.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Box<Div>` containing a new `Div` element instance.
|
||||
pub fn div() -> Box<Div> {
|
||||
Box::new(Div::new())
|
||||
}
|
||||
|
||||
+54
-6
@@ -1,64 +1,87 @@
|
||||
/// Represents a style configuration for TUI elements, including background,
|
||||
/// foreground, outline colors, and font settings for different element states
|
||||
/// (hovered, clicked, selected).
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Style {
|
||||
/// Background color of the element.
|
||||
pub bg: Color,
|
||||
/// Foreground color of the element.
|
||||
pub fg: Color,
|
||||
/// Outline color of the element.
|
||||
pub outline: Color,
|
||||
/// Font style for the element.
|
||||
pub font: Font,
|
||||
|
||||
/// Background color when the element is hovered.
|
||||
pub hover_bg: Color,
|
||||
/// Foreground color when the element is hovered.
|
||||
pub hover_fg: Color,
|
||||
/// Outline color when the element is hovered.
|
||||
pub hover_outline: Color,
|
||||
/// Font style when the element is hovered.
|
||||
pub hover_font: Font,
|
||||
/// Foreground color for the cursor when hovered.
|
||||
pub hover_cursor_fg: Color,
|
||||
/// Background color for the cursor when hovered.
|
||||
pub hover_cursor_bg: Color,
|
||||
|
||||
/// Background color when the element is clicked.
|
||||
pub clicked_bg: Color,
|
||||
/// Foreground color when the element is clicked.
|
||||
pub clicked_fg: Color,
|
||||
/// Outline color when the element is clicked.
|
||||
pub clicked_outline: Color,
|
||||
/// Font style when the element is clicked.
|
||||
pub clicked_font: Font,
|
||||
|
||||
/// Background color when the element is selected.
|
||||
pub selected_bg: Color,
|
||||
/// Foreground color when the element is selected.
|
||||
pub selected_fg: Color,
|
||||
/// Font style when the element is selected.
|
||||
pub selected_font: Font,
|
||||
|
||||
/// Foreground color for the cursor.
|
||||
pub cursor_fg: Color,
|
||||
/// Background color for the cursor.
|
||||
pub cursor_bg: Color,
|
||||
|
||||
/// Indicates if the style is active, affecting its state-based styling.
|
||||
pub is_active: bool,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
/// Creates a default `Style` instance with `None` for all color and font fields
|
||||
/// and inactive (`is_active` set to false).
|
||||
fn default() -> Style {
|
||||
Style {
|
||||
bg: Color::None,
|
||||
fg: Color::None,
|
||||
outline: Color::None,
|
||||
font: Font::None,
|
||||
|
||||
hover_bg: Color::None,
|
||||
hover_fg: Color::None,
|
||||
hover_outline: Color::None,
|
||||
hover_font: Font::None,
|
||||
hover_cursor_fg: Color::None,
|
||||
hover_cursor_bg: Color::None,
|
||||
|
||||
clicked_bg: Color::None,
|
||||
clicked_fg: Color::None,
|
||||
clicked_outline: Color::None,
|
||||
clicked_font: Font::None,
|
||||
|
||||
selected_bg: Color::None,
|
||||
selected_fg: Color::None,
|
||||
selected_font: Font::None,
|
||||
cursor_fg: Color::None,
|
||||
cursor_bg: Color::None,
|
||||
|
||||
is_active: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
/// Generates the ANSI code for the current style, using the active color
|
||||
/// and font if `is_active` is true.
|
||||
pub fn get(&self) -> String {
|
||||
if self.is_active {
|
||||
format!(
|
||||
@@ -77,6 +100,7 @@ impl Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the outline color in ANSI format, applying hover style if active.
|
||||
pub fn get_outline(&self) -> String {
|
||||
if self.is_active {
|
||||
self.outline.prioritize(&self.hover_outline).ansi()
|
||||
@@ -85,6 +109,7 @@ impl Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the clicked style ANSI sequence for the element.
|
||||
pub fn get_clicked(&self) -> String {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
@@ -94,6 +119,7 @@ impl Style {
|
||||
)
|
||||
}
|
||||
|
||||
/// Retrieves the selected style ANSI sequence for the element.
|
||||
pub fn get_selected(&self) -> String {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
@@ -103,6 +129,7 @@ impl Style {
|
||||
)
|
||||
}
|
||||
|
||||
/// Retrieves the cursor style ANSI sequence for the element, considering hover.
|
||||
pub fn get_cursor(&self) -> String {
|
||||
if self.is_active {
|
||||
format!(
|
||||
@@ -115,27 +142,40 @@ impl Style {
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes the given string with the active style's ANSI sequences.
|
||||
pub fn write(&self, s: &str) -> String {
|
||||
format!("{}{}\x1b[0m", self.get(), s)
|
||||
}
|
||||
|
||||
/// Writes a string with the outline color applied.
|
||||
pub fn write_outline(&self, s: &str) -> String {
|
||||
format!("{}{}\x1b[0m", self.get_outline(), s)
|
||||
}
|
||||
|
||||
/// Writes a string with the clicked style applied.
|
||||
pub fn write_clicked(&self, s: &str) -> String {
|
||||
format!("{}{}\x1b[0m", self.get_clicked(), s)
|
||||
}
|
||||
|
||||
/// Writes a string with the selected style applied.
|
||||
pub fn write_selected(&self, s: &str) -> String {
|
||||
format!("{}{}\x1b[0m", self.get_selected(), s)
|
||||
}
|
||||
|
||||
/// Writes a string with the cursor style applied.
|
||||
pub fn write_cursor(&self, s: &str) -> String {
|
||||
format!("{}{}\x1b[0m", self.get_cursor(), s)
|
||||
}
|
||||
|
||||
/// Clones the style and sets `is_active` based on the given state.
|
||||
pub fn use_style(&self, state: &usize) -> Style {
|
||||
let mut style = self.clone();
|
||||
style.is_active = *state == 1;
|
||||
style
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents different font styles that can be applied to TUI elements.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Font {
|
||||
None,
|
||||
@@ -144,10 +184,11 @@ pub enum Font {
|
||||
Italic,
|
||||
Reverse,
|
||||
Strike,
|
||||
Mul(Vec<Font>),
|
||||
Mul(Vec<Font>), // Allows combining multiple font styles.
|
||||
}
|
||||
|
||||
impl Font {
|
||||
/// Converts the font style to an ANSI sequence.
|
||||
pub fn ansi(&self) -> String {
|
||||
String::from(match self {
|
||||
Font::None => "",
|
||||
@@ -166,6 +207,8 @@ impl Font {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the prioritized font between `self` and `secondary`, favoring `secondary`
|
||||
/// if it is not `None`.
|
||||
pub fn prioritize<'a>(&'a self, secondary: &'a Font) -> &Font {
|
||||
if secondary == &Font::None {
|
||||
self
|
||||
@@ -175,10 +218,11 @@ impl Font {
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents color options for elements, supporting both named colors and RGB values.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Color {
|
||||
None,
|
||||
Rgb(u8, u8, u8),
|
||||
Rgb(u8, u8, u8), // RGB color representation.
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
@@ -190,6 +234,7 @@ pub enum Color {
|
||||
}
|
||||
|
||||
impl Color {
|
||||
/// Converts the color to an ANSI foreground sequence.
|
||||
pub fn ansi(&self) -> String {
|
||||
String::from(match self {
|
||||
Color::None => "",
|
||||
@@ -207,6 +252,7 @@ impl Color {
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts the color to an ANSI background sequence.
|
||||
pub fn ansi_bg(&self) -> String {
|
||||
String::from(match self {
|
||||
Color::None => "",
|
||||
@@ -224,6 +270,8 @@ impl Color {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the prioritized color between `self` and `secondary`, favoring `secondary`
|
||||
/// if it is not `None`.
|
||||
pub fn prioritize<'a>(&'a self, secondary: &'a Color) -> &Color {
|
||||
if secondary == &Color::None {
|
||||
self
|
||||
|
||||
+13
-11
@@ -10,14 +10,6 @@ lazy_static! {
|
||||
pub static ref ANSI: Regex = Regex::new(r"(\x1b\[([0-9;]*)[a-zA-Z])+").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Int(i32),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
/// Compress a string by a regex pattern
|
||||
pub fn compress_string(input: &str, re: &Regex) -> (String, HashMap<usize, String>) {
|
||||
let mut matches_map = HashMap::new();
|
||||
@@ -82,9 +74,9 @@ fn merge_line(frame_: &str, line_: &str, x: usize) -> String {
|
||||
}
|
||||
|
||||
/// Render to a frame
|
||||
pub fn render_to_frame(tick: usize, frame: &mut Vec<String>, element: &mut Box<dyn Element>) {
|
||||
pub fn render_to_frame(state: usize, frame: &mut Vec<String>, element: &Box<dyn Element>) {
|
||||
let data = element.get_data();
|
||||
for (i, line) in element.render(tick).split('\n').enumerate() {
|
||||
for (i, line) in element.render(state).split('\n').enumerate() {
|
||||
if (data.y + i) < frame.len() {
|
||||
let frame_line = frame.get_mut(data.y + i).unwrap();
|
||||
*frame_line = merge_line(&frame_line, line, data.x);
|
||||
@@ -103,7 +95,7 @@ pub fn hide_cursor() {
|
||||
}
|
||||
|
||||
pub fn show_cursor() {
|
||||
print!("\x1b[?25H");
|
||||
print!("\x1B[?25h");
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
|
||||
@@ -111,6 +103,7 @@ pub fn flush() {
|
||||
stdout().flush().unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Direction {
|
||||
Left,
|
||||
Right,
|
||||
@@ -144,3 +137,12 @@ pub fn closest_component(
|
||||
.map(|(index, _)| index) // Return the index of the closest component
|
||||
.unwrap_or(current_index) // If no component is found, return the current index
|
||||
}
|
||||
|
||||
pub fn create_frame(width: crate::ElementSize, height: crate::ElementSize) -> Vec<String> {
|
||||
vec![" ".repeat(width.get_size()); height.get_size()]
|
||||
}
|
||||
|
||||
pub fn get_term_size() -> (usize, usize) {
|
||||
let (width, height) = crossterm::terminal::size().unwrap();
|
||||
(width as usize, height as usize)
|
||||
}
|
||||
Reference in New Issue
Block a user