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::WrappingCalculator;
12use super::{CodeEditor, GUTTER_WIDTH, Message};
13
14impl CodeEditor {
15 pub fn view(&self) -> Element<'_, Message> {
20 let wrapping_calc = WrappingCalculator::new(
23 self.wrap_enabled,
24 self.wrap_column,
25 self.full_char_width,
26 self.char_width,
27 );
28
29 let visual_lines = wrapping_calc.calculate_visual_lines(
31 &self.buffer,
32 self.viewport_width,
33 self.gutter_width(),
34 );
35
36 let total_visual_lines = visual_lines.len();
37 let content_height = total_visual_lines as f32 * self.line_height;
38
39 let canvas_height = content_height.max(self.viewport_height);
43
44 let canvas = Canvas::new(self)
46 .width(Length::Fill)
47 .height(Length::Fixed(canvas_height));
48
49 let scrollbar_bg = self.style.scrollbar_background;
51 let scroller_color = self.style.scroller_color;
52 let background_color = self.style.background;
53 let gutter_background = self.style.gutter_background;
54
55 let scrollable = Scrollable::new(canvas)
59 .id(self.scrollable_id.clone())
60 .width(Length::Fill)
61 .height(Length::Fill)
62 .on_scroll(Message::Scrolled)
63 .style(move |_theme, _status| scrollable::Style {
64 container: container::Style {
65 background: Some(Background::Color(Color::TRANSPARENT)),
66 ..container::Style::default()
67 },
68 vertical_rail: scrollable::Rail {
69 background: Some(scrollbar_bg.into()),
70 border: Border {
71 radius: 4.0.into(),
72 width: 0.0,
73 color: Color::TRANSPARENT,
74 },
75 scroller: scrollable::Scroller {
76 background: scroller_color.into(),
77 border: Border {
78 radius: 4.0.into(),
79 width: 0.0,
80 color: Color::TRANSPARENT,
81 },
82 },
83 },
84 horizontal_rail: scrollable::Rail {
85 background: Some(scrollbar_bg.into()),
86 border: Border {
87 radius: 4.0.into(),
88 width: 0.0,
89 color: Color::TRANSPARENT,
90 },
91 scroller: scrollable::Scroller {
92 background: scroller_color.into(),
93 border: Border {
94 radius: 4.0.into(),
95 width: 0.0,
96 color: Color::TRANSPARENT,
97 },
98 },
99 },
100 gap: None,
101 auto_scroll: scrollable::AutoScroll {
102 background: Color::TRANSPARENT.into(),
103 border: Border::default(),
104 shadow: Shadow::default(),
105 icon: Color::TRANSPARENT,
106 },
107 });
108
109 let gutter_container = if self.line_numbers_enabled {
112 Some(
113 container(
114 Space::new().width(Length::Fill).height(Length::Fill),
115 )
116 .width(Length::Fixed(GUTTER_WIDTH))
117 .height(Length::Fill)
118 .style(move |_| container::Style {
119 background: Some(Background::Color(gutter_background)),
120 ..container::Style::default()
121 }),
122 )
123 } else {
124 None
125 };
126
127 let code_background_container =
129 container(Space::new().width(Length::Fill).height(Length::Fill))
130 .width(Length::Fill)
131 .height(Length::Fill)
132 .style(move |_| container::Style {
133 background: Some(Background::Color(background_color)),
134 ..container::Style::default()
135 });
136
137 let background_row = if let Some(gutter) = gutter_container {
140 Row::new().push(gutter).push(code_background_container)
141 } else {
142 Row::new().push(code_background_container)
143 };
144
145 let mut editor_stack = iced::widget::Stack::new()
146 .push(
147 background_row,
149 )
150 .push(
151 scrollable,
153 );
154
155 let ime_enabled = self.is_focused() && self.has_canvas_focus;
156 let cursor_rect = if ime_enabled {
157 if let Some(cursor_visual) = WrappingCalculator::logical_to_visual(
158 &visual_lines,
159 self.cursor.0,
160 self.cursor.1,
161 ) {
162 let vl = &visual_lines[cursor_visual];
163 let line_content = self.buffer.line(vl.logical_line);
164 let prefix_len = self.cursor.1.saturating_sub(vl.start_col);
165 let prefix_text: String = line_content
166 .chars()
167 .skip(vl.start_col)
168 .take(prefix_len)
169 .collect();
170 let cursor_x = self.gutter_width()
171 + 5.0
172 + super::measure_text_width(
173 &prefix_text,
174 self.full_char_width,
175 self.char_width,
176 );
177
178 let cursor_y = (cursor_visual as f32 * self.line_height)
182 - self.viewport_scroll;
183
184 Rectangle::new(
185 iced::Point::new(cursor_x, cursor_y + 2.0),
186 Size::new(2.0, self.line_height - 4.0),
187 )
188 } else {
189 Rectangle::new(iced::Point::new(0.0, 0.0), Size::new(0.0, 0.0))
190 }
191 } else {
192 Rectangle::new(iced::Point::new(0.0, 0.0), Size::new(0.0, 0.0))
193 };
194
195 let preedit =
196 self.ime_preedit.as_ref().map(|p| input_method::Preedit {
197 content: p.content.clone(),
198 selection: p.selection.clone(),
199 text_size: None,
200 });
201
202 let ime_layer = ImeRequester::new(ime_enabled, cursor_rect, preedit);
205 editor_stack = editor_stack.push(iced::Element::new(ime_layer));
206
207 if self.search_state.is_open {
209 let search_dialog =
210 search_dialog::view(&self.search_state, &self.translations);
211
212 let positioned_dialog = container(
215 Row::new()
216 .push(Space::new().width(Length::Fill)) .push(search_dialog),
218 )
219 .padding(20) .width(Length::Fill)
221 .height(Length::Shrink);
222
223 editor_stack = editor_stack.push(positioned_dialog);
224 }
225
226 container(editor_stack)
228 .width(Length::Fill)
229 .height(Length::Fill)
230 .clip(true)
231 .into()
232 }
233}