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 a11y_id = use_a11y();
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            if !e.data().is_primary() {
100                return;
101            }
102            e.stop_propagation();
103            drag_origin.set(Some(e.global_location() - e.element_location()));
104            editable.process_event(EditableEvent::Down {
105                location: e.element_location(),
106                editor_line: EditorLine::SingleParagraph,
107                holder: &holder.read(),
108            });
109            a11y_id.request_focus();
110        };
111
112        let on_global_pointer_move = move |e: Event<PointerEventData>| {
113            if a11y_id.is_focused()
114                && let Some(drag_origin) = drag_origin()
115            {
116                let mut element_location = e.element_location();
117                element_location.x -= drag_origin.x;
118                element_location.y -= drag_origin.y;
119                editable.process_event(EditableEvent::Move {
120                    location: element_location,
121                    editor_line: EditorLine::SingleParagraph,
122                    holder: &holder.read(),
123                });
124            }
125        };
126
127        let on_global_pointer_down = move |_: Event<PointerEventData>| {
128            if *status.read() == SelectableTextStatus::Idle {
129                editable.editor_mut().write().clear_selection();
130            }
131        };
132
133        let on_pointer_enter = move |_| {
134            *status.write() = SelectableTextStatus::Hovering;
135        };
136
137        let on_pointer_leave = move |_| {
138            *status.write() = SelectableTextStatus::default();
139        };
140
141        let on_mouse_up = move |_| {
142            editable.process_event(EditableEvent::Release);
143        };
144
145        let on_key_down = move |e: Event<KeyboardEventData>| {
146            editable.process_event(EditableEvent::KeyDown {
147                key: &e.key,
148                modifiers: e.modifiers,
149            });
150        };
151
152        let on_key_up = move |e: Event<KeyboardEventData>| {
153            editable.process_event(EditableEvent::KeyUp { key: &e.key });
154        };
155
156        let on_global_pointer_press = move |_: Event<PointerEventData>| {
157            match *status.read() {
158                SelectableTextStatus::Idle if a11y_id.is_focused() => {
159                    editable.process_event(EditableEvent::Release);
160                }
161                SelectableTextStatus::Hovering => {
162                    editable.process_event(EditableEvent::Release);
163                }
164                _ => {}
165            };
166
167            if drag_origin.read().is_some() {
168                drag_origin.set(None);
169            } else if a11y_id.is_focused() {
170                a11y_id.request_unfocus();
171            }
172        };
173
174        paragraph()
175            .layout(self.layout.clone())
176            .accessibility(self.accessibility.clone())
177            .text_style(self.text_style_data.clone())
178            .max_lines(self.max_lines)
179            .line_height(self.line_height)
180            .a11y_id(a11y_id)
181            .a11y_focusable(true)
182            .holder(holder.read().clone())
183            .cursor_color(Color::BLACK)
184            .highlights(highlights.map(|h| vec![h]))
185            .on_mouse_up(on_mouse_up)
186            .on_global_pointer_move(on_global_pointer_move)
187            .on_global_pointer_down(on_global_pointer_down)
188            .on_pointer_down(on_pointer_down)
189            .on_pointer_enter(on_pointer_enter)
190            .on_pointer_leave(on_pointer_leave)
191            .on_global_pointer_press(on_global_pointer_press)
192            .on_key_down(on_key_down)
193            .on_key_up(on_key_up)
194            .span(Span::new(editable.editor().read().to_string()))
195    }
196
197    fn render_key(&self) -> DiffKey {
198        self.key.clone().or(self.default_key())
199    }
200}