Skip to main content

freya_components/
selectable_text.rs

1use std::borrow::Cow;
2
3use freya_core::prelude::*;
4use freya_edit::*;
5
6/// Current status of the SelectableText.
7#[derive(Debug, Default, PartialEq, Clone, Copy)]
8pub enum SelectableTextStatus {
9    /// Default state.
10    #[default]
11    Idle,
12    /// Mouse is hovering the text.
13    Hovering,
14}
15
16#[derive(Clone, PartialEq)]
17pub struct SelectableText {
18    value: Cow<'static, str>,
19    layout: LayoutData,
20    accessibility: AccessibilityData,
21    text_style_data: TextStyleData,
22    max_lines: Option<usize>,
23    line_height: Option<f32>,
24    key: DiffKey,
25}
26
27impl KeyExt for SelectableText {
28    fn write_key(&mut self) -> &mut DiffKey {
29        &mut self.key
30    }
31}
32
33impl LayoutExt for SelectableText {
34    fn get_layout(&mut self) -> &mut LayoutData {
35        &mut self.layout
36    }
37}
38
39impl ContainerExt for SelectableText {}
40
41impl AccessibilityExt for SelectableText {
42    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
43        &mut self.accessibility
44    }
45}
46
47impl TextStyleExt for SelectableText {
48    fn get_text_style_data(&mut self) -> &mut TextStyleData {
49        &mut self.text_style_data
50    }
51}
52
53impl SelectableText {
54    pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
55        Self {
56            value: value.into(),
57            layout: LayoutData::default(),
58            accessibility: AccessibilityData::default(),
59            text_style_data: TextStyleData::default(),
60            max_lines: None,
61            line_height: None,
62            key: DiffKey::None,
63        }
64    }
65
66    pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
67        self.max_lines = max_lines.into();
68        self
69    }
70
71    pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
72        self.line_height = line_height.into();
73        self
74    }
75}
76
77impl Component for SelectableText {
78    fn render(&self) -> impl IntoElement {
79        let holder = use_state(ParagraphHolder::default);
80        let mut editable = use_editable(
81            || self.value.to_string(),
82            move || EditableConfig::new().with_allow_changes(false),
83        );
84        let mut status = use_state(SelectableTextStatus::default);
85        let focus = use_focus();
86        let mut drag_origin = use_state(|| None);
87
88        if self.value.as_ref() != editable.editor().read().rope() {
89            editable.editor_mut().write().set(self.value.as_ref());
90            editable.editor_mut().write().editor_history().clear();
91        }
92
93        let highlights = editable
94            .editor()
95            .read()
96            .get_visible_selection(EditorLine::SingleParagraph);
97
98        let on_pointer_down = move |e: Event<PointerEventData>| {
99            e.stop_propagation();
100            drag_origin.set(Some(e.global_location() - e.element_location()));
101            editable.process_event(EditableEvent::Down {
102                location: e.element_location(),
103                editor_line: EditorLine::SingleParagraph,
104                holder: &holder.read(),
105            });
106            focus.request_focus();
107        };
108
109        let on_global_pointer_move = move |e: Event<PointerEventData>| {
110            if focus.is_focused()
111                && let Some(drag_origin) = drag_origin()
112            {
113                let mut element_location = e.element_location();
114                element_location.x -= drag_origin.x;
115                element_location.y -= drag_origin.y;
116                editable.process_event(EditableEvent::Move {
117                    location: element_location,
118                    editor_line: EditorLine::SingleParagraph,
119                    holder: &holder.read(),
120                });
121            }
122        };
123
124        let on_global_pointer_down = move |_: Event<PointerEventData>| {
125            if *status.read() == SelectableTextStatus::Idle {
126                editable.editor_mut().write().clear_selection();
127            }
128        };
129
130        let on_pointer_enter = move |_| {
131            *status.write() = SelectableTextStatus::Hovering;
132        };
133
134        let on_pointer_leave = move |_| {
135            *status.write() = SelectableTextStatus::default();
136        };
137
138        let on_mouse_up = move |_| {
139            editable.process_event(EditableEvent::Release);
140        };
141
142        let on_key_down = move |e: Event<KeyboardEventData>| {
143            editable.process_event(EditableEvent::KeyDown {
144                key: &e.key,
145                modifiers: e.modifiers,
146            });
147        };
148
149        let on_key_up = move |e: Event<KeyboardEventData>| {
150            editable.process_event(EditableEvent::KeyUp { key: &e.key });
151        };
152
153        let on_global_pointer_press = move |_: Event<PointerEventData>| {
154            match *status.read() {
155                SelectableTextStatus::Idle if focus.is_focused() => {
156                    editable.process_event(EditableEvent::Release);
157                }
158                SelectableTextStatus::Hovering => {
159                    editable.process_event(EditableEvent::Release);
160                }
161                _ => {}
162            };
163
164            if drag_origin.read().is_some() {
165                drag_origin.set(None);
166            } else if focus.is_focused() {
167                focus.request_unfocus();
168            }
169        };
170
171        paragraph()
172            .layout(self.layout.clone())
173            .accessibility(self.accessibility.clone())
174            .text_style(self.text_style_data.clone())
175            .max_lines(self.max_lines)
176            .line_height(self.line_height)
177            .a11y_id(focus.a11y_id())
178            .a11y_focusable(true)
179            .holder(holder.read().clone())
180            .cursor_color(Color::BLACK)
181            .highlights(highlights.map(|h| vec![h]))
182            .on_mouse_up(on_mouse_up)
183            .on_global_pointer_move(on_global_pointer_move)
184            .on_global_pointer_down(on_global_pointer_down)
185            .on_pointer_down(on_pointer_down)
186            .on_pointer_enter(on_pointer_enter)
187            .on_pointer_leave(on_pointer_leave)
188            .on_global_pointer_press(on_global_pointer_press)
189            .on_key_down(on_key_down)
190            .on_key_up(on_key_up)
191            .span(Span::new(editable.editor().read().to_string()))
192    }
193
194    fn render_key(&self) -> DiffKey {
195        self.key.clone().or(self.default_key())
196    }
197}