From 7f5a8d9b0a4f0c910bdcff5bfff88647b9c98461 Mon Sep 17 00:00:00 2001 From: Leo dev Date: Sat, 2 Nov 2024 23:49:48 +0100 Subject: [PATCH] better tick system --- src/lib.rs | 80 +++++++++---- src/macros.rs | 17 +++ src/main.rs | 48 +------- src/ui/elements.rs | 280 +++------------------------------------------ src/ui/mod.rs | 52 +-------- src/utils.rs | 4 +- 6 files changed, 96 insertions(+), 385 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 53efca4..33a3eee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,13 +9,11 @@ pub trait Element: std::fmt::Debug { fn get_child(&mut self) -> Option<&mut Box>; fn get_data(&self) -> ElementData; fn set_data(&mut self, _: ElementData); - fn render(&mut self) -> String { + fn clear_ticks(&mut self); + fn render(&mut self, _tick: usize) -> String { String::new() } - fn update(&mut self, _: crate::key::Key) -> UpdateResponse { - UpdateResponse::None - } - fn tick(&mut self, _: usize) {} + fn update(&mut self, _ctx: &mut UpdateContext) {} } #[derive(Debug)] @@ -32,7 +30,12 @@ pub enum UpdateResponse { Exit, Done, None, - Tick(Vec), +} + +pub struct UpdateContext { + pub key: key::Key, + pub tick: usize, + pub response: UpdateResponse, } pub struct App { @@ -47,6 +50,13 @@ impl App { } } + /// Creates a new screen to render components with a pre-existing component + pub fn from(elem: Box) -> App { + let mut app = App::new(); + app.set_component(elem); + app + } + /// Sets a component pub fn set_component(&mut self, element: Box) { let (width, height) = crossterm::terminal::size().unwrap(); @@ -63,7 +73,7 @@ impl App { } /// Render to the screen - pub fn render(&mut self) { + fn render(&mut self, tick: usize) { let (width, height) = crossterm::terminal::size().unwrap(); let mut data = self.element.get_data(); if data.width == 0 { @@ -74,7 +84,7 @@ impl App { } self.element.set_data(data); let mut frame: Vec = create_frame!(width as usize, height as usize); - utils::render_to_frame(&mut frame, &mut self.element); + utils::render_to_frame(tick, &mut frame, &mut self.element); utils::clear(); print!("{}", frame.join("")); utils::flush(); @@ -88,8 +98,23 @@ impl App { self.element.set_data(data); } + fn update(&mut self, ctx: &mut UpdateContext) { + self.element.update(ctx); + match ctx.response { + UpdateResponse::Exit => { + crossterm::terminal::disable_raw_mode().unwrap(); + utils::clear(); + utils::show_cursor(); + println!(""); + return; + } + _ => {} + } + } + /// Run the screen pub fn run(&mut self) { + // Initialize utils::hide_cursor(); utils::clear(); let mut stdout = std::io::stdout(); @@ -97,24 +122,33 @@ impl App { .execute(crossterm::terminal::EnterAlternateScreen) .unwrap(); crossterm::terminal::enable_raw_mode().unwrap(); + + // Start the update thread + let (tx, rx) = std::sync::mpsc::channel(); + std::thread::spawn(move || loop { + tx.send(key::read_key()).unwrap(); + }); + + // Start the render loop + let mut tick: usize = 0; loop { - self.render(); - match self.element.update(key::read_key()) { - UpdateResponse::Exit => { - crossterm::terminal::disable_raw_mode().unwrap(); - utils::clear(); - utils::show_cursor(); - println!(""); - return; + if tick > 99 { + tick = 0; + } + self.render(tick); + tick += 1; + match rx.try_recv() { + Ok(k) => self.update(&mut UpdateContext { + key: k, + tick, + response: UpdateResponse::None, + }), + Err(std::sync::mpsc::TryRecvError::Empty) => { + std::thread::sleep(std::time::Duration::from_millis(10)); } - UpdateResponse::Tick(ticks) => { - for (i, n) in ticks.into_iter().enumerate() { - std::thread::sleep(std::time::Duration::from_millis(n as u64)); - self.element.tick(i); - self.render(); - } + Err(std::sync::mpsc::TryRecvError::Disconnected) => { + panic!("disconnected") } - _ => {} } } } diff --git a/src/macros.rs b/src/macros.rs index a1e9722..f186480 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -160,6 +160,7 @@ macro_rules! element { pub child: usize, pub text: String, pub style: Style, + pub tick_line: std::collections::HashMap, $( $inner )* } @@ -184,6 +185,10 @@ macro_rules! element { self.height = data.height; self.style = data.style; } + + fn clear_ticks(&mut self) { + self.tick_line.clear(); + } $( $functions )* } @@ -198,9 +203,21 @@ macro_rules! element { child: 0, text: String::new(), style: Style::default(), + tick_line: std::collections::HashMap::new(), $( $defaults )* } } + + pub fn get_action(&self, tick: usize) -> String { + match self.tick_line.get(&tick) { + Some(action) => action.clone(), + None => String::new(), + } + } + + pub fn add_action(&mut self, tick: usize, action: &str) { + self.tick_line.insert(tick, action.to_string()); + } } }; } diff --git a/src/main.rs b/src/main.rs index 8dbaa3b..9842461 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,53 +1,9 @@ -use osui::{key::KeyKind, oml, ui::*, App}; +use osui::{oml, ui::*, App}; fn main() { let mut app_screen = App::new(); app_screen.set_component(oml!( - - tab { - div { - // Some welcome text - text("Welcome! This is the OSUI Tutorial! Press tab to go to the next Page or press Shift+Tab to go to a previous page";) - } - - // A button example With a div - - div { - text("On the bottom there is a button. To hover it click the down key, Then press enter to select it";) - - // Button - button("Click me!"; y = 3, style = Style { - hover_fg: Color::Magenta, - ..Default::default() - }, on_click = |btn| { - btn.text = "Press tab to go to the next page".to_string(); - }) - - } - - div { - text("This is a menu, ";) - - // Button - menu(y = 2, items = vec![ - String::from("Item 1"), - String::from("Item 2"), - String::from("Item 3"), - String::from("Item 4"), - String::from("Item 5"), - String::from("Item 6"), - ], on_click = |m, k| { - if k.kind == KeyKind::Escape { - return ClickResponse::Exit; - } else if k.kind == KeyKind::Enter { - m.items[m.selected] = "Press Tab to go to the next page".to_string(); - } - ClickResponse::None - }) - - } - } - + button("okay";) )); app_screen.run(); } diff --git a/src/ui/elements.rs b/src/ui/elements.rs index 6bf83d1..1975613 100644 --- a/src/ui/elements.rs +++ b/src/ui/elements.rs @@ -1,146 +1,48 @@ use std::collections::HashMap; use crate::{ - create_frame, element, - key::{Key, KeyKind}, + element, + key::KeyKind, ui::Style, - Element, ElementData, UpdateResponse, + Element, ElementData, UpdateContext, }; element! { Text {} defaults {} - fn render(&mut self) -> String { + fn render(&mut self, _: usize) -> String { 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() { - let mut data = child.get_data(); - if data.width == 0 { - data.width = self.width; - } - if data.height == 0 { - data.height = self.height; - } - data.style.is_active = self.style.is_active && i == self.child; - child.set_data(data); - crate::utils::render_to_frame(&mut frame, child); - let mut data = child.get_data(); - if data.width == self.width { - data.width = 0; - } - if data.height == self.height { - data.height = 0; - } - child.set_data(data); - } - 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, + clicked: bool } defaults { binds: HashMap::from([(KeyKind::Enter, String::from("click"))]), - toggle: false, - clicked: false, on_click: |_|{}, + clicked: false, } - fn update(&mut self, k: Key) -> UpdateResponse { - if let Some(v) = self.binds.get(&k.kind) { + + fn update(&mut self, ctx: &mut UpdateContext) { + if let Some(v) = self.binds.get(&ctx.key.kind) { if v == "click" { - if self.toggle { - self.clicked = !self.clicked; - (self.on_click)(self); - } else { - return UpdateResponse::Tick(vec![1, 100]); - } + self.clicked = true; + self.add_action(ctx.tick+5, "un_click"); + (self.on_click)(self); } } - UpdateResponse::None } - fn tick(&mut self, i: usize) { - if i == 0 { - self.clicked = true; - (self.on_click)(self); - } else if i == 1 { + fn render(&mut self, tick: usize) -> String { + + if self.get_action(tick)=="un_click" { self.clicked = false; - (self.on_click)(self); } - } - fn render(&mut self) -> String { if self.clicked { return self.style.write_clicked(&self.text); } @@ -148,155 +50,3 @@ element! { } } - -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 this = self.get_data(); - if let Some(child) = self.get_child() { - let mut data = child.get_data(); - if data.width == 0 { - data.width = this.width; - } - if data.height == 0 { - data.height = this.height; - } - data.style.is_active = true; - child.set_data(data); - crate::utils::render_to_frame(&mut frame, child); - let mut data = child.get_data(); - if data.width == this.width { - data.width = 0; - } - if data.height == this.height { - data.height = 0; - } - child.set_data(data); - } - 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, - pub binds: HashMap, - } - defaults { - items: Vec::new(), - selected: 0, - on_click: |_, _|ClickResponse::None, - binds: HashMap::from([ - (KeyKind::Char(String::from("w")), String::from("up")), - (KeyKind::Char(String::from("s")), String::from("down")) - ]) - } - 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 let Some(c) = self.binds.get(&k.kind) { - match c.as_str() { - "up" => { - if self.selected > 0 { - self.selected -= 1; - } else { - self.selected = self.items.len() - 1; - } - } - "down" => { - if self.items.len() > self.selected + 1 { - self.selected += 1; - } else { - self.selected = 0; - } - } - _ => { - return match (self.on_click)(self, k) { - ClickResponse::Exit => UpdateResponse::Exit, - ClickResponse::None => UpdateResponse::None, - } - } - } - } 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 e952bbd..8093cad 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -16,19 +16,6 @@ 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 @@ -39,40 +26,7 @@ pub fn div() -> Box
{ /// /// A boxed `Button` element, initialized and ready for configuration. pub fn button() -> Box