MCP server (bin/fxt/src/mcp/): - JSON-RPC 2.0 protocol over stdin/stdout - 32 tool definitions across 11 domains - McpServer with initialize/tools_list/tools_call handlers - 21 unit tests TUI cockpits (bin/fxt/src/tui/): - Purple/cyan/dark navy theme from design spec - 6 cockpit views: overview, training, trading, services, risk, data - Crossterm event loop with key handling (1-6 switch, q quit, ? help) - CockpitView trait for pluggable cockpit rendering All 75 tests pass, 0 clippy warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
141 lines
4.2 KiB
Rust
141 lines
4.2 KiB
Rust
//! Color theme and style helpers for the Foxhunt TUI.
|
|
//!
|
|
//! All colors are derived from the Foxhunt brand: purple + cyan on dark navy.
|
|
|
|
use ratatui::style::{Color, Modifier, Style};
|
|
use ratatui::widgets::{Block, Borders};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Brand palette
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Dark navy background (#0F0F23).
|
|
pub const BG: Color = Color::Rgb(15, 15, 35);
|
|
|
|
/// Slightly lighter surface (#1A1A2E).
|
|
pub const SURFACE: Color = Color::Rgb(26, 26, 46);
|
|
|
|
/// Primary text -- light gray (#E0E0E0).
|
|
pub const TEXT: Color = Color::Rgb(224, 224, 224);
|
|
|
|
/// Primary accent -- purple (#8B5CF6).
|
|
pub const PRIMARY: Color = Color::Rgb(139, 92, 246);
|
|
|
|
/// Secondary accent -- cyan (#06B6D4).
|
|
pub const SECONDARY: Color = Color::Rgb(6, 182, 212);
|
|
|
|
/// Success -- green (#10B981).
|
|
pub const SUCCESS: Color = Color::Rgb(16, 185, 129);
|
|
|
|
/// Warning -- amber (#F59E0B).
|
|
pub const WARNING: Color = Color::Rgb(245, 158, 11);
|
|
|
|
/// Error -- red (#EF4444).
|
|
pub const ERROR: Color = Color::Rgb(239, 68, 68);
|
|
|
|
/// Muted text -- gray (#6B7280).
|
|
pub const MUTED: Color = Color::Rgb(107, 114, 128);
|
|
|
|
/// Border -- dim purple (#4C1D95).
|
|
pub const BORDER: Color = Color::Rgb(76, 29, 149);
|
|
|
|
/// Highlight -- bright cyan (#22D3EE).
|
|
pub const HIGHLIGHT: Color = Color::Rgb(34, 211, 238);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Style helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Bold primary-colored text for titles.
|
|
pub fn title_style() -> Style {
|
|
Style::default().fg(PRIMARY).add_modifier(Modifier::BOLD)
|
|
}
|
|
|
|
/// Dim purple border style.
|
|
pub fn border_style() -> Style {
|
|
Style::default().fg(BORDER)
|
|
}
|
|
|
|
/// Green for healthy, red for unhealthy.
|
|
pub fn status_style(healthy: bool) -> Style {
|
|
if healthy {
|
|
Style::default().fg(SUCCESS)
|
|
} else {
|
|
Style::default().fg(ERROR)
|
|
}
|
|
}
|
|
|
|
/// Header row style for tables -- secondary accent, bold.
|
|
pub fn header_style() -> Style {
|
|
Style::default()
|
|
.fg(SECONDARY)
|
|
.add_modifier(Modifier::BOLD)
|
|
}
|
|
|
|
/// Standard bordered block with a purple title.
|
|
pub fn block(title: &str) -> Block<'_> {
|
|
Block::default()
|
|
.borders(Borders::ALL)
|
|
.border_style(border_style())
|
|
.title(title.to_owned())
|
|
.title_style(title_style())
|
|
}
|
|
|
|
/// Muted text style.
|
|
pub fn muted_style() -> Style {
|
|
Style::default().fg(MUTED)
|
|
}
|
|
|
|
/// Accent style for highlighted values.
|
|
pub fn accent_style() -> Style {
|
|
Style::default().fg(HIGHLIGHT)
|
|
}
|
|
|
|
/// Warning style.
|
|
pub fn warning_style() -> Style {
|
|
Style::default().fg(WARNING)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Formatting helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Format a float as a dollar string with thousand separators and 2 decimal places.
|
|
/// Example: `format_usd(1_250_000.0)` => `"$1,250,000.00"`.
|
|
pub fn format_usd(value: f64) -> String {
|
|
let abs = value.abs();
|
|
let integer_part = abs as u64;
|
|
let frac = abs - integer_part as f64;
|
|
let int_str = format_u64_with_commas(integer_part);
|
|
let sign = if value < 0.0 { "-" } else { "" };
|
|
format!("{sign}${int_str}.{:02}", (frac * 100.0).round() as u64)
|
|
}
|
|
|
|
/// Format a float as a signed dollar string with thousand separators.
|
|
/// Example: `format_usd_signed(-4375.0)` => `"-$4,375.00"`.
|
|
pub fn format_usd_signed(value: f64) -> String {
|
|
let abs = value.abs();
|
|
let integer_part = abs as u64;
|
|
let frac = abs - integer_part as f64;
|
|
let int_str = format_u64_with_commas(integer_part);
|
|
let sign = if value < 0.0 { "-" } else { "+" };
|
|
format!("{sign}${int_str}.{:02}", (frac * 100.0).round() as u64)
|
|
}
|
|
|
|
/// Format a u64 with thousand separators.
|
|
fn format_u64_with_commas(n: u64) -> String {
|
|
let s = n.to_string();
|
|
let bytes = s.as_bytes();
|
|
let len = bytes.len();
|
|
#[allow(clippy::integer_division)]
|
|
let cap = len + len / 3;
|
|
let mut result = String::with_capacity(cap);
|
|
for (i, &b) in bytes.iter().enumerate() {
|
|
if i > 0 && (len - i) % 3 == 0 {
|
|
result.push(',');
|
|
}
|
|
result.push(b as char);
|
|
}
|
|
result
|
|
}
|