Large structure update

This commit is contained in:
2024-10-30 14:14:00 +01:00
parent 943bcf874a
commit 87ae71072e
6 changed files with 161 additions and 629 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}
) )
} }
-505
View File
@@ -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
View File
@@ -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
} }