195 lines
8.5 KiB
Rust
195 lines
8.5 KiB
Rust
// src/components/handlers/canvas.rs
|
|
use ratatui::{
|
|
widgets::{Paragraph, Block, Borders},
|
|
layout::{Layout, Constraint, Direction, Rect},
|
|
style::{Style, Modifier},
|
|
text::{Line, Span},
|
|
Frame,
|
|
prelude::Alignment,
|
|
};
|
|
use crate::config::colors::themes::Theme;
|
|
use crate::state::pages::canvas_state::CanvasState;
|
|
use crate::state::app::highlight::HighlightState; // Ensure correct import path
|
|
use std::cmp::{min, max};
|
|
|
|
pub fn render_canvas(
|
|
f: &mut Frame,
|
|
area: Rect,
|
|
form_state: &impl CanvasState,
|
|
fields: &[&str],
|
|
current_field_idx: &usize,
|
|
inputs: &[&String],
|
|
theme: &Theme,
|
|
is_edit_mode: bool,
|
|
highlight_state: &HighlightState, // Using the enum state
|
|
) -> Option<Rect> {
|
|
// ... (setup code remains the same) ...
|
|
let columns = Layout::default()
|
|
.direction(Direction::Horizontal)
|
|
.constraints([Constraint::Percentage(30), Constraint::Percentage(70)])
|
|
.split(area);
|
|
|
|
let border_style = if form_state.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 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 inputs and cursor
|
|
for (i, input) in inputs.iter().enumerate() {
|
|
let is_active = i == *current_field_idx;
|
|
let current_cursor_pos = form_state.current_cursor_pos();
|
|
let text = input.as_str();
|
|
let text_len = text.chars().count();
|
|
|
|
let line: Line;
|
|
|
|
// --- Use match on the highlight_state enum ---
|
|
match highlight_state {
|
|
HighlightState::Off => {
|
|
// Not in highlight mode, render normally
|
|
line = Line::from(Span::styled(
|
|
text,
|
|
if is_active { Style::default().fg(theme.highlight) } else { Style::default().fg(theme.fg) }
|
|
));
|
|
}
|
|
HighlightState::Characterwise { anchor } => {
|
|
// --- Character-wise Highlight Logic ---
|
|
let (anchor_field, anchor_char) = *anchor;
|
|
let start_field = min(anchor_field, *current_field_idx);
|
|
let end_field = max(anchor_field, *current_field_idx);
|
|
|
|
// Use start_char and end_char consistently
|
|
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 {
|
|
// This line is within the character-wise highlight range
|
|
if start_field == end_field { // Case 1: Single Line Highlight
|
|
// Use start_char and end_char here
|
|
let clamped_start = start_char.min(text_len);
|
|
let clamped_end = end_char.min(text_len); // Use text_len for slicing logic
|
|
|
|
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();
|
|
// Define 'after' here
|
|
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), // Use defined 'after'
|
|
]);
|
|
} else if i == start_field { // Case 2: Multi-Line Highlight - Start Line
|
|
// Use start_char here
|
|
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 { // Case 3: Multi-Line Highlight - End Line (Corrected index)
|
|
// Use end_char here
|
|
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 { // Case 4: Multi-Line Highlight - Middle Line (Corrected index)
|
|
line = Line::from(Span::styled(text, highlight_style)); // Highlight whole line
|
|
}
|
|
} else { // Case 5: Line Outside Character-wise Highlight Range
|
|
line = Line::from(Span::styled(
|
|
text,
|
|
// Use normal styling (active or inactive)
|
|
if is_active { normal_style_in_highlight } else { normal_style_outside }
|
|
));
|
|
}
|
|
}
|
|
HighlightState::Linewise { anchor_line } => {
|
|
// --- Linewise Highlight Logic ---
|
|
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 {
|
|
// Highlight the entire line
|
|
line = Line::from(Span::styled(text, highlight_style));
|
|
} else {
|
|
// Line outside linewise highlight range
|
|
line = Line::from(Span::styled(
|
|
text,
|
|
// Use normal styling (active or inactive)
|
|
if is_active { normal_style_in_highlight } else { normal_style_outside }
|
|
));
|
|
}
|
|
}
|
|
} // End match highlight_state
|
|
|
|
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]);
|
|
let cursor_x = input_rows[i].x + form_state.current_cursor_pos() as u16;
|
|
let cursor_y = input_rows[i].y;
|
|
f.set_cursor_position((cursor_x, cursor_y));
|
|
}
|
|
}
|
|
|
|
active_field_input_rect
|
|
}
|
|
|