Large structure changes

This commit is contained in:
2024-11-11 17:58:26 +01:00
parent e0ce761d8e
commit 994b7d2580
12 changed files with 202 additions and 648 deletions
-1
View File
@@ -18,5 +18,4 @@ path = "src/main.rs"
crossterm = "0.28.1"
regex = "*"
lazy_static = "1.4"
dyn-clone = "1.0.17"
paste = "1.0.15"
+67
View File
@@ -0,0 +1,67 @@
use std::sync::{mpsc, Arc};
use std::thread;
use crate::{
clear, create_frame, flush, get_term_size, hide_cursor, render_to_frame, Element, State, Value,
};
/// Renders an element with a given state to the terminal.
///
/// This macro handles the process of creating a frame, rendering the element to it,
/// clearing the terminal, and then printing the rendered frame.
///
/// # Parameters
///
/// * `$element` - The element to be rendered.
/// * `$state` - The current state of the element.
///
/// # Returns
///
/// This macro does not return a value. It performs side effects by printing to the terminal.
macro_rules! render {
($element:expr, $state:expr) => {
let (width, height) = get_term_size();
let mut frame: Vec<String> = create_frame(Value::new(width), Value::new(height));
render_to_frame($state, width, &mut frame, $element);
clear();
print!("{}", frame.join(""));
flush();
};
}
pub fn run(elem: Box<dyn Element>) {
hide_cursor();
crossterm::terminal::enable_raw_mode().unwrap();
clear();
let (tx, rx) = mpsc::channel();
let element = Arc::new(elem);
thread::spawn({
let element_thread = Arc::clone(&element);
move || loop {
let event = crossterm::event::read().unwrap();
let e = Arc::clone(&element_thread);
let command_handler = crate::CommandHandler(tx.clone());
thread::spawn(move || {
e.event(&command_handler, event);
});
}
});
let mut current_state = State::Hover;
loop {
if let Ok(command) = rx.try_recv() {
match command {
crate::Command::SetState(state) => {
current_state = state;
}
crate::Command::Exit => break,
}
} else {
render!(&element, current_state.clone());
}
thread::sleep(std::time::Duration::from_millis(20));
}
}
-135
View File
@@ -1,135 +0,0 @@
use crate::{arc, key::Key, Element};
/// Enum defining commands that can be issued by an `Element`.
#[derive(Clone)]
pub enum Command {
/// Renders the element with the specified state.
Render(usize),
/// Exits the application.
Exit,
/// Updates the element with the specified state.
Event(Event),
/// Pauses execution for the specified duration in milliseconds.
Sleep(u64),
}
/// Enum representing the possible responses from an element event.
#[derive(Clone)]
pub enum EventResponse {
/// Indicates that the event is complete.
Done,
/// Indicates no response.
None,
/// Issues a single command.
/// Issues a list of commands.
CommandList(Vec<Command>),
/// Update the current element
UpdateSelf(Box<dyn Element>),
/// Update a element by a id
UpdateElementById(String, Box<dyn Element>),
/// Multiple Responses
/// # DO NOT USE MANUALLY
Mul(Vec<EventResponse>),
}
impl EventResponse {
pub fn add_command(&mut self, command: Command) {
match self {
EventResponse::CommandList(commands) => {
commands.push(command);
}
EventResponse::Mul(responses) => {
for response in responses {
match response {
EventResponse::CommandList(commands) => {
commands.push(command.clone());
return;
}
_ => {}
}
}
self.add_response(EventResponse::CommandList(vec![command.clone()]));
}
_ => {}
}
}
pub fn update_self(&mut self, elem: Box<dyn Element>) {
self.add_response(Self::UpdateSelf(elem));
}
pub fn update_element_by_id(&mut self, id: &str, elem: Box<dyn Element>) {
self.add_response(Self::UpdateElementById(id.to_string(), elem));
}
pub fn add_response(&mut self, response: EventResponse) {
match self {
EventResponse::Mul(responses) => {
responses.insert(0, response);
}
_ => *self = EventResponse::Mul(vec![response, self.clone()]),
}
}
pub fn execute(&self) -> Vec<EventResponse> {
return match self {
EventResponse::Mul(v) => v.clone(),
_ => vec![self.clone()],
};
}
}
/// Enum representing the possible responses from an element update.
#[derive(Debug, Clone, PartialEq)]
pub enum Event {
Key(Key),
State(usize),
Hover,
}
/// A handler is a function that runs on a event of a element
///
/// # Params
/// - &mut T: The element,
/// - &mut EventResponse: The event response
pub type Handler<T> = std::sync::Arc<std::sync::Mutex<dyn FnMut(&mut T, &mut EventResponse)>>;
/// Creates a new `Handler<T>` from a given function closure.
///
/// This function takes a closure and wraps it in an `Arc<Mutex<dyn FnMut>>`, allowing it to be
/// used as a thread-safe, mutable handler that can be shared across threads. The handler function
/// receives a mutable reference to a `T` type and a mutable reference to an `EventResponse`, allowing
/// it to mutate both the state and response.
///
/// # Type Parameters
///
/// - `T`: The type of the data that the handler will operate on.
/// - `F`: The type of the handler function, which must implement `FnMut(&mut T, &mut EventResponse) + 'static`.
///
/// # Parameters
///
/// - `handler_fn`: The closure or function that will serve as the handler, taking mutable references
/// to both the `T` data and an `EventResponse`.
///
/// # Returns
///
/// Returns a `Handler<T>`, which is an `Arc<Mutex<dyn FnMut(&mut T, &mut EventResponse)>>`.
///
/// This type alias makes it easy to manage the handler in a multi-threaded context, allowing shared ownership
/// with safe, mutable access to the underlying function.
///
/// # Examples
///
/// ```rust
/// use osui::ui::button;
/// rsx! {
/// button {
/// // Create a new handler and assign it to on_click
/// on_click: new_handler(move |btn: &mut Button, _response| {
/// btn.text = "Clicked".to_string();
/// }
/// ), "click me" }
/// }
/// ```
pub fn new_handler<T, F>(handler_fn: F) -> Handler<T>
where
F: FnMut(&mut T, &mut EventResponse) + 'static,
{
arc!(handler_fn)
}
-89
View File
@@ -1,89 +0,0 @@
use std::io::{self, Read};
/// Represents different key inputs that can be detected from the terminal.
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
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,
/// 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 {
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();
String::from_utf8(buffer)
.unwrap()
.trim_matches('\0')
.to_string()
}
+48 -185
View File
@@ -16,134 +16,18 @@
//! ```
//!
//! ## 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 event;
pub mod key;
pub mod macros;
mod test;
pub mod ui;
pub mod utils;
use event::{Event, EventResponse};
use std::sync::mpsc;
pub use utils::*;
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.
pub mod app;
use crate::{
clear, create_frame,
event::{Command, Event, EventResponse},
flush, get_term_size, hide_cursor,
key::read_key,
render_to_frame, show_cursor, Element, Value,
};
/// 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(Value::Custom(width), Value::Custom(height));
render_to_frame(state, width, &mut frame, elem);
clear();
print!("{}", frame.join(""));
flush();
}
/// Updates the UI element based on keyboard input and issues any commands in response.
///
/// Processes the result of `Element::event` 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>, event: Event) -> bool {
match elem.event(event) {
EventResponse::CommandList(commands) => {
for command in commands {
if run_command(command, elem) {
return true;
}
}
false
}
_ => 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>) -> bool {
match command {
Command::Render(state) => {
render(elem, state);
false
}
Command::Event(event) => update(elem, event),
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);
if update(elem, Event::Key(read_key())) {
break;
}
}
}
}
mod test;
/// Enum representing the wether it's the default or custom.
///
@@ -191,86 +75,65 @@ impl<T: Copy + PartialEq> Value<T> {
}
}
pub trait ToValue<T: Copy + PartialEq> {
fn to_value(&self) -> Value<T>;
}
use dyn_clone::{clone_trait_object, DynClone};
/// A trait for defining UI elements in OSUI.
///
/// Elements must implement methods for updating and rendering data.
pub trait Element: DynClone {
/// Retrieves the data for the `Element`.
///
/// # Returns
///
/// `ElementData` containing position and size information.
fn get_data(&self) -> ElementData;
fn set_data(&mut self, data: ElementData);
pub trait Element: Send + Sync {
fn get_data(&self) -> (Value<usize>, usize, String);
/// 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);
fn get_id(&self) -> String;
fn update_data(&mut self, width: usize, height: usize);
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>>;
/// 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 {
fn render(&self, state: State) -> String {
_ = state;
String::new()
}
/// Updates the `Element` based on a `Key` event and the current state.
///
/// # Arguments
///
/// * `_event` - The current event.
///
/// # Returns
///
/// An `EventResponse` enum indicating the result of the event.
fn event(&mut self, _event: Event) -> EventResponse {
EventResponse::None
fn event(&self, ch: &CommandHandler, event: crossterm::event::Event) {
_ = (ch, event);
}
}
clone_trait_object!(Element);
pub struct Handler<T>(
pub std::sync::Mutex<Box<dyn FnMut(&T, &CommandHandler, crossterm::event::Event) + Send>>,
);
/// Struct holding data relevant to an `Element`, including position and size.
pub struct ElementData {
/// X coordinate of the `Element`.
pub x: Value<usize>,
/// Y coordinate of the `Element`.
pub y: usize,
/// Width of the `Element`, which can be default or custom.
pub width: Value<usize>,
/// Height of the `Element`, which can be default or custom.
pub height: Value<usize>,
/// Children of the `Element`
pub children: Vec<Box<dyn Element>>,
/// Active Child of the `Element`
pub child: usize,
/// Text of the `Element`
pub text: String,
/// Identifier of the `Element`
pub id: String,
impl<T> Handler<T> {
pub fn new<F>(handler_fn: F) -> Handler<T>
where
F: FnMut(&T, &CommandHandler, crossterm::event::Event) + 'static + Send,
{
Handler(std::sync::Mutex::new(Box::new(handler_fn)))
}
}
pub struct CommandHandler(mpsc::Sender<Command>);
impl CommandHandler {
pub fn set_state(&self, state: State) {
self.0.send(Command::SetState(state)).unwrap();
}
pub fn exit(&self) {
self.0.send(Command::Exit).unwrap();
}
}
#[derive(Debug, Clone)]
pub enum Command {
SetState(State),
Exit,
}
#[derive(Debug, Clone)]
pub enum State {
Normal,
Hover,
Custom(usize),
CustomString(String),
}
pub fn run_test() {
app::run(&mut test::app());
app::run(test::app());
}
+55 -52
View File
@@ -84,31 +84,21 @@ macro_rules! element {
$( $functions:tt )*
) => {
$(#[$meta])*
#[derive(Clone)]
pub struct $name<'a> {
pub x: Value<usize>,
pub x: $crate::Value<usize>,
pub y: usize,
pub width: Value<usize>,
pub height: Value<usize>,
pub children: Vec<Box<dyn Element>>,
pub width: $crate::Value<usize>,
pub height: $crate::Value<usize>,
pub children: Vec<Box<dyn $crate::Element>>,
pub child: usize,
pub text: String,
pub id: &'a str,
$( $inner )*
}
impl Element for $name<'_> {
fn get_data(&self) -> ElementData {
ElementData {
x: self.x,
y: self.y,
width: self.width.clone(),
height: self.height.clone(),
children: self.children.clone(),
child: self.child.clone(),
text: self.text.clone(),
id: self.id.to_string(),
}
impl $crate::Element for $name<'_> {
fn get_data(&self) -> ($crate::Value<usize>, usize, String) {
(self.x.clone(), self.y.clone(), self.id.to_string())
}
fn update_data(&mut self, width: usize, height: usize) {
@@ -119,29 +109,15 @@ macro_rules! element {
}
}
fn get_id(&self) -> String {
self.id.to_string()
}
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn Element>> {
fn get_element_by_id(&mut self, id: &str) -> Option<&mut Box<dyn $crate::Element>> {
for elem in &mut self.children {
if elem.get_id() == id {
if elem.get_data().2 == id {
return Some(elem);
}
}
None
}
fn set_data(&mut self, data: ElementData) {
self.x = data.x;
self.y = data.y;
self.width = data.width;
self.height = data.height;
self.children = data.children;
self.child = data.child;
self.text = data.text;
}
$( $functions )*
}
@@ -150,10 +126,10 @@ macro_rules! element {
pub fn new() -> $name<'a> {
$name {
children: Vec::new(),
x: Value::Default(0),
x: $crate::Value::Default(0),
y: 0,
width: Value::Default(0),
height: Value::Default(0),
width: $crate::Value::Default(0),
height: $crate::Value::Default(0),
child: 0,
text: String::new(),
id: "",
@@ -165,7 +141,7 @@ macro_rules! element {
///
/// # Returns
/// An `Option` containing a mutable reference to the child `Element` or `None`.
pub fn get_child(&mut self) -> Option<&mut Box<dyn Element>> {
pub fn get_child(&mut self) -> Option<&mut Box<dyn $crate::Element>> {
self.children.get_mut(self.child)
}
}
@@ -205,7 +181,7 @@ macro_rules! parse_rsx_param {
($elem:expr, $($k:ident).+: $v:expr, $($rest:tt)*) => {
$elem.$($k).+ = $v;
osui::parse_rsx_param!($elem, $($rest)*);
$crate::parse_rsx_param!($elem, $($rest)*);
};
// for completion purposes
@@ -214,12 +190,12 @@ macro_rules! parse_rsx_param {
};
($elem:expr, $($k:ident).+, $($rest:tt)*) => {
$elem.$($k).+;
osui::parse_rsx_param!($elem, $($rest)*);
$crate::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+., $($rest:tt)*) => {
$elem.$($k).
+.;
osui::parse_rsx_param!($elem, $($rest)*);
$crate::parse_rsx_param!($elem, $($rest)*);
};
($elem:expr, $($k:ident).+.: $v:expr) => {
@@ -227,7 +203,7 @@ macro_rules! parse_rsx_param {
};
($elem:expr, $($k:ident).+.: $v:expr, $($rest:tt)*) => {
$elem.$($k).+. = $v;
osui::parse_rsx_param!($elem, $($rest)*);
$crate::parse_rsx_param!($elem, $($rest)*);
};
// For loop
@@ -235,19 +211,19 @@ macro_rules! parse_rsx_param {
for $($for)* {
$elem.children.push({$($inner)*})
}
osui::parse_rsx_param!($elem, $($rest)*);
$crate::parse_rsx_param!($elem, $($rest)*);
};
// Expression
($elem:expr, {$ielem:expr} $($rest:tt)*) => {
$elem.children.push($ielem);
osui::parse_rsx_param!($elem, $($rest)*);
$crate::parse_rsx_param!($elem, $($rest)*);
};
// Element
($elem:expr, $pelem:path { $($inner:tt)* } $($rest:tt)*) => {
$elem.children.push(osui::rsx_elem!($pelem { $($inner)* }));
osui::parse_rsx_param!($elem, $($rest)*)
$elem.children.push($crate::rsx_elem!($pelem { $($inner)* }));
$crate::parse_rsx_param!($elem, $($rest)*)
};
// Text
@@ -291,8 +267,8 @@ macro_rules! rsx_elem {
($elem:path { $($inner:tt)* }) => {{
#[allow(unused_mut)]
let mut elem = $elem();
osui::parse_rsx_param!(elem, $($inner)*);
elem as Box<dyn osui::Element>
$crate::parse_rsx_param!(elem, $($inner)*);
elem as Box<dyn $crate::Element>
}};
}
@@ -301,7 +277,7 @@ macro_rules! rsx_elem_raw {
($elem:path { $($inner:tt)* }) => {{
#[allow(unused_mut)]
let mut elem = $elem();
osui::parse_rsx_param!(elem, $($inner)*);
$crate::parse_rsx_param!(elem, $($inner)*);
elem
}};
}
@@ -322,7 +298,7 @@ macro_rules! rsx_elem_raw {
#[macro_export]
macro_rules! rsx {
($($inner:tt)*) => {{
osui::rsx_elem_raw!( osui::ui::div { $($inner)* } )
$crate::rsx_elem_raw!( $crate::ui::div { $($inner)* } )
}};
}
@@ -349,6 +325,13 @@ macro_rules! css {
}
/// Macro for defining a `Arc<Mutex< T >>`
#[macro_export]
macro_rules! mux {
($a:expr) => {
std::sync::Mutex::new($a)
};
}
#[macro_export]
macro_rules! arc {
($a:expr) => {
@@ -360,7 +343,7 @@ macro_rules! arc {
#[macro_export]
macro_rules! val {
($a:expr) => {
osui::Value::new($a)
$crate::Value::new($a)
};
}
@@ -380,9 +363,21 @@ macro_rules! write {
// Default state styling
(($self:ident, $state:expr), $expr:expr) => {{
if $state == 0 {
return format!("{}{}{}{}\x1b[0m", $self.style.color.ansi(), $self.style.background.ansi_bg(), $self.style.font.ansi(), $expr);
return format!(
"{}{}{}{}\x1b[0m",
$self.style.color.ansi(),
$self.style.background.ansi_bg(),
$self.style.font.ansi(),
$expr
);
}
format!("{}{}{}{}\x1b[0m", $self.style.hover_color.ansi(), $self.style.hover_background.ansi_bg(), $self.style.hover_font.ansi(), $expr)
format!(
"{}{}{}{}\x1b[0m",
$self.style.hover_color.ansi(),
$self.style.hover_background.ansi_bg(),
$self.style.hover_font.ansi(),
$expr
)
}};
// Custom styles
@@ -428,3 +423,11 @@ macro_rules! execute_response {
}
};
}
#[macro_export]
macro_rules! run_handler {
($self:ident.$handler:ident ($renderer:expr, $event:expr)) => {
let mut o = $self.$handler.0.lock().unwrap();
o($self, $renderer, $event);
};
}
+3 -1
View File
@@ -1,3 +1,5 @@
use osui::run_test;
fn main() {
osui::run_test();
run_test();
}
+5
View File
@@ -0,0 +1,5 @@
use crate::{rsx_elem, Element, ui::*};
pub(crate) fn app() -> Box<dyn Element> {
rsx_elem! { text { "Hello, World!" } }
}
-9
View File
@@ -1,9 +0,0 @@
use crate::{self as osui, event::new_handler, rsx, ui::*, Element};
pub fn app() -> Box<dyn Element> {
rsx! {
button { on_click: new_handler(move |btn: &mut Button, _response| {
btn.text = "Clicked".to_string();
}), "click me" }
}
}
+2 -123
View File
@@ -1,130 +1,9 @@
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use crate::{
command, create_frame, element,
event::{Command, Event, Handler, },
execute_response,
key::Key,
render_to_frame,
ui::{Color, Font},
write, Direction, Element, ElementData, EventResponse, Value,
};
use crate::{element, State};
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(&self, _: usize) -> String {
fn render(&self, _: State) -> String {
self.text.clone()
}
}
element! {
style ButtonStyle {
clicked_color: Color,
clicked_background: Color,
clicked_font: Font,
}
/// 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. use `arc!` to use function
pub on_click: Handler<Button<'a>>,
}
defaults {
on_click: Arc::new(Mutex::new(|_btn: &mut Button<'_>, _: &mut EventResponse| {})),
}
fn render(&self, state: usize) -> String {
if state == 2 {
return write!(self, clicked, self.text);
}
write!((self, state), self.text)
}
fn event(&mut self, event: Event) -> EventResponse {
match event {
Event::Key(k) => {
if k == Key::Enter {
let mut btn = self.clone();
let mut on_click = self.on_click.lock().unwrap();
let mut response = command!(
Command::Render(2),
Command::Sleep(120)
);
(on_click)(&mut btn, &mut response);
drop(on_click);
*self = btn;
return response;
}
}
_ => {}
}
EventResponse::None
}
}
element! {
/// 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>
}
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 = create_frame(self.width, self.height);
for (i, child) in (&self.children).iter().enumerate() {
if i==self.child {
render_to_frame(state, self.width.get_value(), &mut frame, child);
} else {
render_to_frame(0, self.width.get_value(), &mut frame, child);
}
}
frame.join("\n")
}
fn event(&mut self, event: Event) -> EventResponse {
let mut res = EventResponse::None;
match event.clone() {
Event::Key(k) => {
if let Some(direction) = self.keybinds.get(&k) {
self.child = crate::closest_component(&self.children, self.child, direction.clone());
if let Some(child) = self.get_child() {
child.event(Event::Hover);
}
} else if let Some(child) = self.get_child() {
res = child.event(event);
}
}
Event::Hover => {
if let Some(child) = self.get_child() {
res = child.event(event);
}
}
_ => {}
}
execute_response!(self, res);
res
}
}
-31
View File
@@ -7,37 +7,6 @@ pub use styles::*;
pub mod elements;
pub use elements::*;
/// Creates a new `Text` element.
///
/// The `Text` element displays static text in the TUI, suitable for labels or headers.
///
/// # Returns
///
/// A `Box<Text>` containing a new `Text` element instance.
pub fn text<'a>() -> Box<Text<'a>> {
Box::new(Text::new())
}
/// Creates a new `Button` element with a customized style.
///
/// The `Button` element responds to user clicks, triggering an action. This function
/// customizes the button's clicked background and foreground colors.
///
/// # Returns
///
/// A `Box<Button>` containing a new `Button` element instance.
pub fn button<'a>() -> Box<Button<'a>> {
Box::new(Button::new())
}
/// 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<'a>() -> Box<Div<'a>> {
Box::new(Div::new())
}
+16 -16
View File
@@ -75,16 +75,16 @@ fn merge_line(frame_: &str, line_: &str, x: usize) -> String {
/// Render to a frame
pub fn render_to_frame(
state: usize,
state: crate::State,
width: usize,
frame: &mut Vec<String>,
element: &Box<dyn Element>,
) {
let data = element.get_data();
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();
match data.x {
if (data.1 + i) < frame.len() {
let frame_line = frame.get_mut(data.1 + i).unwrap();
match data.0 {
Value::Custom(x) => {
*frame_line = merge_line(&frame_line, line, x);
}
@@ -142,34 +142,34 @@ pub fn closest_component(
.filter(|(i, comp_)| {
let comp = comp_.get_data();
match direction {
Direction::Left => match comp.x {
Direction::Left => match comp.0 {
Value::Custom(_) => {
comp.x.get_value() < current.x.get_value()
&& comp.y == current.y
comp.0.get_value() < current.0.get_value()
&& comp.1 == current.1
}
Value::Default(_) => *i < current_index,
}, // Left
Direction::Right => match comp.x {
Direction::Right => match comp.0 {
Value::Custom(_) => {
comp.x.get_value() > current.x.get_value()
&& comp.y == current.y
comp.0.get_value() > current.0.get_value()
&& comp.1 == current.1
}
Value::Default(_) => *i > current_index,
}, // Right
Direction::Up => {
comp.y < current.y
&& comp.x.get_value() == current.x.get_value()
comp.1 < current.1
&& comp.0.get_value() == current.0.get_value()
} // Up
Direction::Down => {
comp.y > current.y
&& comp.x.get_value() == current.x.get_value()
comp.1 > current.1
&& comp.0.get_value() == current.0.get_value()
} // Down
}
})
.min_by_key(|(_, comp_)| {
let comp = comp_.get_data();
current.x.get_value().abs_diff(comp.x.get_value())
+ current.y.abs_diff(comp.y)
current.0.get_value().abs_diff(comp.0.get_value())
+ current.1.abs_diff(comp.1)
}) // Find the 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