gui of canvas is from the canvas crate now
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -472,7 +472,7 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "canvas"
|
name = "canvas"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
@@ -555,7 +555,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "client"
|
name = "client"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -606,7 +606,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
"prost-types",
|
"prost-types",
|
||||||
@@ -2892,7 +2892,7 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "search"
|
name = "search"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
@@ -2991,7 +2991,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server"
|
name = "server"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bcrypt",
|
"bcrypt",
|
||||||
|
|||||||
@@ -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 = "komp_ac"
|
# name = "komp_ac"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
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>"]
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ categories.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true, optional = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
@@ -21,6 +21,10 @@ serde = { workspace = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio-test = "0.4.4"
|
tokio-test = "0.4.4"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
gui = ["ratatui"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "simple_login"
|
name = "simple_login"
|
||||||
path = "examples/simple_login.rs"
|
path = "examples/simple_login.rs"
|
||||||
|
|||||||
7
canvas/src/gui/mod.rs
Normal file
7
canvas/src/gui/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// canvas/src/gui/mod.rs
|
||||||
|
|
||||||
|
pub mod theme;
|
||||||
|
pub mod render;
|
||||||
|
|
||||||
|
pub use theme::CanvasTheme;
|
||||||
|
pub use render::render_canvas;
|
||||||
244
canvas/src/gui/render.rs
Normal file
244
canvas/src/gui/render.rs
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
// canvas/src/gui/render.rs
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||||
|
style::{Modifier, Style},
|
||||||
|
text::{Line, Span},
|
||||||
|
widgets::{Block, Borders, Paragraph},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
use crate::state::CanvasState;
|
||||||
|
use crate::modes::HighlightState;
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
use super::theme::CanvasTheme;
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
use std::cmp::{max, min};
|
||||||
|
|
||||||
|
/// Render canvas using the CanvasState trait and CanvasTheme
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
pub fn render_canvas<T: CanvasTheme>(
|
||||||
|
f: &mut Frame,
|
||||||
|
area: Rect,
|
||||||
|
form_state: &impl CanvasState,
|
||||||
|
theme: &T,
|
||||||
|
is_edit_mode: bool,
|
||||||
|
highlight_state: &HighlightState,
|
||||||
|
) -> Option<Rect> {
|
||||||
|
let fields: Vec<&str> = form_state.fields();
|
||||||
|
let current_field_idx = form_state.current_field();
|
||||||
|
let inputs: Vec<&String> = form_state.inputs();
|
||||||
|
|
||||||
|
render_canvas_impl(
|
||||||
|
f,
|
||||||
|
area,
|
||||||
|
&fields,
|
||||||
|
¤t_field_idx,
|
||||||
|
&inputs,
|
||||||
|
theme,
|
||||||
|
is_edit_mode,
|
||||||
|
highlight_state,
|
||||||
|
form_state.current_cursor_pos(),
|
||||||
|
form_state.has_unsaved_changes(),
|
||||||
|
|i| form_state.get_display_value_for_field(i).to_string(),
|
||||||
|
|i| form_state.has_display_override(i),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal implementation of canvas rendering
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
fn render_canvas_impl<T: CanvasTheme, F1, F2>(
|
||||||
|
f: &mut Frame,
|
||||||
|
area: Rect,
|
||||||
|
fields: &[&str],
|
||||||
|
current_field_idx: &usize,
|
||||||
|
inputs: &[&String],
|
||||||
|
theme: &T,
|
||||||
|
is_edit_mode: bool,
|
||||||
|
highlight_state: &HighlightState,
|
||||||
|
current_cursor_pos: usize,
|
||||||
|
has_unsaved_changes: bool,
|
||||||
|
get_display_value: F1,
|
||||||
|
has_display_override: F2,
|
||||||
|
) -> Option<Rect>
|
||||||
|
where
|
||||||
|
F1: Fn(usize) -> String,
|
||||||
|
F2: Fn(usize) -> bool,
|
||||||
|
{
|
||||||
|
let columns = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
|
||||||
|
.split(area);
|
||||||
|
|
||||||
|
let border_style = if has_unsaved_changes {
|
||||||
|
Style::default().fg(theme.warning())
|
||||||
|
} else if is_edit_mode {
|
||||||
|
Style::default().fg(theme.accent())
|
||||||
|
} else {
|
||||||
|
Style::default().fg(theme.secondary())
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_container = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.border_style(border_style)
|
||||||
|
.style(Style::default().bg(theme.bg()));
|
||||||
|
|
||||||
|
let input_block = Rect {
|
||||||
|
x: columns[1].x,
|
||||||
|
y: columns[1].y,
|
||||||
|
width: columns[1].width,
|
||||||
|
height: fields.len() as u16 + 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
f.render_widget(&input_container, input_block);
|
||||||
|
|
||||||
|
let input_area = input_container.inner(input_block);
|
||||||
|
let input_rows = Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints(vec![Constraint::Length(1); fields.len()])
|
||||||
|
.split(input_area);
|
||||||
|
|
||||||
|
let mut active_field_input_rect = None;
|
||||||
|
|
||||||
|
// Render field labels
|
||||||
|
for (i, field) in fields.iter().enumerate() {
|
||||||
|
let label = Paragraph::new(Line::from(Span::styled(
|
||||||
|
format!("{}:", field),
|
||||||
|
Style::default().fg(theme.fg()),
|
||||||
|
)));
|
||||||
|
f.render_widget(
|
||||||
|
label,
|
||||||
|
Rect {
|
||||||
|
x: columns[0].x,
|
||||||
|
y: input_block.y + 1 + i as u16,
|
||||||
|
width: columns[0].width,
|
||||||
|
height: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render field values
|
||||||
|
for (i, _input) in inputs.iter().enumerate() {
|
||||||
|
let is_active = i == *current_field_idx;
|
||||||
|
|
||||||
|
// Use the provided closure to get display value
|
||||||
|
let text = get_display_value(i);
|
||||||
|
let text_len = text.chars().count();
|
||||||
|
let line: Line;
|
||||||
|
|
||||||
|
match highlight_state {
|
||||||
|
HighlightState::Off => {
|
||||||
|
line = Line::from(Span::styled(
|
||||||
|
&text,
|
||||||
|
if is_active {
|
||||||
|
Style::default().fg(theme.highlight())
|
||||||
|
} else {
|
||||||
|
Style::default().fg(theme.fg())
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
HighlightState::Characterwise { anchor } => {
|
||||||
|
let (anchor_field, anchor_char) = *anchor;
|
||||||
|
let start_field = min(anchor_field, *current_field_idx);
|
||||||
|
let end_field = max(anchor_field, *current_field_idx);
|
||||||
|
|
||||||
|
let (start_char, end_char) = if anchor_field == *current_field_idx {
|
||||||
|
(min(anchor_char, current_cursor_pos), max(anchor_char, current_cursor_pos))
|
||||||
|
} else if anchor_field < *current_field_idx {
|
||||||
|
(anchor_char, current_cursor_pos)
|
||||||
|
} else {
|
||||||
|
(current_cursor_pos, anchor_char)
|
||||||
|
};
|
||||||
|
|
||||||
|
let highlight_style = Style::default()
|
||||||
|
.fg(theme.highlight())
|
||||||
|
.bg(theme.highlight_bg())
|
||||||
|
.add_modifier(Modifier::BOLD);
|
||||||
|
let normal_style_in_highlight = Style::default().fg(theme.highlight());
|
||||||
|
let normal_style_outside = Style::default().fg(theme.fg());
|
||||||
|
|
||||||
|
if i >= start_field && i <= end_field {
|
||||||
|
if start_field == end_field {
|
||||||
|
let clamped_start = start_char.min(text_len);
|
||||||
|
let clamped_end = end_char.min(text_len);
|
||||||
|
|
||||||
|
let before: String = text.chars().take(clamped_start).collect();
|
||||||
|
let highlighted: String = text.chars()
|
||||||
|
.skip(clamped_start)
|
||||||
|
.take(clamped_end.saturating_sub(clamped_start) + 1)
|
||||||
|
.collect();
|
||||||
|
let after: String = text.chars().skip(clamped_end + 1).collect();
|
||||||
|
|
||||||
|
line = Line::from(vec![
|
||||||
|
Span::styled(before, normal_style_in_highlight),
|
||||||
|
Span::styled(highlighted, highlight_style),
|
||||||
|
Span::styled(after, normal_style_in_highlight),
|
||||||
|
]);
|
||||||
|
} else if i == start_field {
|
||||||
|
let safe_start = start_char.min(text_len);
|
||||||
|
let before: String = text.chars().take(safe_start).collect();
|
||||||
|
let highlighted: String = text.chars().skip(safe_start).collect();
|
||||||
|
line = Line::from(vec![
|
||||||
|
Span::styled(before, normal_style_in_highlight),
|
||||||
|
Span::styled(highlighted, highlight_style),
|
||||||
|
]);
|
||||||
|
} else if i == end_field {
|
||||||
|
let safe_end_inclusive = if text_len > 0 { end_char.min(text_len - 1) } else { 0 };
|
||||||
|
let highlighted: String = text.chars().take(safe_end_inclusive + 1).collect();
|
||||||
|
let after: String = text.chars().skip(safe_end_inclusive + 1).collect();
|
||||||
|
line = Line::from(vec![
|
||||||
|
Span::styled(highlighted, highlight_style),
|
||||||
|
Span::styled(after, normal_style_in_highlight),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
line = Line::from(Span::styled(&text, highlight_style));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
line = Line::from(Span::styled(
|
||||||
|
&text,
|
||||||
|
if is_active { normal_style_in_highlight } else { normal_style_outside }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HighlightState::Linewise { anchor_line } => {
|
||||||
|
let start_field = min(*anchor_line, *current_field_idx);
|
||||||
|
let end_field = max(*anchor_line, *current_field_idx);
|
||||||
|
let highlight_style = Style::default()
|
||||||
|
.fg(theme.highlight())
|
||||||
|
.bg(theme.highlight_bg())
|
||||||
|
.add_modifier(Modifier::BOLD);
|
||||||
|
let normal_style_in_highlight = Style::default().fg(theme.highlight());
|
||||||
|
let normal_style_outside = Style::default().fg(theme.fg());
|
||||||
|
|
||||||
|
if i >= start_field && i <= end_field {
|
||||||
|
line = Line::from(Span::styled(&text, highlight_style));
|
||||||
|
} else {
|
||||||
|
line = Line::from(Span::styled(
|
||||||
|
&text,
|
||||||
|
if is_active { normal_style_in_highlight } else { normal_style_outside }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let input_display = Paragraph::new(line).alignment(Alignment::Left);
|
||||||
|
f.render_widget(input_display, input_rows[i]);
|
||||||
|
|
||||||
|
if is_active {
|
||||||
|
active_field_input_rect = Some(input_rows[i]);
|
||||||
|
|
||||||
|
// Use the provided closure to check for display override
|
||||||
|
let cursor_x = if has_display_override(i) {
|
||||||
|
// If an override exists, place the cursor at the end.
|
||||||
|
input_rows[i].x + text.chars().count() as u16
|
||||||
|
} else {
|
||||||
|
// Otherwise, use the real cursor position.
|
||||||
|
input_rows[i].x + current_cursor_pos as u16
|
||||||
|
};
|
||||||
|
let cursor_y = input_rows[i].y;
|
||||||
|
f.set_cursor_position((cursor_x, cursor_y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
active_field_input_rect
|
||||||
|
}
|
||||||
17
canvas/src/gui/theme.rs
Normal file
17
canvas/src/gui/theme.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// canvas/src/gui/theme.rs
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
|
/// Theme trait that must be implemented by applications using the canvas GUI
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
pub trait CanvasTheme {
|
||||||
|
fn bg(&self) -> Color;
|
||||||
|
fn fg(&self) -> Color;
|
||||||
|
fn border(&self) -> Color;
|
||||||
|
fn accent(&self) -> Color;
|
||||||
|
fn secondary(&self) -> Color;
|
||||||
|
fn highlight(&self) -> Color;
|
||||||
|
fn highlight_bg(&self) -> Color;
|
||||||
|
fn warning(&self) -> Color;
|
||||||
|
}
|
||||||
@@ -11,6 +11,10 @@ pub mod config;
|
|||||||
pub mod suggestions;
|
pub mod suggestions;
|
||||||
pub mod dispatcher;
|
pub mod dispatcher;
|
||||||
|
|
||||||
|
// GUI module (optional, enabled with "gui" feature)
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
pub mod gui;
|
||||||
|
|
||||||
// Re-export the main types for easy use
|
// Re-export the main types for easy use
|
||||||
pub use state::{CanvasState, ActionContext};
|
pub use state::{CanvasState, ActionContext};
|
||||||
pub use actions::{CanvasAction, ActionResult, execute_edit_action, execute_canvas_action};
|
pub use actions::{CanvasAction, ActionResult, execute_edit_action, execute_canvas_action};
|
||||||
@@ -18,6 +22,10 @@ pub use modes::{AppMode, ModeManager, HighlightState};
|
|||||||
pub use suggestions::SuggestionState;
|
pub use suggestions::SuggestionState;
|
||||||
pub use dispatcher::ActionDispatcher;
|
pub use dispatcher::ActionDispatcher;
|
||||||
|
|
||||||
|
// Re-export GUI types when available
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
pub use gui::{CanvasTheme, render_canvas};
|
||||||
|
|
||||||
// High-level convenience API
|
// High-level convenience API
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
@@ -33,4 +41,7 @@ pub mod prelude {
|
|||||||
HighlightState,
|
HighlightState,
|
||||||
SuggestionState,
|
SuggestionState,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "gui")]
|
||||||
|
pub use crate::{CanvasTheme, render_canvas};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ license.workspace = true
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
common = { path = "../common" }
|
common = { path = "../common" }
|
||||||
canvas = { path = "../canvas" }
|
canvas = { path = "../canvas", features = ["gui"] }
|
||||||
|
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
crossterm = { workspace = true }
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ move_up = ["Up"]
|
|||||||
move_down = ["Down"]
|
move_down = ["Down"]
|
||||||
toggle_sidebar = ["ctrl+t"]
|
toggle_sidebar = ["ctrl+t"]
|
||||||
toggle_buffer_list = ["ctrl+b"]
|
toggle_buffer_list = ["ctrl+b"]
|
||||||
|
revert = ["space+b+r"]
|
||||||
|
|
||||||
# MODE SPECIFIC
|
# MODE SPECIFIC
|
||||||
# READ ONLY MODE
|
# READ ONLY MODE
|
||||||
@@ -37,7 +38,6 @@ enter_edit_mode_before = ["i"]
|
|||||||
enter_edit_mode_after = ["a"]
|
enter_edit_mode_after = ["a"]
|
||||||
previous_entry = ["left","q"]
|
previous_entry = ["left","q"]
|
||||||
next_entry = ["right","1"]
|
next_entry = ["right","1"]
|
||||||
revert = ["space+b+r"]
|
|
||||||
|
|
||||||
move_left = ["h"]
|
move_left = ["h"]
|
||||||
move_right = ["l"]
|
move_right = ["l"]
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
// src/components/form/form.rs
|
// src/components/form/form.rs
|
||||||
use crate::components::common::autocomplete;
|
use crate::components::common::autocomplete;
|
||||||
use crate::components::handlers::canvas::render_canvas;
|
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::state::app::highlight::HighlightState;
|
use canvas::{CanvasState, render_canvas, HighlightState}; // CHANGED: Import HighlightState from canvas
|
||||||
use canvas::CanvasState;
|
|
||||||
use crate::state::pages::form::FormState;
|
use crate::state::pages::form::FormState;
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
|
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
|
||||||
@@ -15,14 +13,14 @@ use ratatui::{
|
|||||||
pub fn render_form(
|
pub fn render_form(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
form_state: &FormState, // <--- CHANGE THIS to the concrete type
|
form_state: &FormState,
|
||||||
fields: &[&str],
|
fields: &[&str],
|
||||||
current_field_idx: &usize,
|
current_field_idx: &usize,
|
||||||
inputs: &[&String],
|
inputs: &[&String],
|
||||||
table_name: &str,
|
table_name: &str,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
is_edit_mode: bool,
|
is_edit_mode: bool,
|
||||||
highlight_state: &HighlightState,
|
highlight_state: &HighlightState, // Now using canvas::HighlightState
|
||||||
total_count: u64,
|
total_count: u64,
|
||||||
current_position: u64,
|
current_position: u64,
|
||||||
) {
|
) {
|
||||||
@@ -63,27 +61,23 @@ pub fn render_form(
|
|||||||
.alignment(Alignment::Left);
|
.alignment(Alignment::Left);
|
||||||
f.render_widget(count_para, main_layout[0]);
|
f.render_widget(count_para, main_layout[0]);
|
||||||
|
|
||||||
// Get the active field's rect from render_canvas
|
// Use the canvas library's render_canvas function
|
||||||
let active_field_rect = crate::components::handlers::canvas::render_canvas_library(
|
let active_field_rect = render_canvas(
|
||||||
f,
|
f,
|
||||||
main_layout[1],
|
main_layout[1],
|
||||||
form_state,
|
form_state,
|
||||||
fields,
|
|
||||||
current_field_idx,
|
|
||||||
inputs,
|
|
||||||
theme,
|
theme,
|
||||||
is_edit_mode,
|
is_edit_mode,
|
||||||
highlight_state,
|
highlight_state,
|
||||||
);
|
);
|
||||||
|
|
||||||
// --- NEW: RENDER AUTOCOMPLETE ---
|
// --- RENDER AUTOCOMPLETE ---
|
||||||
if form_state.autocomplete_active {
|
if form_state.autocomplete_active {
|
||||||
if let Some(active_rect) = active_field_rect {
|
if let Some(active_rect) = active_field_rect {
|
||||||
let selected_index = form_state.get_selected_suggestion_index();
|
let selected_index = form_state.get_selected_suggestion_index();
|
||||||
|
|
||||||
if let Some(rich_suggestions) = form_state.get_rich_suggestions() {
|
if let Some(rich_suggestions) = form_state.get_rich_suggestions() {
|
||||||
if !rich_suggestions.is_empty() {
|
if !rich_suggestions.is_empty() {
|
||||||
// CHANGE THIS to call the renamed function
|
|
||||||
autocomplete::render_hit_autocomplete_dropdown(
|
autocomplete::render_hit_autocomplete_dropdown(
|
||||||
f,
|
f,
|
||||||
active_rect,
|
active_rect,
|
||||||
@@ -95,8 +89,6 @@ pub fn render_form(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The fallback to simple suggestions is now correctly handled
|
|
||||||
// because the original render_autocomplete_dropdown exists again.
|
|
||||||
else if let Some(simple_suggestions) = form_state.get_suggestions() {
|
else if let Some(simple_suggestions) = form_state.get_suggestions() {
|
||||||
if !simple_suggestions.is_empty() {
|
if !simple_suggestions.is_empty() {
|
||||||
autocomplete::render_autocomplete_dropdown(
|
autocomplete::render_autocomplete_dropdown(
|
||||||
@@ -112,4 +104,3 @@ pub fn render_form(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// src/client/themes/colors.rs
|
// src/config/colors/themes.rs
|
||||||
use ratatui::style::Color;
|
use ratatui::style::Color;
|
||||||
|
use canvas::CanvasTheme;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
@@ -74,3 +75,37 @@ impl Default for Theme {
|
|||||||
Self::light() // Default to light theme
|
Self::light() // Default to light theme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CanvasTheme for Theme {
|
||||||
|
fn bg(&self) -> Color {
|
||||||
|
self.bg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fg(&self) -> Color {
|
||||||
|
self.fg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn border(&self) -> Color {
|
||||||
|
self.border
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accent(&self) -> Color {
|
||||||
|
self.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
fn secondary(&self) -> Color {
|
||||||
|
self.secondary
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight(&self) -> Color {
|
||||||
|
self.highlight
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_bg(&self) -> Color {
|
||||||
|
self.highlight_bg
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warning(&self) -> Color {
|
||||||
|
self.warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
// src/state/pages/form.rs
|
// src/state/pages/form.rs
|
||||||
|
|
||||||
use crate::config::colors::themes::Theme;
|
use crate::config::colors::themes::Theme;
|
||||||
use crate::state::app::highlight::HighlightState;
|
use canvas::{CanvasState, CanvasAction, ActionContext, HighlightState};
|
||||||
use canvas::{CanvasState, CanvasAction, ActionContext}; // CHANGED: Use canvas crate
|
|
||||||
use common::proto::komp_ac::search::search_response::Hit;
|
use common::proto::komp_ac::search::search_response::Hit;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
@@ -113,7 +112,7 @@ impl FormState {
|
|||||||
area: Rect,
|
area: Rect,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
is_edit_mode: bool,
|
is_edit_mode: bool,
|
||||||
highlight_state: &HighlightState,
|
highlight_state: &HighlightState, // Now using canvas::HighlightState
|
||||||
) {
|
) {
|
||||||
let fields_str_slice: Vec<&str> =
|
let fields_str_slice: Vec<&str> =
|
||||||
self.fields().iter().map(|s| *s).collect();
|
self.fields().iter().map(|s| *s).collect();
|
||||||
@@ -146,7 +145,7 @@ impl FormState {
|
|||||||
} else {
|
} else {
|
||||||
self.current_position = 1;
|
self.current_position = 1;
|
||||||
}
|
}
|
||||||
self.deactivate_suggestions(); // CHANGED: Use canvas trait method
|
self.deactivate_suggestions();
|
||||||
self.link_display_map.clear();
|
self.link_display_map.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,12 +204,10 @@ impl FormState {
|
|||||||
self.has_unsaved_changes = false;
|
self.has_unsaved_changes = false;
|
||||||
self.current_field = 0;
|
self.current_field = 0;
|
||||||
self.current_cursor_pos = 0;
|
self.current_cursor_pos = 0;
|
||||||
self.deactivate_suggestions(); // CHANGED: Use canvas trait method
|
self.deactivate_suggestions();
|
||||||
self.link_display_map.clear();
|
self.link_display_map.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
// REMOVED: deactivate_autocomplete() - now using trait method
|
|
||||||
|
|
||||||
// NEW: Keep the rich suggestions methods for compatibility
|
// NEW: Keep the rich suggestions methods for compatibility
|
||||||
pub fn get_rich_suggestions(&self) -> Option<&[Hit]> {
|
pub fn get_rich_suggestions(&self) -> Option<&[Hit]> {
|
||||||
if self.autocomplete_active {
|
if self.autocomplete_active {
|
||||||
@@ -264,7 +261,7 @@ impl CanvasState for FormState {
|
|||||||
if index < self.fields.len() {
|
if index < self.fields.len() {
|
||||||
self.current_field = index;
|
self.current_field = index;
|
||||||
}
|
}
|
||||||
self.deactivate_suggestions(); // CHANGED: Use canvas trait method
|
self.deactivate_suggestions();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_current_cursor_pos(&mut self, pos: usize) {
|
fn set_current_cursor_pos(&mut self, pos: usize) {
|
||||||
@@ -312,18 +309,18 @@ impl CanvasState for FormState {
|
|||||||
match action {
|
match action {
|
||||||
CanvasAction::SelectSuggestion => {
|
CanvasAction::SelectSuggestion => {
|
||||||
if let Some(selected_idx) = self.selected_suggestion_index {
|
if let Some(selected_idx) = self.selected_suggestion_index {
|
||||||
if let Some(hit) = self.autocomplete_suggestions.get(selected_idx).cloned() { // ADD .cloned()
|
if let Some(hit) = self.autocomplete_suggestions.get(selected_idx).cloned() {
|
||||||
// Extract the value from the selected suggestion
|
// Extract the value from the selected suggestion
|
||||||
if let Ok(content_map) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&hit.content_json) {
|
if let Ok(content_map) = serde_json::from_str::<HashMap<String, serde_json::Value>>(&hit.content_json) {
|
||||||
let current_field_def = &self.fields[self.current_field];
|
let current_field_def = &self.fields[self.current_field];
|
||||||
if let Some(value) = content_map.get(¤t_field_def.data_key) {
|
if let Some(value) = content_map.get(¤t_field_def.data_key) {
|
||||||
let new_value = json_value_to_string(value);
|
let new_value = json_value_to_string(value);
|
||||||
let display_name = self.get_display_name_for_hit(&hit); // Calculate first
|
let display_name = self.get_display_name_for_hit(&hit);
|
||||||
*self.get_current_input_mut() = new_value.clone();
|
*self.get_current_input_mut() = new_value.clone();
|
||||||
self.set_current_cursor_pos(new_value.len());
|
self.set_current_cursor_pos(new_value.len());
|
||||||
self.set_has_unsaved_changes(true);
|
self.set_has_unsaved_changes(true);
|
||||||
self.deactivate_suggestions();
|
self.deactivate_suggestions();
|
||||||
return Some(format!("Selected: {}", display_name)); // Use calculated value
|
return Some(format!("Selected: {}", display_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ use crate::config::colors::themes::Theme;
|
|||||||
use crate::modes::general::command_navigation::NavigationState;
|
use crate::modes::general::command_navigation::NavigationState;
|
||||||
use crate::state::pages::canvas_state::CanvasState;
|
use crate::state::pages::canvas_state::CanvasState;
|
||||||
use crate::state::app::buffer::BufferState;
|
use crate::state::app::buffer::BufferState;
|
||||||
use crate::state::app::highlight::HighlightState;
|
use crate::state::app::highlight::HighlightState as LocalHighlightState; // CHANGED: Alias local version
|
||||||
|
use canvas::HighlightState as CanvasHighlightState; // CHANGED: Import canvas version with alias
|
||||||
use crate::state::app::state::AppState;
|
use crate::state::app::state::AppState;
|
||||||
use crate::state::pages::admin::AdminState;
|
use crate::state::pages::admin::AdminState;
|
||||||
use crate::state::pages::auth::AuthState;
|
use crate::state::pages::auth::AuthState;
|
||||||
@@ -32,6 +33,15 @@ use ratatui::{
|
|||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper function to convert between HighlightState types
|
||||||
|
fn convert_highlight_state(local: &LocalHighlightState) -> CanvasHighlightState {
|
||||||
|
match local {
|
||||||
|
LocalHighlightState::Off => CanvasHighlightState::Off,
|
||||||
|
LocalHighlightState::Characterwise { anchor } => CanvasHighlightState::Characterwise { anchor: *anchor },
|
||||||
|
LocalHighlightState::Linewise { anchor_line } => CanvasHighlightState::Linewise { anchor_line: *anchor_line },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn render_ui(
|
pub fn render_ui(
|
||||||
f: &mut Frame,
|
f: &mut Frame,
|
||||||
@@ -44,7 +54,7 @@ pub fn render_ui(
|
|||||||
buffer_state: &BufferState,
|
buffer_state: &BufferState,
|
||||||
theme: &Theme,
|
theme: &Theme,
|
||||||
is_event_handler_edit_mode: bool,
|
is_event_handler_edit_mode: bool,
|
||||||
highlight_state: &HighlightState,
|
highlight_state: &LocalHighlightState, // Keep using local version
|
||||||
event_handler_command_input: &str,
|
event_handler_command_input: &str,
|
||||||
event_handler_command_mode_active: bool,
|
event_handler_command_mode_active: bool,
|
||||||
event_handler_command_message: &str,
|
event_handler_command_message: &str,
|
||||||
@@ -69,7 +79,6 @@ pub fn render_ui(
|
|||||||
|
|
||||||
const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15;
|
const PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT: u16 = 15;
|
||||||
|
|
||||||
|
|
||||||
let mut bottom_area_constraints: Vec<Constraint> = vec![Constraint::Length(status_line_height)];
|
let mut bottom_area_constraints: Vec<Constraint> = vec![Constraint::Length(status_line_height)];
|
||||||
let command_palette_area_height = if navigation_state.active {
|
let command_palette_area_height = if navigation_state.active {
|
||||||
1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT
|
1 + PALETTE_OPTIONS_HEIGHT_FOR_LAYOUT
|
||||||
@@ -129,7 +138,7 @@ pub fn render_ui(
|
|||||||
register_state,
|
register_state,
|
||||||
app_state,
|
app_state,
|
||||||
register_state.current_field() < 4,
|
register_state.current_field() < 4,
|
||||||
highlight_state,
|
highlight_state, // Uses local version
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_add_table {
|
} else if app_state.ui.show_add_table {
|
||||||
render_add_table(
|
render_add_table(
|
||||||
@@ -139,7 +148,7 @@ pub fn render_ui(
|
|||||||
app_state,
|
app_state,
|
||||||
&mut admin_state.add_table_state,
|
&mut admin_state.add_table_state,
|
||||||
is_event_handler_edit_mode,
|
is_event_handler_edit_mode,
|
||||||
highlight_state,
|
highlight_state, // Uses local version
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_add_logic {
|
} else if app_state.ui.show_add_logic {
|
||||||
render_add_logic(
|
render_add_logic(
|
||||||
@@ -149,7 +158,7 @@ pub fn render_ui(
|
|||||||
app_state,
|
app_state,
|
||||||
&mut admin_state.add_logic_state,
|
&mut admin_state.add_logic_state,
|
||||||
is_event_handler_edit_mode,
|
is_event_handler_edit_mode,
|
||||||
highlight_state,
|
highlight_state, // Uses local version
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_login {
|
} else if app_state.ui.show_login {
|
||||||
render_login(
|
render_login(
|
||||||
@@ -159,7 +168,7 @@ pub fn render_ui(
|
|||||||
login_state,
|
login_state,
|
||||||
app_state,
|
app_state,
|
||||||
login_state.current_field() < 2,
|
login_state.current_field() < 2,
|
||||||
highlight_state,
|
highlight_state, // Uses local version
|
||||||
);
|
);
|
||||||
} else if app_state.ui.show_admin {
|
} else if app_state.ui.show_admin {
|
||||||
crate::components::admin::admin_panel::render_admin_panel(
|
crate::components::admin::admin_panel::render_admin_panel(
|
||||||
@@ -201,12 +210,14 @@ pub fn render_ui(
|
|||||||
.split(form_actual_area)[1]
|
.split(form_actual_area)[1]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CHANGED: Convert local HighlightState to canvas HighlightState for FormState
|
||||||
|
let canvas_highlight_state = convert_highlight_state(highlight_state);
|
||||||
form_state.render(
|
form_state.render(
|
||||||
f,
|
f,
|
||||||
form_render_area,
|
form_render_area,
|
||||||
theme,
|
theme,
|
||||||
is_event_handler_edit_mode,
|
is_event_handler_edit_mode,
|
||||||
highlight_state,
|
&canvas_highlight_state, // Use converted version
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user