From 5a138aecdee4d3657011db3935e5bf440742561c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:11:18 -0400 Subject: [PATCH 1/6] Fix gap --- pulse-ui/src/layout.rs | 45 +++++++++++++++++++++++++++++++++++------- pulse-ui/src/unit.rs | 15 +++++++++++++- src/main.rs | 2 +- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/pulse-ui/src/layout.rs b/pulse-ui/src/layout.rs index ca37247..9209c9d 100644 --- a/pulse-ui/src/layout.rs +++ b/pulse-ui/src/layout.rs @@ -26,23 +26,54 @@ impl LayoutItem { let allocation = if is_row { alloc.height } else { alloc.width }; - let remaining = allocation - - items - .iter() - .map(|item| item.get_size().get_fixed(allocation)) - .sum::(); + let fixed_total: u16 = items + .iter() + .map(|item| item.get_size().get_fixed(allocation)) + .sum(); + + let remaining = allocation.saturating_sub(fixed_total); let total_weight = items .iter() .map(|item| item.get_size().get_flex()) .sum::(); + let base_allocs: Vec = items + .iter() + .map(|item| item.get_size().get(allocation, remaining, total_weight)) + .collect(); + + let flex_alloc_total: u16 = items + .iter() + .zip(base_allocs.iter()) + .filter(|(item, _)| item.get_size().get_flex() > 0) + .map(|(_, a)| a) + .sum(); + + let gap = remaining.saturating_sub(flex_alloc_total); + + let fill_count = items + .iter() + .filter(|item| item.get_size().is_fill()) + .count() as u16; + + let gap_per_fill = if fill_count > 0 { gap / fill_count } else { 0 }; + let gap_remainder = if fill_count > 0 { gap % fill_count } else { 0 }; + let mut curr = if is_row { alloc.y } else { alloc.x }; + let mut fill_idx = 0u16; - for item in items { + for (i, item) in items.iter().enumerate() { let size = item.get_size(); + let mut unit_alloc = base_allocs[i]; - let unit_alloc = size.get(allocation, remaining, total_weight); + if size.is_fill() { + unit_alloc += gap_per_fill; + if fill_idx == 0 { + unit_alloc += gap_remainder; + } + fill_idx += 1; + } let item_alloc = if is_row { Rect { diff --git a/pulse-ui/src/unit.rs b/pulse-ui/src/unit.rs index 83ff31d..c21b503 100644 --- a/pulse-ui/src/unit.rs +++ b/pulse-ui/src/unit.rs @@ -9,6 +9,7 @@ pub enum Size { Fixed(u16), Flex(u16), Percent(u16), + Fill, } #[derive(Debug, Clone, Copy)] @@ -20,17 +21,22 @@ pub struct Rect { } impl Size { + pub fn is_fill(&self) -> bool { + matches!(self, Self::Fill) + } + pub fn get_fixed(&self, alloc: u16) -> u16 { match self { Self::Fixed(v) => *v, Self::Percent(v) => alloc * (*v) / 100, - Self::Flex(_) => 0, + Self::Flex(_) | Self::Fill => 0, } } pub fn get_flex(&self) -> u16 { match self { Self::Flex(f) => *f, + Self::Fill => 1, _ => 0, } } @@ -46,6 +52,13 @@ impl Size { remaining * v / total_weight } } + Self::Fill => { + if total_weight == 0 { + 0 + } else { + remaining / total_weight + } + } } } } diff --git a/src/main.rs b/src/main.rs index 6577301..2b1de93 100644 --- a/src/main.rs +++ b/src/main.rs @@ -111,7 +111,7 @@ impl App for PulseTradeApp { LayoutItem::Widget(Size::Flex(1)), // Event logs LayoutItem::Spacing(Size::Fixed(1)), - LayoutItem::Widget(Size::Flex(1)), + LayoutItem::Widget(Size::Fill), // Input LayoutItem::Spacing(Size::Fixed(1)), LayoutItem::Widget(Size::Fixed(1)), From 4f9a498f413547436958202014c7fef5c78baa84 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:19:21 -0400 Subject: [PATCH 2/6] Added system --- src/formatting.rs | 14 +++++++++++++- src/main.rs | 23 ++++++++++++----------- src/types.rs | 15 +++++++++++++++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index 2eff77f..c05f91f 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,4 +1,4 @@ -use crate::types::{Account, ActivePosition, WatchListItem}; +use crate::types::{Account, ActivePosition, System, WatchListItem}; pub trait Formatted { fn get_formatted(&self) -> Vec; @@ -55,6 +55,18 @@ impl Formatted for Account { } } +impl Formatted for System { + fn get_formatted(&self) -> Vec { + vec![ + Property("Feed", format!("{:?}", self.feed)), + Property("Exchange", self.exchange.clone()), + Property("DEX", self.dex.clone()), + Property("Latency", format!("{} ms", self.latency)), + ] + .get_formatted() + } +} + impl Formatted for Property { fn get_formatted(&self) -> Vec { vec![format!("\x1b[91m{}\x1b[0m", self.0), self.1.clone()] diff --git a/src/main.rs b/src/main.rs index 2b1de93..646219a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use pulse_ui::{ use crate::{ formatting::{Formatted, apply_padding}, - types::{Account, ActivePosition, WatchListItem}, + types::{Account, ActivePosition, System, WatchListItem}, }; pub struct PulseTradeApp { @@ -26,6 +26,7 @@ pub struct PulseTradeApp { watch_list: State>, active_positions: State>, account: State, + system: State, } impl App for PulseTradeApp { @@ -178,16 +179,10 @@ impl App for PulseTradeApp { ), ( LayoutItem::Widget(Size::Flex(1)), - Box::new( - vec![ - " SYSTEM", - " Feed: Connected", - " Exchange: Binance", - " DEX: DEX SCREENER", - " Latency: 18 ms", - ] - .join("\n"), - ), + Box::new(format![ + " SYSTEM\n{}", + apply_padding(self.system.lock().await.get_formatted()).join("\n") + ]), ), ]), ); @@ -209,6 +204,12 @@ async fn main() { realized: 2_182.49, margin: 0.0, }), + system: ctx.use_state(System { + feed: types::Feed::Connected, + exchange: "Binance".to_string(), + dex: "DEX SCREENER".to_string(), + latency: 18, + }), }) .await; } diff --git a/src/types.rs b/src/types.rs index 3c3b4c1..7167380 100644 --- a/src/types.rs +++ b/src/types.rs @@ -17,3 +17,18 @@ pub struct Account { pub realized: f64, pub margin: f64, } + +#[derive(Debug, Clone, Copy)] +pub enum Feed { + Connected, + Disconnected, + Connecting, + Failed, +} + +pub struct System { + pub feed: Feed, + pub exchange: String, + pub dex: String, + pub latency: u16, +} From a37d21afd1868e0c4c0f873db362b6ba130f4f7a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:34:20 -0400 Subject: [PATCH 3/6] Signals --- src/formatting.rs | 21 ++++++++++++++++++--- src/main.rs | 43 ++++++++++++++++++++++++++++++++----------- src/types.rs | 21 +++++++++++++++++++++ 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index c05f91f..d01c9c3 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,13 +1,28 @@ -use crate::types::{Account, ActivePosition, System, WatchListItem}; +use crate::types::{Account, ActivePosition, Signal, SignalKind, System, WatchListItem}; pub trait Formatted { fn get_formatted(&self) -> Vec; } +impl Formatted for Signal { + fn get_formatted(&self) -> Vec { + vec![ + if matches!(self.kind, SignalKind::BUY) { + format!("\x1b[32m{:?}\x1b[0m", self.kind) + } else { + format!("\x1b[31m{:?}\x1b[0m", self.kind) + }, + format!("\x1b[35m{}\x1b[0m", self.symbol), + format!("\x1b[34m{:?}\x1b[0m", self.param), + format_f64(self.price), + ] + } +} + impl Formatted for WatchListItem { fn get_formatted(&self) -> Vec { vec![ - format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()), + format!("\x1b[35m{}\x1b[0m", self.symbol), format_f64(self.price), format!( "{} {}", @@ -25,7 +40,7 @@ impl Formatted for WatchListItem { impl Formatted for ActivePosition { fn get_formatted(&self) -> Vec { vec![ - format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()), + format!("\x1b[35m{}\x1b[0m", self.symbol), format_f64(self.amount), format!( "{} {}", diff --git a/src/main.rs b/src/main.rs index 646219a..0406138 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,13 +18,14 @@ use pulse_ui::{ use crate::{ formatting::{Formatted, apply_padding}, - types::{Account, ActivePosition, System, WatchListItem}, + types::{Account, ActivePosition, Signal, System, WatchListItem}, }; pub struct PulseTradeApp { command: State, watch_list: State>, active_positions: State>, + signals: State>, account: State, system: State, } @@ -76,6 +77,29 @@ impl App for PulseTradeApp { profit: 12.30, amount: 0.75, }); + + let mut signals = self.signals.lock().await; + + signals.push(Signal { + kind: types::SignalKind::BUY, + symbol: "BTC".to_string(), + param: types::SignalParameter::LIM, + price: 118_800.0, + }); + + signals.push(Signal { + kind: types::SignalKind::BUY, + symbol: "BTC".to_string(), + param: types::SignalParameter::TAP, + price: 120_000.0, + }); + + signals.push(Signal { + kind: types::SignalKind::BUY, + symbol: "BTC".to_string(), + param: types::SignalParameter::STL, + price: 118_000.0, + }); } async fn update(&mut self, ctx: &pulse_ui::state::Context, event: Box) { @@ -167,22 +191,18 @@ impl App for PulseTradeApp { ), ( LayoutItem::Widget(Size::Flex(1)), - Box::new( - vec![ - " SIGNALS", - " BUY BTC LIM 118,800.12", - " BUY BTC STL 118,000.00", - " BUY BTC TAP 120,000.00", - ] - .join("\n"), - ), + Box::new(format!( + " SIGNALS\n{}", + apply_padding(self.signals.lock().await.get_formatted()) + .join("\n") + )), ), ( LayoutItem::Widget(Size::Flex(1)), Box::new(format![ " SYSTEM\n{}", apply_padding(self.system.lock().await.get_formatted()).join("\n") - ]), + ]), ), ]), ); @@ -197,6 +217,7 @@ async fn main() { command: ctx.use_state(InputState::new()), watch_list: ctx.use_state(Vec::new()), active_positions: ctx.use_state(Vec::new()), + signals: ctx.use_state(Vec::new()), account: ctx.use_state(Account { equity: 25_483.21, liquid: 11_928.43, diff --git a/src/types.rs b/src/types.rs index 7167380..d294fe9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -32,3 +32,24 @@ pub struct System { pub dex: String, pub latency: u16, } + +#[derive(Debug, Clone, Copy)] +pub enum SignalKind { + BUY, + SELL, +} + +#[derive(Debug, Clone, Copy)] +pub enum SignalParameter { + LIM, + STL, + TAP, + CHK, +} + +pub struct Signal { + pub kind: SignalKind, + pub symbol: String, + pub param: SignalParameter, + pub price: f64, +} From ea156338a3423e279ce4e7b82c3928d6bd3151ec Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:48:19 -0400 Subject: [PATCH 4/6] Event logs --- src/formatting.rs | 23 ++++++++++++++++++++++- src/main.rs | 12 +++++++++--- src/types.rs | 15 +++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index d01c9c3..00b677d 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,9 +1,30 @@ -use crate::types::{Account, ActivePosition, Signal, SignalKind, System, WatchListItem}; +use crate::types::{ + Account, ActivePosition, EventLog, LogKind, Signal, SignalKind, System, WatchListItem, +}; pub trait Formatted { fn get_formatted(&self) -> Vec; } +impl Formatted for EventLog { + fn get_formatted(&self) -> Vec { + vec![ + format!( + "{}{:?}\x1b[0m", + match &self.kind { + LogKind::INFO => "\x1b[34m", + LogKind::DEBUG => "\x1b[36m", + LogKind::ERR => "\x1b[31m", + LogKind::WARN => "\x1b[33m", + }, + self.kind + ), + format!("(\x1b[37m{}\x1b[0m)", self.name), + self.message.clone(), + ] + } +} + impl Formatted for Signal { fn get_formatted(&self) -> Vec { vec![ diff --git a/src/main.rs b/src/main.rs index 0406138..eadd47b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,13 +18,14 @@ use pulse_ui::{ use crate::{ formatting::{Formatted, apply_padding}, - types::{Account, ActivePosition, Signal, System, WatchListItem}, + types::{Account, ActivePosition, EventLog, Signal, System, WatchListItem}, }; pub struct PulseTradeApp { command: State, watch_list: State>, active_positions: State>, + logs: State>, signals: State>, account: State, system: State, @@ -193,8 +194,7 @@ impl App for PulseTradeApp { LayoutItem::Widget(Size::Flex(1)), Box::new(format!( " SIGNALS\n{}", - apply_padding(self.signals.lock().await.get_formatted()) - .join("\n") + apply_padding(self.signals.lock().await.get_formatted()).join("\n") )), ), ( @@ -207,6 +207,11 @@ impl App for PulseTradeApp { ]), ); + layout.draw( + 5, + apply_padding(self.logs.lock().await.get_formatted()).join("\n"), + ); + layout.draw(6, Input(" > ", &*self.command.lock().await)); } } @@ -218,6 +223,7 @@ async fn main() { watch_list: ctx.use_state(Vec::new()), active_positions: ctx.use_state(Vec::new()), signals: ctx.use_state(Vec::new()), + logs: ctx.use_state(Vec::new()), account: ctx.use_state(Account { equity: 25_483.21, liquid: 11_928.43, diff --git a/src/types.rs b/src/types.rs index d294fe9..758d400 100644 --- a/src/types.rs +++ b/src/types.rs @@ -53,3 +53,18 @@ pub struct Signal { pub param: SignalParameter, pub price: f64, } + +#[derive(Debug, Clone, Copy)] +pub enum LogKind { + INFO, + WARN, + ERR, + DEBUG, +} + +#[derive(Debug, Clone)] +pub struct EventLog { + pub kind: LogKind, + pub name: &'static str, + pub message: String, +} From 1eba11c8b19f80bdbc68dd7a36592e7100d01977 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 08:50:41 -0400 Subject: [PATCH 5/6] labled event logs --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index eadd47b..d990a7c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -209,7 +209,10 @@ impl App for PulseTradeApp { layout.draw( 5, - apply_padding(self.logs.lock().await.get_formatted()).join("\n"), + format![ + " EVENT LOGS\n{}", + apply_padding(self.logs.lock().await.get_formatted()).join("\n") + ], ); layout.draw(6, Input(" > ", &*self.command.lock().await)); From 0f6dbc29fe9b578f2dd9f1d2185d63fe4a1f7b3c Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 09:03:27 -0400 Subject: [PATCH 6/6] Delete key and backspace for ascii --- pulse-ui/src/widget/input.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pulse-ui/src/widget/input.rs b/pulse-ui/src/widget/input.rs index 76bcefe..051763e 100644 --- a/pulse-ui/src/widget/input.rs +++ b/pulse-ui/src/widget/input.rs @@ -67,6 +67,41 @@ impl InputState { true } + Event::Key(KeyEvent { + code: KeyCode::Char('h'), + modifiers, + .. + }) if modifiers.contains(KeyModifiers::CONTROL) => { + if self.cursor > 0 { + let prev = self.text[..self.cursor] + .char_indices() + .last() + .map(|(i, _)| i) + .unwrap(); + + self.text.remove(prev); + self.cursor = prev; + } + true + } + + Event::Key(KeyEvent { + code: KeyCode::Delete, + .. + }) => { + if self.cursor < self.text.len() { + let prev = self.text[..self.cursor+1] + .char_indices() + .last() + .map(|(i, _)| i) + .unwrap(); + + self.text.remove(prev); + self.cursor = prev; + } + true + } + Event::Key(KeyEvent { code: KeyCode::Char(c), modifiers,