From 92ab35c4efa98046eb421febed022426f98d5cce Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 31 Oct 2024 12:36:59 +0100 Subject: [PATCH] added components --- src/main.rs | 14 ++- src/ui/elements.rs | 273 ++++++++++++++++++++++++++++++++++++++++++++- src/ui/mod.rs | 70 ++++++++++++ 3 files changed, 354 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index d086b0a..3c145cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,18 @@ fn main() { app_screen.run(); } -fn app() -> Box { +fn app() -> Box { oml!( - text("Hello, World!";) + tab { + text("Hello!"; style = Style { + hover_fg: Color::Red, + ..Default::default() + }) + button("Hello 2!"; y = 1, style = Style { + hover_fg: Color::Green, + ..Default::default() + }) + menu(items = vec![String::from("Hello, World!"), String::from("Item 2!")]) + } ) } diff --git a/src/ui/elements.rs b/src/ui/elements.rs index d385928..7c04941 100644 --- a/src/ui/elements.rs +++ b/src/ui/elements.rs @@ -1,4 +1,37 @@ -use crate::{element, ui::Style, Element, ElementData}; +use std::collections::HashMap; + +use crate::{ + create_frame, element, + key::{Key, KeyKind}, + ui::Style, + Element, ElementData, UpdateResponse, +}; + +macro_rules! update_child_data { + ($elem:ident, $child:ident, $i:expr) => { + let mut child_data = $child.get_data(); + if child_data.width == 0 { + child_data.width = $elem.width; + } + if child_data.height == 0 { + child_data.height = $elem.height; + } + child_data.style.is_active = $elem.style.is_active && $i == $elem.child; + $child.set_data(child_data); + }; + + ($elem:expr, $child:ident) => { + let mut child_data = $child.get_data(); + if child_data.width == 0 { + child_data.width = $elem.width; + } + if child_data.height == 0 { + child_data.height = $elem.height; + } + child_data.style.is_active = true; + $child.set_data(child_data); + }; +} element! { Text {} @@ -7,3 +40,241 @@ element! { self.style.write(&self.text) } } + +element! { + Div { + pub binds: HashMap + } + defaults { + 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(&mut self, k: Key) -> UpdateResponse { + if let Some(v) = self.binds.get(&k.kind) { + match v.as_str() { + "up" => { + self.child = + crate::utils::closest_component(&self.children, self.child, crate::utils::Direction::Up); + } + + "down" => { + self.child = crate::utils::closest_component( + &self.children, + self.child, + crate::utils::Direction::Down, + ); + } + + "left" => { + self.child = crate::utils::closest_component( + &self.children, + self.child, + crate::utils::Direction::Left, + ); + } + + "right" => { + self.child = crate::utils::closest_component( + &self.children, + self.child, + crate::utils::Direction::Right, + ); + } + + _ => {} + } + } else { + if let Some(child) = self.get_child() { + return child.update(k); + } + } + + UpdateResponse::None + } + + fn render(&mut self) -> String { + let mut frame: Vec = create_frame!(self.width, self.height); + for (i, child) in &mut self.children.iter_mut().enumerate() { + update_child_data!(self, child, i); + crate::utils::render_to_frame(&mut frame, child); + } + frame.join("\n") + } + + fn tick(&mut self, i: usize) { + if let Some(child) = self.get_child() { + return child.tick(i); + } + } + +} + +element! { + Button { + pub binds: HashMap, + pub toggle: bool, + pub on_click: fn(&mut Button), + clicked: bool, + } + defaults { + binds: HashMap::from([(KeyKind::Enter, String::from("click"))]), + toggle: false, + clicked: false, + on_click: |_|{}, + } + fn update(&mut self, k: Key) -> UpdateResponse { + if let Some(v) = self.binds.get(&k.kind) { + if v == "click" { + if self.toggle { + self.clicked = !self.clicked; + (self.on_click)(self); + } else { + return UpdateResponse::Tick(vec![1, 100]); + } + } + } + UpdateResponse::None + } + + fn tick(&mut self, i: usize) { + if i == 0 { + self.clicked = true; + (self.on_click)(self); + } else if i == 1 { + self.clicked = false; + (self.on_click)(self); + } + } + + fn render(&mut self) -> String { + if self.clicked { + return self.style.write_clicked(&self.text); + } + self.style.write(&self.text) + } + +} + +element! { + Tab { + pub binds: HashMap, + } + defaults { + binds: HashMap::from([ + (KeyKind::Tab, String::from("next")), + (KeyKind::ShiftTab, String::from("previous")), + ]), + } + fn update(&mut self, k: Key) -> UpdateResponse { + if let Some(v) = self.binds.get(&k.kind) { + match v.as_str() { + "next" => { + if self.child + 1 < self.children.len() { + self.child += 1; + } else { + self.child = 0; + } + } + "previous" => { + if self.child == 0 { + self.child = self.children.len() - 1; + } else { + self.child -= 1; + } + } + _ => {} + } + } else { + if let Some(child) = self.get_child() { + return child.update(k); + } else { + } + } + UpdateResponse::None + } + + fn render(&mut self) -> String { + let mut frame: Vec = create_frame!(self.width, self.height - 1); + let d = self.get_data(); + if let Some(child) = self.get_child() { + update_child_data!(d, child); + crate::utils::render_to_frame(&mut frame, child); + } + let mut v: String = " ".repeat((self.width / 2) - self.children.len()); + for (i, _) in (&self.children).into_iter().enumerate() { + if i == self.child { + v += self.style.write_selected("*").as_str() + } else { + v += self.style.write("*").as_str() + } + } + format!("{}\n{}", v, frame.join("\n")) + } + + fn tick(&mut self, i: usize) { + if let Some(child) = self.get_child() { + return child.tick(i); + } + } +} + +/// The response of a on_click function in a element +pub enum ClickResponse { + None, + Exit, +} + +element! { + Menu { + pub items: Vec, + pub selected: usize, + pub on_click: fn(&mut Menu, _: Key) -> ClickResponse, + } + defaults { + items: Vec::new(), + selected: 0, + on_click: |_, _|ClickResponse::None + } + fn render(&mut self) -> String { + let mut res: Vec = Vec::new(); + + for (i, item) in self.items.iter_mut().enumerate() { + if i == self.selected { + res.push(format!( + "{}{}", + self.style.write_cursor("> "), + self.style.write_selected(&item) + )); + } else { + res.push(format!(" {}", item)); + } + } + res.join(&self.style.write("\n")) + } + + fn update(&mut self, k: Key) -> UpdateResponse { + if k.kind == KeyKind::Down { + if self.items.len() > self.selected + 1 { + self.selected += 1; + } else { + self.selected = 0; + } + } else if k.kind == KeyKind::Up { + if self.child > 0 { + self.selected -= 1; + } else { + self.selected = self.items.len() - 1; + } + } else { + return match (self.on_click)(self, k) { + ClickResponse::Exit => UpdateResponse::Exit, + ClickResponse::None => UpdateResponse::None, + } + } + UpdateResponse::None + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index a4ddfc3..e952bbd 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -3,6 +3,76 @@ pub use styles::*; pub mod elements; pub use elements::*; +/// Creates and returns a `Box` containing a new `Text` element. +/// +/// This function constructs a `Text` element and wraps it in a `Box` to +/// manage heap allocation and ownership. +/// +/// # Returns +/// +/// A boxed `Text` element, initialized and ready to be styled or populated +/// with text content. pub fn text() -> Box { Box::new(Text::new()) } + +/// Creates and returns a `Box` containing a new `Div` element. +/// +/// This function constructs a `Div` element and wraps it in a `Box` to +/// manage heap allocation and ownership. A `Div` can be used as a container +/// for other elements or to organize layout. +/// +/// # Returns +/// +/// A boxed `Div` element, initialized and ready for layout management. +pub fn div() -> Box
{ + Box::new(Div::new()) +} + +/// Creates and returns a `Box` containing a new `Button` element. +/// +/// This function constructs a `Button` element and wraps it in a `Box` to +/// manage heap allocation and ownership. Buttons can be styled and +/// configured for interactive functionality. +/// +/// # Returns +/// +/// A boxed `Button` element, initialized and ready for configuration. +pub fn button() -> Box