Large structure update
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ version = "0.0.3"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
description = "A TUI library"
|
description = "A TUI library"
|
||||||
repository = "https://github.com/orus-dev/osui-rs"
|
repository = "https://github.com/orus-dev/osui"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "osui"
|
name = "osui"
|
||||||
|
|||||||
+38
-63
@@ -1,89 +1,72 @@
|
|||||||
|
use crossterm::ExecutableCommand;
|
||||||
|
|
||||||
pub mod key;
|
pub mod key;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use crossterm::{self, ExecutableCommand};
|
pub trait Element: std::fmt::Debug {
|
||||||
use std::collections::HashMap;
|
fn get_child(&mut self) -> Option<&mut Box<dyn Element>>;
|
||||||
|
fn get_data(&self) -> ElementData;
|
||||||
|
fn set_data(&mut self, _: ElementData);
|
||||||
|
fn render(&mut self) -> String {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
fn update(&mut self, _: crate::key::Key) -> UpdateResponse {
|
||||||
|
UpdateResponse::None
|
||||||
|
}
|
||||||
|
fn tick(&mut self, _: usize) {}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug)]
|
||||||
pub struct Component {
|
pub struct ElementData {
|
||||||
pub render: fn(&mut Component) -> String,
|
|
||||||
pub update: fn(&mut Component, key::Key) -> UpdateResponse,
|
|
||||||
pub tick: fn(&mut Component, usize),
|
|
||||||
pub on_click: fn(&mut Component),
|
|
||||||
pub x: usize,
|
pub x: usize,
|
||||||
pub y: usize,
|
pub y: usize,
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub expr: String,
|
pub style: ui::styles::Style,
|
||||||
pub children: Vec<Component>,
|
|
||||||
pub child: usize,
|
|
||||||
pub clicked: bool,
|
|
||||||
pub toggle: bool,
|
|
||||||
pub binds: HashMap<key::KeyKind, String>,
|
|
||||||
pub style: ui::Style,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component {
|
|
||||||
/// Creates a new component
|
|
||||||
fn new() -> Component {
|
|
||||||
Component {
|
|
||||||
render: |_| String::new(),
|
|
||||||
update: |_, _| UpdateResponse::None,
|
|
||||||
tick: |_, _| {},
|
|
||||||
on_click: |_| {},
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
expr: String::new(),
|
|
||||||
children: Vec::new(),
|
|
||||||
child: 0,
|
|
||||||
clicked: false,
|
|
||||||
toggle: false,
|
|
||||||
binds: HashMap::new(),
|
|
||||||
style: ui::Style::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_child(&mut self) -> Option<&mut Component> {
|
|
||||||
self.children.get_mut(self.child)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum UpdateResponse {
|
||||||
|
Exit,
|
||||||
|
Done,
|
||||||
|
None,
|
||||||
|
Tick(Vec<u32>),
|
||||||
|
}
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
component: Component,
|
element: Box<dyn Element>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
/// Creates a new screen to render components
|
/// Creates a new screen to render components
|
||||||
pub fn new() -> App {
|
pub fn new() -> App {
|
||||||
App {
|
App {
|
||||||
component: Component::new(),
|
element: ui::text(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a component
|
/// Sets a component
|
||||||
pub fn set_component(&mut self, component: Component) {
|
pub fn set_component(&mut self, element: Box<dyn Element>) {
|
||||||
let (width, height) = crossterm::terminal::size().unwrap();
|
let (width, height) = crossterm::terminal::size().unwrap();
|
||||||
self.component.style.is_active = true;
|
self.element = element;
|
||||||
self.component = component;
|
let mut data = self.element.get_data();
|
||||||
if self.component.width == 0 {
|
data.style.is_active = true;
|
||||||
self.component.width = width as usize;
|
if data.width == 0 {
|
||||||
|
data.width = width as usize;
|
||||||
}
|
}
|
||||||
if self.component.height == 0 {
|
if data.height == 0 {
|
||||||
self.component.height = height as usize;
|
data.height = height as usize;
|
||||||
}
|
}
|
||||||
self.component.style.is_active = true;
|
self.element.set_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render to the screen
|
/// Render to the screen
|
||||||
pub fn render(&mut self) {
|
pub fn render(&mut self) {
|
||||||
let (width, height) = crossterm::terminal::size().unwrap();
|
let (width, height) = crossterm::terminal::size().unwrap();
|
||||||
let mut frame: Vec<String> = create_frame!(width as usize, height as usize);
|
let mut frame: Vec<String> = create_frame!(width as usize, height as usize);
|
||||||
utils::render_to_frame(&mut frame, &mut self.component);
|
utils::render_to_frame(&mut frame, &mut self.element);
|
||||||
utils::clear();
|
utils::clear();
|
||||||
print!("{}", frame.join(""));
|
print!("{}", frame.join(""));
|
||||||
utils::flush();
|
utils::flush();
|
||||||
@@ -100,7 +83,7 @@ impl App {
|
|||||||
crossterm::terminal::enable_raw_mode().unwrap();
|
crossterm::terminal::enable_raw_mode().unwrap();
|
||||||
loop {
|
loop {
|
||||||
self.render();
|
self.render();
|
||||||
match (self.component.update)(&mut self.component, key::read_key()) {
|
match self.element.update(key::read_key()) {
|
||||||
UpdateResponse::Exit => {
|
UpdateResponse::Exit => {
|
||||||
crossterm::terminal::disable_raw_mode().unwrap();
|
crossterm::terminal::disable_raw_mode().unwrap();
|
||||||
utils::show_cursor();
|
utils::show_cursor();
|
||||||
@@ -110,7 +93,7 @@ impl App {
|
|||||||
UpdateResponse::Tick(ticks) => {
|
UpdateResponse::Tick(ticks) => {
|
||||||
for (i, n) in ticks.into_iter().enumerate() {
|
for (i, n) in ticks.into_iter().enumerate() {
|
||||||
std::thread::sleep(std::time::Duration::from_millis(n as u64));
|
std::thread::sleep(std::time::Duration::from_millis(n as u64));
|
||||||
(self.component.tick)(&mut self.component, i);
|
self.element.tick(i);
|
||||||
self.render();
|
self.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,11 +102,3 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum UpdateResponse {
|
|
||||||
Exit,
|
|
||||||
Done,
|
|
||||||
None,
|
|
||||||
Tick(Vec<u32>),
|
|
||||||
}
|
|
||||||
|
|||||||
+94
-24
@@ -8,7 +8,7 @@ macro_rules! __oml {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
($tag:ident ($($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{
|
($tag:ident ($($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{
|
||||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||||
comps.insert(0, oml!($tag ($($k = $v),*) {$($inner)*}));
|
comps.insert(0, oml!($tag ($($k = $v),*) {$($inner)*}));
|
||||||
comps
|
comps
|
||||||
}};
|
}};
|
||||||
@@ -19,30 +19,30 @@ macro_rules! __oml {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
($tag:ident {$($inner:tt)*} $($rest:tt)+) => {{
|
($tag:ident {$($inner:tt)*} $($rest:tt)+) => {{
|
||||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||||
comps.insert(0, oml!($tag {$($inner)*}));
|
comps.insert(0, oml!($tag {$($inner)*}));
|
||||||
comps
|
comps
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Expression, With components (EC)
|
// Expression, With components (EC)
|
||||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*) {$($inner:tt)*}) => {{
|
($tag:ident ($text:expr; $($k:ident = $v:expr),*) {$($inner:tt)*}) => {{
|
||||||
vec![oml!($tag ($expr; $($k = $v),*) {$($inner)*})]
|
vec![oml!($tag ($text; $($k = $v),*) {$($inner)*})]
|
||||||
}};
|
}};
|
||||||
|
|
||||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{
|
($tag:ident ($text:expr; $($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{
|
||||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||||
comps.insert(0, oml!($tag ($expr; $($k = $v),*) {$($inner)*}));
|
comps.insert(0, oml!($tag ($text; $($k = $v),*) {$($inner)*}));
|
||||||
comps
|
comps
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Expression (E)
|
// Expression (E)
|
||||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*)) => {{
|
($tag:ident ($text:expr; $($k:ident = $v:expr),*)) => {{
|
||||||
vec![oml!($tag ($expr; $($k = $v),*))]
|
vec![oml!($tag ($text; $($k = $v),*))]
|
||||||
}};
|
}};
|
||||||
|
|
||||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*) $($rest:tt)+) => {{
|
($tag:ident ($text:expr; $($k:ident = $v:expr),*) $($rest:tt)+) => {{
|
||||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||||
comps.insert(0, oml!($tag ($expr; $($k = $v),*)));
|
comps.insert(0, oml!($tag ($text; $($k = $v),*)));
|
||||||
comps
|
comps
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@@ -52,19 +52,19 @@ macro_rules! __oml {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
($tag:ident ($($k:ident = $v:expr),*) $($rest:tt)+) => {{
|
($tag:ident ($($k:ident = $v:expr),*) $($rest:tt)+) => {{
|
||||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||||
comps.insert(0, oml!($tag ($($k = $v),*)));
|
comps.insert(0, oml!($tag ($($k = $v),*)));
|
||||||
comps
|
comps
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// Pre-Defined (D)
|
// Pre-Defined (D)
|
||||||
($expr:expr;) => {{
|
($text:expr;) => {{
|
||||||
vec![oml!($expr;)]
|
vec![oml!($text;)]
|
||||||
}};
|
}};
|
||||||
|
|
||||||
($expr:expr; $($rest:tt)+) => {{
|
($text:expr; $($rest:tt)+) => {{
|
||||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||||
comps.insert(0, oml!($expr));
|
comps.insert(0, oml!($text));
|
||||||
comps
|
comps
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
@@ -82,10 +82,10 @@ macro_rules! oml {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
// Expression, With components (EC)
|
// Expression, With components (EC)
|
||||||
( $tag:ident ($expr:expr; $($k:ident = $v:expr),* ) {$($inner:tt)*} ) => {{
|
( $tag:ident ($text:expr; $($k:ident = $v:expr),* ) {$($inner:tt)*} ) => {{
|
||||||
let mut c = $tag();
|
let mut c = $tag();
|
||||||
c.children = osui::__oml!($($inner)*);
|
c.children = osui::__oml!($($inner)*);
|
||||||
c.params = String::from($expr);
|
c.text = String::from($text);
|
||||||
$(
|
$(
|
||||||
c.$k = $v;
|
c.$k = $v;
|
||||||
)*
|
)*
|
||||||
@@ -93,9 +93,9 @@ macro_rules! oml {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
// Expression (E)
|
// Expression (E)
|
||||||
( $tag:ident ($expr:expr; $($k:ident = $v:expr),*) ) => {{
|
( $tag:ident ($text:expr; $($k:ident = $v:expr),*) ) => {{
|
||||||
let mut c = $tag();
|
let mut c = $tag();
|
||||||
c.expr = String::from($expr);
|
c.text = String::from($text);
|
||||||
$(
|
$(
|
||||||
c.$k = $v;
|
c.$k = $v;
|
||||||
)*
|
)*
|
||||||
@@ -120,8 +120,8 @@ macro_rules! oml {
|
|||||||
}};
|
}};
|
||||||
|
|
||||||
// Pre-Defined (D)
|
// Pre-Defined (D)
|
||||||
($expr:expr;) => {{
|
($text:expr;) => {{
|
||||||
$expr
|
$text
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,3 +134,73 @@ macro_rules! create_frame {
|
|||||||
vec![" ".repeat($width); $height]
|
vec![" ".repeat($width); $height]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
///```component!{
|
||||||
|
/// MyElem {}
|
||||||
|
/// defaults {}
|
||||||
|
/// }```
|
||||||
|
///
|
||||||
|
/// Create a frame for rendering multiple components
|
||||||
|
macro_rules! element {
|
||||||
|
(
|
||||||
|
$name:ident {
|
||||||
|
$( $inner:tt )*
|
||||||
|
}
|
||||||
|
defaults {$( $defaults:tt )*}
|
||||||
|
$( $functions:tt )*
|
||||||
|
) => {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct $name {
|
||||||
|
pub x: usize,
|
||||||
|
pub y: usize,
|
||||||
|
pub width: usize,
|
||||||
|
pub height: usize,
|
||||||
|
pub children: Vec<Box<dyn Element>>,
|
||||||
|
pub child: usize,
|
||||||
|
pub text: String,
|
||||||
|
pub style: Style,
|
||||||
|
$( $inner )*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for $name {
|
||||||
|
fn get_child(&mut self) -> Option<&mut Box<dyn Element>> {
|
||||||
|
self.children.get_mut(self.child)
|
||||||
|
}
|
||||||
|
fn get_data(&self) -> ElementData {
|
||||||
|
ElementData {
|
||||||
|
x: self.x,
|
||||||
|
y: self.y,
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
style: self.style.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_data(&mut self, data: ElementData) {
|
||||||
|
self.x = data.x;
|
||||||
|
self.y = data.y;
|
||||||
|
self.width = data.width;
|
||||||
|
self.height = data.height;
|
||||||
|
self.style = data.style;
|
||||||
|
}
|
||||||
|
$( $functions )*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
pub fn new() -> $name {
|
||||||
|
$name {
|
||||||
|
children: Vec::new(),
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
child: 0,
|
||||||
|
text: String::new(),
|
||||||
|
style: Style::default(),
|
||||||
|
$( $defaults )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
+6
-22
@@ -1,29 +1,13 @@
|
|||||||
use osui::{oml, ui::*, App, Component};
|
use osui::{oml, ui::*, App};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new();
|
let mut app_screen = App::new();
|
||||||
app.set_component(app_elem());
|
app_screen.set_component(app());
|
||||||
app.run();
|
app_screen.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn onclick(c: &mut Component) {
|
fn app() -> Box<Text> {
|
||||||
if c.clicked {
|
|
||||||
c.expr = "Clicked!".to_string();
|
|
||||||
} else {
|
|
||||||
c.expr = "click me".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn app_elem() -> Component {
|
|
||||||
oml!(
|
oml!(
|
||||||
tab {
|
text("Hello, World!";)
|
||||||
div {
|
|
||||||
text("Hello, World!";)
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
button("click me"; toggle = false, on_click = onclick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,505 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
create_frame,
|
|
||||||
key::{self, KeyKind},
|
|
||||||
utils::{closest_component, render_to_frame},
|
|
||||||
Component, UpdateResponse,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct Style {
|
|
||||||
pub bg: Color,
|
|
||||||
pub fg: Color,
|
|
||||||
pub outline: Color,
|
|
||||||
pub font: Font,
|
|
||||||
|
|
||||||
pub hover_bg: Color,
|
|
||||||
pub hover_fg: Color,
|
|
||||||
pub hover_outline: Color,
|
|
||||||
pub hover_font: Font,
|
|
||||||
pub hover_cursor_fg: Color,
|
|
||||||
pub hover_cursor_bg: Color,
|
|
||||||
|
|
||||||
pub clicked_bg: Color,
|
|
||||||
pub clicked_fg: Color,
|
|
||||||
pub clicked_outline: Color,
|
|
||||||
pub clicked_font: Font,
|
|
||||||
|
|
||||||
pub selected_bg: Color,
|
|
||||||
pub selected_fg: Color,
|
|
||||||
pub selected_font: Font,
|
|
||||||
|
|
||||||
pub cursor_fg: Color,
|
|
||||||
pub cursor_bg: Color,
|
|
||||||
|
|
||||||
pub is_active: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Style {
|
|
||||||
fn default() -> Style {
|
|
||||||
Style {
|
|
||||||
bg: Color::None,
|
|
||||||
fg: Color::None,
|
|
||||||
outline: Color::None,
|
|
||||||
font: Font::None,
|
|
||||||
|
|
||||||
hover_bg: Color::None,
|
|
||||||
hover_fg: Color::None,
|
|
||||||
hover_outline: Color::None,
|
|
||||||
hover_font: Font::None,
|
|
||||||
hover_cursor_fg: Color::None,
|
|
||||||
hover_cursor_bg: Color::None,
|
|
||||||
|
|
||||||
clicked_bg: Color::None,
|
|
||||||
clicked_fg: Color::None,
|
|
||||||
clicked_outline: Color::None,
|
|
||||||
clicked_font: Font::None,
|
|
||||||
|
|
||||||
selected_bg: Color::None,
|
|
||||||
selected_fg: Color::None,
|
|
||||||
selected_font: Font::None,
|
|
||||||
cursor_fg: Color::None,
|
|
||||||
cursor_bg: Color::None,
|
|
||||||
|
|
||||||
is_active: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Style {
|
|
||||||
pub fn get(&self) -> String {
|
|
||||||
if self.is_active {
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.fg.prioritize(&self.hover_fg).ansi(),
|
|
||||||
self.bg.prioritize(&self.hover_bg).ansi_bg(),
|
|
||||||
self.font.prioritize(&self.hover_font).ansi()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.fg.ansi(),
|
|
||||||
self.bg.ansi_bg(),
|
|
||||||
self.font.ansi()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_outline(&self) -> String {
|
|
||||||
if self.is_active {
|
|
||||||
self.outline.prioritize(&self.hover_outline).ansi()
|
|
||||||
} else {
|
|
||||||
self.outline.ansi()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_clicked(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.fg.prioritize(&self.clicked_fg).ansi(),
|
|
||||||
self.bg.prioritize(&self.clicked_bg).ansi_bg(),
|
|
||||||
self.font.prioritize(&self.clicked_font).ansi()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_selected(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"{}{}{}",
|
|
||||||
self.fg.prioritize(&self.selected_fg).ansi(),
|
|
||||||
self.bg.prioritize(&self.selected_bg).ansi_bg(),
|
|
||||||
self.font.prioritize(&self.selected_font).ansi()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_cursor(&self) -> String {
|
|
||||||
if self.is_active {
|
|
||||||
format!(
|
|
||||||
"{}{}",
|
|
||||||
self.cursor_fg.prioritize(&self.hover_cursor_fg).ansi(),
|
|
||||||
self.cursor_bg.prioritize(&self.hover_cursor_bg).ansi_bg(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
format!("{}{}", self.cursor_fg.ansi(), self.cursor_bg.ansi_bg())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(&self, s: &str) -> String {
|
|
||||||
format!("{}{}\x1b[0m", self.get(), s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_outline(&self, s: &str) -> String {
|
|
||||||
format!("{}{}\x1b[0m", self.get_outline(), s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_clicked(&self, s: &str) -> String {
|
|
||||||
format!("{}{}\x1b[0m", self.get_clicked(), s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_selected(&self, s: &str) -> String {
|
|
||||||
format!("{}{}\x1b[0m", self.get_selected(), s)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_cursor(&self, s: &str) -> String {
|
|
||||||
format!("{}{}\x1b[0m", self.get_cursor(), s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum Font {
|
|
||||||
None,
|
|
||||||
Bold,
|
|
||||||
Underline,
|
|
||||||
Italic,
|
|
||||||
Reverse,
|
|
||||||
Strike,
|
|
||||||
Mul(Vec<Font>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Font {
|
|
||||||
pub fn ansi(&self) -> String {
|
|
||||||
String::from(match self {
|
|
||||||
Font::None => "",
|
|
||||||
Font::Bold => "\x1b[1m",
|
|
||||||
Font::Underline => "\x1b[4m",
|
|
||||||
Font::Italic => "\x1b[3m",
|
|
||||||
Font::Reverse => "\x1b[7m",
|
|
||||||
Font::Strike => "\x1b[9m",
|
|
||||||
Font::Mul(v) => {
|
|
||||||
let mut s = String::new();
|
|
||||||
for n in v {
|
|
||||||
s += n.ansi().as_str();
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prioritize<'a>(&'a self, secondary: &'a Font) -> &Font {
|
|
||||||
if secondary == &Font::None {
|
|
||||||
self
|
|
||||||
} else {
|
|
||||||
secondary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub enum Color {
|
|
||||||
None,
|
|
||||||
Rgb(u8, u8, u8),
|
|
||||||
Black,
|
|
||||||
Red,
|
|
||||||
Green,
|
|
||||||
Yellow,
|
|
||||||
Blue,
|
|
||||||
Magenta,
|
|
||||||
Cyan,
|
|
||||||
White,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Color {
|
|
||||||
pub fn ansi(&self) -> String {
|
|
||||||
String::from(match self {
|
|
||||||
Color::None => "",
|
|
||||||
Self::Rgb(r, g, b) => {
|
|
||||||
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",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ansi_bg(&self) -> String {
|
|
||||||
String::from(match self {
|
|
||||||
Color::None => "",
|
|
||||||
Self::Rgb(r, g, b) => {
|
|
||||||
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",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prioritize<'a>(&'a self, secondary: &'a Color) -> &Color {
|
|
||||||
if secondary == &Color::None {
|
|
||||||
self
|
|
||||||
} else {
|
|
||||||
secondary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn div() -> Component {
|
|
||||||
let mut component = Component::new();
|
|
||||||
|
|
||||||
component.binds = HashMap::from([
|
|
||||||
(KeyKind::Up, String::from("up")),
|
|
||||||
(KeyKind::Down, String::from("down")),
|
|
||||||
(KeyKind::Left, String::from("left")),
|
|
||||||
(KeyKind::Right, String::from("right")),
|
|
||||||
]);
|
|
||||||
|
|
||||||
fn update(this: &mut Component, k: key::Key) -> UpdateResponse {
|
|
||||||
if let Some(v) = this.binds.get(&k.kind) {
|
|
||||||
match v.as_str() {
|
|
||||||
"up" => {
|
|
||||||
this.child =
|
|
||||||
closest_component(&this.children, this.child, crate::utils::Direction::Up);
|
|
||||||
}
|
|
||||||
|
|
||||||
"down" => {
|
|
||||||
this.child = closest_component(
|
|
||||||
&this.children,
|
|
||||||
this.child,
|
|
||||||
crate::utils::Direction::Down,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
"left" => {
|
|
||||||
this.child = closest_component(
|
|
||||||
&this.children,
|
|
||||||
this.child,
|
|
||||||
crate::utils::Direction::Left,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
"right" => {
|
|
||||||
this.child = closest_component(
|
|
||||||
&this.children,
|
|
||||||
this.child,
|
|
||||||
crate::utils::Direction::Right,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let Some(child) = this.get_child() {
|
|
||||||
return (child.update)(child, k);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateResponse::None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(this: &mut Component) -> String {
|
|
||||||
let mut frame: Vec<String> = create_frame!(this.width, this.height);
|
|
||||||
for (i, child) in &mut this.children.iter_mut().enumerate() {
|
|
||||||
if child.width == 0 {
|
|
||||||
child.width = this.width;
|
|
||||||
}
|
|
||||||
if child.height == 0 {
|
|
||||||
child.height = this.height;
|
|
||||||
}
|
|
||||||
child.style.is_active = this.style.is_active && i == this.child;
|
|
||||||
render_to_frame(&mut frame, child);
|
|
||||||
}
|
|
||||||
frame.join("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(this: &mut Component, i: usize) {
|
|
||||||
if let Some(child) = this.get_child() {
|
|
||||||
return (child.tick)(child, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component.update = update;
|
|
||||||
component.render = render;
|
|
||||||
component.tick = tick;
|
|
||||||
component
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text() -> Component {
|
|
||||||
let mut component = Component::new();
|
|
||||||
|
|
||||||
fn update(_: &mut Component, _: key::Key) -> UpdateResponse {
|
|
||||||
UpdateResponse::None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(s: &mut Component) -> String {
|
|
||||||
s.expr.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
component.update = update;
|
|
||||||
component.render = render;
|
|
||||||
component
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn button() -> Component {
|
|
||||||
let mut component = Component::new();
|
|
||||||
|
|
||||||
component.binds = HashMap::from([(key::KeyKind::Enter, String::from("click"))]);
|
|
||||||
|
|
||||||
fn update(this: &mut Component, k: key::Key) -> UpdateResponse {
|
|
||||||
if let Some(v) = this.binds.get(&k.kind) {
|
|
||||||
if v == "click" {
|
|
||||||
if this.toggle {
|
|
||||||
this.clicked = !this.clicked;
|
|
||||||
(this.on_click)(this);
|
|
||||||
} else {
|
|
||||||
return UpdateResponse::Tick(vec![1, 100]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UpdateResponse::None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(this: &mut Component, i: usize) {
|
|
||||||
if i == 0 {
|
|
||||||
this.clicked = true;
|
|
||||||
(this.on_click)(this);
|
|
||||||
} else if i == 1 {
|
|
||||||
this.clicked = false;
|
|
||||||
(this.on_click)(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(this: &mut Component) -> String {
|
|
||||||
if this.clicked {
|
|
||||||
return this.style.write_clicked(&this.expr);
|
|
||||||
}
|
|
||||||
this.style.write(&this.expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
component.update = update;
|
|
||||||
component.render = render;
|
|
||||||
component.tick = tick;
|
|
||||||
component
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn tab() -> Component {
|
|
||||||
let mut component = Component::new();
|
|
||||||
|
|
||||||
component.binds = HashMap::from([
|
|
||||||
(key::KeyKind::Tab, String::from("next")),
|
|
||||||
(key::KeyKind::ShiftTab, String::from("previous")),
|
|
||||||
]);
|
|
||||||
|
|
||||||
component.style.clicked_fg = crate::ui::Color::Red;
|
|
||||||
|
|
||||||
fn update(this: &mut Component, k: key::Key) -> UpdateResponse {
|
|
||||||
if let Some(v) = this.binds.get(&k.kind) {
|
|
||||||
match v.as_str() {
|
|
||||||
"next" => {
|
|
||||||
if this.child + 1 < this.children.len() {
|
|
||||||
this.child += 1;
|
|
||||||
} else {
|
|
||||||
this.child = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"previous" => {
|
|
||||||
if this.child == 0 {
|
|
||||||
this.child = this.children.len() - 1;
|
|
||||||
} else {
|
|
||||||
this.child -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let Some(child) = this.get_child() {
|
|
||||||
return (child.update)(child, k);
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UpdateResponse::None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(this: &mut Component) -> String {
|
|
||||||
let mut frame: Vec<String> = create_frame!(this.width, this.height - 1);
|
|
||||||
let w = this.width;
|
|
||||||
let h = this.height;
|
|
||||||
if let Some(child) = this.get_child() {
|
|
||||||
if child.width == 0 {
|
|
||||||
child.width = w;
|
|
||||||
}
|
|
||||||
if child.height == 0 {
|
|
||||||
child.height = h;
|
|
||||||
}
|
|
||||||
child.style.is_active = true;
|
|
||||||
render_to_frame(&mut frame, child);
|
|
||||||
}
|
|
||||||
let mut v: String = " ".repeat((this.width / 2) - this.children.len());
|
|
||||||
for (i, _) in (&this.children).into_iter().enumerate() {
|
|
||||||
if i == this.child {
|
|
||||||
v += this.style.write_clicked("*").as_str()
|
|
||||||
} else {
|
|
||||||
v += this.style.write("*").as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
format!("{}\n{}", v, frame.join("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tick(this: &mut Component, i: usize) {
|
|
||||||
if let Some(child) = this.get_child() {
|
|
||||||
return (child.tick)(child, i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component.update = update;
|
|
||||||
component.tick = tick;
|
|
||||||
component.render = render;
|
|
||||||
component
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn menu() -> Component {
|
|
||||||
let mut component = Component::new();
|
|
||||||
|
|
||||||
component.style.selected_bg = Color::Magenta;
|
|
||||||
component.style.selected_fg = Color::Black;
|
|
||||||
component.style.cursor_fg = Color::Magenta;
|
|
||||||
|
|
||||||
fn render(this: &mut Component) -> String {
|
|
||||||
let mut res: Vec<String> = Vec::new();
|
|
||||||
|
|
||||||
for (i, item) in this.children.iter_mut().enumerate() {
|
|
||||||
if i == this.child {
|
|
||||||
res.push(format!(
|
|
||||||
"{}{}",
|
|
||||||
this.style.write_cursor("> "),
|
|
||||||
this.style.write_selected(&item.expr)
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
res.push(format!(" {}", &item.expr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.join(&this.style.write("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(this: &mut Component, k: key::Key) -> UpdateResponse {
|
|
||||||
if k.kind == key::KeyKind::Down {
|
|
||||||
if this.children.len() > this.child + 1 {
|
|
||||||
this.child += 1;
|
|
||||||
} else {
|
|
||||||
this.child = 0;
|
|
||||||
}
|
|
||||||
} else if k.kind == key::KeyKind::Up {
|
|
||||||
if this.child > 0 {
|
|
||||||
this.child -= 1;
|
|
||||||
} else {
|
|
||||||
this.child = this.children.len() - 1;
|
|
||||||
}
|
|
||||||
} else if k.kind == key::KeyKind::Enter {
|
|
||||||
(this.on_click)(this)
|
|
||||||
}
|
|
||||||
UpdateResponse::None
|
|
||||||
}
|
|
||||||
|
|
||||||
component.render = render;
|
|
||||||
component.update = update;
|
|
||||||
component
|
|
||||||
}
|
|
||||||
+22
-14
@@ -1,10 +1,11 @@
|
|||||||
use crate::Component;
|
use crate::Element;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
io::{stdout, Write},
|
io::{stdout, Write},
|
||||||
};
|
};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
pub static ref ANSI: Regex = Regex::new(r"(\x1b\[([0-9;]*)[a-zA-Z])+").unwrap();
|
pub static ref ANSI: Regex = Regex::new(r"(\x1b\[([0-9;]*)[a-zA-Z])+").unwrap();
|
||||||
}
|
}
|
||||||
@@ -81,11 +82,12 @@ fn merge_line(frame_: &str, line_: &str, x: usize) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render to a frame
|
/// Render to a frame
|
||||||
pub fn render_to_frame(frame: &mut Vec<String>, component: &mut Component) {
|
pub fn render_to_frame(frame: &mut Vec<String>, element: &mut Box<dyn Element>) {
|
||||||
for (i, line) in (component.render)(component).split('\n').enumerate() {
|
let data = element.get_data();
|
||||||
if (component.y + i) < frame.len() {
|
for (i, line) in element.render().split('\n').enumerate() {
|
||||||
let frame_line = frame.get_mut(component.y + i).unwrap();
|
if (data.y + i) < frame.len() {
|
||||||
*frame_line = merge_line(&frame_line, line, component.x);
|
let frame_line = frame.get_mut(data.y + i).unwrap();
|
||||||
|
*frame_line = merge_line(&frame_line, line, data.x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,22 +119,28 @@ pub enum Direction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn closest_component(
|
pub fn closest_component(
|
||||||
components: &[Component],
|
components: &[Box<dyn Element>],
|
||||||
current_index: usize,
|
current_index: usize,
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
let current = &components[current_index];
|
let current = &components[current_index].get_data();
|
||||||
|
|
||||||
components
|
components
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate() // Keep track of indices
|
.enumerate() // Keep track of indices
|
||||||
.filter(|(_, comp)| match direction {
|
.filter(|(_, comp_)| {
|
||||||
Direction::Left => comp.x < current.x && comp.y == current.y, // Left
|
let comp = comp_.get_data();
|
||||||
Direction::Right => comp.x > current.x && comp.y == current.y, // Right
|
match direction {
|
||||||
Direction::Up => comp.y < current.y && comp.x == current.x, // Up
|
Direction::Left => comp.x < current.x && comp.y == current.y, // Left
|
||||||
Direction::Down => comp.y > current.y && comp.x == current.x, // Down
|
Direction::Right => comp.x > current.x && comp.y == current.y, // Right
|
||||||
|
Direction::Up => comp.y < current.y && comp.x == current.x, // Up
|
||||||
|
Direction::Down => comp.y > current.y && comp.x == current.x, // Down
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.min_by_key(|(_, comp)| current.x.abs_diff(comp.x) + current.y.abs_diff(comp.y)) // Find the closest component
|
.min_by_key(|(_, comp_)| {
|
||||||
|
let comp = comp_.get_data();
|
||||||
|
current.x.abs_diff(comp.x) + current.y.abs_diff(comp.y)
|
||||||
|
}) // Find the closest component
|
||||||
.map(|(index, _)| index) // Return the index of 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
|
.unwrap_or(current_index) // If no component is found, return the current index
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user