test
This commit is contained in:
@@ -25,6 +25,7 @@ cc = "1.0"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crossterm = "0.28.1"
|
crossterm = "0.28.1"
|
||||||
|
downcast-rs = "2.0.1"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|||||||
-139
@@ -1,139 +0,0 @@
|
|||||||
//! Console module
|
|
||||||
//!
|
|
||||||
//! This module provides utilities for managing terminal interactions, including event handling,
|
|
||||||
//! rendering, and terminal state management.
|
|
||||||
|
|
||||||
use crate::{Element, Frame};
|
|
||||||
|
|
||||||
/// Represents the console state, containing a frame for rendering and a mouse capture flag.
|
|
||||||
pub struct Console(Frame, bool);
|
|
||||||
|
|
||||||
/// Enum representing various events that can occur in the console.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum Event {
|
|
||||||
/// A keyboard event.
|
|
||||||
Key(crossterm::event::KeyEvent),
|
|
||||||
/// A terminal resize event with new dimensions (width, height).
|
|
||||||
Resize(u16, u16),
|
|
||||||
/// A mouse event.
|
|
||||||
Mouse(crossterm::event::MouseEvent),
|
|
||||||
/// A paste event with the pasted content.
|
|
||||||
Paste(String),
|
|
||||||
/// An event indicating the terminal gained focus.
|
|
||||||
FocusGained,
|
|
||||||
/// An event indicating the terminal lost focus.
|
|
||||||
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> {
|
|
||||||
crossterm::terminal::enable_raw_mode()?;
|
|
||||||
crate::utils::clear()?;
|
|
||||||
crate::utils::hide_cursor()?;
|
|
||||||
if mouse {
|
|
||||||
crossterm::execute!(std::io::stdout(), crossterm::event::EnableMouseCapture)?;
|
|
||||||
}
|
|
||||||
Ok(Console(Frame::new(crossterm::terminal::size()?), mouse))
|
|
||||||
}
|
|
||||||
|
|
||||||
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<()> {
|
|
||||||
self.0.clear()?;
|
|
||||||
ui(&mut self.0, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Runs the console loop, rendering the UI and handling events.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `ui` - The UI element to render.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// A `Result` indicating success or failure.
|
|
||||||
pub fn run(&mut self, ui: Element) -> crate::Result<()> {
|
|
||||||
self.draw(ui.clone(), None)?;
|
|
||||||
loop {
|
|
||||||
let event = read()?;
|
|
||||||
if let Event::Resize(w, h) = event {
|
|
||||||
self.0.width = w;
|
|
||||||
self.0.height = h;
|
|
||||||
} else if let Event::Mouse(crossterm::event::MouseEvent {
|
|
||||||
kind: crossterm::event::MouseEventKind::Moved,
|
|
||||||
row,
|
|
||||||
column,
|
|
||||||
..
|
|
||||||
}) = event
|
|
||||||
{
|
|
||||||
self.0.mouse_pos = Some((column, row));
|
|
||||||
|
|
||||||
self.draw(ui.clone(), None)?;
|
|
||||||
} else {
|
|
||||||
self.draw(ui.clone(), Some(event))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ends the console session, restoring the terminal state.
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
/// A `Result` indicating success or failure.
|
|
||||||
pub fn end(&self) -> crate::Result<()> {
|
|
||||||
if self.1 {
|
|
||||||
crossterm::execute!(std::io::stdout(), crossterm::event::DisableMouseCapture)?;
|
|
||||||
}
|
|
||||||
crossterm::terminal::disable_raw_mode()?;
|
|
||||||
crate::utils::clear()?;
|
|
||||||
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) {
|
|
||||||
(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> {
|
|
||||||
let event = crossterm::event::read()?;
|
|
||||||
|
|
||||||
Ok(match event {
|
|
||||||
crossterm::event::Event::Key(k) => Event::Key(k),
|
|
||||||
crossterm::event::Event::Resize(w, h) => Event::Resize(w, h),
|
|
||||||
crossterm::event::Event::FocusGained => Event::FocusGained,
|
|
||||||
crossterm::event::Event::FocusLost => Event::FocusLost,
|
|
||||||
crossterm::event::Event::Mouse(m) => Event::Mouse(m),
|
|
||||||
crossterm::event::Event::Paste(p) => Event::Paste(p),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
if crossterm::event::poll(std::time::Duration::ZERO).unwrap_or(false) {
|
|
||||||
read().ok()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
impl Widget for &str {
|
|
||||||
fn render(&self) -> String {
|
|
||||||
self.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for String {
|
|
||||||
fn render(&self) -> String {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Button {
|
|
||||||
pub on_click: Box<dyn FnMut()>,
|
|
||||||
children: String,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
last_elem: (u16, u16),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn btn() -> Button {
|
|
||||||
Button {
|
|
||||||
on_click: Box::new(|| {}),
|
|
||||||
children: String::new(),
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
last_elem: (0, 0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Button {
|
|
||||||
fn render(&self) -> String {
|
|
||||||
self.children.clone()
|
|
||||||
}
|
|
||||||
fn event(&mut self, _: crate::console::Event) {
|
|
||||||
(self.on_click)()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Button {
|
|
||||||
pub fn draw<W>(&mut self, w: &W, props: crate::Props) -> crate::Result<()>
|
|
||||||
where
|
|
||||||
W: Widget,
|
|
||||||
{
|
|
||||||
let written = w.render();
|
|
||||||
|
|
||||||
let (ww, wh) = utils::str_size(&written);
|
|
||||||
|
|
||||||
let (width, height) = (
|
|
||||||
props.width.get(ww, self.width),
|
|
||||||
props.height.get(wh, self.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (x, y) = (
|
|
||||||
props.x.get(self.last_elem.0, width, self.width),
|
|
||||||
props.y.get(self.last_elem.1, height, self.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.children += written
|
|
||||||
.lines()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(i, _)| -> bool { height > (*i as u16) })
|
|
||||||
.map(|(_, s)| s.chars().take(width as usize).collect::<String>())
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("\n")
|
|
||||||
.as_str();
|
|
||||||
|
|
||||||
self.last_elem.0 = x + width;
|
|
||||||
self.last_elem.1 = y + height;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+19
-189
@@ -1,205 +1,35 @@
|
|||||||
//! # Library Documentation
|
use std::{any::TypeId, thread, time::Duration};
|
||||||
//! This library provides a framework for creating terminal-based user interfaces.
|
|
||||||
//! It includes support for widgets, layouts, and event handling using `crossterm`.
|
|
||||||
|
|
||||||
//! ## Modules
|
pub mod output;
|
||||||
//! - `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 trait Extension {
|
||||||
#[cfg(not(feature = "no_elem"))]
|
fn tick(&mut self, args: &mut Args);
|
||||||
pub mod elements;
|
|
||||||
#[cfg(not(feature = "no_rsx"))]
|
|
||||||
pub mod rsx;
|
|
||||||
pub mod state;
|
|
||||||
pub mod utils;
|
|
||||||
|
|
||||||
/// Commonly used imports for convenience.
|
|
||||||
pub mod prelude {
|
|
||||||
pub use crate::console::*;
|
|
||||||
pub use crate::elements::*;
|
|
||||||
pub use crate::rsx;
|
|
||||||
pub use crate::utils;
|
|
||||||
pub use crate::{Element, Frame, Pos, Props, Result, Size, Widget};
|
|
||||||
pub use crossterm::event::{KeyCode, KeyEvent};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type alias for simplifying error handling.
|
pub trait BaseElement {
|
||||||
/// Represents an I/O operation result.
|
fn init(&mut self);
|
||||||
pub use std::io::Result;
|
fn get_components<'a>(&'a mut self) -> &'a mut Vec<Box<dyn ElementComponent>>;
|
||||||
|
|
||||||
/// 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<()>>;
|
|
||||||
|
|
||||||
/// A trait representing a UI widget.
|
|
||||||
pub trait Widget {
|
|
||||||
/// Renders the widget as a `String`.
|
|
||||||
fn render(&self) -> String;
|
|
||||||
|
|
||||||
/// Handles an event for the widget. Defaults to a no-op.
|
|
||||||
fn event(&mut self, event: console::Event) {
|
|
||||||
_ = event;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Struct representing a rectangular props in the terminal.
|
pub trait ElementComponent {
|
||||||
#[derive(Debug, Clone, Copy)]
|
fn get_type_id(&self) -> TypeId;
|
||||||
pub struct Props {
|
|
||||||
pub width: Size,
|
|
||||||
pub height: Size,
|
|
||||||
pub x: Pos,
|
|
||||||
pub y: Pos,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Struct representing a rendering frame.
|
pub struct Args {
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
pub tick_rate: u16,
|
||||||
pub struct Frame {
|
pub base_elements: Vec<Box<dyn BaseElement>>,
|
||||||
pub width: u16,
|
|
||||||
pub height: u16,
|
|
||||||
mouse_pos: Option<(u16, u16)>,
|
|
||||||
last_elem: (u16, u16),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enum representing positioning options for widgets.
|
pub fn run(mut args: Args, mut extensions: Vec<Box<dyn Extension>>) {
|
||||||
#[derive(Debug, Clone, Copy)]
|
for elem in &mut args.base_elements {
|
||||||
pub enum Pos {
|
elem.init();
|
||||||
/// Automatic positioning.
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
auto,
|
|
||||||
/// Centered positioning.
|
|
||||||
#[allow(non_camel_case_types)]
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pos {
|
|
||||||
/// Calculates the position based on the current frame dimensions.
|
|
||||||
pub fn get(self, auto: u16, width: u16, frame: u16) -> u16 {
|
|
||||||
match self {
|
|
||||||
Self::auto => auto,
|
|
||||||
Self::center => (frame - width) / 2,
|
|
||||||
Self::Num(n) => n,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Size {
|
|
||||||
/// Internal method to compute size.
|
|
||||||
fn get_(self, written: u16) -> u16 {
|
|
||||||
match self {
|
|
||||||
Self::Auto => written,
|
|
||||||
Self::Num(n) => n,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the size based on written content or frame dimensions.
|
loop {
|
||||||
pub fn get(self, written: u16, _frame: u16) -> u16 {
|
for extension in &mut extensions {
|
||||||
self.get_(written)
|
extension.tick(&mut args);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Props {
|
|
||||||
/// Creates a new `Props` with default values.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Props {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
width: Size::Auto,
|
|
||||||
height: Size::Auto,
|
|
||||||
x: Pos::Num(0),
|
|
||||||
y: Pos::auto,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Frame {
|
|
||||||
/// Draws a widget on the frame.
|
|
||||||
pub fn draw<W>(&mut self, w: &W, props: Props) -> Result<()>
|
|
||||||
where
|
|
||||||
W: Widget,
|
|
||||||
{
|
|
||||||
let written = w.render();
|
|
||||||
|
|
||||||
let (ww, wh) = utils::str_size(&written);
|
|
||||||
|
|
||||||
let (width, height) = (
|
|
||||||
props.width.get(ww, self.width),
|
|
||||||
props.height.get(wh, self.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (x, y) = (
|
|
||||||
props.x.get(self.last_elem.0, width, self.width),
|
|
||||||
props.y.get(self.last_elem.1, height, self.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
let ansi = if let Some((mouse_x, mouse_y)) = &self.mouse_pos {
|
|
||||||
if (x..x + width).contains(mouse_x) && (y..y + height).contains(mouse_y) {
|
|
||||||
"\x1b[32m"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i, line) in written.lines().enumerate() {
|
|
||||||
if i as u16 > height {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"\x1b[{};{}H{ansi}{}\x1b[0m",
|
|
||||||
y + (i as u16) + 1,
|
|
||||||
x + 1,
|
|
||||||
line.chars().take(width as usize).collect::<String>()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_elem.0 = x + width;
|
thread::sleep(Duration::from_millis(1000 / args.tick_rate as u64));
|
||||||
self.last_elem.1 = y + height;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the frame.
|
|
||||||
pub fn clear(&mut self) -> Result<()> {
|
|
||||||
self.last_elem.0 = 0;
|
|
||||||
self.last_elem.1 = 0;
|
|
||||||
utils::clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new frame with the specified dimensions.
|
|
||||||
pub fn new((width, height): (u16, u16)) -> Self {
|
|
||||||
Self {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
last_elem: (0, 0),
|
|
||||||
mouse_pos: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new state object.
|
|
||||||
pub fn use_state<T>(v: T) -> state::State<T> {
|
|
||||||
state::State(Box::into_raw(Box::new(v)))
|
|
||||||
}
|
|
||||||
|
|||||||
+27
-11
@@ -1,15 +1,31 @@
|
|||||||
use osui::prelude::*;
|
use osui::{
|
||||||
|
output::{OutputExtension, RenderAble},
|
||||||
|
run, Args, BaseElement,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
pub struct MyElem(*mut Vec<Box<dyn osui::ElementComponent>>);
|
||||||
let mut con = init(true)?;
|
impl BaseElement for MyElem {
|
||||||
|
fn init(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
(*self.0).push(RenderAble::new(|| String::from("value")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
con.run(app())?;
|
fn get_components<'a>(&'a mut self) -> &'a mut Vec<Box<dyn osui::ElementComponent>> {
|
||||||
|
unsafe { &mut *self.0 }
|
||||||
con.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn app() -> Element {
|
|
||||||
rsx! {
|
|
||||||
btn { "Click me" } (x-center y-center)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MyElem {
|
||||||
|
pub fn new(mut v: Vec<Box<dyn osui::ElementComponent>>) -> Box<MyElem> {
|
||||||
|
Box::new(MyElem(&mut v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args {
|
||||||
|
tick_rate: 40,
|
||||||
|
base_elements: vec![],
|
||||||
|
};
|
||||||
|
run(args, vec![Box::new(OutputExtension)]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
use std::{any::TypeId, fmt::Debug};
|
||||||
|
|
||||||
|
use crate::{ElementComponent, Extension};
|
||||||
|
|
||||||
|
pub struct OutputExtension;
|
||||||
|
|
||||||
|
impl Extension for OutputExtension {
|
||||||
|
fn tick(&mut self, args: &mut crate::Args) {
|
||||||
|
for elem in &mut args.base_elements {
|
||||||
|
for comp in elem.get_components() {
|
||||||
|
if comp.get_type_id() == TypeId::of::<RenderAble>() {
|
||||||
|
comp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RenderAble(pub Box<dyn Fn() -> String>);
|
||||||
|
|
||||||
|
impl ElementComponent for RenderAble {
|
||||||
|
fn get_type_id(&self) -> std::any::TypeId {
|
||||||
|
TypeId::of::<RenderAble>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderAble {
|
||||||
|
pub fn new<F: Fn() -> String + 'static>(f: F) -> Box<RenderAble> {
|
||||||
|
Box::new(RenderAble(Box::new(f)))
|
||||||
|
}
|
||||||
|
}
|
||||||
-186
@@ -1,186 +0,0 @@
|
|||||||
/// 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_rules! rsx {
|
|
||||||
($($inner:tt)*) => {
|
|
||||||
std::sync::Arc::new(
|
|
||||||
move |frame: &mut $crate::Frame, event: Option<$crate::console::Event>| -> $crate::Result<()> {
|
|
||||||
$crate::rsx_inner!(frame, event; $($inner)*);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Warning
|
|
||||||
///
|
|
||||||
/// **Don't use this macro manually** use `rsx!` instead
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! rsx_inner {
|
|
||||||
// Field
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
$n:ident: $v:expr
|
|
||||||
$(, $($rest:tt)*)?) => {
|
|
||||||
$frame.$n = $v;
|
|
||||||
$crate::rsx_inner!($frame, $event; $($($rest)*)?);
|
|
||||||
};
|
|
||||||
|
|
||||||
// For loop
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
for ($i:pat in $e:expr) $(%$s:ident),* {$($inner:tt)*}
|
|
||||||
$($rest:tt)*) => {
|
|
||||||
for $i in $e {
|
|
||||||
$(let $s = $s.copy_state();)*
|
|
||||||
$crate::rsx_inner!($frame, $event; $($inner)*);
|
|
||||||
}
|
|
||||||
$crate::rsx_inner!($frame, $event; $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// If statement
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
if ($e:expr) $(%$s:ident),* {$($inner:tt)*}
|
|
||||||
$(else if ($ee:expr) {$($elif_inner:tt)*})*
|
|
||||||
else {$($else_inner:tt)*}
|
|
||||||
$($rest:tt)*) => {
|
|
||||||
$(let $s = $s.copy_state();)*
|
|
||||||
if $e {
|
|
||||||
$crate::rsx_inner!($frame, $event; $($inner)*);
|
|
||||||
} $(else if $ee {
|
|
||||||
$crate::rsx_inner!($frame, $event; $($elif_inner)*);
|
|
||||||
})* else {
|
|
||||||
$crate::rsx_inner!($frame, $event; $($else_inner)*);
|
|
||||||
}
|
|
||||||
$crate::rsx_inner!($frame, $event; $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
if ($e:expr) $(%$s:ident),* {$($inner:tt)*}
|
|
||||||
$($rest:tt)*) => {
|
|
||||||
$(let $s = $s.copy_state();)*
|
|
||||||
if $e {
|
|
||||||
$crate::rsx_inner!($frame, $event; $($inner)*);
|
|
||||||
}
|
|
||||||
$crate::rsx_inner!($frame, $event; $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Literal
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
$elem:literal ($($inner:tt)*)
|
|
||||||
$($rest:tt)*) => {
|
|
||||||
let mut props = $crate::Props::new();
|
|
||||||
$crate::tw_props!(props, $($inner)*);
|
|
||||||
$frame.draw(&format!($elem), props)?;
|
|
||||||
$crate::rsx_inner!($frame, $event; $($rest)*);
|
|
||||||
};
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
$elem:literal
|
|
||||||
$($rest:tt)*) => {
|
|
||||||
$frame.draw(&format!($elem), Props::new())?;
|
|
||||||
$crate::rsx_inner!($frame, $event; $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Name { rsx } ( props )
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
$elem:path {
|
|
||||||
$($inner:tt)*
|
|
||||||
} ($($props_inner:tt)*)
|
|
||||||
$($rest:tt)*) => {
|
|
||||||
let mut props = $crate::Props::new();
|
|
||||||
$crate::tw_props!(props, $($props_inner)*);
|
|
||||||
|
|
||||||
let mut e = $elem();
|
|
||||||
$crate::rsx_inner!(e, $event; $($inner)*);
|
|
||||||
|
|
||||||
$frame.draw(&e, props)?;
|
|
||||||
$crate::rsx_inner!($frame, $event; $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Name { rsx }
|
|
||||||
($frame:expr, $event:expr;
|
|
||||||
$elem:path {
|
|
||||||
$($inner:tt)*
|
|
||||||
}
|
|
||||||
$($rest:tt)*) => {
|
|
||||||
let mut e = $elem();
|
|
||||||
$crate::rsx_inner!(e, $event; $($inner)*);
|
|
||||||
|
|
||||||
$frame.draw(&e, $crate::Props::new())?;
|
|
||||||
$crate::rsx_inner!($frame, $event; $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
($frame:expr, $event:expr;) => {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// # Warning
|
|
||||||
///
|
|
||||||
/// **Don't use this macro manually** use `rsx!` instead
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! tw_props {
|
|
||||||
($a:expr, $n:ident) => {
|
|
||||||
$a.$n;
|
|
||||||
};
|
|
||||||
|
|
||||||
// X
|
|
||||||
($a:expr, x-$v:ident $($rest:tt)*) => {
|
|
||||||
$a.x = $crate::Pos::$v;
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
($a:expr, x-$v:literal $($rest:tt)*) => {
|
|
||||||
$a.x = $crate::Pos::Num($v);
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Y
|
|
||||||
($a:expr, y-$v:ident $($rest:tt)*) => {
|
|
||||||
$a.y = $crate::Pos::$v;
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
($a:expr, y-$v:literal $($rest:tt)*) => {
|
|
||||||
$a.y = $crate::Pos::Num($v);
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Width
|
|
||||||
($a:expr, width-$v:ident $($rest:tt)*) => {
|
|
||||||
$a.width = $crate::Size::$v;
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
($a:expr, width-$v:literal $($rest:tt)*) => {
|
|
||||||
$a.width = $crate::Size::Num($v);
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Height
|
|
||||||
($a:expr, height-$v:ident $($rest:tt)*) => {
|
|
||||||
$a.height = $crate::Size::$v;
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
($a:expr, height-$v:literal $($rest:tt)*) => {
|
|
||||||
$a.height = $crate::Size::Num($v);
|
|
||||||
$crate::tw_props!($a, $($rest)*);
|
|
||||||
};
|
|
||||||
|
|
||||||
($a:expr,) => {};
|
|
||||||
}
|
|
||||||
-114
@@ -1,114 +0,0 @@
|
|||||||
//! 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)]
|
|
||||||
pub struct State<T>(pub(crate) *mut 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 {
|
|
||||||
write!(f, "{}", unsafe { &*self.0 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
write!(f, "State({:?})", unsafe { &*self.0 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//// State Operations ////
|
|
||||||
|
|
||||||
impl<T: std::ops::Add<Output = T> + Clone> std::ops::Add<T> for State<T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
/// Adds a value to the state, returning the result.
|
|
||||||
fn add(self, rhs: T) -> Self::Output {
|
|
||||||
unsafe {
|
|
||||||
let lhs = &mut *self.0;
|
|
||||||
lhs.clone() + rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: std::ops::Sub<Output = T> + Clone> std::ops::Sub<T> for State<T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
/// Subtracts a value from the state, returning the result.
|
|
||||||
fn sub(self, rhs: T) -> Self::Output {
|
|
||||||
unsafe {
|
|
||||||
let lhs = &mut *self.0;
|
|
||||||
lhs.clone() - rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: std::ops::Div<Output = T> + Clone> std::ops::Div<T> for State<T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
/// Divides the state by a value, returning the result.
|
|
||||||
fn div(self, rhs: T) -> Self::Output {
|
|
||||||
unsafe {
|
|
||||||
let lhs = &mut *self.0;
|
|
||||||
lhs.clone() / rhs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
unsafe { *self.0 += rhs }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
unsafe { *self.0 -= rhs }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> std::ops::Deref for State<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
/// Dereferences the state to access the underlying value.
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { &*self.0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
unsafe { &mut *self.0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> State<T> {
|
|
||||||
/// Returns a copy of the current state.
|
|
||||||
pub fn copy_state(&self) -> Self {
|
|
||||||
Self(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq<T>> PartialEq<T> for State<T> {
|
|
||||||
/// Compares the state with another value for equality.
|
|
||||||
fn eq(&self, other: &T) -> bool {
|
|
||||||
unsafe { &*self.0 == other }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
//! The `utils` module provides utility functions for terminal manipulation
|
|
||||||
//! and string operations, designed to enhance terminal-based applications.
|
|
||||||
|
|
||||||
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<()> {
|
|
||||||
print!("\x1B[2J\x1B[H");
|
|
||||||
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<()> {
|
|
||||||
print!("\x1b[?25l");
|
|
||||||
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<()> {
|
|
||||||
print!("\x1B[?25h");
|
|
||||||
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<()> {
|
|
||||||
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) {
|
|
||||||
let mut height = 1;
|
|
||||||
let mut max_width = 0;
|
|
||||||
let mut current_width = 0;
|
|
||||||
|
|
||||||
for b in s.bytes() {
|
|
||||||
if b == b'\n' {
|
|
||||||
height += 1;
|
|
||||||
max_width = max_width.max(current_width);
|
|
||||||
current_width = 0;
|
|
||||||
} else {
|
|
||||||
current_width += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
max_width = max_width.max(current_width);
|
|
||||||
|
|
||||||
(max_width, height)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user