Compare commits

...

14 Commits

Author SHA1 Message Date
filipriec
f4689125e0 minor changes 2025-04-15 20:28:31 +02:00
filipriec
bbba67a253 buffer for form fix performed 2025-04-15 20:19:30 +02:00
filipriec
800b857e53 buffers are in the layers now 2025-04-15 19:42:12 +02:00
filipriec
921059bed8 buffer logic going hard, we are killing buffers from login and register now 2025-04-15 18:56:11 +02:00
filipriec
e8b585dc07 killing of the buffers now works 2025-04-15 18:43:36 +02:00
filipriec
afce27184a better defaults 2025-04-15 18:37:27 +02:00
filipriec
8d41beef0b killing of the buffer working 2025-04-15 18:33:42 +02:00
filipriec
5e482cd77b buffer close, not kill implemented yet 2025-04-15 18:29:59 +02:00
filipriec
09068fd4e5 FPS counter added 2025-04-15 18:11:26 +02:00
filipriec
d8c2b9089b fixes are now in place properly well 2025-04-15 17:52:58 +02:00
filipriec
bb577bc276 now buffer handling is global but not allowed from edit mode 2025-04-15 16:23:26 +02:00
filipriec
6267e3d593 working switching of the buffers properly well now 2025-04-15 16:03:14 +02:00
filipriec
c091a39802 compiled 2025-04-15 14:12:42 +02:00
filipriec
f94006dd08 buffer its independent, needs fixes 2025-04-15 13:57:17 +02:00
19 changed files with 356 additions and 176 deletions

23
Cargo.lock generated
View File

@@ -421,7 +421,7 @@ dependencies = [
[[package]] [[package]]
name = "client" name = "client"
version = "0.3.5" version = "0.3.13"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"common", "common",
@@ -432,6 +432,7 @@ dependencies = [
"prost", "prost",
"ratatui", "ratatui",
"serde", "serde",
"time",
"tokio", "tokio",
"toml", "toml",
"tonic", "tonic",
@@ -461,7 +462,7 @@ dependencies = [
[[package]] [[package]]
name = "common" name = "common"
version = "0.3.5" version = "0.3.13"
dependencies = [ dependencies = [
"prost", "prost",
"serde", "serde",
@@ -713,9 +714,9 @@ dependencies = [
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.11" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
dependencies = [ dependencies = [
"powerfmt", "powerfmt",
] ]
@@ -2592,7 +2593,7 @@ dependencies = [
[[package]] [[package]]
name = "server" name = "server"
version = "0.3.5" version = "0.3.13"
dependencies = [ dependencies = [
"bcrypt", "bcrypt",
"chrono", "chrono",
@@ -3212,9 +3213,9 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.39" version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
@@ -3229,15 +3230,15 @@ dependencies = [
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.3" version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.20" version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [ dependencies = [
"num-conv", "num-conv",
"time-core", "time-core",

View File

@@ -5,7 +5,7 @@ resolver = "2"
[workspace.package] [workspace.package]
# TODO: idk how to do the name, fix later # TODO: idk how to do the name, fix later
# name = "Multieko2" # name = "Multieko2"
version = "0.3.5" version = "0.3.13"
edition = "2021" edition = "2021"
license = "GPL-3.0-or-later" license = "GPL-3.0-or-later"
authors = ["Filip Priečinský <filippriec@gmail.com>"] authors = ["Filip Priečinský <filippriec@gmail.com>"]

View File

@@ -1,5 +1,7 @@
# Hey # Hey
This is only work in progress, until release 1.0.0 this is for development use cases only.
I run development like this: I run development like this:
Server: Server:

View File

@@ -15,6 +15,7 @@ lazy_static = "1.5.0"
prost = "0.13.5" prost = "0.13.5"
ratatui = "0.29.0" ratatui = "0.29.0"
serde = { version = "1.0.218", features = ["derive"] } serde = { version = "1.0.218", features = ["derive"] }
time = "0.3.41"
tokio = { version = "1.43.0", features = ["full", "macros"] } tokio = { version = "1.43.0", features = ["full", "macros"] }
toml = "0.8.20" toml = "0.8.20"
tonic = "0.12.3" tonic = "0.12.3"

View File

@@ -2,6 +2,8 @@
[keybindings] [keybindings]
enter_command_mode = [":", "ctrl+;"] enter_command_mode = [":", "ctrl+;"]
next_buffer = ["ctrl+l"]
previous_buffer = ["ctrl+h"]
[keybindings.general] [keybindings.general]
move_up = ["k", "Up"] move_up = ["k", "Up"]

View File

@@ -1,11 +1,11 @@
// src/client/components/handlers/status_line.rs
use ratatui::{ use ratatui::{
widgets::Paragraph,
style::Style, style::Style,
layout::Rect, layout::Rect,
Frame, Frame,
text::{Line, Span}, text::{Line, Span},
widgets::Paragraph,
}; };
use unicode_width::UnicodeWidthStr;
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use std::path::Path; use std::path::Path;
@@ -15,17 +15,11 @@ pub fn render_status_line(
current_dir: &str, current_dir: &str,
theme: &Theme, theme: &Theme,
is_edit_mode: bool, is_edit_mode: bool,
current_fps: f64,
) { ) {
// Program name and version
let program_info = format!("multieko2 v{}", env!("CARGO_PKG_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 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) { let display_dir = if current_dir.starts_with(&home_dir) {
current_dir.replacen(&home_dir, "~", 1) current_dir.replacen(&home_dir, "~", 1)
@@ -33,47 +27,51 @@ pub fn render_status_line(
current_dir.to_string() 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 available_width = area.width as usize;
let mut display_text = if full_text.len() <= available_width { let mode_width = UnicodeWidthStr::width(mode_text);
// If it fits, use the full text let program_info_width = UnicodeWidthStr::width(program_info.as_str());
full_text 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 { } else {
// If it doesn't fit, prioritize mode and program info, and show only the directory name
let dir_name = Path::new(current_dir) let dir_name = Path::new(current_dir)
.file_name() .file_name()
.and_then(|n| n.to_str()) .and_then(|n| n.to_str())
.unwrap_or(current_dir); .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 let mut spans = vec![
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![
Span::styled(mode_text, Style::default().fg(theme.accent)), Span::styled(mode_text, Style::default().fg(theme.accent)),
Span::styled(" | ", Style::default().fg(theme.border)), Span::styled(" | ", Style::default().fg(theme.border)),
Span::styled( Span::styled(dir_display_text, Style::default().fg(theme.fg)),
display_text.split(" | ").nth(1).unwrap_or(""), // Directory part
Style::default().fg(theme.fg),
),
Span::styled(" | ", Style::default().fg(theme.border)), Span::styled(" | ", Style::default().fg(theme.border)),
Span::styled( Span::styled(program_info, Style::default().fg(theme.secondary)),
program_info, ];
Style::default()
.fg(theme.secondary)
.add_modifier(ratatui::style::Modifier::BOLD),
),
]);
// Render the status line if show_fps {
let paragraph = Paragraph::new(status_line) 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)); .style(Style::default().bg(theme.bg));
f.render_widget(paragraph, area); f.render_widget(paragraph, area);

View File

@@ -1,7 +1,7 @@
// src/components/handlers/buffer_list.rs // src/components/handlers/buffer_list.rs
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::state::app::state::AppState; use crate::state::app::buffer::BufferState;
use ratatui::{ use ratatui::{
layout::{Alignment, Rect}, layout::{Alignment, Rect},
style::{Style, Stylize}, style::{Style, Stylize},
@@ -10,12 +10,13 @@ use ratatui::{
Frame, Frame,
}; };
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use crate::functions::common::buffer::get_view_layer;
pub fn render_buffer_list( pub fn render_buffer_list(
f: &mut Frame, f: &mut Frame,
area: Rect, area: Rect,
theme: &Theme, theme: &Theme,
app_state: &AppState, buffer_state: &BufferState,
) { ) {
// --- Style Definitions --- // --- Style Definitions ---
let active_style = Style::default() let active_style = Style::default()
@@ -26,13 +27,23 @@ pub fn render_buffer_list(
.fg(theme.fg) .fg(theme.fg)
.bg(theme.bg); .bg(theme.bg);
// --- Determine Active Layer ---
let active_layer = match buffer_state.history.get(buffer_state.active_index) {
Some(view) => get_view_layer(view),
None => 1,
};
// --- Create Spans --- // --- Create Spans ---
let mut spans = Vec::new(); let mut spans = Vec::new();
let history = &app_state.ui.buffer_history;
let mut current_width = 0; let mut current_width = 0;
for (i, view) in history.iter().enumerate() { for (original_index, view) in buffer_state.history.iter().enumerate() {
let is_active = i == app_state.ui.active_buffer_index; // Filter: Only process views matching the active layer
if get_view_layer(view) != active_layer {
continue;
}
let is_active = original_index == buffer_state.active_index;
let buffer_name = view.display_name(); let buffer_name = view.display_name();
let buffer_text = format!(" {} ", buffer_name); let buffer_text = format!(" {} ", buffer_name);
let text_width = UnicodeWidthStr::width(buffer_text.as_str()); let text_width = UnicodeWidthStr::width(buffer_text.as_str());
@@ -51,10 +62,12 @@ pub fn render_buffer_list(
// --- Filler Span --- // --- Filler Span ---
let remaining_width = area.width.saturating_sub(current_width as u16); let remaining_width = area.width.saturating_sub(current_width as u16);
spans.push(Span::styled( if !spans.is_empty() || remaining_width > 0 {
" ".repeat(remaining_width as usize), spans.push(Span::styled(
inactive_style, " ".repeat(remaining_width as usize),
)); inactive_style,
));
}
// --- Render --- // --- Render ---
let buffer_line = Line::from(spans); let buffer_line = Line::from(spans);

View File

@@ -1,2 +1,5 @@
// src/functions/common.rs // src/functions/common.rs
pub mod buffer;
pub use buffer::*;

View File

@@ -0,0 +1,35 @@
// src/functions/common/buffer.rs
use crate::state::app::buffer::BufferState;
use crate::state::app::buffer::AppView;
pub fn get_view_layer(view: &AppView) -> u8 {
match view {
AppView::Intro => 1,
AppView::Login | AppView::Register | AppView::Admin => 2,
AppView::Form(_) | AppView::Scratch => 3,
}
}
/// Switches the active buffer index.
pub fn switch_buffer(buffer_state: &mut BufferState, next: bool) -> bool {
if buffer_state.history.len() <= 1 {
return false;
}
let len = buffer_state.history.len();
let current_index = buffer_state.active_index;
let new_index = if next {
(current_index + 1) % len
} else {
(current_index + len - 1) % len
};
if new_index != current_index {
buffer_state.active_index = new_index;
true
} else {
false
}
}

View File

@@ -4,6 +4,7 @@ use crossterm::event::{Event, KeyCode};
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::app::buffer::BufferState;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState; use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState; use crate::state::pages::auth::RegisterState;
@@ -20,6 +21,7 @@ pub async fn handle_dialog_event(
auth_state: &mut AuthState, auth_state: &mut AuthState,
login_state: &mut LoginState, login_state: &mut LoginState,
register_state: &mut RegisterState, register_state: &mut RegisterState,
buffer_state: &mut BufferState,
) -> Option<Result<EventOutcome, Box<dyn std::error::Error>>> { ) -> Option<Result<EventOutcome, Box<dyn std::error::Error>>> {
if let Event::Key(key) = event { if let Event::Key(key) = event {
// Always allow Esc to dismiss // Always allow Esc to dismiss
@@ -62,7 +64,7 @@ pub async fn handle_dialog_event(
match selected_index { match selected_index {
0 => { // "Menu" button selected 0 => { // "Menu" button selected
app_state.hide_dialog(); app_state.hide_dialog();
let message = login::back_to_main(login_state, app_state).await; let message = login::back_to_main(login_state, app_state, buffer_state).await;
return Some(Ok(EventOutcome::Ok(message))); return Some(Ok(EventOutcome::Ok(message)));
} }
1 => { 1 => {
@@ -91,8 +93,7 @@ pub async fn handle_dialog_event(
match selected_index { match selected_index {
0 => { // "OK" button for RegisterSuccess 0 => { // "OK" button for RegisterSuccess
app_state.hide_dialog(); app_state.hide_dialog();
// Go back to intro after successful registration dialog let message = register::back_to_login(register_state, app_state, buffer_state).await;
let message = register::back_to_main(register_state, app_state).await;
return Some(Ok(EventOutcome::Ok(message))); return Some(Ok(EventOutcome::Ok(message)));
} }
_ => { // Default for RegisterSuccess _ => { // Default for RegisterSuccess

View File

@@ -1,32 +1,43 @@
// src/modes/handlers/event.rs // src/modes/handlers/event.rs
use crossterm::event::Event; use crossterm::event::Event;
use crossterm::cursor::SetCursorStyle; use crossterm::cursor::SetCursorStyle;
use crate::tui::terminal::core::TerminalCore;
use crate::services::grpc_client::GrpcClient; use crate::services::grpc_client::GrpcClient;
use crate::services::auth::AuthClient; use crate::services::auth::AuthClient;
use crate::modes::common::commands::CommandHandler;
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::state::pages::form::FormState;
use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState;
use crate::state::pages::intro::IntroState;
use crate::state::pages::admin::AdminState;
use crate::state::app::state::AppState;
use crate::state::pages::canvas_state::CanvasState;
use crate::ui::handlers::rat_state::UiStateHandler; use crate::ui::handlers::rat_state::UiStateHandler;
use crate::ui::handlers::context::UiContext; use crate::ui::handlers::context::UiContext;
use crate::tui::functions::{intro, admin}; use crate::functions::common::buffer;
use crate::tui::functions::common::{login, register}; use crate::tui::{
terminal::core::TerminalCore,
functions::{
common::{
form::SaveOutcome,
login,
register,
},
},
{intro, admin},
};
use crate::state::{
app::{
state::AppState,
buffer::{AppView, BufferState},
},
pages::{
auth::{AuthState, LoginState, RegisterState},
admin::AdminState,
canvas_state::CanvasState,
form::FormState,
intro::IntroState,
},
};
use crate::modes::{ use crate::modes::{
common::command_mode, common::{command_mode, commands::CommandHandler},
handlers::mode_manager::{ModeManager, AppMode},
canvas::{edit, read_only, common_mode}, canvas::{edit, read_only, common_mode},
general::{navigation, dialog}, general::{navigation, dialog},
}; };
use crate::config::binds::key_sequences::KeySequenceTracker; use crate::config::binds::key_sequences::KeySequenceTracker;
use crate::modes::handlers::mode_manager::{ModeManager, AppMode};
use crate::state::app::state::AppView;
use crate::tui::functions::common::form::SaveOutcome;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum EventOutcome { pub enum EventOutcome {
@@ -74,6 +85,7 @@ impl EventHandler {
register_state: &mut RegisterState, register_state: &mut RegisterState,
intro_state: &mut IntroState, intro_state: &mut IntroState,
admin_state: &mut AdminState, admin_state: &mut AdminState,
buffer_state: &mut BufferState,
app_state: &mut AppState, app_state: &mut AppState,
total_count: u64, total_count: u64,
current_position: &mut u64, current_position: &mut u64,
@@ -94,12 +106,12 @@ impl EventHandler {
} }
else { AppView::Scratch } else { AppView::Scratch }
}; };
app_state.ui.update_buffer_history(current_view); buffer_state.update_history(current_view);
// --- DIALOG MODALITY --- // --- DIALOG MODALITY ---
if app_state.ui.dialog.dialog_show { if app_state.ui.dialog.dialog_show {
if let Some(dialog_result) = dialog::handle_dialog_event( if let Some(dialog_result) = dialog::handle_dialog_event(
&event, config, app_state, auth_state, login_state, register_state &event, config, app_state, auth_state, login_state, register_state, buffer_state
).await { ).await {
return dialog_result; return dialog_result;
} }
@@ -123,6 +135,26 @@ impl EventHandler {
); );
return Ok(EventOutcome::Ok(message)); return Ok(EventOutcome::Ok(message));
} }
// --- Buffer Switching (Check Global) ---
if !matches!(current_mode, AppMode::Edit | AppMode::Command) {
if let Some(action) = config.get_action_for_key_in_mode(
&config.keybindings.global, key_code, modifiers // Check global bindings
) {
match action {
"next_buffer" => {
if buffer::switch_buffer(buffer_state, true) {
return Ok(EventOutcome::Ok("Switched to next buffer".to_string()));
}
}
"previous_buffer" => {
if buffer::switch_buffer(buffer_state, false) {
return Ok(EventOutcome::Ok("Switched to previous buffer".to_string()));
}
}
_ => {} // Other global actions could be handled here if needed
}
}
}
// --- End Global UI Toggles --- // --- End Global UI Toggles ---
match current_mode { match current_mode {
@@ -145,7 +177,7 @@ impl EventHandler {
let mut message = String::from("Selected"); // Default message let mut message = String::from("Selected"); // Default message
match context { match context {
UiContext::Intro => { UiContext::Intro => {
intro::handle_intro_selection(app_state, index); intro::handle_intro_selection(app_state, buffer_state, index);
if app_state.ui.show_admin { if app_state.ui.show_admin {
let profile_names = app_state.profile_tree.profiles.iter() let profile_names = app_state.profile_tree.profiles.iter()
.map(|p| p.name.clone()) .map(|p| p.name.clone())
@@ -157,14 +189,14 @@ impl EventHandler {
UiContext::Login => { UiContext::Login => {
message = match index { message = match index {
0 => login::save(auth_state, login_state, &mut self.auth_client, app_state).await?, 0 => login::save(auth_state, login_state, &mut self.auth_client, app_state).await?,
1 => login::back_to_main(login_state, app_state).await, 1 => login::back_to_main(login_state, app_state, buffer_state).await,
_ => "Invalid Login Option".to_string(), _ => "Invalid Login Option".to_string(),
}; };
} }
UiContext::Register => { UiContext::Register => {
message = match index { message = match index {
0 => register::save(register_state, &mut self.auth_client, app_state).await?, 0 => register::save(register_state, &mut self.auth_client, app_state).await?,
1 => register::back_to_main(register_state, app_state).await, 1 => register::back_to_login(register_state, app_state, buffer_state).await,
_ => "Invalid Login Option".to_string(), _ => "Invalid Login Option".to_string(),
}; };
} }

View File

@@ -1,3 +1,4 @@
// src/state/app.rs // src/state/app.rs
pub mod state; pub mod state;
pub mod buffer;

View File

@@ -0,0 +1,92 @@
// src/state/app/buffer.rs
/// Represents the distinct views or "buffers" the user can navigate.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AppView {
Intro,
Login,
Register,
Admin,
Form(String),
Scratch,
}
impl AppView {
/// Returns the display name for the view.
pub fn display_name(&self) -> &str {
match self {
AppView::Intro => "Intro",
AppView::Login => "Login",
AppView::Register => "Register",
AppView::Admin => "Admin Panel",
AppView::Form(name) => name.as_str(),
AppView::Scratch => "*scratch*",
}
}
}
/// Holds the state related to buffer management (navigation history).
#[derive(Debug, Clone)]
pub struct BufferState {
pub history: Vec<AppView>,
pub active_index: usize,
}
impl Default for BufferState {
fn default() -> Self {
Self {
history: vec![AppView::Intro], // Start with Intro view
active_index: 0,
}
}
}
impl BufferState {
/// Updates the buffer history and active index.
/// If the view already exists, it sets it as active.
/// Otherwise, it adds the new view and makes it active.
pub fn update_history(&mut self, view: AppView) {
let existing_pos = self.history.iter().position(|v| v == &view);
match existing_pos {
Some(pos) => {
self.active_index = pos;
}
None => {
self.history.push(view.clone());
self.active_index = self.history.len() - 1;
}
}
}
/// Gets the currently active view.
pub fn get_active_view(&self) -> Option<&AppView> {
self.history.get(self.active_index)
}
/// Removes the currently active buffer from the history, unless it's the Intro buffer.
/// Sets the new active buffer to the one preceding the closed one.
/// # Returns
/// * `true` if a non-Intro buffer was closed.
/// * `false` if the active buffer was Intro or only Intro remained.
pub fn close_active_buffer(&mut self) -> bool {
let current_index = self.active_index;
// Rule 1: Cannot close Intro buffer.
if matches!(self.history.get(current_index), Some(AppView::Intro)) {
return false;
}
// Rule 2: Cannot close if only Intro would remain (or already remains).
// This check implicitly covers the case where len <= 1.
if self.history.len() <= 1 {
return false;
}
self.history.remove(current_index);
self.active_index = current_index.saturating_sub(1).min(self.history.len() - 1);
true
}
}

View File

@@ -5,31 +5,6 @@ use common::proto::multieko2::table_definition::ProfileTreeResponse;
use crate::modes::handlers::mode_manager::AppMode; use crate::modes::handlers::mode_manager::AppMode;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
/// Represents the distinct views or "buffers" the user can navigate.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AppView {
Intro,
Login,
Register,
Admin,
Form(String),
Scratch,
}
impl AppView {
/// Returns the display name for the view.
pub fn display_name(&self) -> &str {
match self {
AppView::Intro => "Intro",
AppView::Login => "Login",
AppView::Register => "Register",
AppView::Admin => "Admin Panel",
AppView::Form(name) => name.as_str(),
AppView::Scratch => "*scratch*",
}
}
}
pub struct DialogState { pub struct DialogState {
pub dialog_show: bool, pub dialog_show: bool,
pub dialog_title: String, pub dialog_title: String,
@@ -48,8 +23,6 @@ pub struct UiState {
pub show_login: bool, pub show_login: bool,
pub show_register: bool, pub show_register: bool,
pub focus_outside_canvas: bool, pub focus_outside_canvas: bool,
pub buffer_history: Vec<AppView>,
pub active_buffer_index: usize,
pub dialog: DialogState, pub dialog: DialogState,
} }
@@ -158,16 +131,14 @@ impl AppState {
impl Default for UiState { impl Default for UiState {
fn default() -> Self { fn default() -> Self {
Self { Self {
show_sidebar: true, show_sidebar: false,
show_intro: true, show_intro: true,
show_admin: false, show_admin: false,
show_form: false, show_form: false,
show_login: false, show_login: false,
show_register: false, show_register: false,
show_buffer_list: false, show_buffer_list: true,
focus_outside_canvas: false, focus_outside_canvas: false,
buffer_history: vec![AppView::Intro],
active_buffer_index: 0,
dialog: DialogState::default(), dialog: DialogState::default(),
} }
} }
@@ -186,20 +157,3 @@ impl Default for DialogState {
} }
} }
} }
impl UiState {
/// Updates the buffer history and active index.
pub fn update_buffer_history(&mut self, view: AppView) {
let existing_pos = self.buffer_history.iter().position(|v| v == &view);
match existing_pos {
Some(pos) => {
self.active_buffer_index = pos;
}
None => {
self.buffer_history.push(view.clone());
self.active_buffer_index = self.buffer_history.len() - 1;
}
}
}
}

View File

@@ -4,6 +4,7 @@ use crate::services::auth::AuthClient;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState; use crate::state::pages::auth::LoginState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::app::buffer::{AppView, BufferState};
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState;
use crate::ui::handlers::context::DialogPurpose; use crate::ui::handlers::context::DialogPurpose;
@@ -87,6 +88,7 @@ pub async fn revert(
pub async fn back_to_main( pub async fn back_to_main(
login_state: &mut LoginState, login_state: &mut LoginState,
app_state: &mut AppState, app_state: &mut AppState,
buffer_state: &mut BufferState,
) -> String { ) -> String {
// Clear the input fields // Clear the input fields
login_state.username.clear(); login_state.username.clear();
@@ -97,9 +99,9 @@ pub async fn back_to_main(
// Ensure dialog is hidden if revert is called // Ensure dialog is hidden if revert is called
app_state.hide_dialog(); // Uncomment if needed app_state.hide_dialog(); // Uncomment if needed
// Navigation logic (currently disabled in original code) // Navigation logic
app_state.ui.show_login = false; buffer_state.close_active_buffer();
app_state.ui.show_intro = true; buffer_state.update_history(AppView::Intro);
// Reset focus state // Reset focus state
app_state.ui.focus_outside_canvas = false; app_state.ui.focus_outside_canvas = false;

View File

@@ -1,14 +1,13 @@
// src/tui/functions/common/register.rs // src/tui/functions/common/register.rs
use crate::{ use crate::services::auth::AuthClient;
services::auth::AuthClient, use crate::state::{
state::{ pages::auth::RegisterState,
pages::auth::RegisterState, app::state::AppState,
app::state::AppState, pages::canvas_state::CanvasState,
pages::canvas_state::CanvasState,
},
ui::handlers::context::DialogPurpose,
}; };
use crate::ui::handlers::context::DialogPurpose;
use crate::state::app::buffer::{AppView, BufferState};
/// Attempts to register the user using the provided details via gRPC. /// Attempts to register the user using the provided details via gRPC.
/// Updates RegisterState and AppState on success or failure. /// Updates RegisterState and AppState on success or failure.
@@ -131,9 +130,10 @@ pub async fn revert(
} }
/// Clears the form and returns to the intro screen. /// Clears the form and returns to the intro screen.
pub async fn back_to_main( pub async fn back_to_login(
register_state: &mut RegisterState, register_state: &mut RegisterState,
app_state: &mut AppState, app_state: &mut AppState,
buffer_state: &mut BufferState,
) -> String { ) -> String {
// Clear fields first // Clear fields first
let _ = revert(register_state, app_state).await; let _ = revert(register_state, app_state).await;
@@ -142,8 +142,8 @@ pub async fn back_to_main(
app_state.hide_dialog(); app_state.hide_dialog();
// Navigation logic // Navigation logic
app_state.ui.show_register = false; buffer_state.close_active_buffer();
app_state.ui.show_intro = true; buffer_state.update_history(AppView::Login);
// Reset focus state // Reset focus state
app_state.ui.focus_outside_canvas = false; app_state.ui.focus_outside_canvas = false;

View File

@@ -1,31 +1,33 @@
// src/tui/functions/intro.rs // src/tui/functions/intro.rs
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::app::buffer::{AppView, BufferState};
pub fn handle_intro_selection(app_state: &mut AppState, index: usize) { /// Handles intro screen selection by updating view history and managing focus state.
match index { // Use index directly /// 0: Continue (restores last form or default)
0 => { // Continue /// 1: Admin view
app_state.ui.show_form = true; /// 2: Login view
app_state.ui.show_admin = false; /// 3: Register view (with focus reset)
app_state.ui.show_login = false; pub fn handle_intro_selection(
app_state: &mut AppState,
buffer_state: &mut BufferState,
index: usize,
) {
let target_view = match index {
0 => {
let form_name = app_state.selected_profile.clone().unwrap_or_else(|| "Data Form".to_string());
AppView::Form(form_name)
} }
1 => { // Admin 1 => AppView::Admin,
app_state.ui.show_form = false; 2 => AppView::Login,
app_state.ui.show_admin = true; 3 => AppView::Register,
app_state.ui.show_login = false; _ => return,
} };
2 => { // Login
app_state.ui.show_form = false; buffer_state.update_history(target_view);
app_state.ui.show_admin = false;
app_state.ui.show_login = true; // Register view requires focus reset
} if index == 3 {
3 => { // Register app_state.ui.focus_outside_canvas = false;
app_state.ui.show_intro = false; app_state.focused_button_index = 0;
app_state.ui.show_register = true;
app_state.ui.focus_outside_canvas = false;
app_state.focused_button_index = 0;
}
_ => {}
} }
app_state.ui.show_intro = false;
} }

View File

@@ -18,6 +18,7 @@ use crate::state::pages::auth::AuthState;
use crate::state::pages::auth::LoginState; use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState; use crate::state::pages::auth::RegisterState;
use crate::state::pages::intro::IntroState; use crate::state::pages::intro::IntroState;
use crate::state::app::buffer::BufferState;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminState;
@@ -29,6 +30,7 @@ pub fn render_ui(
register_state: &RegisterState, register_state: &RegisterState,
intro_state: &IntroState, intro_state: &IntroState,
admin_state: &mut AdminState, admin_state: &mut AdminState,
buffer_state: &BufferState,
theme: &Theme, theme: &Theme,
is_edit_mode: bool, is_edit_mode: bool,
total_count: u64, total_count: u64,
@@ -37,6 +39,7 @@ pub fn render_ui(
command_input: &str, command_input: &str,
command_mode: bool, command_mode: bool,
command_message: &str, command_message: &str,
current_fps: f64,
app_state: &AppState, app_state: &AppState,
) { ) {
render_background(f, f.area(), theme); render_background(f, f.area(), theme);
@@ -170,9 +173,9 @@ pub fn render_ui(
// Render buffer list if enabled and area is available // Render buffer list if enabled and area is available
if let Some(area) = buffer_list_area { if let Some(area) = buffer_list_area {
if app_state.ui.show_buffer_list { if app_state.ui.show_buffer_list {
render_buffer_list(f, area, theme, app_state); 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); render_command_line(f, command_line_area, command_input, command_mode, theme, command_message);
} }

View File

@@ -2,11 +2,11 @@
use crate::config::binds::config::Config; use crate::config::binds::config::Config;
use crate::config::colors::themes::Theme; use crate::config::colors::themes::Theme;
use crate::modes::common::commands::CommandHandler;
use crate::modes::handlers::event::{EventHandler, EventOutcome}; // Import EventOutcome
use crate::modes::handlers::mode_manager::{AppMode, ModeManager};
use crate::services::grpc_client::GrpcClient; use crate::services::grpc_client::GrpcClient;
use crate::services::ui_service::UiService; use crate::services::ui_service::UiService;
use crate::modes::common::commands::CommandHandler;
use crate::modes::handlers::event::{EventHandler, EventOutcome};
use crate::modes::handlers::mode_manager::{AppMode, ModeManager};
use crate::state::pages::canvas_state::CanvasState; use crate::state::pages::canvas_state::CanvasState;
use crate::state::pages::form::FormState; use crate::state::pages::form::FormState;
use crate::state::pages::auth::AuthState; use crate::state::pages::auth::AuthState;
@@ -14,10 +14,13 @@ use crate::state::pages::auth::LoginState;
use crate::state::pages::auth::RegisterState; use crate::state::pages::auth::RegisterState;
use crate::state::pages::admin::AdminState; use crate::state::pages::admin::AdminState;
use crate::state::pages::intro::IntroState; use crate::state::pages::intro::IntroState;
use crate::state::app::buffer::BufferState;
use crate::state::app::buffer::AppView;
use crate::state::app::state::AppState; use crate::state::app::state::AppState;
// Import SaveOutcome // Import SaveOutcome
use crate::tui::terminal::{EventReader, TerminalCore}; use crate::tui::terminal::{EventReader, TerminalCore};
use crate::ui::handlers::render::render_ui; use crate::ui::handlers::render::render_ui;
use std::time::Instant;
use crossterm::cursor::SetCursorStyle; use crossterm::cursor::SetCursorStyle;
pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> { pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
@@ -35,6 +38,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
let mut register_state = RegisterState::default(); let mut register_state = RegisterState::default();
let mut intro_state = IntroState::default(); let mut intro_state = IntroState::default();
let mut admin_state = AdminState::default(); let mut admin_state = AdminState::default();
let mut buffer_state = BufferState::default();
let mut app_state = AppState::new()?; let mut app_state = AppState::new()?;
// Initialize app state with profile tree and table structure // Initialize app state with profile tree and table structure
@@ -48,10 +52,33 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
.await?; .await?;
form_state.reset_to_empty(); form_state.reset_to_empty();
// --- FPS Calculation State ---
let mut last_frame_time = Instant::now();
let mut current_fps = 0.0;
loop { loop {
// Determine edit mode based on EventHandler state // Determine edit mode based on EventHandler state
let is_edit_mode = event_handler.is_edit_mode; let is_edit_mode = event_handler.is_edit_mode;
// --- Synchronize UI View from Active Buffer ---
if let Some(active_view) = buffer_state.get_active_view() {
// Reset all flags first
app_state.ui.show_intro = false;
app_state.ui.show_login = false;
app_state.ui.show_register = false;
app_state.ui.show_admin = false;
app_state.ui.show_form = false;
match active_view {
AppView::Intro => app_state.ui.show_intro = true,
AppView::Login => app_state.ui.show_login = true,
AppView::Register => app_state.ui.show_register = true,
AppView::Admin => app_state.ui.show_admin = true,
AppView::Form(_) => app_state.ui.show_form = true,
AppView::Scratch => {} // Or show a scratchpad component
}
}
// --- End Synchronization ---
terminal.draw(|f| { terminal.draw(|f| {
render_ui( render_ui(
f, f,
@@ -61,6 +88,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
&register_state, &register_state,
&intro_state, &intro_state,
&mut admin_state, &mut admin_state,
&buffer_state,
&theme, &theme,
is_edit_mode, is_edit_mode,
app_state.total_count, app_state.total_count,
@@ -69,6 +97,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
&event_handler.command_input, &event_handler.command_input,
event_handler.command_mode, event_handler.command_mode,
&event_handler.command_message, &event_handler.command_message,
current_fps,
&app_state, &app_state,
); );
})?; })?;
@@ -122,6 +151,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
&mut register_state, &mut register_state,
&mut intro_state, &mut intro_state,
&mut admin_state, &mut admin_state,
&mut buffer_state,
&mut app_state, &mut app_state,
total_count, total_count,
&mut current_position, &mut current_position,
@@ -278,6 +308,14 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
// terminal.cleanup()?; // Optional: Drop handles this // terminal.cleanup()?; // Optional: Drop handles this
return Ok(()); 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();
}
} }
} }