Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4689125e0 | ||
|
|
bbba67a253 | ||
|
|
800b857e53 | ||
|
|
921059bed8 | ||
|
|
e8b585dc07 | ||
|
|
afce27184a | ||
|
|
8d41beef0b | ||
|
|
5e482cd77b | ||
|
|
09068fd4e5 | ||
|
|
d8c2b9089b | ||
|
|
bb577bc276 | ||
|
|
6267e3d593 | ||
|
|
c091a39802 | ||
|
|
f94006dd08 |
23
Cargo.lock
generated
23
Cargo.lock
generated
@@ -421,7 +421,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client"
|
||||
version = "0.3.5"
|
||||
version = "0.3.13"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"common",
|
||||
@@ -432,6 +432,7 @@ dependencies = [
|
||||
"prost",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"time",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tonic",
|
||||
@@ -461,7 +462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "common"
|
||||
version = "0.3.5"
|
||||
version = "0.3.13"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"serde",
|
||||
@@ -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",
|
||||
]
|
||||
@@ -2592,7 +2593,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "server"
|
||||
version = "0.3.5"
|
||||
version = "0.3.13"
|
||||
dependencies = [
|
||||
"bcrypt",
|
||||
"chrono",
|
||||
@@ -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",
|
||||
|
||||
@@ -5,7 +5,7 @@ resolver = "2"
|
||||
[workspace.package]
|
||||
# TODO: idk how to do the name, fix later
|
||||
# name = "Multieko2"
|
||||
version = "0.3.5"
|
||||
version = "0.3.13"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Filip Priečinský <filippriec@gmail.com>"]
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Hey
|
||||
|
||||
This is only work in progress, until release 1.0.0 this is for development use cases only.
|
||||
|
||||
I run development like this:
|
||||
|
||||
Server:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
[keybindings]
|
||||
|
||||
enter_command_mode = [":", "ctrl+;"]
|
||||
next_buffer = ["ctrl+l"]
|
||||
previous_buffer = ["ctrl+h"]
|
||||
|
||||
[keybindings.general]
|
||||
move_up = ["k", "Up"]
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/components/handlers/buffer_list.rs
|
||||
|
||||
use crate::config::colors::themes::Theme;
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::state::app::buffer::BufferState;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Rect},
|
||||
style::{Style, Stylize},
|
||||
@@ -10,12 +10,13 @@ use ratatui::{
|
||||
Frame,
|
||||
};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use crate::functions::common::buffer::get_view_layer;
|
||||
|
||||
pub fn render_buffer_list(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
theme: &Theme,
|
||||
app_state: &AppState,
|
||||
buffer_state: &BufferState,
|
||||
) {
|
||||
// --- Style Definitions ---
|
||||
let active_style = Style::default()
|
||||
@@ -26,13 +27,23 @@ pub fn render_buffer_list(
|
||||
.fg(theme.fg)
|
||||
.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 ---
|
||||
let mut spans = Vec::new();
|
||||
let history = &app_state.ui.buffer_history;
|
||||
let mut current_width = 0;
|
||||
|
||||
for (i, view) in history.iter().enumerate() {
|
||||
let is_active = i == app_state.ui.active_buffer_index;
|
||||
for (original_index, view) in buffer_state.history.iter().enumerate() {
|
||||
// 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_text = format!(" {} ", buffer_name);
|
||||
let text_width = UnicodeWidthStr::width(buffer_text.as_str());
|
||||
@@ -51,10 +62,12 @@ pub fn render_buffer_list(
|
||||
|
||||
// --- Filler Span ---
|
||||
let remaining_width = area.width.saturating_sub(current_width as u16);
|
||||
spans.push(Span::styled(
|
||||
" ".repeat(remaining_width as usize),
|
||||
inactive_style,
|
||||
));
|
||||
if !spans.is_empty() || remaining_width > 0 {
|
||||
spans.push(Span::styled(
|
||||
" ".repeat(remaining_width as usize),
|
||||
inactive_style,
|
||||
));
|
||||
}
|
||||
|
||||
// --- Render ---
|
||||
let buffer_line = Line::from(spans);
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
// src/functions/common.rs
|
||||
|
||||
pub mod buffer;
|
||||
|
||||
pub use buffer::*;
|
||||
|
||||
35
client/src/functions/common/buffer.rs
Normal file
35
client/src/functions/common/buffer.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crossterm::event::{Event, KeyCode};
|
||||
use crate::config::binds::config::Config;
|
||||
use crate::ui::handlers::context::DialogPurpose;
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::state::app::buffer::BufferState;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::pages::auth::LoginState;
|
||||
use crate::state::pages::auth::RegisterState;
|
||||
@@ -20,6 +21,7 @@ pub async fn handle_dialog_event(
|
||||
auth_state: &mut AuthState,
|
||||
login_state: &mut LoginState,
|
||||
register_state: &mut RegisterState,
|
||||
buffer_state: &mut BufferState,
|
||||
) -> Option<Result<EventOutcome, Box<dyn std::error::Error>>> {
|
||||
if let Event::Key(key) = event {
|
||||
// Always allow Esc to dismiss
|
||||
@@ -62,7 +64,7 @@ pub async fn handle_dialog_event(
|
||||
match selected_index {
|
||||
0 => { // "Menu" button selected
|
||||
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)));
|
||||
}
|
||||
1 => {
|
||||
@@ -91,8 +93,7 @@ pub async fn handle_dialog_event(
|
||||
match selected_index {
|
||||
0 => { // "OK" button for RegisterSuccess
|
||||
app_state.hide_dialog();
|
||||
// Go back to intro after successful registration dialog
|
||||
let message = register::back_to_main(register_state, app_state).await;
|
||||
let message = register::back_to_login(register_state, app_state, buffer_state).await;
|
||||
return Some(Ok(EventOutcome::Ok(message)));
|
||||
}
|
||||
_ => { // Default for RegisterSuccess
|
||||
|
||||
@@ -1,32 +1,43 @@
|
||||
// src/modes/handlers/event.rs
|
||||
use crossterm::event::Event;
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use crate::tui::terminal::core::TerminalCore;
|
||||
use crate::services::grpc_client::GrpcClient;
|
||||
use crate::services::auth::AuthClient;
|
||||
use crate::modes::common::commands::CommandHandler;
|
||||
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::context::UiContext;
|
||||
use crate::tui::functions::{intro, admin};
|
||||
use crate::tui::functions::common::{login, register};
|
||||
use crate::functions::common::buffer;
|
||||
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::{
|
||||
common::command_mode,
|
||||
common::{command_mode, commands::CommandHandler},
|
||||
handlers::mode_manager::{ModeManager, AppMode},
|
||||
canvas::{edit, read_only, common_mode},
|
||||
general::{navigation, dialog},
|
||||
};
|
||||
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)]
|
||||
pub enum EventOutcome {
|
||||
@@ -74,6 +85,7 @@ impl EventHandler {
|
||||
register_state: &mut RegisterState,
|
||||
intro_state: &mut IntroState,
|
||||
admin_state: &mut AdminState,
|
||||
buffer_state: &mut BufferState,
|
||||
app_state: &mut AppState,
|
||||
total_count: u64,
|
||||
current_position: &mut u64,
|
||||
@@ -94,12 +106,12 @@ impl EventHandler {
|
||||
}
|
||||
else { AppView::Scratch }
|
||||
};
|
||||
app_state.ui.update_buffer_history(current_view);
|
||||
buffer_state.update_history(current_view);
|
||||
|
||||
// --- DIALOG MODALITY ---
|
||||
if app_state.ui.dialog.dialog_show {
|
||||
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 {
|
||||
return dialog_result;
|
||||
}
|
||||
@@ -123,6 +135,26 @@ impl EventHandler {
|
||||
);
|
||||
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 ---
|
||||
|
||||
match current_mode {
|
||||
@@ -145,7 +177,7 @@ impl EventHandler {
|
||||
let mut message = String::from("Selected"); // Default message
|
||||
match context {
|
||||
UiContext::Intro => {
|
||||
intro::handle_intro_selection(app_state, index);
|
||||
intro::handle_intro_selection(app_state, buffer_state, index);
|
||||
if app_state.ui.show_admin {
|
||||
let profile_names = app_state.profile_tree.profiles.iter()
|
||||
.map(|p| p.name.clone())
|
||||
@@ -157,14 +189,14 @@ impl EventHandler {
|
||||
UiContext::Login => {
|
||||
message = match index {
|
||||
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(),
|
||||
};
|
||||
}
|
||||
UiContext::Register => {
|
||||
message = match index {
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// src/state/app.rs
|
||||
|
||||
pub mod state;
|
||||
pub mod buffer;
|
||||
|
||||
92
client/src/state/app/buffer.rs
Normal file
92
client/src/state/app/buffer.rs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,31 +5,6 @@ use common::proto::multieko2::table_definition::ProfileTreeResponse;
|
||||
use crate::modes::handlers::mode_manager::AppMode;
|
||||
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 dialog_show: bool,
|
||||
pub dialog_title: String,
|
||||
@@ -48,8 +23,6 @@ pub struct UiState {
|
||||
pub show_login: bool,
|
||||
pub show_register: bool,
|
||||
pub focus_outside_canvas: bool,
|
||||
pub buffer_history: Vec<AppView>,
|
||||
pub active_buffer_index: usize,
|
||||
pub dialog: DialogState,
|
||||
}
|
||||
|
||||
@@ -158,16 +131,14 @@ impl AppState {
|
||||
impl Default for UiState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
show_sidebar: true,
|
||||
show_sidebar: false,
|
||||
show_intro: true,
|
||||
show_admin: false,
|
||||
show_form: false,
|
||||
show_login: false,
|
||||
show_register: false,
|
||||
show_buffer_list: false,
|
||||
show_buffer_list: true,
|
||||
focus_outside_canvas: false,
|
||||
buffer_history: vec![AppView::Intro],
|
||||
active_buffer_index: 0,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::services::auth::AuthClient;
|
||||
use crate::state::pages::auth::AuthState;
|
||||
use crate::state::pages::auth::LoginState;
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::state::app::buffer::{AppView, BufferState};
|
||||
use crate::state::pages::canvas_state::CanvasState;
|
||||
use crate::ui::handlers::context::DialogPurpose;
|
||||
|
||||
@@ -87,6 +88,7 @@ pub async fn revert(
|
||||
pub async fn back_to_main(
|
||||
login_state: &mut LoginState,
|
||||
app_state: &mut AppState,
|
||||
buffer_state: &mut BufferState,
|
||||
) -> String {
|
||||
// Clear the input fields
|
||||
login_state.username.clear();
|
||||
@@ -97,9 +99,9 @@ pub async fn back_to_main(
|
||||
// Ensure dialog is hidden if revert is called
|
||||
app_state.hide_dialog(); // Uncomment if needed
|
||||
|
||||
// Navigation logic (currently disabled in original code)
|
||||
app_state.ui.show_login = false;
|
||||
app_state.ui.show_intro = true;
|
||||
// Navigation logic
|
||||
buffer_state.close_active_buffer();
|
||||
buffer_state.update_history(AppView::Intro);
|
||||
|
||||
// Reset focus state
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
// src/tui/functions/common/register.rs
|
||||
|
||||
use crate::{
|
||||
services::auth::AuthClient,
|
||||
state::{
|
||||
pages::auth::RegisterState,
|
||||
app::state::AppState,
|
||||
pages::canvas_state::CanvasState,
|
||||
},
|
||||
ui::handlers::context::DialogPurpose,
|
||||
use crate::services::auth::AuthClient;
|
||||
use crate::state::{
|
||||
pages::auth::RegisterState,
|
||||
app::state::AppState,
|
||||
pages::canvas_state::CanvasState,
|
||||
};
|
||||
use crate::ui::handlers::context::DialogPurpose;
|
||||
use crate::state::app::buffer::{AppView, BufferState};
|
||||
|
||||
/// Attempts to register the user using the provided details via gRPC.
|
||||
/// 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.
|
||||
pub async fn back_to_main(
|
||||
pub async fn back_to_login(
|
||||
register_state: &mut RegisterState,
|
||||
app_state: &mut AppState,
|
||||
buffer_state: &mut BufferState,
|
||||
) -> String {
|
||||
// Clear fields first
|
||||
let _ = revert(register_state, app_state).await;
|
||||
@@ -142,8 +142,8 @@ pub async fn back_to_main(
|
||||
app_state.hide_dialog();
|
||||
|
||||
// Navigation logic
|
||||
app_state.ui.show_register = false;
|
||||
app_state.ui.show_intro = true;
|
||||
buffer_state.close_active_buffer();
|
||||
buffer_state.update_history(AppView::Login);
|
||||
|
||||
// Reset focus state
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
// src/tui/functions/intro.rs
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::state::app::buffer::{AppView, BufferState};
|
||||
|
||||
pub fn handle_intro_selection(app_state: &mut AppState, index: usize) {
|
||||
match index { // Use index directly
|
||||
0 => { // Continue
|
||||
app_state.ui.show_form = true;
|
||||
app_state.ui.show_admin = false;
|
||||
app_state.ui.show_login = false;
|
||||
/// Handles intro screen selection by updating view history and managing focus state.
|
||||
/// 0: Continue (restores last form or default)
|
||||
/// 1: Admin view
|
||||
/// 2: Login view
|
||||
/// 3: Register view (with focus reset)
|
||||
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
|
||||
app_state.ui.show_form = false;
|
||||
app_state.ui.show_admin = true;
|
||||
app_state.ui.show_login = false;
|
||||
}
|
||||
2 => { // Login
|
||||
app_state.ui.show_form = false;
|
||||
app_state.ui.show_admin = false;
|
||||
app_state.ui.show_login = true;
|
||||
}
|
||||
3 => { // Register
|
||||
app_state.ui.show_intro = false;
|
||||
app_state.ui.show_register = true;
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
app_state.focused_button_index = 0;
|
||||
}
|
||||
_ => {}
|
||||
1 => AppView::Admin,
|
||||
2 => AppView::Login,
|
||||
3 => AppView::Register,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
buffer_state.update_history(target_view);
|
||||
|
||||
// Register view requires focus reset
|
||||
if index == 3 {
|
||||
app_state.ui.focus_outside_canvas = false;
|
||||
app_state.focused_button_index = 0;
|
||||
}
|
||||
app_state.ui.show_intro = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ 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::app::buffer::BufferState;
|
||||
use crate::state::app::state::AppState;
|
||||
use crate::state::pages::admin::AdminState;
|
||||
|
||||
@@ -29,6 +30,7 @@ pub fn render_ui(
|
||||
register_state: &RegisterState,
|
||||
intro_state: &IntroState,
|
||||
admin_state: &mut AdminState,
|
||||
buffer_state: &BufferState,
|
||||
theme: &Theme,
|
||||
is_edit_mode: bool,
|
||||
total_count: u64,
|
||||
@@ -37,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);
|
||||
@@ -170,9 +173,9 @@ pub fn render_ui(
|
||||
// Render buffer list if enabled and area is available
|
||||
if let Some(area) = buffer_list_area {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
use crate::config::binds::config::Config;
|
||||
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::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::form::FormState;
|
||||
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::admin::AdminState;
|
||||
use crate::state::pages::intro::IntroState;
|
||||
use crate::state::app::buffer::BufferState;
|
||||
use crate::state::app::buffer::AppView;
|
||||
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<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 intro_state = IntroState::default();
|
||||
let mut admin_state = AdminState::default();
|
||||
let mut buffer_state = BufferState::default();
|
||||
let mut app_state = AppState::new()?;
|
||||
|
||||
// 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?;
|
||||
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;
|
||||
|
||||
// --- 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| {
|
||||
render_ui(
|
||||
f,
|
||||
@@ -61,6 +88,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||
®ister_state,
|
||||
&intro_state,
|
||||
&mut admin_state,
|
||||
&buffer_state,
|
||||
&theme,
|
||||
is_edit_mode,
|
||||
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_mode,
|
||||
&event_handler.command_message,
|
||||
current_fps,
|
||||
&app_state,
|
||||
);
|
||||
})?;
|
||||
@@ -122,6 +151,7 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||
&mut register_state,
|
||||
&mut intro_state,
|
||||
&mut admin_state,
|
||||
&mut buffer_state,
|
||||
&mut app_state,
|
||||
total_count,
|
||||
&mut current_position,
|
||||
@@ -278,6 +308,14 @@ pub async fn run_ui() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user