freya_code_editor/
editor_line.rs1use std::borrow::Cow;
2
3use freya_core::prelude::*;
4use freya_edit::{
5 EditableEvent,
6 EditorLine,
7 TextEditor,
8};
9use torin::{
10 gaps::Gaps,
11 prelude::Alignment,
12 size::Size,
13};
14
15use crate::{
16 editor_data::CodeEditorData,
17 editor_theme::EditorTheme,
18 syntax::TextNode,
19};
20
21#[derive(Clone, PartialEq)]
22pub struct EditorLineUI {
23 pub(crate) editor: Writable<CodeEditorData>,
24 pub(crate) font_size: f32,
25 pub(crate) line_height: f32,
26 pub(crate) line_index: usize,
27 pub(crate) read_only: bool,
28 pub(crate) gutter: bool,
29 pub(crate) show_whitespace: bool,
30 pub(crate) font_family: Cow<'static, str>,
31 pub(crate) theme: Readable<EditorTheme>,
32 pub(crate) a11y_id: AccessibilityId,
33}
34
35impl Component for EditorLineUI {
36 fn render_key(&self) -> DiffKey {
37 DiffKey::from(&self.line_index)
38 }
39 fn render(&self) -> impl IntoElement {
40 let EditorLineUI {
41 mut editor,
42 font_size,
43 line_height,
44 line_index,
45 read_only,
46 gutter,
47 show_whitespace,
48 font_family,
49 theme,
50 a11y_id,
51 } = self.clone();
52
53 let holder = use_state(ParagraphHolder::default);
54
55 let editor_data = editor.read();
56 let theme = theme.read();
57
58 let longest_width = editor_data.metrics.longest_width;
59 let line = editor_data.metrics.syntax_blocks.get_line(line_index);
60 let highlights = editor_data.get_visible_selection(EditorLine::Paragraph(line_index));
61 let gutter_width = font_size * 5.0;
62 let is_line_selected = editor_data.cursor_row() == line_index;
63
64 let on_tap = {
65 let mut editor = editor.clone();
66 let font_family = font_family.clone();
67 move |e: Event<FocusPressEventData>| {
68 let processed = editor.write_if(|mut editor_editor| {
69 editor_editor.process(
70 font_size,
71 &font_family,
72 EditableEvent::Down {
73 location: e.element_location(),
74 editor_line: EditorLine::Paragraph(line_index),
75 holder: &holder.read(),
76 },
77 )
78 });
79 if processed {
80 a11y_id.request_focus();
81 }
82 }
83 };
84
85 let on_pointer_move = {
86 let font_family = font_family.clone();
87 move |e: Event<PointerEventData>| {
88 editor.write_if(|mut editor_editor| {
89 editor_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 }
100 };
101
102 let cursor_index = if read_only {
103 None
104 } else {
105 is_line_selected.then(|| editor_data.cursor_col())
106 };
107 let gutter_color = if is_line_selected {
108 theme.gutter_selected
109 } else {
110 theme.gutter_unselected
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 .maybe(gutter, |el| {
129 el.child(
130 rect()
131 .width(Size::px(gutter_width))
132 .height(Size::fill())
133 .padding(Gaps::new(0., 0., 0., 20.))
134 .main_align(Alignment::Center)
135 .child(
136 label()
137 .color(gutter_color)
138 .text(format!("{} ", line_index + 1)),
139 ),
140 )
141 })
142 .child(
143 paragraph()
144 .holder(holder.read().clone())
145 .on_pointer_move(on_pointer_move)
146 .on_focus_press(on_tap)
147 .cursor_color(theme.cursor)
148 .cursor_style(CursorStyle::Block)
149 .cursor_index(cursor_index)
150 .cursor_mode(CursorMode::Expanded)
151 .vertical_align(VerticalAlign::Center)
152 .highlights(highlights.map(|h| vec![h]))
153 .highlight_color(theme.highlight)
154 .width(Size::px(longest_width))
155 .min_width(Size::fill())
156 .height(Size::fill())
157 .font_family(font_family)
158 .max_lines(1)
159 .color(theme.text)
160 .spans_iter(line.iter().map(|span| {
161 let text: Cow<str> = match &span.1 {
162 TextNode::Range(word_pos) => {
163 editor_data.rope.slice(word_pos.clone()).into()
164 }
165 TextNode::LineOfChars { len, char } => {
166 if show_whitespace {
167 Cow::Owned(char.to_string().repeat(*len))
168 } else {
169 Cow::Owned(" ".repeat(*len))
170 }
171 }
172 };
173 Span::new(Cow::Owned(text.to_string())).color(span.0)
174 })),
175 )
176 }
177}