Skip to main content

freya_code_editor/
editor_line.rs

1use 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}