freya_code_editor/
editor_line.rs1use std::borrow::Cow;
2
3use freya_components::scrollviews::ScrollController;
4use freya_core::prelude::*;
5use freya_edit::{
6 EditableEvent,
7 EditorLine,
8 TextEditor,
9};
10use torin::size::Size;
11
12use crate::{
13 editor_data::CodeEditorData,
14 editor_theme::EditorTheme,
15 syntax::TextNode,
16};
17
18#[derive(Clone, PartialEq)]
19pub struct EditorLineUI {
20 pub(crate) editor: Writable<CodeEditorData>,
21 pub(crate) font_size: f32,
22 pub(crate) line_height: f32,
23 pub(crate) line_index: usize,
24 pub(crate) read_only: bool,
25 pub(crate) show_whitespace: bool,
26 pub(crate) font_family: Cow<'static, str>,
27 pub(crate) cursor_mode: CursorMode,
28 pub(crate) theme: EditorTheme,
29 pub(crate) a11y_id: AccessibilityId,
30 pub(crate) scroll_controller: ScrollController,
31}
32
33impl Component for EditorLineUI {
34 fn render_key(&self) -> DiffKey {
35 DiffKey::from(&self.line_index)
36 }
37 fn render(&self) -> impl IntoElement {
38 let EditorLineUI {
39 mut editor,
40 font_size,
41 line_height,
42 line_index,
43 read_only,
44 show_whitespace,
45 font_family,
46 cursor_mode,
47 theme,
48 a11y_id,
49 scroll_controller,
50 } = self.clone();
51
52 let holder = use_state(ParagraphHolder::default);
53
54 let editor_data = editor.read_unchecked();
55
56 let longest_width = editor_data.metrics.longest_width;
57 let line = editor_data.metrics.syntax_blocks.get_line(line_index);
58 let highlights = editor_data.get_visible_selection(EditorLine::Paragraph(line_index));
59 let is_line_selected = editor_data.cursor_row() == line_index;
60
61 let on_tap = {
62 let mut editor = editor.clone();
63 let font_family = font_family.clone();
64 move |e: Event<FocusPressEventData>| {
65 let processed = editor.write_if(|mut editor| {
66 editor.process(
67 font_size,
68 &font_family,
69 EditableEvent::Down {
70 location: e.element_location(),
71 editor_line: EditorLine::Paragraph(line_index),
72 holder: &holder.read(),
73 },
74 )
75 });
76 if processed {
77 a11y_id.request_focus();
78 editor
79 .read()
80 .scroll_to_cursor(scroll_controller, line_height);
81 }
82 }
83 };
84
85 let on_pointer_move = {
86 let font_family = font_family.clone();
87 move |e: Event<PointerEventData>| {
88 let processed = editor.write_if(|mut editor| {
89 editor.process(
90 font_size,
91 &font_family,
92 EditableEvent::Move {
93 location: e.element_location(),
94 editor_line: EditorLine::Paragraph(line_index),
95 holder: &holder.read(),
96 },
97 )
98 });
99 if processed {
100 editor
101 .read()
102 .scroll_to_cursor(scroll_controller, line_height);
103 }
104 }
105 };
106
107 let cursor_index = if read_only {
108 None
109 } else {
110 is_line_selected.then(|| editor_data.cursor_col())
111 };
112 let visible_selection = match editor_data.get_selection() {
113 None => false,
114 Some((s, e)) if s != e => true,
115 _ => false,
116 };
117 let line_background = if is_line_selected && !visible_selection {
118 theme.line_selected_background
119 } else {
120 Color::TRANSPARENT
121 };
122
123 rect()
124 .horizontal()
125 .height(Size::px(line_height))
126 .background(line_background)
127 .font_size(font_size)
128 .child(
129 paragraph()
130 .holder(holder.read().clone())
131 .on_pointer_move(on_pointer_move)
132 .on_focus_press(on_tap)
133 .cursor_color(theme.cursor)
134 .cursor_style(CursorStyle::Block)
135 .cursor_index(cursor_index)
136 .cursor_mode(cursor_mode)
137 .vertical_align(VerticalAlign::Center)
138 .highlights(highlights.map(|h| vec![h]))
139 .highlight_color(theme.highlight)
140 .width(Size::px(longest_width))
141 .min_width(Size::fill())
142 .height(Size::fill())
143 .font_family(font_family)
144 .max_lines(1)
145 .color(theme.text)
146 .spans_iter(line.iter().map(|span| {
147 let text: Cow<str> = match &span.1 {
148 TextNode::Range(word_pos) => {
149 editor_data.rope.slice(word_pos.clone()).into()
150 }
151 TextNode::LineOfChars { len, char } => {
152 if show_whitespace {
153 Cow::Owned(char.to_string().repeat(*len))
154 } else {
155 Cow::Owned(" ".repeat(*len))
156 }
157 }
158 };
159 Span::new(Cow::Owned(text.to_string())).color(span.0)
160 })),
161 )
162 }
163}
164
165#[derive(Clone, PartialEq)]
166pub(crate) struct EditorGutterLineUI {
167 pub(crate) editor: Writable<CodeEditorData>,
168 pub(crate) font_size: f32,
169 pub(crate) line_height: f32,
170 pub(crate) line_index: usize,
171 pub(crate) theme: EditorTheme,
172}
173
174impl Component for EditorGutterLineUI {
175 fn render_key(&self) -> DiffKey {
176 DiffKey::from(&self.line_index)
177 }
178
179 fn render(&self) -> impl IntoElement {
180 let EditorGutterLineUI {
181 editor,
182 font_size,
183 line_height,
184 line_index,
185 theme,
186 } = self.clone();
187 let editor_data = editor.read();
188 let is_line_selected = editor_data.cursor_row() == line_index;
189 let visible_selection = matches!(
190 editor_data.get_selection(),
191 Some((start, end)) if start != end
192 );
193 let line_background = if is_line_selected && !visible_selection {
194 theme.line_selected_background
195 } else {
196 Color::TRANSPARENT
197 };
198 let gutter_color = if is_line_selected {
199 theme.gutter_selected
200 } else {
201 theme.gutter_unselected
202 };
203
204 rect()
205 .height(Size::px(line_height))
206 .width(Size::fill())
207 .background(line_background)
208 .font_size(font_size)
209 .padding((0., 0., 0., 20.))
210 .main_align(torin::prelude::Alignment::Center)
211 .child(
212 label()
213 .color(gutter_color)
214 .text(format!("{} ", line_index + 1)),
215 )
216 }
217}