Added RSSI Indicator

This commit is contained in:
KaraZajac
2026-02-17 20:12:32 -05:00
parent 0cffd71a87
commit 76451bb458
4 changed files with 158 additions and 22 deletions
+67 -20
View File
@@ -3,7 +3,7 @@
use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
text::{Line, Span},
text::{Line, Span, Text},
widgets::{Block, Borders, Clear, Paragraph, Wrap},
Frame,
};
@@ -21,52 +21,65 @@ use crate::app::InputMode as IM;
const VERSION: &str = env!("CARGO_PKG_VERSION");
/// RSSI bar width (right side)
const RSSI_BAR_WIDTH: u16 = 5;
/// Draw the entire UI
pub fn draw_ui(frame: &mut Frame, app: &App) {
let show_settings = matches!(app.input_mode, IM::SettingsSelect | IM::SettingsEdit);
let show_command = app.input_mode == IM::Command;
let mut constraints = vec![Constraint::Length(3)]; // Header
// Full-width rows: header, settings (optional), then middle row split into [captures | RX bar], status, command (optional), help
let main_area = frame.area();
let mut v_constraints = vec![
Constraint::Length(3), // Header (full width)
Constraint::Min(8), // Middle: captures + RX bar
Constraint::Length(3), // Status bar (full width)
Constraint::Length(1), // Help bar (full width)
];
if show_settings {
constraints.push(Constraint::Length(3)); // Settings tabs
v_constraints.insert(1, Constraint::Length(3)); // Settings tabs (full width)
}
constraints.push(Constraint::Min(8)); // Captures list
constraints.push(Constraint::Length(3)); // Status bar
if show_command {
constraints.push(Constraint::Length(3)); // Command input
v_constraints.insert(v_constraints.len() - 1, Constraint::Length(3)); // Command (full width)
}
constraints.push(Constraint::Length(1)); // Help bar
let chunks = Layout::default()
let rows = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(frame.area());
.constraints(v_constraints)
.split(main_area);
let mut idx = 0;
render_header(frame, chunks[idx], app);
render_header(frame, rows[idx], app);
idx += 1;
if show_settings {
render_settings_tabs(frame, chunks[idx], app);
render_settings_tabs(frame, rows[idx], app);
idx += 1;
}
render_captures_list(frame, chunks[idx], app);
// Only the middle row is split: captures (left) | RX bar (right)
let middle_row = rows[idx];
let h_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Min(0), Constraint::Length(RSSI_BAR_WIDTH)])
.split(middle_row);
let captures_area = h_chunks[0];
let rssi_bar_rect = h_chunks[1];
idx += 1;
render_status_bar(frame, chunks[idx], app);
render_captures_list(frame, captures_area, app);
render_rssi_bar(frame, rssi_bar_rect, app);
render_status_bar(frame, rows[idx], app);
idx += 1;
if show_command {
render_command_line(frame, chunks[idx], app);
render_command_line(frame, rows[idx], app);
idx += 1;
}
render_help_bar(frame, chunks[idx], app);
render_help_bar(frame, rows[idx], app);
// Overlay widgets (rendered on top of everything else)
if app.input_mode == InputMode::SignalMenu {
@@ -111,6 +124,40 @@ pub fn draw_ui(frame: &mut Frame, app: &App) {
}
}
/// Render the RSSI bar on the right (vertical bar, bottom = strong).
fn render_rssi_bar(frame: &mut Frame, area: Rect, app: &App) {
let block = Block::default()
.borders(Borders::ALL)
.title(" RX ");
let inner = block.inner(area);
frame.render_widget(block, area);
if inner.width == 0 || inner.height == 0 {
return;
}
// Normalize RSSI (0..~1) to fill ratio; scale so ~0.5 magnitude ≈ full bar
let fill_ratio = (app.rssi / 0.6).min(1.0);
let filled_rows = (inner.height as f32 * fill_ratio).round() as u16;
let filled_style = Style::default().fg(Color::Green);
let empty_style = Style::default().fg(Color::DarkGray);
let mut lines = Vec::with_capacity(inner.height as usize);
for r in 0..inner.height {
let fill = r >= inner.height.saturating_sub(filled_rows);
let (style, ch) = if fill {
(filled_style, "")
} else {
(empty_style, " ")
};
let s = ch.repeat(inner.width as usize);
lines.push(Line::from(Span::styled(s, style)));
}
let paragraph = Paragraph::new(Text::from(lines));
frame.render_widget(paragraph, inner);
}
/// Render the header with title and radio status
fn render_header(frame: &mut Frame, area: Rect, app: &App) {
let (status_symbol, status_style) = match app.radio_state {