From d0bb988750863869353c0261656675bf5db1e6ad Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 07:35:41 +0200 Subject: [PATCH 1/5] Crossterm event polling --- pulse-ui/src/lib.rs | 12 +++++++----- src/main.rs | 4 ++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pulse-ui/src/lib.rs b/pulse-ui/src/lib.rs index 5336b83..fa05603 100644 --- a/pulse-ui/src/lib.rs +++ b/pulse-ui/src/lib.rs @@ -1,4 +1,4 @@ -use std::any::Any; +use std::{any::Any, time::Duration}; use tokio::sync::mpsc; @@ -38,10 +38,12 @@ pub async fn run(create_app: impl FnOnce(&state::Context) -> A) { crossterm::terminal::enable_raw_mode().unwrap(); tokio::spawn(async move { - loop { - tx.send(Box::new(crossterm::event::read().unwrap())) - .await - .unwrap(); + while !tx.is_closed() { + if crossterm::event::poll(Duration::from_millis(100)).unwrap() { + tx.send(Box::new(crossterm::event::read().unwrap())) + .await + .unwrap(); + } } }); diff --git a/src/main.rs b/src/main.rs index 2af3330..21980b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,10 @@ impl App for PulseTradeApp { } *self.count.lock().await += 1; + + if *self.count.lock().await > 10 { + ctx.close().await; + } } async fn layout(&self) -> pulse_ui::layout::LayoutItem { From 8df34ba7626ea808f6fc47fd18cd45b53a1dcaf4 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:03:05 +0200 Subject: [PATCH 2/5] Center --- pulse-ui/src/widget/center.rs | 20 ++++++++++++++++++++ pulse-ui/src/widget/mod.rs | 1 + src/main.rs | 5 ++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 pulse-ui/src/widget/center.rs diff --git a/pulse-ui/src/widget/center.rs b/pulse-ui/src/widget/center.rs new file mode 100644 index 0000000..33ac06e --- /dev/null +++ b/pulse-ui/src/widget/center.rs @@ -0,0 +1,20 @@ +use crate::widget::Widget; + +pub struct Center(pub String); + +impl Widget for Center { + fn render(&self, scope: &mut crate::render::RenderScope) { + let mut width = 0; + let mut height = 0; + + for line in self.0.lines() { + height += 1; + width = width.max(line.len() as u16); + } + + let x = (scope.rect.width - width) / 2; + let y = (scope.rect.height - height) / 2; + + scope.draw_text((x, y), &self.0); + } +} diff --git a/pulse-ui/src/widget/mod.rs b/pulse-ui/src/widget/mod.rs index 819165e..d75c0e6 100644 --- a/pulse-ui/src/widget/mod.rs +++ b/pulse-ui/src/widget/mod.rs @@ -1,3 +1,4 @@ +pub mod center; pub mod outline; pub mod spaced; diff --git a/src/main.rs b/src/main.rs index 21980b4..cddf97d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,10 @@ impl App for PulseTradeApp { (LayoutItem::Widget(Size::Flex(1)), Box::new("one")), (LayoutItem::Widget(Size::Flex(1)), Box::new("two")), (LayoutItem::Widget(Size::Flex(1)), Box::new("three")), - (LayoutItem::Widget(Size::Flex(1)), Box::new(self.count.display().await)), + ( + LayoutItem::Widget(Size::Flex(1)), + Box::new(self.count.display().await), + ), ]), ); } From 88e5d69087a248774fd31a303a52ad36edc5c4cb Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:21:14 +0200 Subject: [PATCH 3/5] Fix spacing --- pulse-ui/src/layout.rs | 2 +- pulse-ui/src/widget/spaced.rs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pulse-ui/src/layout.rs b/pulse-ui/src/layout.rs index 4e94d14..79b313d 100644 --- a/pulse-ui/src/layout.rs +++ b/pulse-ui/src/layout.rs @@ -28,7 +28,7 @@ impl LayoutItem { let remaining = allocation - items .iter() - .map(|item| item.get_size().get_fixed(alloc.height)) + .map(|item| item.get_size().get_fixed(allocation)) .sum::(); let total_weight = items diff --git a/pulse-ui/src/widget/spaced.rs b/pulse-ui/src/widget/spaced.rs index 23cec09..070e3f7 100644 --- a/pulse-ui/src/widget/spaced.rs +++ b/pulse-ui/src/widget/spaced.rs @@ -4,7 +4,7 @@ pub struct SpacedRows(pub Vec<(LayoutItem, Box)>); impl Widget for SpacedRows { fn render(&self, scope: &mut crate::render::RenderScope) { - let alloc = scope.rect.allocate(&crate::layout::LayoutItem::Rows { + let mut alloc = scope.rect.allocate(&crate::layout::LayoutItem::Rows { unit: Percent(100), items: self.0.iter().map(|v| v.0.clone()).collect(), }); @@ -13,9 +13,12 @@ impl Widget for SpacedRows { let item = &self.0[i]; if i > 0 { - let rect = &alloc.widgets[i]; + let rect = &mut alloc.widgets[i]; - scope.draw_text((0, rect.y - 2), "─".repeat(rect.width as usize)); + scope.draw_text((0, rect.y - 1), "─".repeat(rect.width as usize)); + + rect.y += 1; + rect.height -= 1; } alloc.draw(i, &*item.1); @@ -42,6 +45,7 @@ impl Widget for SpacedColumns { scope.draw_text((rect.x - 1, i), "│"); } rect.x += 1; + rect.width -= 1; } alloc.draw(i, &*item.1); From c503e8d58378d5c5ae3388a8154df5051d44945d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:52:39 +0200 Subject: [PATCH 4/5] Fix spaced rows --- pulse-ui/src/layout.rs | 7 ++++++- pulse-ui/src/widget/spaced.rs | 17 ++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pulse-ui/src/layout.rs b/pulse-ui/src/layout.rs index 79b313d..2d992cb 100644 --- a/pulse-ui/src/layout.rs +++ b/pulse-ui/src/layout.rs @@ -7,6 +7,7 @@ pub enum LayoutItem { Frame { padding: u16, item: Box }, Widget(Size), + Spacing(Size), } #[derive(Debug, Clone)] @@ -82,6 +83,10 @@ impl LayoutItem { Self::Widget(_) => { full_alloc.widgets.push(*alloc); } + + Self::Spacing(_) => { + full_alloc.frame.push(*alloc); + } } } @@ -89,7 +94,7 @@ impl LayoutItem { match self { Self::Rows { unit, .. } => unit, Self::Columns { unit, .. } => unit, - Self::Widget(unit) => unit, + Self::Widget(unit) | Self::Spacing(unit) => unit, Self::Frame { item, .. } => item.get_size(), } } diff --git a/pulse-ui/src/widget/spaced.rs b/pulse-ui/src/widget/spaced.rs index 070e3f7..72a7e01 100644 --- a/pulse-ui/src/widget/spaced.rs +++ b/pulse-ui/src/widget/spaced.rs @@ -4,9 +4,19 @@ pub struct SpacedRows(pub Vec<(LayoutItem, Box)>); impl Widget for SpacedRows { fn render(&self, scope: &mut crate::render::RenderScope) { + let mut items = Vec::new(); + + for (i, (item, _)) in self.0.iter().enumerate() { + if i > 0 { + items.push(LayoutItem::Spacing(crate::unit::Size::Fixed(1))) + } + + items.push(item.clone()); + } + let mut alloc = scope.rect.allocate(&crate::layout::LayoutItem::Rows { unit: Percent(100), - items: self.0.iter().map(|v| v.0.clone()).collect(), + items, }); for i in 0..alloc.widgets.len() { @@ -15,10 +25,7 @@ impl Widget for SpacedRows { if i > 0 { let rect = &mut alloc.widgets[i]; - scope.draw_text((0, rect.y - 1), "─".repeat(rect.width as usize)); - - rect.y += 1; - rect.height -= 1; + scope.draw_text((0, rect.y - 2), "─".repeat(rect.width as usize)); } alloc.draw(i, &*item.1); From 6b2261795ff4b1d48e6b283cfd87f857aabd5f93 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:56:59 +0200 Subject: [PATCH 5/5] Fix spaced columns --- pulse-ui/src/widget/spaced.rs | 16 ++++++++++++---- src/main.rs | 8 ++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/pulse-ui/src/widget/spaced.rs b/pulse-ui/src/widget/spaced.rs index 72a7e01..8e28ce3 100644 --- a/pulse-ui/src/widget/spaced.rs +++ b/pulse-ui/src/widget/spaced.rs @@ -37,9 +37,19 @@ pub struct SpacedColumns(pub Vec<(LayoutItem, Box)>); impl Widget for SpacedColumns { fn render(&self, scope: &mut crate::render::RenderScope) { + let mut items = Vec::new(); + + for (i, (item, _)) in self.0.iter().enumerate() { + if i > 0 { + items.push(LayoutItem::Spacing(crate::unit::Size::Fixed(1))) + } + + items.push(item.clone()); + } + let mut alloc = scope.rect.allocate(&crate::layout::LayoutItem::Columns { unit: Percent(100), - items: self.0.iter().map(|v| v.0.clone()).collect(), + items, }); for i in 0..alloc.widgets.len() { @@ -49,10 +59,8 @@ impl Widget for SpacedColumns { let rect = &mut alloc.widgets[i]; for i in 0..rect.height { - scope.draw_text((rect.x - 1, i), "│"); + scope.draw_text((rect.x - 2, i), "│"); } - rect.x += 1; - rect.width -= 1; } alloc.draw(i, &*item.1); diff --git a/src/main.rs b/src/main.rs index cddf97d..414d870 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,7 @@ use std::any::Any; use pulse_ui::{ - App, - layout::{LayoutItem, layout}, - state::{Refresh, State}, - unit::Size, - widget::{outline::Outline, spaced::SpacedRows}, + App, layout::{LayoutItem, layout}, state::{Refresh, State}, unit::Size, widget::{outline::Outline, spaced::{SpacedColumns, SpacedRows}}, }; pub struct PulseTradeApp { @@ -39,7 +35,7 @@ impl App for PulseTradeApp { layout.draw( 0, - SpacedRows(vec![ + SpacedColumns(vec![ (LayoutItem::Widget(Size::Flex(1)), Box::new("one")), (LayoutItem::Widget(Size::Flex(1)), Box::new("two")), (LayoutItem::Widget(Size::Flex(1)), Box::new("three")),