iced_code_editor/canvas_editor/
view.rs1use iced::Size;
4use iced::advanced::input_method;
5use iced::widget::canvas::Canvas;
6use iced::widget::{Row, Scrollable, Space, container, scrollable};
7use iced::{Background, Border, Color, Element, Length, Rectangle, Shadow};
8
9use super::ime_requester::ImeRequester;
10use super::search_dialog;
11use super::wrapping::{self, WrappingCalculator};
12use super::{CodeEditor, GUTTER_WIDTH, Message};
13use std::rc::Rc;
14
15impl CodeEditor {
16 fn calculate_canvas_height(&self) -> (Rc<Vec<wrapping::VisualLine>>, f32) {
22 let visual_lines = self.visual_lines_cached(self.viewport_width);
25 let total_visual_lines = visual_lines.len();
26 let content_height = total_visual_lines as f32 * self.line_height;
27
28 let canvas_height = content_height.max(self.viewport_height);
32
33 (visual_lines, canvas_height)
34 }
35
36 fn create_scrollable_style(
40 &self,
41 ) -> impl Fn(&iced::Theme, scrollable::Status) -> scrollable::Style {
42 let scrollbar_bg = self.style.scrollbar_background;
43 let scroller_color = self.style.scroller_color;
44
45 move |_theme, _status| scrollable::Style {
46 container: container::Style {
47 background: Some(Background::Color(Color::TRANSPARENT)),
48 ..container::Style::default()
49 },
50 vertical_rail: scrollable::Rail {
51 background: Some(scrollbar_bg.into()),
52 border: Border {
53 radius: 4.0.into(),
54 width: 0.0,
55 color: Color::TRANSPARENT,
56 },
57 scroller: scrollable::Scroller {
58 background: scroller_color.into(),
59 border: Border {
60 radius: 4.0.into(),
61 width: 0.0,
62 color: Color::TRANSPARENT,
63 },
64 },
65 },
66 horizontal_rail: scrollable::Rail {
67 background: Some(scrollbar_bg.into()),
68 border: Border {
69 radius: 4.0.into(),
70 width: 0.0,
71 color: Color::TRANSPARENT,
72 },
73 scroller: scrollable::Scroller {
74 background: scroller_color.into(),
75 border: Border {
76 radius: 4.0.into(),
77 width: 0.0,
78 color: Color::TRANSPARENT,
79 },
80 },
81 },
82 gap: None,
83 auto_scroll: scrollable::AutoScroll {
84 background: Color::TRANSPARENT.into(),
85 border: Border::default(),
86 shadow: Shadow::default(),
87 icon: Color::TRANSPARENT,
88 },
89 }
90 }
91
92 fn create_canvas_with_scrollable(
102 &self,
103 canvas_height: f32,
104 ) -> Scrollable<'_, Message> {
105 let canvas = Canvas::new(self)
106 .width(Length::Fill)
107 .height(Length::Fixed(canvas_height));
108
109 Scrollable::new(canvas)
110 .id(self.scrollable_id.clone())
111 .width(Length::Fill)
112 .height(Length::Fill)
113 .on_scroll(Message::Scrolled)
114 .style(self.create_scrollable_style())
115 }
116
117 fn create_gutter_container(
123 &self,
124 ) -> Option<container::Container<'_, Message>> {
125 if self.line_numbers_enabled {
126 let gutter_background = self.style.gutter_background;
127 Some(
128 container(
129 Space::new().width(Length::Fill).height(Length::Fill),
130 )
131 .width(Length::Fixed(GUTTER_WIDTH))
132 .height(Length::Fill)
133 .style(move |_| container::Style {
134 background: Some(Background::Color(gutter_background)),
135 ..container::Style::default()
136 }),
137 )
138 } else {
139 None
140 }
141 }
142
143 fn create_code_background_container(
149 &self,
150 ) -> container::Container<'_, Message> {
151 let background_color = self.style.background;
152 container(Space::new().width(Length::Fill).height(Length::Fill))
153 .width(Length::Fill)
154 .height(Length::Fill)
155 .style(move |_| container::Style {
156 background: Some(Background::Color(background_color)),
157 ..container::Style::default()
158 })
159 }
160
161 fn create_background_layer(&self) -> Row<'_, Message> {
167 let gutter_container = self.create_gutter_container();
168 let code_background_container = self.create_code_background_container();
169
170 if let Some(gutter) = gutter_container {
171 Row::new().push(gutter).push(code_background_container)
172 } else {
173 Row::new().push(code_background_container)
174 }
175 }
176
177 fn calculate_ime_cursor_rect(
187 &self,
188 visual_lines: &[wrapping::VisualLine],
189 ) -> Rectangle {
190 let ime_enabled = self.is_focused() && self.has_canvas_focus;
191
192 if !ime_enabled {
193 return Rectangle::new(
194 iced::Point::new(0.0, 0.0),
195 Size::new(0.0, 0.0),
196 );
197 }
198
199 if let Some(cursor_visual) = WrappingCalculator::logical_to_visual(
200 visual_lines,
201 self.cursor.0,
202 self.cursor.1,
203 ) {
204 let vl = &visual_lines[cursor_visual];
205 let line_content = self.buffer.line(vl.logical_line);
206 let prefix_len = self.cursor.1.saturating_sub(vl.start_col);
207 let prefix_text: String = line_content
208 .chars()
209 .skip(vl.start_col)
210 .take(prefix_len)
211 .collect();
212 let cursor_x = self.gutter_width()
213 + 5.0
214 + super::measure_text_width(
215 &prefix_text,
216 self.full_char_width,
217 self.char_width,
218 );
219
220 let cursor_y = (cursor_visual as f32 * self.line_height)
224 - self.viewport_scroll;
225
226 Rectangle::new(
227 iced::Point::new(cursor_x, cursor_y + 2.0),
228 Size::new(2.0, self.line_height - 4.0),
229 )
230 } else {
231 Rectangle::new(iced::Point::new(0.0, 0.0), Size::new(0.0, 0.0))
232 }
233 }
234
235 fn create_ime_layer(&self, cursor_rect: Rectangle) -> Element<'_, Message> {
245 let ime_enabled = self.is_focused() && self.has_canvas_focus;
246
247 let preedit =
248 self.ime_preedit.as_ref().map(|p| input_method::Preedit {
249 content: p.content.clone(),
250 selection: p.selection.clone(),
251 text_size: None,
252 });
253
254 let ime_layer = ImeRequester::new(ime_enabled, cursor_rect, preedit);
255 iced::Element::new(ime_layer)
256 }
257
258 pub fn view(&self) -> Element<'_, Message> {
263 let (visual_lines, canvas_height) = self.calculate_canvas_height();
265
266 let scrollable = self.create_canvas_with_scrollable(canvas_height);
268
269 let background_row = self.create_background_layer();
271
272 let mut editor_stack =
274 iced::widget::Stack::new().push(background_row).push(scrollable);
275
276 let cursor_rect = self.calculate_ime_cursor_rect(visual_lines.as_ref());
280 let ime_layer = self.create_ime_layer(cursor_rect);
281 editor_stack = editor_stack.push(ime_layer);
282
283 if self.search_state.is_open {
285 let search_dialog =
286 search_dialog::view(&self.search_state, &self.translations);
287
288 let positioned_dialog = container(
290 Row::new()
291 .push(Space::new().width(Length::Fill))
292 .push(search_dialog),
293 )
294 .padding(20)
295 .width(Length::Fill)
296 .height(Length::Shrink);
297
298 editor_stack = editor_stack.push(positioned_dialog);
299 }
300
301 container(editor_stack)
303 .width(Length::Fill)
304 .height(Length::Fill)
305 .clip(true)
306 .into()
307 }
308}