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: 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
57 let longest_width = editor_data.metrics.longest_width;
58 let line = editor_data.metrics.syntax_blocks.get_line(line_index);
59 let highlights = editor_data.get_visible_selection(EditorLine::Paragraph(line_index));
60 let gutter_width = font_size * 5.0;
61 let is_line_selected = editor_data.cursor_row() == line_index;
62
63 let on_tap = {
64 let mut editor = editor.clone();
65 let font_family = font_family.clone();
66 move |e: Event<FocusPressEventData>| {
67 let processed = editor.write_if(|mut editor_editor| {
68 editor_editor.process(
69 font_size,
70 &font_family,
71 EditableEvent::Down {
72 location: e.element_location(),
73 editor_line: EditorLine::Paragraph(line_index),
74 holder: &holder.read(),
75 },
76 )
77 });
78 if processed {
79 a11y_id.request_focus();
80 }
81 }
82 };
83
84 let on_pointer_move = {
85 let font_family = font_family.clone();
86 move |e: Event<PointerEventData>| {
87 editor.write_if(|mut editor_editor| {
88 editor_editor.process(
89 font_size,
90 &font_family,
91 EditableEvent::Move {
92 location: e.element_location(),
93 editor_line: EditorLine::Paragraph(line_index),
94 holder: &holder.read(),
95 },
96 )
97 });
98 }
99 };
100
101 let cursor_index = if read_only {
102 None
103 } else {
104 is_line_selected.then(|| editor_data.cursor_col())
105 };
106 let gutter_color = if is_line_selected {
107 theme.gutter_selected
108 } else {
109 theme.gutter_unselected
110 };
111 let visible_selection = match editor_data.get_selection() {
112 None => false,
113 Some((s, e)) if s != e => true,
114 _ => false,
115 };
116 let line_background = if is_line_selected && !visible_selection {
117 theme.line_selected_background
118 } else {
119 Color::TRANSPARENT
120 };
121
122 rect()
123 .horizontal()
124 .height(Size::px(line_height))
125 .background(line_background)
126 .font_size(font_size)
127 .maybe(gutter, |el| {
128 el.child(
129 rect()
130 .width(Size::px(gutter_width))
131 .height(Size::fill())
132 .padding(Gaps::new(0., 0., 0., 20.))
133 .main_align(Alignment::Center)
134 .child(
135 label()
136 .color(gutter_color)
137 .text(format!("{} ", line_index + 1)),
138 ),
139 )
140 })
141 .child(
142 paragraph()
143 .holder(holder.read().clone())
144 .on_pointer_move(on_pointer_move)
145 .on_focus_press(on_tap)
146 .cursor_color(theme.cursor)
147 .cursor_style(CursorStyle::Block)
148 .cursor_index(cursor_index)
149 .cursor_mode(CursorMode::Expanded)
150 .vertical_align(VerticalAlign::Center)
151 .highlights(highlights.map(|h| vec![h]))
152 .highlight_color(theme.highlight)
153 .width(Size::px(longest_width))
154 .min_width(Size::fill())
155 .height(Size::fill())
156 .font_family(font_family)
157 .max_lines(1)
158 .color(theme.text)
159 .spans_iter(line.iter().map(|span| {
160 let text: Cow<str> = match &span.1 {
161 TextNode::Range(word_pos) => {
162 editor_data.rope.slice(word_pos.clone()).into()
163 }
164 TextNode::LineOfChars { len, char } => {
165 if show_whitespace {
166 Cow::Owned(char.to_string().repeat(*len))
167 } else {
168 Cow::Owned(" ".repeat(*len))
169 }
170 }
171 };
172 Span::new(Cow::Owned(text.to_string())).color(span.0)
173 })),
174 )
175 }
176}