ferroui_controls/primitives/
text_selection_handle.rs1use super::{
2 SelectionHandleType, TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt, Thumb, ThumbImpl,
3 ThumbImplExt,
4};
5use crate::{Border, Canvas, ControlImpl, ControlImplExt, SizeChangedEventArgs};
6use ferroui_base::input::{
7 InputElement, InputElementImpl, InputElementImplExt, PointerCaptureLostEventArgs, PointerEventArgs,
8 PointerPressedEventArgs, PointerReleasedEventArgs, VectorEventArgs,
9};
10use ferroui_base::interactivity::{InteractiveImpl, RoutedEventArgs, RoutingStrategies};
11use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
12use ferroui_base::reactive::IDisposable;
13use ferroui_base::{
14 ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectImpl, Point, Rect, Ref,
15 StyledElementImpl, Vector, VisualImpl,
16};
17use std::cell::{Cell, RefCell};
18use std::rc::Rc;
19
20const PC_PRESSED: &str = ":pressed";
21const PC_CARET: &str = ":caret";
22const PC_START: &str = ":start";
23const PC_END: &str = ":end";
24
25#[repr(C)]
28pub struct TextSelectionHandle {
29 base: Thumb,
30 selection_handle_type: Cell<SelectionHandleType>,
31 is_rtl: Cell<bool>,
32 is_dragging: Cell<bool>,
33 needs_indicator_update: Cell<bool>,
34 start_position: Cell<Point>,
35 delta: Cell<Vector>,
36 last_point: Cell<Option<Point>>,
37 indicator: RefCell<Option<(Ref<Border>, Rc<dyn IDisposable>)>>,
38 last_requested_position: Cell<Option<Point>>,
39}
40
41ferro_class!(TextSelectionHandle: Thumb);
42
43ferro_class_info!(TextSelectionHandle {
44 new: TextSelectionHandle::new,
45 markup: {
46 attributes: [TemplatePart("PART_Indicator", type(Ref<Border>))],
47 },
48});
49
50ferro_impl_classes!(TextSelectionHandle: FerroObjectImpl, StyledElementImpl, VisualImpl, InteractiveImpl);
51
52impl TemplatedControlImpl for TextSelectionHandle {
53 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
54 Self::parent_on_apply_template(this, e);
55
56 if let Some((_, layout_updated)) = this.indicator.take() {
57 layout_updated.dispose();
58 }
59
60 let indicator = e.name_scope().get_as::<Border>("PART_Indicator");
61
62 let weak = this.to_ref().downgrade();
63 let layout_updated = indicator.layout_updated(move || {
64 if let Some(this) = weak.upgrade() {
65 this.indicator_layout_updated();
66 }
67 });
68 *this.indicator.borrow_mut() = Some((indicator, layout_updated));
69
70 this.update_handle_classes();
71 }
72}
73
74impl ControlImpl for TextSelectionHandle {
75 fn on_size_changed(this: &Self, e: &SizeChangedEventArgs) {
76 Self::parent_on_size_changed(this, e);
77
78 if let Some(last_requested_position) = this.last_requested_position.get() {
79 this.set_top_left(last_requested_position);
80 this.last_requested_position.set(None);
81 this.needs_indicator_update.set(false);
82 }
83 }
84
85 fn on_loaded(this: &Self, args: &RoutedEventArgs) {
86 Self::parent_on_loaded(this, args);
87
88 this.update_handle_classes();
89
90 this.invalidate_visual();
91 }
92}
93
94impl ThumbImpl for TextSelectionHandle {
95 fn on_drag_started(this: &Self, e: &VectorEventArgs) {
96 Self::parent_on_drag_started(this, e);
97
98 this.start_position.set(this.get_top_left());
99 this.delta.set(Vector::default());
100 this.is_dragging.set(true);
101 }
102
103 fn on_drag_delta(this: &Self, e: &VectorEventArgs) {
104 Self::parent_on_drag_delta(this, e);
105 let new_delta = e.vector;
106
107 if !e.handled() && (new_delta - this.delta.get()).length().abs() > 0.0 {
108 this.delta.set(new_delta);
109 let point = this.start_position.get() + new_delta;
110 Canvas::set_top(this, point.y);
111 Canvas::set_left(this, point.x);
112 }
113 }
114
115 fn on_drag_completed(this: &Self, e: &VectorEventArgs) {
116 this.is_dragging.set(false);
117 this.start_position.set(Point::default());
118 Self::parent_on_drag_completed(this, e);
119 }
120}
121
122impl LayoutableImpl for TextSelectionHandle {
123 fn arrange_core(this: &Self, final_rect: Rect) {
124 this.update_handle_classes();
125
126 Self::parent_arrange_core(this, final_rect);
127 }
128}
129
130impl InputElementImpl for TextSelectionHandle {
131 fn on_pointer_capture_lost(this: &Self, e: &PointerCaptureLostEventArgs) {
132 if let Some(last_point) = this.last_point.get() {
133 let mut ev = VectorEventArgs::new();
134 ev.set_routed_event(Some(Thumb::drag_completed_event()));
135 ev.vector = last_point.into();
136
137 this.last_point.set(None);
138
139 this.raise_event(&ev);
140 }
141
142 this.pseudo_classes().remove_pseudo(PC_PRESSED);
143
144 Self::parent_on_pointer_capture_lost(this, e);
145 }
146
147 fn on_pointer_moved(this: &Self, e: &PointerEventArgs) {
148 let this_element: Ref<InputElement> = this.to_ref().upcast();
149 if e.pointer().captured() != Some(this_element.clone()) {
150 return;
151 }
152
153 let root = this.visual_root();
154 let mut ev = VectorEventArgs::new();
155
156 match this.last_point.get() {
157 None => {
158 let last_point = e.get_position(root.as_deref());
159 this.last_point.set(Some(last_point));
160 e.pointer().capture(Some(&this_element));
161
162 ev.set_routed_event(Some(Thumb::drag_started_event()));
163 ev.vector = last_point.into();
164 }
165 Some(last_point) => {
166 let vector = e.get_position(root.as_deref()) - last_point;
167
168 ev.set_routed_event(Some(Thumb::drag_delta_event()));
169 ev.vector = vector.into();
170 }
171 }
172
173 this.raise_event(&ev);
174 }
175
176 fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
177 this.pseudo_classes().add_pseudo(PC_PRESSED);
178 let this_element: Ref<InputElement> = this.to_ref().upcast();
179 e.pointer().capture(Some(&this_element));
180 }
181
182 fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
183 if this.last_point.get().is_some() {
184 e.set_handled(true);
185 this.last_point.set(None);
186
187 let mut ev = VectorEventArgs::new();
188 ev.set_routed_event(Some(Thumb::drag_completed_event()));
189 ev.vector = e.get_position(this.visual_root().as_deref()).into();
190
191 this.raise_event(&ev);
192 }
193
194 this.pseudo_classes().remove_pseudo(PC_PRESSED);
195 e.pointer().capture(None);
196 }
197}
198
199impl TextSelectionHandle {
200 fn static_constructor() {
201 Thumb::drag_started_event().add_class_handler_with::<TextSelectionHandle>(
202 |x, e| x.on_drag_started(e),
203 RoutingStrategies::BUBBLE,
204 false,
205 );
206 Thumb::drag_delta_event().add_class_handler_with::<TextSelectionHandle>(
207 |x, e| x.on_drag_delta(e),
208 RoutingStrategies::BUBBLE,
209 false,
210 );
211 Thumb::drag_completed_event().add_class_handler_with::<TextSelectionHandle>(
212 |x, e| x.on_drag_completed(e),
213 RoutingStrategies::BUBBLE,
214 false,
215 );
216 }
217
218 pub fn construct() -> Self {
220 Self {
221 base: Thumb::construct(),
222 selection_handle_type: Cell::new(SelectionHandleType::Caret),
223 is_rtl: Cell::new(false),
224 is_dragging: Cell::new(false),
225 needs_indicator_update: Cell::new(false),
226 start_position: Cell::new(Point::default()),
227 delta: Cell::new(Vector::default()),
228 last_point: Cell::new(None),
229 indicator: RefCell::new(None),
230 last_requested_position: Cell::new(None),
231 }
232 }
233
234 pub fn new() -> Ref<Self> {
235 instantiate(Self::construct())
236 }
237
238 pub(crate) fn selection_handle_type(&self) -> SelectionHandleType {
239 self.selection_handle_type.get()
240 }
241
242 pub(crate) fn set_selection_handle_type(&self, value: SelectionHandleType) {
243 self.selection_handle_type.set(value);
244 self.update_handle_classes();
245 }
246
247 pub(crate) fn is_rtl(&self) -> bool {
248 self.is_rtl.get()
249 }
250
251 pub(crate) fn set_is_rtl(&self, value: bool) {
252 self.is_rtl.set(value);
253 self.update_handle_classes();
254 }
255
256 pub(crate) fn indicator_position(&self) -> Point {
257 let top_left = self.get_top_left();
258 top_left.with_x(top_left.x + self.get_indicator_offset())
259 }
260
261 pub(crate) fn is_dragging(&self) -> bool {
262 self.is_dragging.get()
263 }
264
265 #[allow(dead_code)]
267 pub(crate) fn needs_indicator_update(&self) -> bool {
268 self.needs_indicator_update.get()
269 }
270
271 pub(crate) fn set_needs_indicator_update(&self, value: bool) {
272 self.needs_indicator_update.set(value)
273 }
274
275 fn indicator(&self) -> Option<Ref<Border>> {
276 self.indicator.borrow().as_ref().map(|(indicator, _)| indicator.clone())
277 }
278
279 fn indicator_layout_updated(&self) {
280 if self.needs_indicator_update.get() {
281 if let Some(last_requested_position) = self.last_requested_position.get() {
282 self.set_top_left(last_requested_position);
283
284 self.needs_indicator_update.set(!self.indicator().is_none_or(|indicator| indicator.is_arrange_valid()));
285 }
286 }
287 }
288
289 pub(crate) fn set_top_left(&self, point: Point) {
290 if self.indicator.borrow().is_none() || self.needs_indicator_update.get() {
291 self.last_requested_position.set(Some(point));
292 }
293 Canvas::set_top(self, point.y);
294 Canvas::set_left(self, point.x - self.get_indicator_offset());
295 }
296
297 pub(crate) fn get_top_left(&self) -> Point {
298 Point::new(Canvas::get_left(self), Canvas::get_top(self))
299 }
300
301 pub(crate) fn get_indicator_offset(&self) -> f64 {
302 if let Some(indicator) = self.indicator() {
303 return indicator.bounds().center().x;
304 }
305
306 match self.selection_handle_type.get() {
307 SelectionHandleType::Caret => self.bounds().width / 2.0,
308 SelectionHandleType::Start => {
309 if self.is_rtl.get() {
310 0.0
311 } else {
312 self.bounds().width
313 }
314 }
315 SelectionHandleType::End => {
316 if self.is_rtl.get() {
317 self.bounds().width
318 } else {
319 0.0
320 }
321 }
322 }
323 }
324
325 fn update_handle_classes(&self) {
326 let pseudo_classes = self.pseudo_classes();
327 pseudo_classes.remove_pseudo(PC_CARET);
328 pseudo_classes.remove_pseudo(PC_START);
329 pseudo_classes.remove_pseudo(PC_END);
330
331 pseudo_classes.add_pseudo(match (self.selection_handle_type.get(), self.is_rtl.get()) {
332 (SelectionHandleType::Caret, _) => PC_CARET,
333 (SelectionHandleType::Start, false) | (SelectionHandleType::End, true) => PC_START,
334 (SelectionHandleType::Start, true) | (SelectionHandleType::End, false) => PC_END,
335 });
336 self.invalidate_visual();
337 }
338}