Large structure update
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ version = "0.0.3"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
description = "A TUI library"
|
||||
repository = "https://github.com/orus-dev/osui-rs"
|
||||
repository = "https://github.com/orus-dev/osui"
|
||||
|
||||
[lib]
|
||||
name = "osui"
|
||||
|
||||
+38
-63
@@ -1,89 +1,72 @@
|
||||
use crossterm::ExecutableCommand;
|
||||
|
||||
pub mod key;
|
||||
pub mod macros;
|
||||
pub mod ui;
|
||||
pub mod utils;
|
||||
|
||||
use crossterm::{self, ExecutableCommand};
|
||||
use std::collections::HashMap;
|
||||
pub trait Element: std::fmt::Debug {
|
||||
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)]
|
||||
pub struct Component {
|
||||
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),
|
||||
#[derive(Debug)]
|
||||
pub struct ElementData {
|
||||
pub x: usize,
|
||||
pub y: usize,
|
||||
pub width: usize,
|
||||
pub height: usize,
|
||||
pub expr: String,
|
||||
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)
|
||||
}
|
||||
pub style: ui::styles::Style,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum UpdateResponse {
|
||||
Exit,
|
||||
Done,
|
||||
None,
|
||||
Tick(Vec<u32>),
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
component: Component,
|
||||
element: Box<dyn Element>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Creates a new screen to render components
|
||||
pub fn new() -> App {
|
||||
App {
|
||||
component: Component::new(),
|
||||
element: ui::text(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
self.component.style.is_active = true;
|
||||
self.component = component;
|
||||
if self.component.width == 0 {
|
||||
self.component.width = width as usize;
|
||||
self.element = element;
|
||||
let mut data = self.element.get_data();
|
||||
data.style.is_active = true;
|
||||
if data.width == 0 {
|
||||
data.width = width as usize;
|
||||
}
|
||||
if self.component.height == 0 {
|
||||
self.component.height = height as usize;
|
||||
if data.height == 0 {
|
||||
data.height = height as usize;
|
||||
}
|
||||
self.component.style.is_active = true;
|
||||
self.element.set_data(data);
|
||||
}
|
||||
|
||||
/// Render to the screen
|
||||
pub fn render(&mut self) {
|
||||
let (width, height) = crossterm::terminal::size().unwrap();
|
||||
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();
|
||||
print!("{}", frame.join(""));
|
||||
utils::flush();
|
||||
@@ -100,7 +83,7 @@ impl App {
|
||||
crossterm::terminal::enable_raw_mode().unwrap();
|
||||
loop {
|
||||
self.render();
|
||||
match (self.component.update)(&mut self.component, key::read_key()) {
|
||||
match self.element.update(key::read_key()) {
|
||||
UpdateResponse::Exit => {
|
||||
crossterm::terminal::disable_raw_mode().unwrap();
|
||||
utils::show_cursor();
|
||||
@@ -110,7 +93,7 @@ impl App {
|
||||
UpdateResponse::Tick(ticks) => {
|
||||
for (i, n) in ticks.into_iter().enumerate() {
|
||||
std::thread::sleep(std::time::Duration::from_millis(n as u64));
|
||||
(self.component.tick)(&mut self.component, i);
|
||||
self.element.tick(i);
|
||||
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)+) => {{
|
||||
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
|
||||
}};
|
||||
@@ -19,30 +19,30 @@ macro_rules! __oml {
|
||||
}};
|
||||
|
||||
($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
|
||||
}};
|
||||
|
||||
// Expression, With components (EC)
|
||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*) {$($inner:tt)*}) => {{
|
||||
vec![oml!($tag ($expr; $($k = $v),*) {$($inner)*})]
|
||||
($tag:ident ($text:expr; $($k:ident = $v:expr),*) {$($inner:tt)*}) => {{
|
||||
vec![oml!($tag ($text; $($k = $v),*) {$($inner)*})]
|
||||
}};
|
||||
|
||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{
|
||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
||||
comps.insert(0, oml!($tag ($expr; $($k = $v),*) {$($inner)*}));
|
||||
($tag:ident ($text:expr; $($k:ident = $v:expr),*) {$($inner:tt)*} $($rest:tt)+) => {{
|
||||
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||
comps.insert(0, oml!($tag ($text; $($k = $v),*) {$($inner)*}));
|
||||
comps
|
||||
}};
|
||||
|
||||
// Expression (E)
|
||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*)) => {{
|
||||
vec![oml!($tag ($expr; $($k = $v),*))]
|
||||
($tag:ident ($text:expr; $($k:ident = $v:expr),*)) => {{
|
||||
vec![oml!($tag ($text; $($k = $v),*))]
|
||||
}};
|
||||
|
||||
($tag:ident ($expr:expr; $($k:ident = $v:expr),*) $($rest:tt)+) => {{
|
||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
||||
comps.insert(0, oml!($tag ($expr; $($k = $v),*)));
|
||||
($tag:ident ($text:expr; $($k:ident = $v:expr),*) $($rest:tt)+) => {{
|
||||
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||
comps.insert(0, oml!($tag ($text; $($k = $v),*)));
|
||||
comps
|
||||
}};
|
||||
|
||||
@@ -52,19 +52,19 @@ macro_rules! __oml {
|
||||
}};
|
||||
|
||||
($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
|
||||
}};
|
||||
|
||||
// Pre-Defined (D)
|
||||
($expr:expr;) => {{
|
||||
vec![oml!($expr;)]
|
||||
($text:expr;) => {{
|
||||
vec![oml!($text;)]
|
||||
}};
|
||||
|
||||
($expr:expr; $($rest:tt)+) => {{
|
||||
let mut comps: Vec<osui::Component> = osui::__oml!($($rest)+);
|
||||
comps.insert(0, oml!($expr));
|
||||
($text:expr; $($rest:tt)+) => {{
|
||||
let mut comps: Vec<Box<dyn osui::Element>> = osui::__oml!($($rest)+);
|
||||
comps.insert(0, oml!($text));
|
||||
comps
|
||||
}};
|
||||
}
|
||||
@@ -82,10 +82,10 @@ macro_rules! oml {
|
||||
}};
|
||||
|
||||
// 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();
|
||||
c.children = osui::__oml!($($inner)*);
|
||||
c.params = String::from($expr);
|
||||
c.text = String::from($text);
|
||||
$(
|
||||
c.$k = $v;
|
||||
)*
|
||||
@@ -93,9 +93,9 @@ macro_rules! oml {
|
||||
}};
|
||||
|
||||
// Expression (E)
|
||||
( $tag:ident ($expr:expr; $($k:ident = $v:expr),*) ) => {{
|
||||
( $tag:ident ($text:expr; $($k:ident = $v:expr),*) ) => {{
|
||||
let mut c = $tag();
|
||||
c.expr = String::from($expr);
|
||||
c.text = String::from($text);
|
||||
$(
|
||||
c.$k = $v;
|
||||
)*
|
||||
@@ -120,8 +120,8 @@ macro_rules! oml {
|
||||
}};
|
||||
|
||||
// Pre-Defined (D)
|
||||
($expr:expr;) => {{
|
||||
$expr
|
||||
($text:expr;) => {{
|
||||
$text
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -134,3 +134,73 @@ macro_rules! create_frame {
|
||||
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 )*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+5
-21
@@ -1,29 +1,13 @@
|
||||
use osui::{oml, ui::*, App, Component};
|
||||
use osui::{oml, ui::*, App};
|
||||
|
||||
fn main() {
|
||||
let mut app = App::new();
|
||||
app.set_component(app_elem());
|
||||
app.run();
|
||||
let mut app_screen = App::new();
|
||||
app_screen.set_component(app());
|
||||
app_screen.run();
|
||||
}
|
||||
|
||||
fn onclick(c: &mut Component) {
|
||||
if c.clicked {
|
||||
c.expr = "Clicked!".to_string();
|
||||
} else {
|
||||
c.expr = "click me".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn app_elem() -> Component {
|
||||
fn app() -> Box<Text> {
|
||||
oml!(
|
||||
tab {
|
||||
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
|
||||
}
|
||||
+18
-10
@@ -1,10 +1,11 @@
|
||||
use crate::Component;
|
||||
use crate::Element;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{stdout, Write},
|
||||
};
|
||||
|
||||
lazy_static! {
|
||||
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
|
||||
pub fn render_to_frame(frame: &mut Vec<String>, component: &mut Component) {
|
||||
for (i, line) in (component.render)(component).split('\n').enumerate() {
|
||||
if (component.y + i) < frame.len() {
|
||||
let frame_line = frame.get_mut(component.y + i).unwrap();
|
||||
*frame_line = merge_line(&frame_line, line, component.x);
|
||||
pub fn render_to_frame(frame: &mut Vec<String>, element: &mut Box<dyn Element>) {
|
||||
let data = element.get_data();
|
||||
for (i, line) in element.render().split('\n').enumerate() {
|
||||
if (data.y + i) < frame.len() {
|
||||
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(
|
||||
components: &[Component],
|
||||
components: &[Box<dyn Element>],
|
||||
current_index: usize,
|
||||
direction: Direction,
|
||||
) -> usize {
|
||||
let current = &components[current_index];
|
||||
let current = &components[current_index].get_data();
|
||||
|
||||
components
|
||||
.iter()
|
||||
.enumerate() // Keep track of indices
|
||||
.filter(|(_, comp)| match direction {
|
||||
.filter(|(_, comp_)| {
|
||||
let comp = comp_.get_data();
|
||||
match direction {
|
||||
Direction::Left => comp.x < current.x && comp.y == current.y, // Left
|
||||
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
|
||||
.unwrap_or(current_index) // If no component is found, return the current index
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user