freya_code_editor/
editor_ui.rs1use freya_components::scrollviews::{
2 ScrollController,
3 ScrollEvent,
4 VirtualScrollView,
5};
6use freya_core::prelude::*;
7use freya_edit::EditableEvent;
8
9use crate::{
10 editor_data::CodeEditorData,
11 editor_line::EditorLineUI,
12 editor_theme::{
13 DEFAULT_EDITOR_THEME,
14 EditorTheme,
15 },
16};
17
18#[derive(PartialEq, Clone)]
19pub struct CodeEditor {
20 editor: Writable<CodeEditorData>,
21 font_size: f32,
22 line_height: f32,
23 read_only: bool,
24 gutter: bool,
25 show_whitespace: bool,
26 a11y_id: AccessibilityId,
27 theme: Readable<EditorTheme>,
28}
29
30impl CodeEditor {
31 pub fn new(editor: impl Into<Writable<CodeEditorData>>, a11y_id: AccessibilityId) -> Self {
35 Self {
36 editor: editor.into(),
37 font_size: 14.0,
38 line_height: 1.4,
39 read_only: false,
40 gutter: true,
41 show_whitespace: true,
42 a11y_id,
43 theme: DEFAULT_EDITOR_THEME.into(),
44 }
45 }
46
47 pub fn font_size(mut self, size: f32) -> Self {
48 self.font_size = size;
49 self
50 }
51
52 pub fn line_height(mut self, height: f32) -> Self {
54 self.line_height = height;
55 self
56 }
57
58 pub fn read_only(mut self, read_only: bool) -> Self {
60 self.read_only = read_only;
61 self
62 }
63
64 pub fn gutter(mut self, gutter: bool) -> Self {
66 self.gutter = gutter;
67 self
68 }
69
70 pub fn show_whitespace(mut self, show_whitespace: bool) -> Self {
72 self.show_whitespace = show_whitespace;
73 self
74 }
75
76 pub fn theme(mut self, theme: impl IntoReadable<EditorTheme>) -> Self {
78 self.theme = theme.into_readable();
79 self
80 }
81}
82
83impl Component for CodeEditor {
84 fn render(&self) -> impl IntoElement {
85 let CodeEditor {
86 editor,
87 font_size,
88 line_height,
89 read_only,
90 gutter,
91 show_whitespace,
92 a11y_id,
93 theme,
94 } = self.clone();
95
96 let editor_data = editor.read();
97
98 let focus = Focus::new_for_id(a11y_id);
99
100 let mut pressing_shift = use_state(|| false);
101 let mut pressing_alt = use_state(|| false);
102
103 let scroll_controller = use_hook(|| {
104 let notifier = State::create(());
105 let requests = State::create(vec![]);
106 ScrollController::managed(
107 notifier,
108 requests,
109 State::create(Callback::new({
110 let mut editor = editor.clone();
111 move |ev| {
112 editor.write_if(|mut editor| {
113 let current = editor.scrolls;
114 match ev {
115 ScrollEvent::X(x) => {
116 editor.scrolls.0 = x;
117 }
118 ScrollEvent::Y(y) => {
119 editor.scrolls.1 = y;
120 }
121 }
122 current != editor.scrolls
123 })
124 }
125 })),
126 State::create(Callback::new({
127 let editor = editor.clone();
128 move |_| {
129 let editor = editor.read();
130 editor.scrolls
131 }
132 })),
133 )
134 });
135
136 let line_height = (font_size * line_height).floor();
137 let lines_len = editor_data.metrics.syntax_blocks.len();
138
139 let on_mouse_down = move |_| {
140 focus.request_focus();
141 };
142
143 let on_key_up = {
144 let mut editor = editor.clone();
145 move |e: Event<KeyboardEventData>| {
146 match &e.key {
147 Key::Named(NamedKey::Shift) => {
148 pressing_shift.set(false);
149 }
150 Key::Named(NamedKey::Alt) => {
151 pressing_alt.set(false);
152 }
153 _ => {}
154 };
155
156 editor.write_if(|mut editor| {
157 editor.process(font_size, EditableEvent::KeyUp { key: &e.key })
158 });
159 }
160 };
161
162 let on_key_down = {
163 let mut editor = editor.clone();
164 move |e: Event<KeyboardEventData>| {
165 e.stop_propagation();
166
167 match &e.key {
168 Key::Named(NamedKey::Shift) => {
169 pressing_shift.set(true);
170 }
171 Key::Named(NamedKey::Alt) => {
172 pressing_alt.set(true);
173 }
174 Key::Named(NamedKey::Tab) => {
175 e.prevent_default();
176 }
177 _ => {}
178 };
179
180 const LINES_JUMP_ALT: usize = 5;
181 const LINES_JUMP_CONTROL: usize = 3;
182
183 editor.write_if(|mut editor| {
184 let lines_jump = (line_height * LINES_JUMP_ALT as f32).ceil() as i32;
185 let min_height = -(lines_len as f32 * line_height) as i32;
186 let max_height = 0; let current_scroll = editor.scrolls.1;
188
189 let events = match &e.key {
190 Key::Named(NamedKey::ArrowUp) if e.modifiers.contains(Modifiers::ALT) => {
191 let jump = (current_scroll + lines_jump).clamp(min_height, max_height);
192 editor.scrolls.1 = jump;
193 (0..LINES_JUMP_ALT)
194 .map(|_| EditableEvent::KeyDown {
195 key: &e.key,
196 modifiers: e.modifiers,
197 })
198 .collect::<Vec<EditableEvent>>()
199 }
200 Key::Named(NamedKey::ArrowDown) if e.modifiers.contains(Modifiers::ALT) => {
201 let jump = (current_scroll - lines_jump).clamp(min_height, max_height);
202 editor.scrolls.1 = jump;
203 (0..LINES_JUMP_ALT)
204 .map(|_| EditableEvent::KeyDown {
205 key: &e.key,
206 modifiers: e.modifiers,
207 })
208 .collect::<Vec<EditableEvent>>()
209 }
210 Key::Named(NamedKey::ArrowDown) | Key::Named(NamedKey::ArrowUp)
211 if e.modifiers.contains(Modifiers::CONTROL) =>
212 {
213 (0..LINES_JUMP_CONTROL)
214 .map(|_| EditableEvent::KeyDown {
215 key: &e.key,
216 modifiers: e.modifiers,
217 })
218 .collect::<Vec<EditableEvent>>()
219 }
220 _ if e.code == Code::Escape
221 || e.modifiers.contains(Modifiers::ALT)
222 || (e.modifiers.contains(Modifiers::CONTROL)
223 && e.code == Code::KeyS) =>
224 {
225 Vec::new()
226 }
227 _ => {
228 vec![EditableEvent::KeyDown {
229 key: &e.key,
230 modifiers: e.modifiers,
231 }]
232 }
233 };
234
235 let mut changed = false;
236
237 for event in events {
238 changed |= editor.process(font_size, event);
239 }
240
241 changed
242 });
243 }
244 };
245
246 let on_global_mouse_up = {
247 let mut editor = editor.clone();
248 move |_: Event<MouseEventData>| {
249 editor.write_if(|mut editor_editor| {
250 editor_editor.process(font_size, EditableEvent::Release)
251 });
252 }
253 };
254
255 rect().expanded().background(theme.read().background).child(
256 rect()
257 .a11y_auto_focus(true)
258 .a11y_focusable(true)
259 .a11y_id(focus.a11y_id())
260 .maybe(!read_only, |el| {
261 el.on_key_down(on_key_down).on_key_up(on_key_up)
262 })
263 .on_global_mouse_up(on_global_mouse_up)
264 .on_mouse_down(on_mouse_down)
265 .child(
266 VirtualScrollView::new(move |line_index, _| {
267 EditorLineUI {
268 editor: editor.clone(),
269 font_size,
270 line_height,
271 line_index,
272 read_only,
273 gutter,
274 show_whitespace,
275 theme: theme.clone(),
276 }
277 .into()
278 })
279 .scroll_controller(scroll_controller)
280 .length(lines_len as i32)
281 .item_size(line_height),
282 ),
283 )
284 }
285}