diff --git a/Cargo.lock b/Cargo.lock index c3f89a6..a8f2d4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -432,6 +432,7 @@ dependencies = [ "prost", "ratatui", "serde", + "time", "tokio", "toml", "tonic", @@ -713,9 +714,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -3212,9 +3213,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.39" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -3229,15 +3230,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", diff --git a/client/Cargo.toml b/client/Cargo.toml index fb6467d..295607a 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -15,6 +15,7 @@ lazy_static = "1.5.0" prost = "0.13.5" ratatui = "0.29.0" serde = { version = "1.0.218", features = ["derive"] } +time = "0.3.41" tokio = { version = "1.43.0", features = ["full", "macros"] } toml = "0.8.20" tonic = "0.12.3" diff --git a/client/src/components/common/status_line.rs b/client/src/components/common/status_line.rs index beffbf3..c3273a8 100644 --- a/client/src/components/common/status_line.rs +++ b/client/src/components/common/status_line.rs @@ -1,11 +1,11 @@ -// src/client/components/handlers/status_line.rs use ratatui::{ - widgets::Paragraph, style::Style, layout::Rect, Frame, text::{Line, Span}, + widgets::Paragraph, }; +use unicode_width::UnicodeWidthStr; use crate::config::colors::themes::Theme; use std::path::Path; @@ -15,17 +15,11 @@ pub fn render_status_line( current_dir: &str, theme: &Theme, is_edit_mode: bool, + current_fps: f64, ) { - // Program name and version let program_info = format!("multieko2 v{}", env!("CARGO_PKG_VERSION")); + let mode_text = if is_edit_mode { "[EDIT]" } else { "[READ-ONLY]" }; - let mode_text = if is_edit_mode { - "[EDIT]" - } else { - "[READ-ONLY]" - }; - - // Shorten the current directory path let home_dir = dirs::home_dir().map(|p| p.to_string_lossy().into_owned()).unwrap_or_default(); let display_dir = if current_dir.starts_with(&home_dir) { current_dir.replacen(&home_dir, "~", 1) @@ -33,47 +27,51 @@ pub fn render_status_line( current_dir.to_string() }; - // Create the full status line text - let full_text = format!("{} | {} | {}", mode_text, display_dir, program_info); - - // Check if the full text fits in the available width let available_width = area.width as usize; - let mut display_text = if full_text.len() <= available_width { - // If it fits, use the full text - full_text + let mode_width = UnicodeWidthStr::width(mode_text); + let program_info_width = UnicodeWidthStr::width(program_info.as_str()); + let fps_text = format!("{:.0} FPS", current_fps); + let fps_width = UnicodeWidthStr::width(fps_text.as_str()); + let separator = " | "; + let separator_width = UnicodeWidthStr::width(separator); + + let fixed_width_with_fps = mode_width + separator_width + separator_width + + program_info_width + separator_width + fps_width; + let show_fps = fixed_width_with_fps < available_width; + + let remaining_width_for_dir = available_width.saturating_sub( + mode_width + separator_width + separator_width + program_info_width + + if show_fps { separator_width + fps_width } else { 0 } + ); + + let dir_display_text = if UnicodeWidthStr::width(display_dir.as_str()) <= remaining_width_for_dir { + display_dir } else { - // If it doesn't fit, prioritize mode and program info, and show only the directory name let dir_name = Path::new(current_dir) .file_name() .and_then(|n| n.to_str()) .unwrap_or(current_dir); - format!("{} | {} | {}", mode_text, dir_name, program_info) + if UnicodeWidthStr::width(dir_name) <= remaining_width_for_dir { + dir_name.to_string() + } else { + dir_name.chars().take(remaining_width_for_dir).collect() + } }; - // If even the shortened version overflows, truncate it - if display_text.len() > available_width { - display_text = display_text.chars().take(available_width).collect(); - } - - // Create the status line text using Line and Span - let status_line = Line::from(vec![ + let mut spans = vec![ Span::styled(mode_text, Style::default().fg(theme.accent)), Span::styled(" | ", Style::default().fg(theme.border)), - Span::styled( - display_text.split(" | ").nth(1).unwrap_or(""), // Directory part - Style::default().fg(theme.fg), - ), + Span::styled(dir_display_text, Style::default().fg(theme.fg)), Span::styled(" | ", Style::default().fg(theme.border)), - Span::styled( - program_info, - Style::default() - .fg(theme.secondary) - .add_modifier(ratatui::style::Modifier::BOLD), - ), - ]); + Span::styled(program_info, Style::default().fg(theme.secondary)), + ]; - // Render the status line - let paragraph = Paragraph::new(status_line) + if show_fps { + spans.push(Span::styled(" | ", Style::default().fg(theme.border))); + spans.push(Span::styled(fps_text, Style::default().fg(theme.secondary))); + } + + let paragraph = Paragraph::new(Line::from(spans)) .style(Style::default().bg(theme.bg)); f.render_widget(paragraph, area); diff --git a/client/src/ui/handlers/render.rs b/client/src/ui/handlers/render.rs index 15fcd0c..cbaa8e4 100644 --- a/client/src/ui/handlers/render.rs +++ b/client/src/ui/handlers/render.rs @@ -39,6 +39,7 @@ pub fn render_ui( command_input: &str, command_mode: bool, command_message: &str, + current_fps: f64, app_state: &AppState, ) { render_background(f, f.area(), theme); @@ -175,6 +176,6 @@ pub fn render_ui( render_buffer_list(f, area, theme, buffer_state); } } - render_status_line(f, status_line_area, current_dir, theme, is_edit_mode); + render_status_line(f, status_line_area, current_dir, theme, is_edit_mode, current_fps); render_command_line(f, command_line_area, command_input, command_mode, theme, command_message); } diff --git a/client/src/ui/handlers/ui.rs b/client/src/ui/handlers/ui.rs index ee77a4c..7443fb5 100644 --- a/client/src/ui/handlers/ui.rs +++ b/client/src/ui/handlers/ui.rs @@ -20,6 +20,7 @@ use crate::state::app::state::AppState; // Import SaveOutcome use crate::tui::terminal::{EventReader, TerminalCore}; use crate::ui::handlers::render::render_ui; +use std::time::Instant; use crossterm::cursor::SetCursorStyle; pub async fn run_ui() -> Result<(), Box> { @@ -51,6 +52,10 @@ pub async fn run_ui() -> Result<(), Box> { .await?; form_state.reset_to_empty(); + // --- FPS Calculation State --- + let mut last_frame_time = Instant::now(); + let mut current_fps = 0.0; + loop { // Determine edit mode based on EventHandler state let is_edit_mode = event_handler.is_edit_mode; @@ -92,6 +97,7 @@ pub async fn run_ui() -> Result<(), Box> { &event_handler.command_input, event_handler.command_mode, &event_handler.command_message, + current_fps, &app_state, ); })?; @@ -302,6 +308,14 @@ pub async fn run_ui() -> Result<(), Box> { // terminal.cleanup()?; // Optional: Drop handles this return Ok(()); } + + // --- FPS Calculation --- + let now = Instant::now(); + let frame_duration = now.duration_since(last_frame_time); + last_frame_time = now; + if frame_duration.as_secs_f64() > 1e-6 { + current_fps = 1.0 / frame_duration.as_secs_f64(); + } } }