From 687877714018654d65040f82b3a200b649b4fb80 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Wed, 15 Jan 2025 09:25:58 -0600 Subject: [PATCH] rewrite --- Cargo.toml | 4 +- src/css.rs | 206 ----------------- src/examples.rs | 132 ----------- src/lib.rs | 587 ++++++----------------------------------------- src/macros.rs | 18 -- src/main.rs | 31 +-- src/rsx.rs | 153 ------------ src/ui/mod.rs | 267 --------------------- src/ui/styles.rs | 347 ---------------------------- src/utils.rs | 135 ++--------- 10 files changed, 87 insertions(+), 1793 deletions(-) delete mode 100644 src/css.rs delete mode 100644 src/examples.rs delete mode 100644 src/macros.rs delete mode 100644 src/rsx.rs delete mode 100644 src/ui/mod.rs delete mode 100644 src/ui/styles.rs diff --git a/Cargo.toml b/Cargo.toml index 38d3fde..3334b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "osui" -version = "0.0.8-alpha.2" +version = "0.0.9-alpha.0" edition = "2021" license = "Apache-2.0" description = "A TUI library for advanced uis" @@ -21,8 +21,6 @@ cc = "1.0" [dependencies] crossterm = "0.28.1" -dyn-clone = "1.0.17" -osui-element = "0.1.23" [profile.dev] opt-level = 1 diff --git a/src/css.rs b/src/css.rs deleted file mode 100644 index 6622ad4..0000000 --- a/src/css.rs +++ /dev/null @@ -1,206 +0,0 @@ -#[macro_export] -macro_rules! __css { - ($_style:expr,)=>{}; - // percentage (n%) - ( - $_style:expr, $name:ident: $value:literal% $(, $($rest:tt)*)? - ) => {{ - $_style.$name = (true, Number::Pe($value)); - $crate::__css!($_style, $($($rest)*)?); - }}; - - // Number - ( - $_style:expr, $name:ident: $value:literal px $(, $($rest:tt)*)? - ) => {{ - $_style.$name = (true, Number::Px($value)); - $crate::__css!($_style, $($($rest)*)?); - }}; - - // Vector - ( - $_style:expr, $name:ident: [$($inner:tt),*] $(, $($rest:tt)*)? - ) => {{ - $_style.$name = (true, vec![$($inner),*]); - $crate::__css!($_style, $($($rest)*)?); - }}; - - // Normal - ( - $_style:expr, $name:ident: $value:expr $(, $($rest:tt)*)? - ) => {{ - $_style.$name = (true, $value); - $crate::__css!($_style, $($($rest)*)?); - }}; -} - -#[macro_export] -macro_rules! _css { - ($_style:expr,) => {}; - ( - $hm:expr, $($($name:ident)-+),* { $($inner:tt)* } $($rest:tt)* - ) => {{ - let mut _style = $crate::ui::StyleElement::default(); - $crate::__css!(_style, $($inner)*); - $( - $hm.insert( - $crate::ui::StyleName::Class(String::from(stringify!($($name)-+).replace(' ', ""))), - _style.clone(), - ); - )* - $crate::_css!($hm, $($rest)*); - }}; - ( - $hm:expr, $($($name:ident)-+),*: $state:literal { $($inner:tt)* } $($rest:tt)* - ) => {{ - let mut _style = $crate::ui::StyleElement::default(); - $crate::__css!(_style, $($inner)*); - $( - $hm.insert( - $crate::ui::StyleName::ClassState(String::from(stringify!($($name)-+).replace(' ', "")), String::from($state)), - _style.clone(), - ); - )* - $crate::_css!($hm, $($rest)*); - }}; - ( - $hm:expr, $($name:literal),+ { $($inner:tt)* } $($rest:tt)* - ) => {{ - let mut _style = $crate::ui::StyleElement::default(); - $crate::__css!(_style, $($inner)*); - $( - $hm.insert( - $crate::ui::StyleName::Class(String::from($name)), - _style, - ); - )+ - $crate::_css!($hm, $($rest)*); - }}; - ( - $hm:expr, $($name:literal),+: $state:literal { $($inner:tt)* } $($rest:tt)* - ) => {{ - let mut _style = $crate::ui::StyleElement::default(); - $crate::__css!(_style, $($inner)*); - $( - $hm.insert( - $crate::ui::StyleName::ClassState(String::from($name), String::from($state)), - _style, - ); - )+ - $crate::_css!($hm, $($rest)*); - }}; -} - -/// Creates a styling map `Css` -/// -/// # Example -/// ``` -/// css! { -/// "btn" { -/// color: Red, -/// } -/// "btn": "clicked" { -/// color: Blue, -/// } -/// } -/// ``` -/// -/// # Returns -/// `Css` -#[macro_export] -macro_rules! css { - ( - $($inner:tt)* - ) => {{ - #[allow(unused_mut)] - let mut hm: $crate::ui::Css = std::collections::HashMap::new(); - $crate::_css!(hm, $($inner)*); - hm - }}; -} - -#[macro_export] -macro_rules! style { - ( - $key:ident: $value:literal% $(, $($rest:tt)*)? - ) => {{ - let mut _style = $crate::ui::Style::default(); - $crate::__css!(_style.0, $key: $crate::ui::Number::Pe($value)); - $crate::style!{ _style; $($($rest)*)? } - }}; - ( - $key:ident: $value:literal px $(, $($rest:tt)*)? - ) => {{ - let mut _style = $crate::ui::Style::default(); - $crate::__css!(_style.0, $key: $crate::ui::Number::Px($value)); - $crate::style!{ _style; $($($rest)*)? } - }}; - ( - $style:expr; $key:ident: $value:literal% $(, $($rest:tt)*)? - ) => {{ - $crate::__css!($style.0, $key: $crate::ui::Number::Pe($value)); - $crate::style!{ $style; $($($rest)*)? } - }}; - ( - $style:expr; $key:ident: $value:literal px $(, $($rest:tt)*)? - ) => {{ - $crate::__css!($style.0, $key: $crate::ui::Number::Px($value)); - $crate::style!{ $style; $($($rest)*)? } - }}; - ( - $key:ident: $value:expr $(, $($rest:tt)*)? - ) => {{ - let mut _style = $crate::ui::Style::default(); - $crate::__css!(_style.0, $key: $value); - $crate::style!{ _style; $($($rest)*)? } - }}; - ( - $style:expr; $key:ident: $value:expr $(, $($rest:tt)*)? - ) => {{ - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!($style.0, $key: $value); - $crate::style!{ $style; $($($rest)*)? } - }}; - ( - ($key:ident): $value:expr $(, $($rest:tt)*)? - ) => {{ - let mut _style = $crate::ui::Style::default(); - $crate::__css!(_style.0, ($key): $value); - $crate::style!{ _style; $($($rest)*)? } - }}; - ( - $style:expr; $key:literal: $value:expr $(, $($rest:tt)*)? - ) => {{ - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!($style.0, $key: $value); - $crate::style!{ $style; $($($rest)*)? } - }}; - - ( - $kind:ident {$($inner:tt)*} $(, $($rest:tt)*)? - ) => {{ - let mut _style = $crate::ui::Style::default(); - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - _style.1.insert(stringify!($kind).to_string(), style_elem); - $crate::style!{ _style; $($($rest)*)? } - }}; - ( - $style:expr; $kind:ident {$($inner:tt)*} $(, $($rest:tt)*)? - ) => {{ - let mut style_elem = $crate::ui::StyleElement::default(); - $crate::__css!(style_elem, $($inner)*); - $style.1.insert(stringify!($kind).to_string(), style_elem); - $crate::style!{ $style; $($($rest)*)? } - }}; - - ( - $style:expr; - ) => {{ - $style - }}; - - () => {{ - $crate::ui::Style::default() - }}; -} diff --git a/src/examples.rs b/src/examples.rs deleted file mode 100644 index 5345acc..0000000 --- a/src/examples.rs +++ /dev/null @@ -1,132 +0,0 @@ -pub mod counter_example { - use crate::prelude::*; - - #[component] - pub fn App() -> Element { - let count = use_state(0); - - rsx! { - @SetStyle(styling()) - - button { - class: "btn", - - on_click: fn(_, _, _) @count { - count += 1; - }, - - "The current count is: {count}" - } - } - } - - pub fn styling() -> Css { - css! { - btn { - outline: true, - outline_color: Red - } - } - } -} - -pub mod todo_example { - use crate::prelude::*; - - #[component] - pub fn App() -> Element { - let todo = vec!["Todo", "Hovering", "Completed", "Foo", "Bar"]; - - rsx! { - @SetStyle(styling()) - @SetChild(2) - - text { class: "title", static "Todo list app made with OSUI" } - text { class: "title", "{}", "-".repeat(crossterm::terminal::size().unwrap().0 as usize) } - for (t in todo) { - ersx!(button { class: "todo", on_click: fn(btn: &mut Button, _, _) { - btn.class = if btn.class == "todo" { - "complete" - } else { - "todo" - }; - }, "{t}" } ) - } - } - } - - pub fn styling() -> Css { - css! { - todo { - x: 0 px, - y: Auto, - color: Red, - } - todo: "hover" { - color: Blue, - } - complete { - x: 0 px, - y: Auto, - color: Green, - } - complete: "hover" { - color: Cyan - } - title { - x: 0 px, - y: Auto, - } - title: "hover" { - color: Blue - } - } - } -} - -pub mod login_example { - use crate::prelude::*; - - #[component] - pub fn App() -> Element { - rsx! { - @SetStyle(styling()) - - input { class: "input", id: "usr", on_click: fn(_, _, document) { - let root = document.get_root::
(); - root.children.set_index(1); - }, "username: " } - - input { class: "input", id: "psw", on_click: fn(_, _, document) { - let root = document.get_root::
(); - root.children.set_index(2); - }, "password: " } - - button { class: "input", on_click: fn(_, _, document) { - if let Some(usr) = document.get_element_by_id::("usr") { - if let Some(psw) = document.get_element_by_id::("psw") { - document.draw(rsx! { - text { static "Username: {}\nPassword: {}", usr.text, psw.text } - }); - } - } - }, "Submit" } - } - } - - pub fn styling() -> Css { - css! { - input { - width: 50 px, - outline: true, - y: Auto, - x: 0 px, - caret: String::new(), - } - input: "hover" { - outline_color: Red, - caret: String::from("█"), - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs index 9dd1ff9..faa11fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,543 +1,86 @@ -//! # 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 events. -//! -//! ## Example Usage -//! -//! ```rust -//! use osui::prelude::*; -//! -//! #[component] -//! pub fn App() -> Element { -//! let count = State::new(0); -//! -//! rsx! { -//! button { -//! class: "btn", -//! -//! on_click: fn(_, _, _) @count { -//! count += 1; -//! }, -//! -//! "The current count is: {count}" -//! } -//! } -//! } -//! fn main() { -//! launch!(App); -//! } -//! ``` -//! -//! ## Modules -//! - `ui` - Contains all user interface components, enabling rich CLI experiences. -//! - `utils` - Utility functions for common TUI tasks such as clearing the screen. - -use std::{ - collections::HashMap, - fmt::Debug, - ops::{AddAssign, SubAssign}, - sync::{Arc, Mutex}, -}; - -use crossterm::event::Event; -use ui::{Style, StyleCore}; -use utils::VisibleLength; - -pub mod css; -pub mod examples; -pub mod macros; -pub mod rsx; -pub mod ui; pub mod utils; -pub mod prelude { - pub use crate::ui::Color::*; - pub use crate::ui::Font::*; - pub use crate::ui::Number::{Auto, Center}; - pub use crate::Instruction::*; - pub use crate::{self as osui, css, ersx, launch, rsx, ui::*, Handler}; - pub use crate::{style, Component, Document}; - pub use crate::{Children, Element, ElementCore, ElementWidget}; - pub use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind}; - pub use osui_element::component; - pub fn sleep(ms: u64) { - std::thread::sleep(std::time::Duration::from_millis(ms)); - } - pub fn use_state<'a, T>(v: T) -> crate::State { - crate::State::new(v) - } +type Result = std::io::Result; + +pub trait Widget { + fn draw(&self, w: &mut Writer); } -////////////////////////////////////////////////////////////////////////////////////////////////// -// Type aliases -////////////////////////////////////////////////////////////////////////////////////////////////// - -pub type Element = Box; - -////////////////////////////////////////////////////////////////////////////////////////////////// -/// Traits -////////////////////////////////////////////////////////////////////////////////////////////////// - -pub trait ElementCore { - fn get_element_by_id(&mut self, id: &str) -> Option<&mut Element>; - fn get_id(&self) -> String; - fn get_class(&self) -> String; - fn get_style(&self) -> &Style; -} - -pub trait ElementWidget: ElementCore + std::fmt::Debug { - fn render(&self, writer: &mut Writer); - fn event(&mut self, event: crossterm::event::Event, document: &mut Document) { - _ = (event, document) - } - fn initialize(&mut self, document: &mut Document) { - _ = document - } -} - -pub trait Component: std::fmt::Debug { - type Element; - fn create_element(self) -> Self::Element; -} - -////////////////////////////////////////////////////////////////////////////////////////////////// -/// Structs -////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct Handler(pub Arc>); - -pub struct Document { - element: *mut Element, - running: Option, - pub css: ui::Css, -} - -#[derive(Debug)] -pub struct Writer { - css: *const ui::Css, - style: ui::StyleElement, - focused: bool, - size: (u16, u16), - pos: (u16, u16), - written: (u16, u16), - parent: Option<*mut Writer>, -} - -pub struct State(Arc>); - -////////////////////////////////////////////////////////////////////////////////////////////////// -/// Enums -////////////////////////////////////////////////////////////////////////////////////////////////// - -pub enum Children { - None, - Text(Arc String>), - StaticText(String), - Children(Vec, usize), -} - -////////////////////////////////////////////////////////////////////////////////////////////////// -/// Implementations -////////////////////////////////////////////////////////////////////////////////////////////////// - -impl Document { - pub fn with_elem(element: &mut Element) -> Document { - Document { - element, - running: None, - css: HashMap::new(), - } - } - pub fn exit(&mut self) { - self.running = Some(false); - } - pub fn restart(&mut self) { - self.running = Some(true); - } - pub fn get_root(&self) -> &mut Box { - unsafe { &mut *(self.element as *mut Element as *mut Box) } - } - pub fn get_element_by_id(&self, id: &str) -> Option<&mut Box> { - if let Some(e) = self.get_element_by_id_raw(id) { - Some(unsafe { &mut *(e as *mut Element as *mut Box) }) - } else { - None - } - } - pub fn get_element_by_id_raw(&self, id: &str) -> Option<&mut Element> { - if !self.element.is_null() { - unsafe { - if (*self.element).get_id() == id { - Some(&mut *self.element) - } else { - (*self.element).get_element_by_id(id) - } - } - } else { - None - } - } - pub fn render(&self) { - if !self.element.is_null() { - let (width, height) = crossterm::terminal::size().unwrap(); - let mut writer = Writer { - css: &self.css, - focused: true, - style: ui::StyleElement { - // outline: (true, true), - ..Default::default() - }, - size: (width, height), - pos: (1, 1), - written: (0, 0), - parent: None, - }; - let mut frame = writer.new_frame(); - utils::clear(); - frame.render(true, unsafe { &*self.element }); - writer.after_render(width, height); - utils::flush(); - } - } - pub fn set_css(&mut self, css: ui::Css) { - self.css = css; - } - pub fn clear_css(&mut self) { - self.css.clear(); - } - pub fn draw(&mut self, element: Element) { - unsafe { - *self.element = element; - (*self.element).initialize(self); - } - } - pub fn run(&mut self) -> bool { - // Set up the screen - utils::hide_cursor(); - crossterm::terminal::enable_raw_mode().unwrap(); - utils::clear(); - - self.running = None; - let element = unsafe { &mut *self.element }; - self.render(); - element.initialize(self); - - // Send initial event - element.event(crossterm::event::Event::FocusGained, self); - - while self.running == None { - self.render(); - element.event(crossterm::event::read().unwrap(), self); - } - - utils::show_cursor(); - crossterm::terminal::disable_raw_mode().unwrap(); - utils::clear(); - - self.running.unwrap() - } -} - -impl Default for Children { - fn default() -> Self { - Self::None - } -} - -impl std::fmt::Debug for Children { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(&match self { - Self::None => String::new(), - Self::Children(a, b) => format!("({b}, {a:?})"), - Self::Text(a) => format!("{:?}", a()), - Self::StaticText(a) => format!("static({a:?})"), - }) - } -} - -impl Children { - pub fn is_none(&self) -> bool { - match self { - Children::None => true, - _ => false, - } - } - - pub fn get_text(&self) -> String { - match self { - Children::Text(text) => text(), - Children::StaticText(text) => text.clone(), - _ => String::new(), - } - } - - pub fn set_text(&mut self, text: Arc String>) { - match self { - Children::Text(t) => { - *t = text; - } - _ => {} - } - } - - pub fn set_static(&mut self, text: String) { - *self = Children::StaticText(text); - } - - pub fn add_child(&mut self, element: Element) { - if let Children::Children(children, _) = self { - children.push(element); - } - } - pub fn get_child(&mut self) -> (Option<&mut Element>, usize) { - if let Children::Children(children, child) = self { - let l = children.len(); - (children.get_mut(*child), l) - } else { - (None, 0) - } - } - pub fn get_child_idx(&mut self) -> (Option<(&mut Element, &mut usize)>, usize) { - if let Children::Children(children, child) = self { - let l = children.len(); - if let Some(d) = children.get_mut(*child) { - (Some((d, child)), l) - } else { - (None, 0) - } - } else { - (None, 0) - } - } - pub fn set_index(&mut self, index: usize) { - if let Children::Children(_, child) = self { - *child = index; - } - } - pub fn len(&self) -> usize { - if let Children::Children(children, _) = self { - children.len() - } else { - 0 - } - } - pub fn index(&self) -> usize { - if let Children::Children(_, c) = self { - *c - } else { - 0 - } - } - pub fn index_mut(&mut self) -> Option<&mut usize> { - if let Children::Children(_, c) = self { - Some(c) - } else { - None - } - } - pub fn insert(&mut self, index: usize, element: Element) { - if let Children::Children(children, _) = self { - children.insert(index, element); - } - } -} - -impl Handler { - pub fn new(handler_fn: F) -> Handler - where - F: FnMut(&mut T, crossterm::event::Event, &mut Document) + 'static, - { - Handler(Arc::new(Mutex::new(handler_fn))) - } - - pub fn call(&self, s: &mut T, event: Event, document: &mut Document) { - let mut o = self.0.lock().unwrap(); - o(s, event, document); - } - - pub fn clone(&self) -> Handler { - Handler(Arc::clone(&self.0)) - } -} +pub struct Writer(String); impl Writer { - pub fn after_render(&self, width: u16, height: u16) { - if self.style.outline.1 { - let ol = self.style.write_outline("│"); - let hl = "─".repeat(width as usize); - self.write_abs_outline(&self.style.write_outline(&format!("╭{}╮", hl)), (0, 0)); - for i in 1..height+1 { - self.write_abs_outline(&ol, (0, i)); - self.write_abs_outline(&ol, (width + 1, i)); - } - self.write_abs_outline(&self.style.write_outline(&format!("╰{}╯", hl)), (0, height + 1)); - } - } - pub fn write(&mut self, s: &str) { - self.write_abs(&self.style.write(&s), (0, 0)); + self.0 += s; + } +} + +pub type Element = Box; + +///////////////////////////////////////// + +pub struct Frame { + pub area: (u16, u16), + pub x: u16, + pub y: u16, + parent: Option<*const Frame>, +} + +impl Frame { + pub fn new(area: (u16, u16), parent: &Frame) -> Frame { + Frame { + area, + x: 0, + y: 0, + parent: Some(parent), + } } - pub fn write_abs_outline(&self, s: &str, pos: (u16, u16)) { - if let Some(parent) = self.parent { - let parent = unsafe { &mut *parent }; - parent.write_abs_outline(s, (self.pos.0 + pos.0, self.pos.1 + pos.1)); + pub fn new_root(area: (u16, u16)) -> Frame { + Frame { + area, + x: 0, + y: 0, + parent: None, + } + } + + pub fn render(&self, elem: E) + where + E: Widget, + { + let mut w = Writer(String::new()); + elem.draw(&mut w); + if let Some(_f) = self.parent { } else { - print!( - "\x1b[{};{}H{}", - self.pos.1 + pos.1 - 1, - self.pos.0 + pos.0 - 1, - self.style.write_outline(s) - ); - } - } - - pub fn write_abs(&mut self, s: &str, pos: (u16, u16)) { - let width = self.get_size().0 as usize; - let mut max_written = self.written.0; - let mut lines_written = 0; - - let truncated_lines: Vec<_> = s - .lines() - .map(|l| { - let truncated = if l.len() > width { - l.chars().take(width).collect::() - } else { - l.to_string() - }; - max_written = max_written.max(truncated.visible_len() as u16); - lines_written += 1; - truncated - }) - .collect(); - - self.written.0 = max_written; - self.written.1 += lines_written; - - if let Some(parent) = self.parent { - let parent = unsafe { &mut *parent }; - let combined_pos = (self.pos.0 + pos.0, self.pos.1 + pos.1); - let s_combined = truncated_lines.join("\n"); - parent.write_abs(&s_combined, combined_pos); - } else { - for (i, line) in truncated_lines.iter().enumerate() { - print!( - "\x1b[{};{}H{}", - self.pos.1 + pos.1 + i as u16, - self.pos.0 + pos.0, - line - ); - } - } - } - - pub fn get_focused(&self) -> bool { - self.focused - } - - pub fn get_size(&self) -> (u16, u16) { - ( - self.style - .width - .1 - .as_size(self.size.0, self.size.0, self.style.outline.1), - self.style - .height - .1 - .as_size(self.size.1, self.size.1, self.style.outline.1), - ) - } - - pub fn get_size_root(&self) -> (u16, u16) { - ( - self.style - .width - .1 - .as_size(self.written.0, self.size.0, self.style.outline.1), - self.style - .height - .1 - .as_size(self.written.1, self.size.1, self.style.outline.1), - ) - } - - pub fn new_frame(&mut self) -> crate::utils::Frame { - crate::utils::Frame::new(self) - } - - pub fn caret(&self) -> String { - format!( - "{}{}\x1b[0m", - self.style.cursor.1.ansi(), - self.style.caret.1 - ) - } -} - -impl Default for Handler { - fn default() -> Self { - Handler(Arc::new(Mutex::new( - |_: &mut T, _: crossterm::event::Event, _: &mut Document| {}, - ))) - } -} - -impl std::fmt::Debug for Handler { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Handler()") - } -} - -impl State { - pub fn new<'a>(v: T) -> State { - State(Arc::new(Mutex::new(v))) - } - pub fn use_state<'a>(&'a self) -> std::sync::MutexGuard<'a, T> { - self.0.lock().unwrap() - } - pub fn clone<'a>(&'a self) -> State { - State(Arc::clone(&self.0)) - } -} - -impl std::fmt::Display for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Acquire the lock, handle potential poisoning - match self.0.lock() { - Ok(guard) => write!(f, "{}", *guard), - Err(poisoned) => write!(f, "", *poisoned.into_inner()), + println!("{}", w.0); } } } -impl AddAssign for State { - fn add_assign(&mut self, rhs: T) { - *self.0.lock().unwrap() += rhs; +pub struct Terminal(Frame); + +impl Terminal { + pub fn new() -> Result { + Ok(Terminal(Frame::new_root(crossterm::terminal::size()?))) + } + + pub fn draw(&mut self, f: F) -> Result<()> + where + F: Fn(&mut Frame), + { + f(&mut self.0); + Ok(()) } } -impl SubAssign for State { - fn sub_assign(&mut self, rhs: T) { - *self.0.lock().unwrap() -= rhs; +impl Widget for &str { + fn draw(&self, w: &mut Writer) { + w.write(self); } } -impl PartialEq for State { - fn eq(&self, other: &T) -> bool { - self.use_state().eq(other) - } - fn ne(&self, other: &T) -> bool { - self.use_state().ne(other) - } -} - -#[derive(Debug)] -pub enum Instruction<'a> { - SetStyle(ui::Css), - SetChild(usize), - Ghost(usize), - Load(Handler>), +pub fn init() -> Result { + utils::clear()?; + utils::hide_cursor()?; + Terminal::new() } diff --git a/src/macros.rs b/src/macros.rs deleted file mode 100644 index da8547f..0000000 --- a/src/macros.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[macro_export] -macro_rules! launch { - ($elem:path) => { - let mut element = <$elem>::default().create_element(); - let mut document = $crate::Document::with_elem(&mut element); - while document.run() { - element = <$elem>::default().create_element(); - } - }; - ($elem:path, $css:expr) => { - let mut element = <$elem>::default().create_element(); - let mut document = $crate::Document::with_elem(&mut element); - document.set_css($css); - while document.run() { - element = <$elem>::default().create_element(); - } - }; -} diff --git a/src/main.rs b/src/main.rs index bff6245..5a7b63d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,27 +1,12 @@ -use osui::{examples, prelude::*}; +use osui::*; fn main() { - launch!(App); -} - -#[component] -fn App() { - rsx! { - @SetStyle(css! { - example: "hover" { - color: Blue, - } - }) - - %text { "Choose an example to run" } - button { class: "example", on_click: |_, _, doc| { - doc.draw(examples::counter_example::App::default().create_element()); - }, "Counter" } - button { class: "example", on_click: |_, _, doc| { - doc.draw(examples::login_example::App::default().create_element()); - }, "Login" } - button { class: "example", on_click: |_, _, doc| { - doc.draw(examples::todo_example::App::default().create_element()); - }, "Todo" } + let mut term = init().unwrap(); + loop { + term.draw(app).unwrap(); } } + +fn app(frame: &mut Frame) { + frame.render("hi"); +} diff --git a/src/rsx.rs b/src/rsx.rs deleted file mode 100644 index a49eda3..0000000 --- a/src/rsx.rs +++ /dev/null @@ -1,153 +0,0 @@ -#[macro_export] -macro_rules! check_expr { - (|| $code:block) => { - $crate::Handler::new(move |_, _, _| $code) - }; - (|$($inner:tt)*| $code:block) => { - $crate::Handler::new(move |$($inner)*| $code) - }; - ($expr:expr) => { - $expr - }; -} -#[macro_export] -macro_rules! parse_rsx_param { - //// FUNCTIONS - - ($elem:expr, $($k:ident).+: fn($($params:tt)*) $(@$($v:ident),+)? $body:expr $(, $($rest:tt)*)?) => { - $elem.$($k).+ = $crate::Handler::new({ - $($( - #[allow(unused_mut)] - let mut $v = $v.clone(); - )+)? - move |$($params)*| { - $body - } - }); - osui::parse_rsx_param!($elem, $($($rest)*)?); - }; - - ($elem:expr, $($k:ident).+: |$($args:tt $(: $aty:ty)?),*| $(@$($v:ident),+)? $body:expr $(, $($rest:tt)*)?) => { - $elem.$($k).+ = $crate::Handler::new({ - $($( - #[allow(unused_mut)] - let mut $v = $v.clone(); - )+)? - move |$($args $(: $aty)?),*| $body - }); - osui::parse_rsx_param!($elem, $($($rest)*)?); - }; - - //// FUNCTIONS END - - - ($elem:expr, async $code:block $($rest:tt)*) => { - let elem_len = $elem.children.len(); - $elem.instructions.push($crate::Instruction::Load(Handler::new( - move |d: &mut Div, _, _| { - d.children.insert(elem_len, $code); - } - ))); - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, %$elem_path:path { $($inner:tt)* } $($rest:tt)*) => { - $elem.ghosts.push($elem.ghosts.len()); - if let $crate::Children::Children(children, _) = &mut $elem.children { - children.push($crate::ersx!($elem_path { $($inner)* })); - } else { - $elem.children = $crate::Children::Children(vec![$crate::ersx!($elem_path { $($inner)* })], 0) - } - osui::parse_rsx_param!($elem, $($rest)*) - }; - - ($elem:expr, @ $($i:ident)::+($($inner:tt)*) $($rest:tt)*) => { - $elem.instructions.push($($i)::+($($inner)*)); - osui::parse_rsx_param!($elem, $($rest)*); - }; - ($elem:expr, @ $($i:ident)::+ $($rest:tt)*) => { - $elem.instructions.push($($i)::+); - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, static $text:literal $($other:tt)*) => { - $elem.children = $crate::Children::StaticText(format!($text $($other)*)); - }; - - ($elem:expr, for ($($for:tt)*) $code:block $($rest:tt)*) => { - if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} - if let $crate::Children::Children(children, _) = &mut $elem.children { - for $($for)* { - children.push($code) - } - } - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, $($k:ident).+: $v:expr $(, $($rest:tt)*)?) => { - $elem.$($k).+ = $crate::check_expr!($v); - osui::parse_rsx_param!($elem, $($($rest)*)?); - }; - - ($elem:expr, $p:path) => { - $p; - }; - - ($elem:expr, $($k:ident).+, $($rest:tt)*) => { - $elem.$($k).+ = !$elem.$($k).+; - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, $code:block $($rest:tt)*) => { - if $elem.children.is_none() {$elem.children = $crate::Children::Children(Vec::new(), 0)} - if let $crate::Children::Children(children, _) = &mut $elem.children { - children.push($code); - } - osui::parse_rsx_param!($elem, $($rest)*); - }; - - ($elem:expr, $elem_path:path { $($inner:tt)* } $($rest:tt)*) => { - if let $crate::Children::Children(children, _) = &mut $elem.children { - children.push($crate::ersx!($elem_path { $($inner)* })); - } else { - $elem.children = $crate::Children::Children(vec![$crate::ersx!($elem_path { $($inner)* })], 0) - } - osui::parse_rsx_param!($elem, $($rest)*) - }; - - ($elem:expr, $text:literal $($other:tt)*) => { - $elem.children = $crate::Children::Text( - std::sync::Arc::new(move || format!($text $($other)*)) - ); - }; - - ($elem:expr,) => {}; -} - -#[macro_export] -macro_rules! ersx { - ($elem:path { $($inner:tt)* }) => {{ - let mut elem = <$elem>::default(); - $crate::parse_rsx_param!(elem, $($inner)*); - elem.create_element() - }}; -} - -/// Makes a div and puts elements into it -/// -/// # Example -/// ``` -/// rsx! { -/// button { class: "btn", "Click me!" } -/// } -/// ``` -/// -/// # Returns -/// A `osui::Element` - Which is a `Box` -#[macro_export] -macro_rules! rsx { - ($($inner:tt)*) => { - $crate::ersx!{ $crate::ui::div { $($inner)* } } - }; - -} diff --git a/src/ui/mod.rs b/src/ui/mod.rs deleted file mode 100644 index 6c2a451..0000000 --- a/src/ui/mod.rs +++ /dev/null @@ -1,267 +0,0 @@ -/// The `ui` module defines the basic building blocks for creating a terminal-based UI framework. -/// -/// # Modules -/// - `styles`: A module for defining UI styles. -pub mod styles; - -pub use styles::*; - -use crate::prelude::*; -use osui_element::element; - -#[element] -#[derive(Default, Debug)] -pub struct Text {} - -impl ElementWidget for Text<'_> { - fn render(&self, writer: &mut crate::Writer) { - writer.write(&self.children.get_text()); - } -} - -#[component] -pub fn text<'a>(class: &'a str, id: &'a str, children: Children, style: Style) -> Box> { - Box::new(Text { - class: self.class, - id: self.id, - children: self.children, - style: self.style, - }) -} - -#[element] -#[derive(Default, Debug)] -pub struct Div { - pub instructions: Vec>, - pub ghosts: Vec, -} - -impl ElementWidget for Div<'_> { - fn render(&self, writer: &mut crate::Writer) { - let focused = writer.get_focused(); - let mut frame = writer.new_frame(); - - if let Children::Children(children, child) = &self.children { - for (i, elem) in children.iter().enumerate() { - frame.render(focused && i == *child, elem); - } - } - } - - fn event(&mut self, event: Event, document: &mut Document) { - if let (Some((child, i)), l) = self.children.get_child_idx() { - match event { - Event::Key(k) => match k.code { - KeyCode::Tab => { - child.event(Event::FocusLost, document); - if *i < l - 1 { - *i += 1; - } else { - *i = 0; - } - while self.ghosts.get(*i).is_some() { - if *i < l - 1 { - *i += 1; - } else { - *i = 0; - } - } - child.event(Event::FocusGained, document); - } - KeyCode::BackTab => { - child.event(Event::FocusLost, document); - if *i > 0 { - *i -= 1; - } else { - *i = l - 1; - } - while self.ghosts.get(*i).is_some() { - if *i > 0 { - *i -= 1; - } else { - *i = l - 1; - } - } - child.event(Event::FocusGained, document); - } - _ => child.event(event, document), - }, - _ => child.event(event, document), - } - } - } - - fn initialize(&mut self, document: &mut Document) { - let l = self.children.len(); - if let Some(i) = self.children.index_mut() { - while self.ghosts.get(*i).is_some() { - if *i < l - 1 { - *i += 1; - } else { - *i = 0; - break; - } - } - } - let mut load_funcs = Vec::new(); - for inst in &self.instructions { - match inst { - crate::Instruction::SetStyle(s) => { - document.css.extend(s.clone().into_iter()); - } - crate::Instruction::SetChild(c) => { - self.children.set_index(*c); - } - crate::Instruction::Ghost(idx) => { - self.ghosts.push(*idx); - } - crate::Instruction::Load(f) => { - load_funcs.push(f.clone()); - } - } - } - self.instructions.clear(); - for i in load_funcs { - i.call(self, Event::FocusGained, document) - } - } -} - -#[component] -pub fn div<'a>( - class: &'a str, - id: &'a str, - children: Children, - style: Style, - instructions: Vec>, - ghosts: Vec, -) -> Box> { - Box::new(Div { - children: self.children, - class: self.class, - id: self.id, - style: self.style, - instructions: self.instructions, - ghosts: self.ghosts, - }) -} - -#[element] -#[derive(Default, Debug)] -pub struct Button { - pub on_click: Handler>, - pub on_hover: Handler>, -} - -impl ElementWidget for Button<'_> { - fn render(&self, writer: &mut crate::Writer) { - writer.write(&self.children.get_text()); - } - - fn event(&mut self, event: Event, document: &mut Document) { - match event { - Event::Key(key) => { - if key.code == KeyCode::Enter { - document.render(); - self.style.set_state("clicked"); - self.on_click.clone().call(self, event, document); - document.render(); - sleep(65); - self.style.set_state(""); - } - } - Event::FocusGained => { - self.on_hover.clone().call(self, event, document); - } - _ => {} - } - } -} - -#[component] -pub fn button<'a>( - class: &'a str, - id: &'a str, - children: Children, - style: Style, - on_click: Handler>, - on_hover: Handler>, -) -> Box> { - Box::new(Button { - class: self.class, - id: self.id, - children: self.children, - on_click: self.on_click.clone(), - on_hover: self.on_hover.clone(), - style: self.style, - }) -} - -#[element] -#[derive(Default, Debug)] -pub struct Input { - pub on_click: Handler>, - pub text: String, - pub placeholder: &'a str, -} - -impl ElementWidget for Input<'_> { - fn render(&self, writer: &mut crate::Writer) { - if self.text.is_empty() { - writer.write(&format!( - "{}{}{}", - self.children.get_text(), - self.placeholder, - writer.caret() - )); - } else { - writer.write(&format!( - "{}{}{}", - self.children.get_text(), - self.text, - writer.caret() - )); - } - } - - fn event(&mut self, event: crossterm::event::Event, document: &mut Document) { - match event { - Event::Key(k) => match k.code { - KeyCode::Backspace => { - if self.text.len() > 0 { - self.text.pop(); - } - } - KeyCode::Enter => { - self.on_click.clone().call(self, event, document); - } - KeyCode::Char(c) => { - self.text.push(c); - } - _ => {} - }, - _ => {} - } - } -} - -#[component] -pub fn input<'a>( - class: &'a str, - id: &'a str, - placeholder: &'a str, - text: String, - children: Children, - style: Style, - on_click: Handler>, -) -> Box> { - Box::new(Input { - class: self.class, - id: self.id, - children: self.children, - style: self.style, - on_click: self.on_click, - placeholder: self.placeholder, - text: self.text, - }) -} diff --git a/src/ui/styles.rs b/src/ui/styles.rs deleted file mode 100644 index 02fceaa..0000000 --- a/src/ui/styles.rs +++ /dev/null @@ -1,347 +0,0 @@ -//! The `styles` module defines various styling options for UI elements. This includes colors, -//! fonts, padding, margins, and other visual properties that can be applied to UI components -//! to change their appearance. - -use std::{collections::HashMap, fmt::Debug}; - -use dyn_clone::{clone_trait_object, DynClone}; - -////////////////////////////////////////////////////////////////////////////////////////////////// -// Type aliases -////////////////////////////////////////////////////////////////////////////////////////////////// - -pub type Css = HashMap; - -////////////////////////////////////////////////////////////////////////////////////////////////// -// Traits -////////////////////////////////////////////////////////////////////////////////////////////////// - -pub trait StyleCore: Debug + Send + Sync + DynClone { - fn ansi(&self) -> String; - fn ansi_bg(&self) -> String; -} -clone_trait_object!(StyleCore); - -////////////////////////////////////////////////////////////////////////////////////////////////// -// Enums -////////////////////////////////////////////////////////////////////////////////////////////////// - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Font { - Bold, - Underline, - Italic, - Reverse, - Strike, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Color { - NoColor, - Rgb(u8, u8, u8), - Hex(String), - Black, - Red, - Green, - Yellow, - Blue, - Magenta, - Cyan, - White, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Number { - Px(u16), - Pe(u16), - Center, - Auto, -} - -#[derive(PartialEq, Eq, Hash, Debug, Clone)] -pub enum StyleName { - Class(String), - ClassState(String, String), -} - -////////////////////////////////////////////////////////////////////////////////////////////////// -// Structs -////////////////////////////////////////////////////////////////////////////////////////////////// - -#[derive(Debug, Clone)] -pub struct StyleElement { - pub color: (bool, Color), - pub background: (bool, Color), - pub outline_color: (bool, Color), - pub font: (bool, Vec), - pub x: (bool, Number), - pub y: (bool, Number), - pub width: (bool, Number), - pub height: (bool, Number), - pub visible: (bool, bool), - pub outline: (bool, bool), - pub cursor: (bool, Color), - pub caret: (bool, String), -} - -#[derive(Debug, Clone)] -pub struct Style( - pub StyleElement, - pub HashMap, - pub String, -); - -////////////////////////////////////////////////////////////////////////////////////////////////// -// Implementations -////////////////////////////////////////////////////////////////////////////////////////////////// - -impl Default for Style { - fn default() -> Self { - Style(StyleElement::default(), HashMap::new(), String::new()) - } -} - -impl Style { - pub fn get(mut self, hover: bool) -> StyleElement { - if let Some(style_element) = self.1.get(&self.2) { - self.0.merge(style_element); - self.0 - } else if hover { - if let Some(style_element) = self.1.get("hover") { - self.0.merge(style_element); - self.0 - } else { - self.0 - } - } else { - self.0 - } - } - - pub fn set_state(&mut self, state: &str) { - self.2 = state.to_string(); - } -} - -impl StyleElement { - pub fn write(&self, s: &str) -> String { - s.split('\n') - .into_iter() - .map(|p| { - format!( - "{}{}{}{p}\x1b[0m", - self.color.1.ansi(), - self.background.1.ansi_bg(), - self.font.1.ansi() - ) - }) - .collect::>() - .join("\n") - } - pub fn write_outline(&self, outline: &str) -> String { - format!("{}{outline}\x1b[0m", self.outline_color.1.ansi(),) - } -} - -impl Default for StyleElement { - fn default() -> Self { - StyleElement { - color: (false, Color::NoColor), - background: (false, Color::NoColor), - cursor: (false, Color::NoColor), - font: (false, Vec::new()), - outline_color: (false, Color::NoColor), - x: (false, Number::Px(0)), - y: (false, Number::Auto), - width: (false, Number::Auto), - height: (false, Number::Auto), - visible: (false, true), - outline: (false, false), - caret: (false, String::from("█")), - } - } -} - -impl StyleElement { - pub fn merge(&mut self, upper: &Self) { - if upper.color.0 { - self.color = upper.color.clone(); - } - if upper.background.0 { - self.background = upper.background.clone(); - } - if upper.font.0 { - self.font = upper.font.clone(); - } - if upper.outline_color.0 { - self.outline_color = upper.outline_color.clone(); - } - if upper.x.0 { - self.x = upper.x.clone(); - } - if upper.y.0 { - self.y = upper.y.clone(); - } - if upper.width.0 { - self.width = upper.width.clone(); - } - if upper.height.0 { - self.height = upper.height.clone(); - } - if upper.visible.0 { - self.visible = upper.visible.clone(); - } - if upper.outline.0 { - self.outline = upper.outline.clone(); - } - if upper.cursor.0 { - self.cursor = upper.cursor.clone(); - } - if upper.caret.0 { - self.caret = upper.caret.clone(); - } - } -} - -impl StyleCore for Color { - fn ansi(&self) -> String { - String::from(match self { - Color::NoColor => "", - Color::Rgb(r, g, b) => { - return format!("\x1b[38;2;{r};{g};{b}m"); - } - Color::Hex(hex) => { - let (r, g, b) = hex_to_rgb(hex); - return format!("\x1b[38;2;{r};{g};{b}m"); - } - Color::Black => "\x1b[30m", - Color::Red => "\x1b[31m", - Color::Green => "\x1b[32m", - Color::Yellow => "\x1b[33m", - Color::Blue => "\x1b[34m", - Color::Magenta => "\x1b[35m", - Color::Cyan => "\x1b[36m", - Color::White => "\x1b[37m", - }) - } - - fn ansi_bg(&self) -> String { - String::from(match self { - Color::NoColor => "", - Color::Rgb(r, g, b) => { - return format!("\x1b[48;2;{r};{g};{b}m"); - } - Color::Hex(hex) => { - let (r, g, b) = hex_to_rgb(hex); - return format!("\x1b[48;2;{r};{g};{b}m"); - } - Color::Black => "\x1b[40m", - Color::Red => "\x1b[41m", - Color::Green => "\x1b[42m", - Color::Yellow => "\x1b[43m", - Color::Blue => "\x1b[44m", - Color::Magenta => "\x1b[45m", - Color::Cyan => "\x1b[46m", - Color::White => "\x1b[47m", - }) - } -} - -impl Default for Color { - fn default() -> Self { - Color::NoColor - } -} - -fn hex_to_rgb(hex: &str) -> (u8, u8, u8) { - let hex = hex.trim_start_matches('#'); - - let hex = if hex.len() == 3 { - format!( - "{}{}{}{}{}{}", - &hex[0..1], - &hex[0..1], - &hex[1..2], - &hex[1..2], - &hex[2..3], - &hex[2..3] - ) - } else if hex.len() == 6 { - hex.to_string() - } else { - return (255, 255, 255); - }; - - let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(255); - let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(255); - let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(255); - - (r, g, b) -} - -impl StyleCore for Font { - fn ansi(&self) -> String { - String::from(match self { - Font::Bold => "\x1b[1m", - Font::Underline => "\x1b[4m", - Font::Italic => "\x1b[3m", - Font::Reverse => "\x1b[7m", - Font::Strike => "\x1b[9m", - }) - } - fn ansi_bg(&self) -> String { - self.ansi() - } -} - -impl StyleCore for Vec { - fn ansi(&self) -> String { - let mut a = String::new(); - for i in self { - a += i.ansi().as_str(); - } - a - } - - fn ansi_bg(&self) -> String { - let mut a = String::new(); - for i in self { - a += i.ansi_bg().as_str(); - } - a - } -} - -impl Number { - pub fn as_size_raw(&self, frame_size: u16) -> u16 { - match self { - Number::Px(s) => *s, - Number::Pe(pe) => (frame_size * pe) / 100, - _ => frame_size, - } - } - pub fn as_size(&self, written: u16, frame_size: u16, outline: bool) -> u16 { - if *self == Number::Auto { - written - } else if outline { - self.as_size_raw(frame_size) - 2 - } else { - self.as_size_raw(frame_size) - } - } - pub fn as_position_raw(&self, used: &u16, frame_size: u16) -> u16 { - match self { - crate::ui::Number::Px(px) => *px, - crate::ui::Number::Pe(pe) => (frame_size * pe) / 100, - crate::ui::Number::Center => (frame_size) / 2, - crate::ui::Number::Auto => *used, - } - } - pub fn as_position(&self, outline: bool, used: &u16, frame_size: u16) -> u16 { - if outline { - self.as_position_raw(used, frame_size) + 1 - } else { - self.as_position_raw(used, frame_size) - } - } -} diff --git a/src/utils.rs b/src/utils.rs index e1e3b8c..2d6d2b7 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,133 +1,24 @@ -use std::io::{stdout, Write}; +use std::io::Write; -pub struct Frame<'a> { - used: Vec, - writer: &'a mut crate::Writer, -} - -impl<'a> Frame<'a> { - pub fn new(writer: &'a mut crate::Writer) -> Self { - Frame { - used: vec![0; writer.size.1 as usize], - writer, - } - } - pub fn render(&mut self, focused: bool, element: &crate::Element) { - let style = element.get_style(); - let mut style_element = style.clone().get(focused); - let css = unsafe { &*self.writer.css }; - - for class in element.get_class().split(" ") { - if let Some(upper) = css.get(&crate::ui::StyleName::Class(class.to_string())) { - style_element.merge(upper); - } - - if let Some(upper) = css.get(&crate::ui::StyleName::ClassState( - class.to_string(), - if style.2 == "" && focused { - "hover".to_string() - } else { - style.2.clone() - }, - )) { - style_element.merge(upper); - } - } - - if !style_element.visible.1 { - return; - } - - let y = style_element.y.1.as_position( - style_element.outline.1, - &{ - let mut y = 0; - while y < self.used.len() && self.used[y] != 0 { - y += 1; - } - y as u16 - }, - self.writer.size.1, - ); - - if let Some(y_) = self.used.get(y as usize) { - let x = style_element - .x - .1 - .as_position(style_element.outline.1, y_, self.writer.size.0); - - let mut writer = crate::Writer { - focused, - style: style_element.clone(), - pos: (x, y), - size: self.writer.size, - css, - parent: Some(self.writer), - written: (0, 0), - }; - element.render(&mut writer); - let (width, height) = writer.get_size_root(); - { - let (x, y, width, height) = if style_element.outline.1 { - (x - 1, y - 1, width + 2, height + 2) - } else { - (x, y, width, height) - }; - - for i in y..y + height { - if let Some(o) = self.used.get_mut(i as usize) { - if style_element.x.1 != crate::ui::Number::Auto { - *o += x; - } - *o += width; - } - } - } - writer.after_render(width, height); - } - } -} - -pub fn clear() { +pub fn clear() -> crate::Result<()> { print!("\x1B[2J\x1B[H"); - stdout().flush().unwrap(); + std::io::stdout().flush()?; + Ok(()) } -pub fn hide_cursor() { +pub fn hide_cursor() -> crate::Result<()> { print!("\x1b[?25l"); - stdout().flush().unwrap(); + std::io::stdout().flush()?; + Ok(()) } -pub fn show_cursor() { +pub fn show_cursor() -> crate::Result<()> { print!("\x1B[?25h"); - stdout().flush().unwrap(); + std::io::stdout().flush()?; + Ok(()) } -pub fn flush() { - stdout().flush().unwrap(); -} - -pub trait VisibleLength { - fn visible_len(&self) -> usize; -} - -impl VisibleLength for String { - fn visible_len(&self) -> usize { - let mut length = 0; - let mut in_escape = false; - - for c in self.chars() { - if c == '\x1b' { - in_escape = true; - } else if in_escape { - if c.is_ascii_alphabetic() { - in_escape = false; - } - } else { - length += 1; - } - } - - length - } +pub fn flush() -> crate::Result<()> { + std::io::stdout().flush()?; + Ok(()) }