working buttons and div

This commit is contained in:
2024-10-18 18:04:25 +02:00
parent d093debfd5
commit 83b9a66515
5 changed files with 171 additions and 103 deletions
+104 -24
View File
@@ -1,44 +1,124 @@
use std::collections::HashMap;
use crate::{ use crate::{
create_frame, key::KeyKind, utils::render_to_frame, Component, Params, UpdateContext, create_frame,
UpdateRequest, key::{Key, KeyKind},
utils::{closest_component, render_to_frame},
Component, Params, UpdateResponse,
}; };
pub fn div(params: Params) -> Component { pub fn div(params: Params) -> Component {
let mut c = Component::new(params); let mut component = Component::new(params);
c.render = |s: &mut Component| {
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<String> = create_frame!(s.width, s.height); let mut frame: Vec<String> = create_frame!(s.width, s.height);
for c in &mut s.children { for c in &mut s.children {
render_to_frame(&mut frame, c); render_to_frame(&mut frame, c);
} }
frame.join("\n") frame.join("\n")
}; }
c
component.update = update;
component.render = render;
component
} }
pub fn text(params: Params) -> Component { pub fn text(params: Params) -> Component {
let mut c = Component::new(params); let mut component = Component::new(params);
c.render = |s: &mut Component| s.expr.clone();
c 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 { pub fn button(params: Params) -> Component {
let mut c = Component::new(params); let mut component = Component::new(params);
c.update = |s: &mut Component, ctx: &mut UpdateContext| {
s.expr = "nah".to_string(); fn update(this: &mut Component, k: Key) -> UpdateResponse {
match ctx.request.clone() {
UpdateRequest::Key(k) => {
if k.kind == KeyKind::Enter { if k.kind == KeyKind::Enter {
s.clicked = !s.clicked; if this.toggle {
let mut cctx = UpdateContext { this.clicked = !this.clicked;
request: UpdateRequest::Key(k), (this.on_click)(this);
response: crate::UpdateResponse::None, } else {
}; this.clicked = true;
(s.on_click)(&mut cctx); (this.on_click)(this);
ctx.response = cctx.response; this.clicked = false;
(this.on_click)(this);
} }
} }
UpdateResponse::None
} }
};
c.render = |s: &mut Component| s.expr.clone(); fn render(s: &mut Component) -> String {
c s.expr.clone()
}
component.update = update;
component.render = render;
component
} }
+4 -42
View File
@@ -1,6 +1,6 @@
use std::io::{self, Read}; use std::io::{self, Read};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub enum KeyKind { pub enum KeyKind {
Enter, Enter,
Escape, Escape,
@@ -11,18 +11,14 @@ pub enum KeyKind {
Char(String), Char(String),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct Key { pub struct Key {
pub kind: KeyKind, pub kind: KeyKind,
pub raw: String, pub raw: String,
pub ctrl: bool,
pub shift: bool,
} }
impl Key { impl Key {
pub fn new(k: String) -> Key { pub fn new(k: String) -> Key {
let mut ctrl = false;
let mut shift = false;
let kind = match k.as_str() { let kind = match k.as_str() {
"\r" => KeyKind::Enter, "\r" => KeyKind::Enter,
"\x1b" => KeyKind::Escape, "\x1b" => KeyKind::Escape,
@@ -31,43 +27,9 @@ impl Key {
"\x1b[C" => KeyKind::Right, "\x1b[C" => KeyKind::Right,
"\x1b[D" => KeyKind::Left, "\x1b[D" => KeyKind::Left,
// Ctrl keys _ => KeyKind::Char(k.clone()),
"\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())
}
}; };
Key { Key { raw: k, kind }
raw: k,
kind,
ctrl,
shift,
}
} }
} }
+22 -29
View File
@@ -4,27 +4,13 @@ pub mod macros;
pub mod utils; pub mod utils;
use crossterm; use crossterm;
use key::Key;
use std::collections::HashMap; use std::collections::HashMap;
use utils::{clear, show_cursor, Value};
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum UpdateResponse { pub enum UpdateResponse {
Exit, Exit,
Done, Done,
None, 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)] #[derive(Debug)]
@@ -36,17 +22,18 @@ pub struct Params {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Component { pub struct Component {
pub render: fn(&mut Component) -> String, pub render: fn(&mut Component) -> String,
pub update: fn(&mut Component, &mut UpdateContext), pub update: fn(&mut Component, key::Key) -> UpdateResponse,
pub on_click: fn(&mut UpdateContext), 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 expr: String,
pub children: Vec<Component>, pub children: Vec<Component>,
pub active_child: usize,
pub clicked: bool, pub clicked: bool,
pub toggle: bool, pub toggle: bool,
pub data: HashMap<String, Value>, pub binds: HashMap<key::KeyKind, String>,
} }
impl Component { impl Component {
@@ -54,7 +41,7 @@ impl Component {
fn new(params: Params) -> Component { fn new(params: Params) -> Component {
Component { Component {
render: |_| String::new(), render: |_| String::new(),
update: |_, _| {}, update: |_, _| UpdateResponse::None,
on_click: |_| {}, on_click: |_| {},
x: 0, x: 0,
y: 0, y: 0,
@@ -62,11 +49,23 @@ impl Component {
height: 0, height: 0,
expr: params.expr, expr: params.expr,
children: params.children, children: params.children,
active_child: 0,
clicked: false, clicked: false,
toggle: 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)] #[derive(Debug, Clone)]
@@ -95,7 +94,6 @@ impl App {
if self.component.height == 0 { if self.component.height == 0 {
self.component.height = height as usize; self.component.height = height as usize;
} }
self.render();
} }
/// Render to the screen /// Render to the screen
@@ -115,19 +113,14 @@ impl App {
crossterm::terminal::enable_raw_mode().unwrap(); crossterm::terminal::enable_raw_mode().unwrap();
loop { loop {
self.render(); self.render();
let mut ctx = UpdateContext { self.component.expr = "Hello".to_string();
response: UpdateResponse::None, match (self.component.update)(&mut self.component, key::read_key()) {
request: UpdateRequest::Key(key::read_key()),
};
(self.component.update)(&mut self.component, &mut ctx);
match ctx.response {
UpdateResponse::Exit => { UpdateResponse::Exit => {
crossterm::terminal::disable_raw_mode().unwrap(); crossterm::terminal::disable_raw_mode().unwrap();
clear(); utils::clear();
show_cursor(); utils::show_cursor();
return; return;
} }
UpdateResponse::SetComponent(c) => self.component = c,
_ => {} _ => {}
} }
} }
+11 -6
View File
@@ -1,6 +1,6 @@
use osui::{ use osui::{
components::{button, div}, components::{button, div},
oml, App, Component, Params, UpdateResponse, oml, App, Component, Params,
}; };
fn main() { fn main() {
@@ -9,14 +9,19 @@ fn main() {
app.run(); 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 { fn app_elem(_: Params) -> Component {
oml!( oml!(
div { div {
button("click me"; on_click = |c| { button("click me"; on_click = onclick)
c.response = UpdateResponse::SetComponent( button("click me"; y = 2, on_click = onclick)
oml!(button("Clicked!";))
);
})
} }
) )
} }
+28
View File
@@ -100,3 +100,31 @@ pub fn show_cursor() {
pub fn flush() { pub fn flush() {
stdout().flush().unwrap(); 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
}