gui of canvas is from the canvas crate now

This commit is contained in:
Priec
2025-07-29 19:54:29 +02:00
parent a1fa42e204
commit aec5f80879
13 changed files with 370 additions and 53 deletions

10
Cargo.lock generated
View File

@@ -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",

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 = "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>"]

View File

@@ -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
View 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
View 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,
&current_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
View 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;
}

View File

@@ -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};
} }

View File

@@ -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 }

View File

@@ -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"]

View File

@@ -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(
} }
} }
} }

View File

@@ -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
}
}

View File

@@ -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 {
@@ -232,45 +229,45 @@ impl CanvasState for FormState {
fn current_field(&self) -> usize { fn current_field(&self) -> usize {
self.current_field self.current_field
} }
fn current_cursor_pos(&self) -> usize { fn current_cursor_pos(&self) -> usize {
self.current_cursor_pos self.current_cursor_pos
} }
fn has_unsaved_changes(&self) -> bool { fn has_unsaved_changes(&self) -> bool {
self.has_unsaved_changes self.has_unsaved_changes
} }
fn inputs(&self) -> Vec<&String> { fn inputs(&self) -> Vec<&String> {
self.values.iter().collect() self.values.iter().collect()
} }
fn get_current_input(&self) -> &str { fn get_current_input(&self) -> &str {
FormState::get_current_input(self) FormState::get_current_input(self)
} }
fn get_current_input_mut(&mut self) -> &mut String { fn get_current_input_mut(&mut self) -> &mut String {
FormState::get_current_input_mut(self) FormState::get_current_input_mut(self)
} }
fn fields(&self) -> Vec<&str> { fn fields(&self) -> Vec<&str> {
self.fields self.fields
.iter() .iter()
.map(|f| f.display_name.as_str()) .map(|f| f.display_name.as_str())
.collect() .collect()
} }
fn set_current_field(&mut self, index: usize) { fn set_current_field(&mut self, index: usize) {
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) {
self.current_cursor_pos = pos; self.current_cursor_pos = pos;
} }
fn set_has_unsaved_changes(&mut self, changed: bool) { fn set_has_unsaved_changes(&mut self, changed: bool) {
self.has_unsaved_changes = changed; self.has_unsaved_changes = changed;
} }
@@ -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(&current_field_def.data_key) { if let Some(value) = content_map.get(&current_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));
} }
} }
} }

View File

@@ -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
); );
} }