freya_components/
selectable_text.rs1use std::borrow::Cow;
2
3use freya_core::prelude::*;
4use freya_edit::*;
5
6#[derive(Debug, Default, PartialEq, Clone, Copy)]
8pub enum SelectableTextStatus {
9 #[default]
11 Idle,
12 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}