iced_code_editor/canvas_editor/mod.rs
1//! Canvas-based text editor widget for maximum performance.
2//!
3//! This module provides a custom Canvas widget that handles all text rendering
4//! and input directly, bypassing Iced's higher-level widgets for optimal speed.
5
6use iced::widget::Id;
7use iced::widget::canvas;
8use std::time::Instant;
9
10use crate::text_buffer::TextBuffer;
11use crate::theme::Style;
12pub use history::CommandHistory;
13
14// Re-export submodules
15mod canvas_impl;
16mod clipboard;
17pub mod command;
18mod cursor;
19pub mod history;
20mod selection;
21mod update;
22mod view;
23
24/// Canvas-based text editor constants
25pub(crate) const FONT_SIZE: f32 = 14.0;
26pub(crate) const LINE_HEIGHT: f32 = 20.0;
27pub(crate) const CHAR_WIDTH: f32 = 8.4; // Monospace character width
28pub(crate) const GUTTER_WIDTH: f32 = 60.0;
29pub(crate) const CURSOR_BLINK_INTERVAL: std::time::Duration =
30 std::time::Duration::from_millis(530);
31
32/// Canvas-based high-performance text editor.
33pub struct CodeEditor {
34 /// Text buffer
35 pub(crate) buffer: TextBuffer,
36 /// Cursor position (line, column)
37 pub(crate) cursor: (usize, usize),
38 /// Scroll offset in pixels
39 pub(crate) scroll_offset: f32,
40 /// Editor theme style
41 pub(crate) style: Style,
42 /// Syntax highlighting language
43 pub(crate) syntax: String,
44 /// Last cursor blink time
45 pub(crate) last_blink: Instant,
46 /// Cursor visible state
47 pub(crate) cursor_visible: bool,
48 /// Selection start (if any)
49 pub(crate) selection_start: Option<(usize, usize)>,
50 /// Selection end (if any) - cursor position during selection
51 pub(crate) selection_end: Option<(usize, usize)>,
52 /// Mouse is currently dragging for selection
53 pub(crate) is_dragging: bool,
54 /// Cache for canvas rendering
55 pub(crate) cache: canvas::Cache,
56 /// Scrollable ID for programmatic scrolling
57 pub(crate) scrollable_id: Id,
58 /// Current viewport scroll position (Y offset)
59 pub(crate) viewport_scroll: f32,
60 /// Viewport height (visible area)
61 pub(crate) viewport_height: f32,
62 /// Command history for undo/redo
63 pub(crate) history: CommandHistory,
64 /// Whether we're currently grouping commands (for smart undo)
65 pub(crate) is_grouping: bool,
66}
67
68/// Messages emitted by the code editor
69#[derive(Debug, Clone)]
70pub enum Message {
71 /// Character typed
72 CharacterInput(char),
73 /// Backspace pressed
74 Backspace,
75 /// Delete pressed
76 Delete,
77 /// Enter pressed
78 Enter,
79 /// Arrow key pressed (direction, shift_pressed)
80 ArrowKey(ArrowDirection, bool),
81 /// Mouse clicked at position
82 MouseClick(iced::Point),
83 /// Mouse drag for selection
84 MouseDrag(iced::Point),
85 /// Mouse released
86 MouseRelease,
87 /// Copy selected text (Ctrl+C)
88 Copy,
89 /// Paste text from clipboard (Ctrl+V)
90 Paste(String),
91 /// Delete selected text (Shift+Delete)
92 DeleteSelection,
93 /// Request redraw for cursor blink
94 Tick,
95 /// Page Up pressed
96 PageUp,
97 /// Page Down pressed
98 PageDown,
99 /// Home key pressed (move to start of line, shift_pressed)
100 Home(bool),
101 /// End key pressed (move to end of line, shift_pressed)
102 End(bool),
103 /// Ctrl+Home pressed (move to start of document)
104 CtrlHome,
105 /// Ctrl+End pressed (move to end of document)
106 CtrlEnd,
107 /// Viewport scrolled - track scroll position
108 Scrolled(iced::widget::scrollable::Viewport),
109 /// Undo last operation (Ctrl+Z)
110 Undo,
111 /// Redo last undone operation (Ctrl+Y)
112 Redo,
113}
114
115/// Arrow key directions
116#[derive(Debug, Clone, Copy)]
117pub enum ArrowDirection {
118 Up,
119 Down,
120 Left,
121 Right,
122}
123
124impl CodeEditor {
125 /// Creates a new canvas-based text editor.
126 ///
127 /// # Arguments
128 ///
129 /// * `content` - Initial text content
130 /// * `syntax` - Syntax highlighting language (e.g., "py", "lua", "rs")
131 ///
132 /// # Returns
133 ///
134 /// A new `CodeEditor` instance
135 pub fn new(content: &str, syntax: &str) -> Self {
136 Self {
137 buffer: TextBuffer::new(content),
138 cursor: (0, 0),
139 scroll_offset: 0.0,
140 style: crate::theme::dark(&iced::Theme::Dark),
141 syntax: syntax.to_string(),
142 last_blink: Instant::now(),
143 cursor_visible: true,
144 selection_start: None,
145 selection_end: None,
146 is_dragging: false,
147 cache: canvas::Cache::default(),
148 scrollable_id: Id::unique(),
149 viewport_scroll: 0.0,
150 viewport_height: 600.0, // Default, will be updated
151 history: CommandHistory::new(100),
152 is_grouping: false,
153 }
154 }
155
156 /// Returns the current text content as a string.
157 ///
158 /// # Returns
159 ///
160 /// The complete text content of the editor
161 pub fn content(&self) -> String {
162 self.buffer.to_string()
163 }
164
165 /// Sets the theme style for the editor.
166 ///
167 /// # Arguments
168 ///
169 /// * `style` - The style to apply to the editor
170 ///
171 /// # Example
172 ///
173 /// ```
174 /// use iced_code_editor::{CodeEditor, theme};
175 ///
176 /// let mut editor = CodeEditor::new("fn main() {}", "rs");
177 /// editor.set_theme(theme::light(&iced::Theme::Light));
178 /// ```
179 pub fn set_theme(&mut self, style: Style) {
180 self.style = style;
181 self.cache.clear(); // Force redraw with new theme
182 }
183
184 /// Resets the cursor blink animation.
185 pub(crate) fn reset_cursor_blink(&mut self) {
186 self.last_blink = Instant::now();
187 self.cursor_visible = true;
188 }
189
190 /// Returns whether the editor has unsaved changes.
191 ///
192 /// # Returns
193 ///
194 /// `true` if there are unsaved modifications, `false` otherwise
195 pub fn is_modified(&self) -> bool {
196 self.history.is_modified()
197 }
198
199 /// Marks the current state as saved.
200 ///
201 /// Call this after successfully saving the file to reset the modified state.
202 pub fn mark_saved(&mut self) {
203 self.history.mark_saved();
204 }
205
206 /// Returns whether undo is available.
207 pub fn can_undo(&self) -> bool {
208 self.history.can_undo()
209 }
210
211 /// Returns whether redo is available.
212 pub fn can_redo(&self) -> bool {
213 self.history.can_redo()
214 }
215}