documented the code

This commit is contained in:
2025-01-20 00:51:03 +01:00
parent fda5387ccc
commit aa55cc059a
5 changed files with 282 additions and 93 deletions
+53
View File
@@ -1,17 +1,37 @@
//! Console module
//!
//! This module provides utilities for managing terminal interactions, including event handling,
//! rendering, and terminal state management.
use crate::{Element, Frame}; use crate::{Element, Frame};
/// Represents the console state, containing a frame for rendering and a mouse capture flag.
pub struct Console(Frame, bool); pub struct Console(Frame, bool);
/// Enum representing various events that can occur in the console.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Event { pub enum Event {
/// A keyboard event.
Key(crossterm::event::KeyEvent), Key(crossterm::event::KeyEvent),
/// A terminal resize event with new dimensions (width, height).
Resize(u16, u16), Resize(u16, u16),
/// A mouse event.
Mouse(crossterm::event::MouseEvent), Mouse(crossterm::event::MouseEvent),
/// A paste event with the pasted content.
Paste(String), Paste(String),
/// An event indicating the terminal gained focus.
FocusGained, FocusGained,
/// An event indicating the terminal lost focus.
FocusLost, FocusLost,
} }
/// Initializes the console with raw mode enabled and optionally mouse capture.
///
/// # Arguments
/// * `mouse` - A boolean flag indicating whether to enable mouse capture.
///
/// # Returns
/// A `Console` instance wrapped in a `Result`.
pub fn init(mouse: bool) -> crate::Result<Console> { pub fn init(mouse: bool) -> crate::Result<Console> {
crossterm::terminal::enable_raw_mode()?; crossterm::terminal::enable_raw_mode()?;
crate::utils::clear()?; crate::utils::clear()?;
@@ -23,11 +43,28 @@ pub fn init(mouse: bool) -> crate::Result<Console> {
} }
impl Console { impl Console {
/// Renders a user interface element with an optional event.
///
/// # Arguments
/// * `ui` - The UI element to render.
/// * `event` - An optional event to pass to the UI element.
///
/// # Returns
/// A `Result` indicating success or failure.
pub fn draw(&mut self, ui: Element, event: Option<Event>) -> crate::Result<()> { pub fn draw(&mut self, ui: Element, event: Option<Event>) -> crate::Result<()> {
self.0.clear()?; self.0.clear()?;
ui(&mut self.0, event) ui(&mut self.0, event)
} }
/// Runs the console loop, rendering the UI and handling events.
///
/// This method is disabled when the `engine` feature is enabled.
///
/// # Arguments
/// * `ui` - The UI element to render.
///
/// # Returns
/// A `Result` indicating success or failure.
#[cfg(not(feature = "engine"))] #[cfg(not(feature = "engine"))]
pub fn run(&mut self, ui: Element) -> crate::Result<()> { pub fn run(&mut self, ui: Element) -> crate::Result<()> {
self.draw(ui.clone(), None)?; self.draw(ui.clone(), None)?;
@@ -41,6 +78,10 @@ impl Console {
} }
} }
/// Ends the console session, restoring the terminal state.
///
/// # Returns
/// A `Result` indicating success or failure.
pub fn end(&self) -> crate::Result<()> { pub fn end(&self) -> crate::Result<()> {
if self.1 { if self.1 {
crossterm::execute!(std::io::stdout(), crossterm::event::DisableMouseCapture)?; crossterm::execute!(std::io::stdout(), crossterm::event::DisableMouseCapture)?;
@@ -50,11 +91,19 @@ impl Console {
crate::utils::show_cursor() crate::utils::show_cursor()
} }
/// Retrieves the current terminal size.
///
/// # Returns
/// A tuple containing the width and height of the terminal.
pub fn size(&self) -> (u16, u16) { pub fn size(&self) -> (u16, u16) {
(self.0.width, self.0.height) (self.0.width, self.0.height)
} }
} }
/// Reads an event from the terminal.
///
/// # Returns
/// An `Event` wrapped in a `Result`.
pub fn read() -> crate::Result<Event> { pub fn read() -> crate::Result<Event> {
let event = crossterm::event::read()?; let event = crossterm::event::read()?;
@@ -68,6 +117,10 @@ pub fn read() -> crate::Result<Event> {
}) })
} }
/// Attempts to read an event from the terminal without blocking.
///
/// # Returns
/// An `Option` containing an `Event` if one is available.
pub fn try_read() -> Option<Event> { pub fn try_read() -> Option<Event> {
if crossterm::event::poll(std::time::Duration::ZERO).unwrap_or(false) { if crossterm::event::poll(std::time::Duration::ZERO).unwrap_or(false) {
read().ok() read().ok()
+120 -92
View File
@@ -1,3 +1,15 @@
//! # Library Documentation
//! This library provides a framework for creating terminal-based user interfaces.
//! It includes support for widgets, layouts, and event handling using `crossterm`.
//! ## Modules
//! - `console`: Handles terminal input and events.
//! - `elements`: Provides pre-defined UI elements. Optional, enabled by default.
//! - `rsx`: Supports declarative UI definition using an XML-like syntax. Optional, enabled by default.
//! - `state`: Manages state for widgets and other components.
//! - `utils`: Utility functions for terminal rendering.
//! - `prelude`: Exports commonly used types and traits for easy access.
pub mod console; pub mod console;
#[cfg(not(feature = "no_elem"))] #[cfg(not(feature = "no_elem"))]
pub mod elements; pub mod elements;
@@ -6,33 +18,74 @@ pub mod rsx;
pub mod state; pub mod state;
pub mod utils; pub mod utils;
/// Commonly used imports for convenience.
pub mod prelude { pub mod prelude {
pub use crate::*; pub use crate::*;
pub use console::Event; pub use console::Event;
pub use crossterm::event::{KeyCode, KeyEvent}; pub use crossterm::event::{KeyCode, KeyEvent};
} }
/// Type alias for simplifying error handling.
/// Represents an I/O operation result.
pub use std::io::Result; pub use std::io::Result;
/// Type alias for a UI element.
/// An `Element` is a thread-safe function that renders onto a `Frame`
/// and optionally handles events.
pub type Element = std::sync::Arc<dyn Fn(&mut Frame, Option<console::Event>) -> crate::Result<()>>; pub type Element = std::sync::Arc<dyn Fn(&mut Frame, Option<console::Event>) -> crate::Result<()>>;
/// A trait representing a UI widget.
pub trait Widget { pub trait Widget {
/// Renders the widget as a `String`.
fn render(&self) -> String; fn render(&self) -> String;
/// Handles an event for the widget. Defaults to a no-op.
fn event(&mut self, event: console::Event) { fn event(&mut self, event: console::Event) {
_ = event; _ = event;
} }
} }
/// Struct representing a rectangular area in the terminal.
#[derive(Debug, Clone, Copy)]
pub struct Area {
pub width: Size,
pub height: Size,
pub x: Pos,
pub y: Pos,
}
/// Struct representing a rendering frame.
#[derive(Debug, Default, Clone, Copy)]
pub struct Frame {
pub width: u16,
pub height: u16,
last_elem: (u16, u16),
}
/// Enum representing positioning options for widgets.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Pos { pub enum Pos {
/// Automatic positioning.
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
auto, auto,
/// Centered positioning.
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
center, center,
/// Fixed position as a number of columns or rows.
Num(u16),
}
/// Enum representing sizing options for widgets.
#[derive(Debug, Clone, Copy)]
pub enum Size {
/// Automatic sizing.
Auto,
/// Fixed size.
Num(u16), Num(u16),
} }
impl Pos { impl Pos {
/// Calculates the position based on the current frame dimensions.
pub fn get(self, auto: u16, width: u16, frame: u16) -> u16 { pub fn get(self, auto: u16, width: u16, frame: u16) -> u16 {
match self { match self {
Self::auto => auto, Self::auto => auto,
@@ -42,13 +95,8 @@ impl Pos {
} }
} }
#[derive(Debug, Clone, Copy)]
pub enum Size {
Auto,
Num(u16),
}
impl Size { impl Size {
/// Internal method to compute size.
fn get_(self, written: u16) -> u16 { fn get_(self, written: u16) -> u16 {
match self { match self {
Self::Auto => written, Self::Auto => written,
@@ -56,27 +104,79 @@ impl Size {
} }
} }
/// Computes the size based on written content or frame dimensions.
pub fn get(self, written: u16, _frame: u16) -> u16 { pub fn get(self, written: u16, _frame: u16) -> u16 {
self.get_(written) self.get_(written)
} }
} }
#[derive(Debug, Clone, Copy)] impl Area {
pub struct Area { /// Creates a new `Area` with default values.
pub width: Size, pub fn new() -> Self {
pub height: Size, Self::default()
pub x: Pos,
pub y: Pos,
} }
#[derive(Debug, Default, Clone, Copy)] /// Center the `Area` horizontally. Only available with the `portable` feature.
pub struct Frame { #[cfg(feature = "portable")]
pub width: u16, pub fn center_x() -> Self {
pub height: u16, let mut s = Self::default();
last_elem: (u16, u16), s.x = Pos::center;
s
}
/// Center the `Area` vertically. Only available with the `portable` feature.
#[cfg(feature = "portable")]
pub fn center_y() -> Self {
let mut s = Self::default();
s.y = Pos::center;
s
}
/// Center the `Area` both horizontally and vertically. Only available with the `portable` feature.
#[cfg(feature = "portable")]
pub fn center() -> Self {
let mut s = Self::default();
s.x = Pos::center;
s.y = Pos::center;
s
}
// Additional utility methods for setting properties are available with the `portable` feature.
#[cfg(feature = "portable")]
pub fn set_width(&mut self, w: Size) -> Self {
self.width = w;
*self
}
#[cfg(feature = "portable")]
pub fn set_height(&mut self, h: Size) -> Self {
self.height = h;
*self
}
#[cfg(feature = "portable")]
pub fn set_x(&mut self, x: Pos) -> Self {
self.x = x;
*self
}
#[cfg(feature = "portable")]
pub fn set_y(&mut self, y: Pos) -> Self {
self.y = y;
*self
}
}
impl Default for Area {
fn default() -> Self {
Self {
width: Size::Auto,
height: Size::Auto,
x: Pos::Num(0),
y: Pos::auto,
}
}
} }
impl Frame { impl Frame {
/// Draws a widget on the frame.
pub fn draw<W>(&mut self, w: &W, props: Area) -> Result<()> pub fn draw<W>(&mut self, w: &W, props: Area) -> Result<()>
where where
W: Widget, W: Widget,
@@ -114,12 +214,14 @@ impl Frame {
Ok(()) Ok(())
} }
/// Clears the frame.
pub fn clear(&mut self) -> Result<()> { pub fn clear(&mut self) -> Result<()> {
self.last_elem.0 = 0; self.last_elem.0 = 0;
self.last_elem.1 = 0; self.last_elem.1 = 0;
utils::clear() utils::clear()
} }
/// Creates a new frame with the specified dimensions.
pub fn new((width, height): (u16, u16)) -> Self { pub fn new((width, height): (u16, u16)) -> Self {
Self { Self {
width, width,
@@ -129,81 +231,7 @@ impl Frame {
} }
} }
impl Area { /// Creates a new state object.
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "portable")]
pub fn center_x() -> Self {
let mut s = Self::default();
s.x = Pos::Center;
s
}
#[cfg(feature = "portable")]
pub fn center_y() -> Self {
let mut s = Self::default();
s.y = Pos::Center;
s
}
#[cfg(feature = "portable")]
pub fn center() -> Self {
let mut s = Self::default();
s.x = Pos::Center;
s.y = Pos::Center;
s
}
#[cfg(feature = "portable")]
pub fn set_width(&mut self, w: Size) -> Self {
self.width = w;
*self
}
#[cfg(feature = "portable")]
pub fn set_height(&mut self, h: Size) -> Self {
self.height = h;
*self
}
#[cfg(feature = "portable")]
pub fn set_x(&mut self, x: Pos) -> Self {
self.x = x;
*self
}
#[cfg(feature = "portable")]
pub fn set_y(&mut self, y: Pos) -> Self {
self.y = y;
*self
}
#[cfg(feature = "portable")]
pub fn x_auto(&mut self) -> Self {
self.x = Pos::Auto;
*self
}
#[cfg(feature = "portable")]
pub fn y_zero(&mut self) -> Self {
self.y = Pos::Num(0);
*self
}
}
impl Default for Area {
fn default() -> Self {
Self {
width: Size::Auto,
height: Size::Auto,
x: Pos::Num(0),
y: Pos::auto,
}
}
}
pub fn use_state<T>(v: T) -> state::State<T> { pub fn use_state<T>(v: T) -> state::State<T> {
state::State(Box::into_raw(Box::new(v))) state::State(Box::into_raw(Box::new(v)))
} }
+26
View File
@@ -1,3 +1,23 @@
/// A brief summary of what the macro does.
///
/// A detailed explanation about the macro's purpose,
/// how it expands, and when to use it.
///
/// # Examples
///
/// Simple
/// ```rust
/// rsx! {
/// "Hello, World!"
/// }
/// ```
///
/// Button example
/// ```rust
/// rsx! {
/// button { "{count}" }
/// }
/// ```
#[macro_export] #[macro_export]
macro_rules! rsx { macro_rules! rsx {
($($inner:tt)*) => { ($($inner:tt)*) => {
@@ -10,6 +30,9 @@ macro_rules! rsx {
}; };
} }
/// # Warning
///
/// **Don't use this macro manually** use `rsx!` instead
#[macro_export] #[macro_export]
macro_rules! rsx_inner { macro_rules! rsx_inner {
// For loop // For loop
@@ -81,6 +104,9 @@ macro_rules! rsx_inner {
($frame:expr, $event:expr;) => {}; ($frame:expr, $event:expr;) => {};
} }
/// # Warning
///
/// **Don't use this macro manually** use `rsx!` instead
#[macro_export] #[macro_export]
macro_rules! tw_area { macro_rules! tw_area {
($a:expr, $n:ident) => { ($a:expr, $n:ident) => {
+29 -1
View File
@@ -1,23 +1,42 @@
//! The `state` module provides the `State` type for managing mutable state across UI components
//! and operations. It wraps a raw pointer to enable shared mutability and integrates with common
//! traits for ease of use.
/// A wrapper around a raw pointer to a mutable value, allowing shared state management.
///
/// # Safety
/// This type uses unsafe code to dereference raw pointers. Proper care must be taken
/// to ensure the validity and lifetime of the referenced value.
///
/// # Examples
/// ```
/// let value = 42;
/// let state = State(Box::into_raw(Box::new(value)));
/// assert_eq!(*state, 42);
/// ```
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct State<T>(pub(crate) *mut T); pub struct State<T>(pub(crate) *mut T);
impl<T: std::fmt::Display> std::fmt::Display for State<T> { impl<T: std::fmt::Display> std::fmt::Display for State<T> {
/// Formats the value of the state for display.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", unsafe { &*self.0 }) write!(f, "{}", unsafe { &*self.0 })
} }
} }
impl<T: std::fmt::Debug> std::fmt::Debug for State<T> { impl<T: std::fmt::Debug> std::fmt::Debug for State<T> {
/// Formats the state for debugging purposes.
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "State({:?})", unsafe { &*self.0 }) write!(f, "State({:?})", unsafe { &*self.0 })
} }
} }
//// State ops //// //// State Operations ////
impl<T: std::ops::Add<Output = T> + Clone> std::ops::Add<T> for State<T> { impl<T: std::ops::Add<Output = T> + Clone> std::ops::Add<T> for State<T> {
type Output = T; type Output = T;
/// Adds a value to the state, returning the result.
fn add(self, rhs: T) -> Self::Output { fn add(self, rhs: T) -> Self::Output {
unsafe { unsafe {
let lhs = &mut *self.0; let lhs = &mut *self.0;
@@ -29,6 +48,7 @@ impl<T: std::ops::Add<Output = T> + Clone> std::ops::Add<T> for State<T> {
impl<T: std::ops::Sub<Output = T> + Clone> std::ops::Sub<T> for State<T> { impl<T: std::ops::Sub<Output = T> + Clone> std::ops::Sub<T> for State<T> {
type Output = T; type Output = T;
/// Subtracts a value from the state, returning the result.
fn sub(self, rhs: T) -> Self::Output { fn sub(self, rhs: T) -> Self::Output {
unsafe { unsafe {
let lhs = &mut *self.0; let lhs = &mut *self.0;
@@ -40,6 +60,7 @@ impl<T: std::ops::Sub<Output = T> + Clone> std::ops::Sub<T> for State<T> {
impl<T: std::ops::Div<Output = T> + Clone> std::ops::Div<T> for State<T> { impl<T: std::ops::Div<Output = T> + Clone> std::ops::Div<T> for State<T> {
type Output = T; type Output = T;
/// Divides the state by a value, returning the result.
fn div(self, rhs: T) -> Self::Output { fn div(self, rhs: T) -> Self::Output {
unsafe { unsafe {
let lhs = &mut *self.0; let lhs = &mut *self.0;
@@ -49,12 +70,14 @@ impl<T: std::ops::Div<Output = T> + Clone> std::ops::Div<T> for State<T> {
} }
impl<T: std::ops::AddAssign + Clone> std::ops::AddAssign<T> for State<T> { impl<T: std::ops::AddAssign + Clone> std::ops::AddAssign<T> for State<T> {
/// Adds a value to the state in place.
fn add_assign(&mut self, rhs: T) { fn add_assign(&mut self, rhs: T) {
unsafe { *self.0 += rhs } unsafe { *self.0 += rhs }
} }
} }
impl<T: std::ops::SubAssign + Clone> std::ops::SubAssign<T> for State<T> { impl<T: std::ops::SubAssign + Clone> std::ops::SubAssign<T> for State<T> {
/// Subtracts a value from the state in place.
fn sub_assign(&mut self, rhs: T) { fn sub_assign(&mut self, rhs: T) {
unsafe { *self.0 -= rhs } unsafe { *self.0 -= rhs }
} }
@@ -62,24 +85,29 @@ impl<T: std::ops::SubAssign + Clone> std::ops::SubAssign<T> for State<T> {
impl<T> std::ops::Deref for State<T> { impl<T> std::ops::Deref for State<T> {
type Target = T; type Target = T;
/// Dereferences the state to access the underlying value.
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
unsafe { &*self.0 } unsafe { &*self.0 }
} }
} }
impl<T> std::ops::DerefMut for State<T> { impl<T> std::ops::DerefMut for State<T> {
/// Dereferences the state to access the underlying mutable value.
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.0 } unsafe { &mut *self.0 }
} }
} }
impl<T> State<T> { impl<T> State<T> {
/// Returns a copy of the current state.
pub fn copy_state(&self) -> Self { pub fn copy_state(&self) -> Self {
Self(self.0) Self(self.0)
} }
} }
impl<T: PartialEq<T>> PartialEq<T> for State<T> { impl<T: PartialEq<T>> PartialEq<T> for State<T> {
/// Compares the state with another value for equality.
fn eq(&self, other: &T) -> bool { fn eq(&self, other: &T) -> bool {
unsafe { &*self.0 == other } unsafe { &*self.0 == other }
} }
+54
View File
@@ -1,24 +1,78 @@
//! The `utils` module provides utility functions for terminal manipulation
//! and string operations, designed to enhance terminal-based applications.
use std::io::Write; use std::io::Write;
/// Clears the terminal screen and moves the cursor to the top-left corner.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::clear().unwrap();
/// ```
pub fn clear() -> crate::Result<()> { pub fn clear() -> crate::Result<()> {
print!("\x1B[2J\x1B[H"); print!("\x1B[2J\x1B[H");
std::io::stdout().flush() std::io::stdout().flush()
} }
/// Hides the terminal cursor.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::hide_cursor().unwrap();
/// ```
pub fn hide_cursor() -> crate::Result<()> { pub fn hide_cursor() -> crate::Result<()> {
print!("\x1b[?25l"); print!("\x1b[?25l");
std::io::stdout().flush() std::io::stdout().flush()
} }
/// Shows the terminal cursor.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::show_cursor().unwrap();
/// ```
pub fn show_cursor() -> crate::Result<()> { pub fn show_cursor() -> crate::Result<()> {
print!("\x1B[?25h"); print!("\x1B[?25h");
std::io::stdout().flush() std::io::stdout().flush()
} }
/// Flushes the terminal's stdout buffer.
///
/// # Returns
/// A `crate::Result<()>` indicating whether the operation succeeded.
///
/// # Example
/// ```
/// utils::flush().unwrap();
/// ```
pub fn flush() -> crate::Result<()> { pub fn flush() -> crate::Result<()> {
std::io::stdout().flush() std::io::stdout().flush()
} }
/// Calculates the width and height of a string when rendered in a terminal.
///
/// # Arguments
/// - `s`: The input string.
///
/// # Returns
/// A tuple `(u16, u16)` where:
/// - The first value is the maximum width of the string in characters.
/// - The second value is the height of the string in lines.
///
/// # Example
/// ```
/// let (width, height) = utils::str_size("Hello\nWorld!");
/// assert_eq!((5, 2), (width, height));
/// ```
pub fn str_size(s: &str) -> (u16, u16) { pub fn str_size(s: &str) -> (u16, u16) {
let mut height = 1; let mut height = 1;
let mut max_width = 0; let mut max_width = 0;