working buttons and div
This commit is contained in:
+104
-24
@@ -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
@@ -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
@@ -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
@@ -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!";))
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user