diff --git a/src/components.rs b/src/components.rs index e942d53..bb2c09a 100644 --- a/src/components.rs +++ b/src/components.rs @@ -1,44 +1,124 @@ +use std::collections::HashMap; + use crate::{ - create_frame, key::KeyKind, utils::render_to_frame, Component, Params, UpdateContext, - UpdateRequest, + create_frame, + key::{Key, KeyKind}, + utils::{closest_component, render_to_frame}, + Component, Params, UpdateResponse, }; pub fn div(params: Params) -> Component { - let mut c = Component::new(params); - c.render = |s: &mut Component| { + let mut component = Component::new(params); + + 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) -> UpdateResponse { + if let Some(v) = this.binds.get(&k.kind) { + match v.as_str() { + "up" => { + this.active_child = closest_component( + &this.children, + this.active_child, + crate::utils::Direction::Up, + ); + } + + "down" => { + this.active_child = closest_component( + &this.children, + this.active_child, + crate::utils::Direction::Down, + ); + } + + "left" => { + this.active_child = closest_component( + &this.children, + this.active_child, + crate::utils::Direction::Left, + ); + } + + "right" => { + this.active_child = closest_component( + &this.children, + this.active_child, + crate::utils::Direction::Right, + ); + } + + _ => { + if let Some(child) = this.get_active_child() { + return (child.update)(child, k); + } + } + } + } else { + if let Some(child) = this.get_active_child() { + return (child.update)(child, k); + } + } + + UpdateResponse::None + } + + fn render(s: &mut Component) -> String { let mut frame: Vec = create_frame!(s.width, s.height); for c in &mut s.children { render_to_frame(&mut frame, c); } frame.join("\n") - }; - c + } + + component.update = update; + component.render = render; + component } pub fn text(params: Params) -> Component { - let mut c = Component::new(params); - c.render = |s: &mut Component| s.expr.clone(); - c + let mut component = Component::new(params); + + fn update(_: &mut Component, _: Key) -> UpdateResponse { + UpdateResponse::None + } + + fn render(s: &mut Component) -> String { + s.expr.clone() + } + + component.update = update; + component.render = render; + component } pub fn button(params: Params) -> Component { - let mut c = Component::new(params); - c.update = |s: &mut Component, ctx: &mut UpdateContext| { - s.expr = "nah".to_string(); - match ctx.request.clone() { - UpdateRequest::Key(k) => { - if k.kind == KeyKind::Enter { - s.clicked = !s.clicked; - let mut cctx = UpdateContext { - request: UpdateRequest::Key(k), - response: crate::UpdateResponse::None, - }; - (s.on_click)(&mut cctx); - ctx.response = cctx.response; - } + let mut component = Component::new(params); + + fn update(this: &mut Component, k: Key) -> UpdateResponse { + if k.kind == KeyKind::Enter { + if this.toggle { + this.clicked = !this.clicked; + (this.on_click)(this); + } else { + this.clicked = true; + (this.on_click)(this); + this.clicked = false; + (this.on_click)(this); } } - }; - c.render = |s: &mut Component| s.expr.clone(); - c + UpdateResponse::None + } + + fn render(s: &mut Component) -> String { + s.expr.clone() + } + + component.update = update; + component.render = render; + component } diff --git a/src/key.rs b/src/key.rs index 29ba932..0931e70 100644 --- a/src/key.rs +++ b/src/key.rs @@ -1,6 +1,6 @@ use std::io::{self, Read}; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, Hash, Eq, PartialEq)] pub enum KeyKind { Enter, Escape, @@ -11,18 +11,14 @@ pub enum KeyKind { Char(String), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, Eq, PartialEq)] pub struct Key { pub kind: KeyKind, pub raw: String, - pub ctrl: bool, - pub shift: bool, } impl Key { pub fn new(k: String) -> Key { - let mut ctrl = false; - let mut shift = false; let kind = match k.as_str() { "\r" => KeyKind::Enter, "\x1b" => KeyKind::Escape, @@ -31,43 +27,9 @@ impl Key { "\x1b[C" => KeyKind::Right, "\x1b[D" => KeyKind::Left, - // Ctrl keys - "\u{17}" => { - ctrl = true; - KeyKind::Char("w".to_string()) - } - - "\u{13}" => { - ctrl = true; - KeyKind::Char("s".to_string()) - } - - "\u{1}" => { - ctrl = true; - KeyKind::Char("a".to_string()) - } - - "\u{4}" => { - ctrl = true; - KeyKind::Char("d".to_string()) - } - - "\u{11}" => { - ctrl = true; - KeyKind::Char("q".to_string()) - } - - _ => { - shift = k.to_uppercase() == k; - KeyKind::Char(k.clone()) - } + _ => KeyKind::Char(k.clone()), }; - Key { - raw: k, - kind, - ctrl, - shift, - } + Key { raw: k, kind } } } diff --git a/src/lib.rs b/src/lib.rs index 1f8f83d..2720592 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,27 +4,13 @@ pub mod macros; pub mod utils; use crossterm; -use key::Key; use std::collections::HashMap; -use utils::{clear, show_cursor, Value}; #[derive(Debug, Clone, PartialEq)] pub enum UpdateResponse { Exit, Done, None, - SetComponent(Component), -} - -#[derive(Debug, Clone)] -pub enum UpdateRequest { - Key(Key), -} - -#[derive(Debug, Clone)] -pub struct UpdateContext { - pub request: UpdateRequest, - pub response: UpdateResponse, } #[derive(Debug)] @@ -36,17 +22,18 @@ pub struct Params { #[derive(Debug, Clone, PartialEq)] pub struct Component { pub render: fn(&mut Component) -> String, - pub update: fn(&mut Component, &mut UpdateContext), - pub on_click: fn(&mut UpdateContext), + pub update: fn(&mut Component, key::Key) -> UpdateResponse, + pub on_click: fn(&mut Component), pub x: usize, pub y: usize, pub width: usize, pub height: usize, pub expr: String, pub children: Vec, + pub active_child: usize, pub clicked: bool, pub toggle: bool, - pub data: HashMap, + pub binds: HashMap, } impl Component { @@ -54,7 +41,7 @@ impl Component { fn new(params: Params) -> Component { Component { render: |_| String::new(), - update: |_, _| {}, + update: |_, _| UpdateResponse::None, on_click: |_| {}, x: 0, y: 0, @@ -62,11 +49,23 @@ impl Component { height: 0, expr: params.expr, children: params.children, + active_child: 0, clicked: false, toggle: false, - data: HashMap::new(), + binds: HashMap::new(), } } + + pub fn get_active_child(&mut self) -> Option<&mut Component> { + if self.active_child < self.children.len() { + return self.children.get_mut(self.active_child); + } + None + } + + fn manhattan_distance(&self, other: &Component) -> usize { + self.x.abs_diff(other.x) + self.y.abs_diff(other.y) + } } #[derive(Debug, Clone)] @@ -95,7 +94,6 @@ impl App { if self.component.height == 0 { self.component.height = height as usize; } - self.render(); } /// Render to the screen @@ -115,19 +113,14 @@ impl App { crossterm::terminal::enable_raw_mode().unwrap(); loop { self.render(); - let mut ctx = UpdateContext { - response: UpdateResponse::None, - request: UpdateRequest::Key(key::read_key()), - }; - (self.component.update)(&mut self.component, &mut ctx); - match ctx.response { + self.component.expr = "Hello".to_string(); + match (self.component.update)(&mut self.component, key::read_key()) { UpdateResponse::Exit => { crossterm::terminal::disable_raw_mode().unwrap(); - clear(); - show_cursor(); + utils::clear(); + utils::show_cursor(); return; } - UpdateResponse::SetComponent(c) => self.component = c, _ => {} } } diff --git a/src/main.rs b/src/main.rs index 469f30b..cb0f062 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use osui::{ components::{button, div}, - oml, App, Component, Params, UpdateResponse, + oml, App, Component, Params, }; fn main() { @@ -9,14 +9,19 @@ fn main() { app.run(); } +fn onclick(c: &mut Component) { + if c.clicked { + c.expr = "Clicked!".to_string(); + } else { + c.expr = "click me".to_string() + } +} + fn app_elem(_: Params) -> Component { oml!( div { - button("click me"; on_click = |c| { - c.response = UpdateResponse::SetComponent( - oml!(button("Clicked!";)) - ); - }) + button("click me"; on_click = onclick) + button("click me"; y = 2, on_click = onclick) } ) } diff --git a/src/utils.rs b/src/utils.rs index 4f625af..345f897 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -100,3 +100,31 @@ pub fn show_cursor() { pub fn flush() { stdout().flush().unwrap(); } + +pub enum Direction { + Left, + Right, + Up, + Down, +} + +pub fn closest_component( + components: &[Component], + current_index: usize, + direction: Direction, +) -> usize { + let current = &components[current_index]; + + components + .iter() + .enumerate() // Keep track of indices + .filter(|(_, comp)| 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.manhattan_distance(comp)) // 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 +}