From f5d30551746b91ff58f5f9bd97c9f3ae28105ad1 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Wed, 15 Jul 2026 12:03:30 +0200 Subject: [PATCH] Separated formatting --- src/formatting.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 47 +++++++++++++----- src/types.rs | 77 +---------------------------- 3 files changed, 157 insertions(+), 87 deletions(-) create mode 100644 src/formatting.rs diff --git a/src/formatting.rs b/src/formatting.rs new file mode 100644 index 0000000..1ba62e7 --- /dev/null +++ b/src/formatting.rs @@ -0,0 +1,120 @@ +use crate::types::{ActivePosition, WatchListItem}; + +pub trait Formatted { + fn get_formatted(&self) -> Vec; +} + +impl Formatted for WatchListItem { + fn get_formatted(&self) -> Vec { + vec![ + format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()), + format_f64(self.price), + format!( + "{} {}", + if self.trend.is_sign_positive() { + "\x1b[32m▲\x1b[0m" + } else { + "\x1b[31m▼\x1b[0m" + }, + format_f64(self.trend.abs()) + ), + ] + } +} + +impl Formatted for ActivePosition { + fn get_formatted(&self) -> Vec { + vec![ + format!("\x1b[35m{}\x1b[0m", self.symbol.to_string()), + format_f64(self.amount), + format!( + "{} {}", + if self.profit.is_sign_positive() { + "\x1b[32m▲\x1b[0m" + } else { + "\x1b[31m▼\x1b[0m" + }, + format_f64(self.profit.abs()) + ), + ] + } +} + +impl Formatted for Vec { + fn get_formatted(&self) -> Vec { + let mut output = Vec::new(); + + let rows: Vec> = self.iter().map(|v| v.get_formatted()).collect(); + + if rows.is_empty() { + return output; + } + + let column_count = rows.iter().map(|r| r.len()).max().unwrap_or(0); + + let mut widths = vec![0; column_count]; + + for row in &rows { + for (i, col) in row.iter().enumerate() { + widths[i] = widths[i].max(col.len()); + } + } + + for row in rows { + let mut row_out = String::new(); + + for (col_idx, col) in row.iter().enumerate() { + let width = widths[col_idx]; + + row_out.push_str(&format!("{:>width$}", col, width = width)); + + if col_idx + 1 != row.len() { + row_out.push_str(" "); + } + } + + output.push(row_out); + } + + output + } +} + +pub fn apply_padding(mut items: Vec) -> Vec { + for item in &mut items { + item.insert(0, ' '); + } + + items +} + +pub fn format_f64(value: f64) -> String { + let val = value.to_string(); + let parts: Vec<&str> = val.split('.').collect(); + + let int = parts[0].to_string(); + let negative = int.starts_with('-'); + + let start = if negative { 1 } else { 0 }; + let mut result = String::new(); + + for (i, c) in int[start..].chars().rev().enumerate() { + if i > 0 && i % 3 == 0 { + result.push(','); + } + result.push(c); + } + + let mut formatted: String = result.chars().rev().collect(); + + if negative { + formatted.insert(0, '-'); + } + + if parts.len() > 1 { + formatted.push('.'); + formatted.push_str(parts[1]); + } + + formatted +} diff --git a/src/main.rs b/src/main.rs index 0a72b37..b1c09d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +pub mod formatting; pub mod types; use std::any::Any; @@ -15,11 +16,14 @@ use pulse_ui::{ }, }; -use crate::types::{Formatted, WatchListItem, space_out}; +use crate::{ + formatting::{Formatted, apply_padding}, types::{ActivePosition, WatchListItem}, +}; pub struct PulseTradeApp { command: State, watch_list: State>, + active_positions: State>, } impl App for PulseTradeApp { @@ -34,7 +38,7 @@ impl App for PulseTradeApp { watch_list.push(WatchListItem { symbol: "ETH".to_string(), price: 3_912.48, - trend: 0.41, + trend: -0.41, }); watch_list.push(WatchListItem { symbol: "SOL".to_string(), @@ -46,6 +50,29 @@ impl App for PulseTradeApp { price: 2.84, trend: 1.22, }); + + let mut active_positions = self.active_positions.lock().await; + + active_positions.push(ActivePosition { + symbol: "BTC".to_string(), + profit: 125.50, + amount: 0.25, + }); + active_positions.push(ActivePosition { + symbol: "ETH".to_string(), + profit: -32.75, + amount: 1.0, + }); + active_positions.push(ActivePosition { + symbol: "SOL".to_string(), + profit: 84.20, + amount: 5.0, + }); + active_positions.push(ActivePosition { + symbol: "XRP".to_string(), + profit: 12.30, + amount: 0.75, + }); } async fn update(&mut self, ctx: &pulse_ui::state::Context, event: Box) { @@ -107,19 +134,16 @@ impl App for PulseTradeApp { LayoutItem::Widget(Size::Flex(1)), Box::new(format!( " WATCHLIST\n{}", - space_out(self.watch_list.lock().await.get_formatted()).join("\n") + apply_padding(self.watch_list.lock().await.get_formatted()).join("\n") )), ), ( LayoutItem::Widget(Size::Flex(1)), - Box::new( - vec![ - " ACTIVE POSITIONS", - " BTC +4,120.40 20,000.00", - " SOL +2,435.40 20,000.00", - ] - .join("\n"), - ), + Box::new(format!( + " ACTIVE POSITIONS\n{}", + apply_padding(self.active_positions.lock().await.get_formatted()) + .join("\n") + )), ), ( LayoutItem::Widget(Size::Flex(1)), @@ -182,6 +206,7 @@ async fn main() { pulse_ui::run(|ctx| PulseTradeApp { command: ctx.use_state(InputState::new()), watch_list: ctx.use_state(Vec::new()), + active_positions: ctx.use_state(Vec::new()), }) .await; } diff --git a/src/types.rs b/src/types.rs index 9a5c1de..3c3b4c1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,7 +1,3 @@ -pub trait Formatted { - fn get_formatted(&self) -> Vec; -} - pub struct WatchListItem { pub symbol: String, pub price: f64, @@ -11,7 +7,7 @@ pub struct WatchListItem { pub struct ActivePosition { pub symbol: String, pub profit: f64, - pub lot: f64, + pub amount: f64, } pub struct Account { @@ -21,74 +17,3 @@ pub struct Account { pub realized: f64, pub margin: f64, } - -impl Formatted for Vec { - fn get_formatted(&self) -> Vec { - let mut output = Vec::new(); - - let rows: Vec> = self.iter().map(|v| v.get_formatted()).collect(); - - if rows.is_empty() { - return output; - } - - let column_count = rows.iter().map(|r| r.len()).max().unwrap_or(0); - - let mut widths = vec![0; column_count]; - - // Find the largest width per column - for row in &rows { - for (i, col) in row.iter().enumerate() { - widths[i] = widths[i].max(col.len()); - } - } - - // Render rows - for (row_idx, row) in rows.iter().enumerate() { - let mut row_out = String::new(); - - for (col_idx, col) in row.iter().enumerate() { - let width = widths[col_idx]; - - // Right-align numbers, left-align text could be customized here - row_out.push_str(&format!("{:>width$}", col, width = width)); - - if col_idx + 1 != row.len() { - row_out.push_str(" "); - } - } - - if row_idx + 1 != rows.len() { - output.push(row_out); - } - } - - output - } -} - -impl Formatted for WatchListItem { - fn get_formatted(&self) -> Vec { - vec![ - self.symbol.to_string(), - self.price.to_string(), - format!( - "{} {}", - if self.trend.is_sign_positive() { - "▲" - } else { - "▼" - }, - self.trend.abs() - ), - ] - } -} - -pub fn space_out(mut items: Vec) -> Vec { - for item in &mut items { - item.insert(0, ' '); - } - - items -}