diff --git a/src/main.rs b/src/main.rs index c1565d2..0a72b37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +pub mod types; + use std::any::Any; use pulse_ui::{ @@ -13,12 +15,38 @@ use pulse_ui::{ }, }; +use crate::types::{Formatted, WatchListItem, space_out}; + pub struct PulseTradeApp { command: State, + watch_list: State>, } impl App for PulseTradeApp { - async fn init(&mut self, ctx: &pulse_ui::state::Context) {} + async fn init(&mut self, ctx: &pulse_ui::state::Context) { + let mut watch_list = self.watch_list.lock().await; + + watch_list.push(WatchListItem { + symbol: "BTC".to_string(), + price: 118_402.12, + trend: 0.82, + }); + watch_list.push(WatchListItem { + symbol: "ETH".to_string(), + price: 3_912.48, + trend: 0.41, + }); + watch_list.push(WatchListItem { + symbol: "SOL".to_string(), + price: 182.91, + trend: 2.18, + }); + watch_list.push(WatchListItem { + symbol: "XRP".to_string(), + price: 2.84, + trend: 1.22, + }); + } async fn update(&mut self, ctx: &pulse_ui::state::Context, event: Box) { if let Some(Refresh) = event.downcast_ref() { @@ -77,16 +105,10 @@ impl App for PulseTradeApp { SpacedColumns(vec![ ( LayoutItem::Widget(Size::Flex(1)), - Box::new( - vec![ - " WATCHLIST", - " BTC 118,402.12 ▲ 0.82%", - " ETH 3,912.48 ▼ 0.41%", - " SOL 182.91 ▲ 2.18%", - " XRP 2.84 ▲ 1.22%", - ] - .join("\n"), - ), + Box::new(format!( + " WATCHLIST\n{}", + space_out(self.watch_list.lock().await.get_formatted()).join("\n") + )), ), ( LayoutItem::Widget(Size::Flex(1)), @@ -129,7 +151,8 @@ impl App for PulseTradeApp { vec![ " SIGNALS", " BUY BTC LIM 118,800.12", - " STL BTC 120,00.00", + " BUY BTC STL 118,000.00", + " BUY BTC TAP 120,000.00", ] .join("\n"), ), @@ -150,7 +173,7 @@ impl App for PulseTradeApp { ]), ); - layout.draw(6, Input("> ", &*self.command.lock().await)); + layout.draw(6, Input(" > ", &*self.command.lock().await)); } } @@ -158,6 +181,7 @@ impl App for PulseTradeApp { async fn main() { pulse_ui::run(|ctx| PulseTradeApp { command: ctx.use_state(InputState::new()), + watch_list: ctx.use_state(Vec::new()), }) .await; } diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..9a5c1de --- /dev/null +++ b/src/types.rs @@ -0,0 +1,94 @@ +pub trait Formatted { + fn get_formatted(&self) -> Vec; +} + +pub struct WatchListItem { + pub symbol: String, + pub price: f64, + pub trend: f64, +} + +pub struct ActivePosition { + pub symbol: String, + pub profit: f64, + pub lot: f64, +} + +pub struct Account { + pub equity: f64, + pub liquid: f64, + pub unreal: f64, + 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 +}