Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/selectable_area.rs
StratoSDK / crates / strato-ui-core / src / elements / selectable_area.rs
1//! This element adds cross-element selectability to the UI framework.
2//!
3//! Any elements underneath a SelectableArea which both implement SelectableElement and
4//! pass in a selectable state handle will be selectable underneath that SelectableArea.
5//!
6//! For an example of basic usage, refer to the selectable UI sample.
7 
8use super::SelectionFragment;
9use super::{
10 AfterLayoutContext, AppContext, ColorU, Element, Event, EventContext, LayoutContext,
11 PaintContext, Point, SizeConstraint,
12};
13use crate::event::{DispatchedEvent, ModifiersState};
14use crate::text::word_boundaries::WordBoundariesPolicy;
15use crate::text::{IsRect, SelectionDirection, SelectionType};
16use pathfinder_geometry::vector::{vec2f, Vector2F};
17 
18use crate::text_offsets::ByteOffset;
19use lazy_static::lazy_static;
20use std::ops::Range;
21use std::sync::Arc;
22use std::sync::Mutex;
23 
24/// A function that, given some content and a double-click index offset in that content,
25/// returns the resulting smart selection range.
26pub type SmartSelectFn = fn(content: &str, click_offset: ByteOffset) -> Option<Range<ByteOffset>>;
27 
28pub struct SelectableArea {
29 child: Box<dyn Element>,
30 size: Option<Vector2F>,
31 origin: Option<Point>,
32 selection_handler: SelectionHandler,
33 selection_updated_handler: Option<SelectionUpdatedHandler>,
34 selection_right_click_handler: Option<SelectionRightClickHandler>,
35 
36 // To preserve selections when scrolling, the selectable area stores the current selection's
37 // state as points relative to the origin. When rendering the selection, these points
38 // are then converted back to absolute points using the origin.
39 selectable_area_state: SelectionHandle,
40 
41 word_boundaries_policy: WordBoundariesPolicy,
42 
43 smart_select_fn: Option<SmartSelectFn>,
44 
45 should_support_rect_select: bool,
46}
47 
48/// Stores the selection start and end points. We include the option to store
49/// bounds alongside raw selection points since we may need to clamp the selection
50/// points to the SelectableArea's bounds when it's not laid out (i.e. to support
51/// across-block selections with AI blocks).
52#[derive(Clone, Copy, Debug, Default)]
53pub struct InternalSelection {
54 /// The point where the user first clicked before dragging.
55 /// This could be after tail if the selection is reversed.
56 pub head: Option<SelectionBound>,
57 /// The latest point the user dragged the selection to.
58 /// This could be before head if the selection is reversed.
59 pub tail: Option<SelectionBound>,
60 /// The head of the selection after semantic expansion. Note the direction of expansion
61 /// depends on whether the selection was reversed.
62 /// This could be after tail if the selection is reversed.
63 pub expanded_head: Option<SelectionBound>,
64 /// The tail of the selection after semantic expansion. Note the direction of expansion
65 /// depends on whether the selection was reversed.
66 /// This could be before head if the selection is reversed.
67 pub expanded_tail: Option<SelectionBound>,
68 /// The initial smart selection on double-click, set only if
69 /// smart_select_fn successfully returned a smart selection.
70 /// We store this separately because selection updates after dragging should never be smaller than
71 /// the initial smart selection range.
72 pub initial_smart_selection: Option<InitialSmartSelection>,
73 /// The semantic selection unit.
74 pub unit: SelectionType,
75 pub is_selecting: bool,
76 /// If true, head is after tail.
77 pub is_reversed: bool,
78 /// Whether we should return the smart selection's start when computing the selection start.
79 /// This is caching whether the smart selection's start is earlier than the expanded start.
80 pub should_use_smart_start: bool,
81 /// Whether we should return the smart selection's end when computing the selection end.
82 /// This is caching whether the smart selection's end is later than the expanded end.
83 pub should_use_smart_end: bool,
84}
85 
86/// The initial smart selection on double-click, set only if
87/// smart_select_fn successfully returned a smart selection.
88/// We store this separately because selection updates after dragging should never be smaller than
89/// the initial smart selection range.
90#[derive(Clone, Copy, Debug)]
91pub struct InitialSmartSelection {
92 /// Always before end.
93 pub start: SelectionBound,
94 /// Always after start.
95 pub end: SelectionBound,
96}
97 
98impl InternalSelection {
99 // Returns the start point of the selection, using expanded points
100 // if they exist. This is always before end.
101 pub fn start(&self) -> Option<SelectionBound> {
102 if self.should_use_smart_start {
103 self.initial_smart_selection
104 .map(|smart_selection| smart_selection.start)
105 } else if self.is_reversed {
106 self.expanded_tail.or(self.tail)
107 } else {
108 self.expanded_head.or(self.head)
109 }
110 }
111 
112 // Returns the end point of the selection, using expanded points
113 // if they exist. This is always after start.
114 pub fn end(&self) -> Option<SelectionBound> {
115 if self.should_use_smart_end {
116 self.initial_smart_selection
117 .map(|smart_selection| smart_selection.end)
118 } else if self.is_reversed {
119 self.expanded_head.or(self.head)
120 } else {
121 self.expanded_tail.or(self.tail)
122 }
123 }
124 
125 /// Clears the current selection state.
126 ///
127 /// This is `pub` so callers may imperatively clear selection state in cases where a
128 /// selection-clearing mouse or keyboard event is handled prior to being received by this
129 /// `SelectableArea`.
130 pub fn clear(&mut self) {
131 *self = InternalSelection::default();
132 }
133}
134 
135#[derive(Clone, Copy, Debug, Default, PartialEq)]
136pub struct Selection {
137 pub start: Vector2F,
138 pub end: Vector2F,
139 pub is_rect: IsRect,
140}
141 
142#[derive(Clone, Copy, Debug, PartialEq)]
143pub enum SelectionBound {
144 /// Relative to the SelectableArea's origin
145 Relative(Vector2F),
146 /// Start from the top left point of the SelectableArea.
147 TopLeft,
148 /// Start from the bottom right of the SelectableArea.
149 BottomRight,
150 /// Start from the top column of the SelectableArea. The row is defined by the x_bound.
151 Top { x_bound: f32 },
152 /// Start from the bottom column of the SelectableArea. The row is defined by the x_bound.
153 Bottom { x_bound: f32 },
154}
155 
156impl SelectionBound {
157 fn as_absolute_point(&self, size: Vector2F, origin: Vector2F) -> Vector2F {
158 match self {
159 SelectionBound::Relative(point) => *point + origin,
160 SelectionBound::TopLeft => origin,
161 SelectionBound::BottomRight => origin + size,
162 SelectionBound::Top { x_bound } => vec2f(*x_bound, origin.y()),
163 SelectionBound::Bottom { x_bound } => vec2f(*x_bound, size.y() + origin.y()),
164 }
165 }
166}
167 
168pub struct SelectionUpdateArgs {
169 pub selection: Option<String>,
170}
171 
172lazy_static! {
173 pub static ref SELECTED_HIGHLIGHT_COLOR: ColorU =
174 ColorU::new(118, 167, 250, (0.4 * 255.) as u8);
175}
176 
177#[derive(Default, Clone, Debug)]
178pub struct SelectionHandle {
179 selection: Arc<Mutex<InternalSelection>>,
180}
181pub type SelectionHandler = Box<dyn FnMut(SelectionUpdateArgs, &mut EventContext, &AppContext)>;
182type SelectionUpdatedHandler = Box<dyn FnMut(&mut EventContext, &AppContext)>;
183type SelectionRightClickHandler = Box<dyn FnMut(&mut EventContext, Vector2F)>;
184 
185impl SelectionHandle {
186 /// This isn't meant for general use. It's used specifically in cases where a selection is started
187 /// outside the SelectableArea's bounds and the SelectableArea's start point needs to be clamped manually.
188 pub fn start_selection_outside(&self, bound: SelectionBound, unit: SelectionType) {
189 let mut selection = self.selection.lock().expect("Should not be poisoned.");
190 selection.head = Some(bound);
191 selection.unit = unit;
192 selection.is_selecting = true;
193 }
194 
195 /// Whether there is an active selection in the SelectableArea.
196 /// An active selection is not necessarily a non-empty selection.
197 pub fn is_selecting(&self) -> bool {
198 self.selection
199 .lock()
200 .expect("Should not be poisoned.")
201 .is_selecting
202 }
203 
204 pub fn clear(&self) {
205 self.selection
206 .lock()
207 .expect("Mutex is not poisoned.")
208 .clear();
209 }
210 
211 #[cfg(feature = "integration_tests")]
212 pub fn selection_type(&self) -> SelectionType {
213 self.selection.lock().expect("Mutex is not poisoned.").unit
214 }
215}
216 
217impl SelectableArea {
218 pub fn new<F>(
219 selectable_area_state: SelectionHandle,
220 selection_handler: F,
221 child: Box<dyn Element>,
222 ) -> Self
223 where
224 F: 'static + FnMut(SelectionUpdateArgs, &mut EventContext, &AppContext),
225 {
226 Self {
227 child,
228 size: None,
229 origin: None,
230 selectable_area_state,
231 selection_handler: Box::new(selection_handler),
232 selection_updated_handler: None,
233 selection_right_click_handler: None,
234 word_boundaries_policy: WordBoundariesPolicy::Default,
235 smart_select_fn: None,
236 should_support_rect_select: false,
237 }
238 }
239 
240 pub fn should_support_rect_select(mut self) -> Self {
241 self.should_support_rect_select = true;
242 self
243 }
244 
245 pub fn with_word_boundaries_policy(self, word_boundaries_policy: WordBoundariesPolicy) -> Self {
246 Self {
247 word_boundaries_policy,
248 ..self
249 }
250 }
251 
252 pub fn with_smart_select_fn(self, smart_select_fn: Option<SmartSelectFn>) -> Self {
253 Self {
254 smart_select_fn,
255 ..self
256 }
257 }
258 
259 /// The selection updated handler is invoked only when a selection is actively being made.
260 /// Clearing the text selection in a `SelectableArea` via `LeftMouseDown` doesn't count.
261 pub fn on_selection_updated<F>(self, selection_updated_handler: F) -> Self
262 where
263 F: 'static + FnMut(&mut EventContext, &AppContext),
264 {
265 Self {
266 selection_updated_handler: Some(Box::new(selection_updated_handler)),
267 ..self
268 }
269 }
270 
271 pub fn on_selection_right_click<F>(self, selection_right_click_fn: F) -> Self
272 where
273 F: 'static + FnMut(&mut EventContext, Vector2F),
274 {
275 Self {
276 selection_right_click_handler: Some(Box::new(selection_right_click_fn)),
277 ..self
278 }
279 }
280 
281 /// Clears any existing selection, and starts a new selection if the click is in the element.
282 /// Does not handle cases where a selection is started outside the `SelectableArea`'s bounds.
283 /// Returns `true` if a new selection was successfully started.
284 fn on_mouse_down(
285 &mut self,
286 position: Vector2F,
287 modifiers: &ModifiersState,
288 click_count: u32,
289 ctx: &mut EventContext,
290 app: &AppContext,
291 ) -> bool {
292 let Some(selectable_child_ref) = self.child.as_selectable_element() else {
293 return false;
294 };
295 // Clear any previously existing selection on mouse down.
296 let mut selection_state = self
297 .selectable_area_state
298 .selection
299 .lock()
300 .expect("Should not be poisoned.");
301 selection_state.clear();
302 
303 // Only if this click was in the element, start a new selection.
304 if !is_mouse_in(self.origin, self.size, ctx, position) {
305 return false;
306 }
307 let Some(origin) = self.origin else {
308 return false;
309 };
310 selection_state.is_selecting = true;
311 
312 let unit = if click_count == 1 {
313 if self.should_support_rect_select && modifiers.alt && modifiers.cmd {
314 SelectionType::Rect
315 } else {
316 SelectionType::Simple
317 }
318 } else if click_count == 2 {
319 SelectionType::Semantic
320 } else {
321 SelectionType::Lines
322 };
323 selection_state.unit = unit;
324 selection_state.head = Some(SelectionBound::Relative(position - origin.xy));
325 selection_state.tail = Some(SelectionBound::Relative(position - origin.xy));
326 
327 // First, try smart selection if it's configured and this is a double click.
328 let smart_select_range = match (unit, self.smart_select_fn) {
329 (SelectionType::Semantic, Some(smart_select_fn)) => {
330 selectable_child_ref.smart_select(position, smart_select_fn)
331 }
332 // In all other cases, use the default selection expansion
333 _ => None,
334 };
335 let (expanded_head, expanded_tail) = match smart_select_range {
336 // If smart_select_range is set, use that as expanded head / tail.
337 Some((start, end)) => {
338 selection_state.initial_smart_selection = Some(InitialSmartSelection {
339 start: SelectionBound::Relative(start - origin.xy),
340 end: SelectionBound::Relative(end - origin.xy),
341 });
342 (Some(start), Some(end))
343 }
344 _ => {
345 // Otherwise, expand the selection normally.
346 let expanded_head = selectable_child_ref.expand_selection(
347 position,
348 SelectionDirection::Backward,
349 unit,
350 &self.word_boundaries_policy,
351 );
352 let expanded_tail = selectable_child_ref.expand_selection(
353 position,
354 SelectionDirection::Forward,
355 unit,
356 &self.word_boundaries_policy,
357 );
358 (expanded_head, expanded_tail)
359 }
360 };
361 
362 // Set the expanded head and tail. Since the resulting expanded selection could be
363 // non-empty, we should invoke the selection update handler as well.
364 if let Some((head, tail)) = expanded_head.zip(expanded_tail) {
365 selection_state.expanded_head = Some(SelectionBound::Relative(head - origin.xy));
366 selection_state.expanded_tail = Some(SelectionBound::Relative(tail - origin.xy));
367 
368 if head != tail {
369 if let Some(selection_updated_handler) = self.selection_updated_handler.as_mut() {
370 selection_updated_handler(ctx, app);
371 }
372 }
373 }
374 
375 // By this point, we've determined that a selection has successfully started.
376 // Returning `true` ensures that parent `SelectableArea`s don't attempt to start their
377 // own text selections at the same time.
378 true
379 }
380 
381 fn on_right_mouse_down(&mut self, position: Vector2F, ctx: &mut EventContext) -> bool {
382 // Ignore this right-click unless it took place within this SelectableArea element
383 if !is_mouse_in(self.origin, self.size, ctx, position) {
384 return false;
385 }
386 
387 let current_selection = self.get_current_selection_absolute();
388 let Some(selectable_child_ref) = self.child.as_selectable_element() else {
389 return false;
390 };
391 let Some(right_click_handler) = self.selection_right_click_handler.as_mut() else {
392 return false;
393 };
394 
395 let clickable_bounds = selectable_child_ref.calculate_clickable_bounds(current_selection);
396 let is_within_bounds = clickable_bounds
397 .iter()
398 .any(|bounds| bounds.contains_point(position));
399 
400 if is_within_bounds {
401 let origin = self
402 .origin
403 .expect("Origin should be defined before mouse clicks")
404 .xy();
405 let position_in_block = position - origin;
406 right_click_handler(ctx, position_in_block);
407 }
408 is_within_bounds
409 }
410 
411 /// Updates the selection using the latest tail position (where the user dragged to).
412 /// Expands selections as needed and computes whether the selection is_reversed.
413 /// Returns whether the selection was actually updated.
414 fn update_selection(&mut self, tail_absolute_position: Vector2F) -> bool {
415 let Some(selectable_child_ref) = self.child.as_selectable_element() else {
416 return false;
417 };
418 let mut selection_state = self
419 .selectable_area_state
420 .selection
421 .lock()
422 .expect("Should not be poisoned.");
423 
424 // We can only update the selection if we have a selection head the selection was initiated from.
425 let (Some(relative_selection_head), Some(origin), Some(size)) =
426 (selection_state.head, self.origin, self.size)
427 else {
428 return false;
429 };
430 
431 let new_selection_tail = SelectionBound::Relative(tail_absolute_position - origin.xy);
432 // Don't update or cache the selection if it hasn't changed
433 if selection_state
434 .tail
435 .is_some_and(|old_selection_tail| old_selection_tail == new_selection_tail)
436 {
437 return false;
438 }
439 
440 // Update the selection's raw end.
441 selection_state.tail = Some(new_selection_tail);
442 
443 // Compute whether the selection is reversed. This is needed
444 // to determine how to expand the selection.
445 let head_absolute_position = relative_selection_head.as_absolute_point(size, origin.xy());
446 let is_reversed = if matches!(
447 relative_selection_head,
448 SelectionBound::TopLeft | SelectionBound::Top { .. }
449 ) {
450 Some(false)
451 } else if matches!(
452 relative_selection_head,
453 SelectionBound::BottomRight | SelectionBound::Bottom { .. }
454 ) {
455 Some(true)
456 } else {
457 // If the end is before the start, this is a reversed selection.
458 selectable_child_ref
459 .is_point_semantically_before(tail_absolute_position, head_absolute_position)
460 };
461 // If we can't tell whether the selection is reversed, don't do semantic expansion.
462 let Some(is_reversed_selection) = is_reversed else {
463 // We return true here because the selection was already successfully updated with the latest unexpanded tail.
464 return true;
465 };
466 
467 let (head_direction, tail_direction) = if is_reversed_selection {
468 // If this is a reversed selection, the tail (point the user dragged to) should be expanded backward
469 // since it will be the start of the selection.
470 (SelectionDirection::Forward, SelectionDirection::Backward)
471 } else {
472 // If this is a forward selection, the head (point user originally clicked before dragging)
473 // should be expanded backward since it will be the start of the selection.
474 (SelectionDirection::Backward, SelectionDirection::Forward)
475 };
476 
477 // We always need to expand the new tail.
478 // If we're changing the value of is_reversed, we also need to re-expand
479 // the head, since the direction of head expansion changes.
480 // There are expected cases where only one is expanded successfully and not the other.
481 // For example, if a semantic selection was started outside the selectable area and then
482 // dragged in, the original head would be a max/min bound of the selectable area which
483 // can't always be expanded.
484 let expanded_tail = selectable_child_ref.expand_selection(
485 tail_absolute_position,
486 tail_direction,
487 selection_state.unit,
488 &self.word_boundaries_policy,
489 );
490 selection_state.expanded_tail =
491 expanded_tail.map(|expanded_tail| SelectionBound::Relative(expanded_tail - origin.xy));
492 if selection_state.is_reversed != is_reversed_selection {
493 let expanded_head = selectable_child_ref.expand_selection(
494 head_absolute_position,
495 head_direction,
496 selection_state.unit,
497 &self.word_boundaries_policy,
498 );
499 selection_state.expanded_head = expanded_head
500 .map(|expanded_head| SelectionBound::Relative(expanded_head - origin.xy));
501 }
502 selection_state.is_reversed = is_reversed_selection;
503 // Now that we've set the new expanded head and tail, make sure our new selection is not smaller than
504 // the original smart selection if there was one.
505 // First reset the cached values to get the selection start/end without considering the initial smart selection.
506 selection_state.should_use_smart_start = false;
507 selection_state.should_use_smart_end = false;
508 let (Some(new_start), Some(new_end)) = (selection_state.start(), selection_state.end())
509 else {
510 return true;
511 };
512 let Some(initial_smart_selection) = selection_state.initial_smart_selection else {
513 return true;
514 };
515 // Use the smart selection start/end if they would make the selection range bigger than the expanded selection.
516 if selectable_child_ref
517 .is_point_semantically_before(
518 initial_smart_selection
519 .start
520 .as_absolute_point(size, origin.xy()),
521 new_start.as_absolute_point(size, origin.xy()),
522 )
523 .unwrap_or(false)
524 {
525 selection_state.should_use_smart_start = true
526 }
527 if selectable_child_ref
528 .is_point_semantically_before(
529 new_end.as_absolute_point(size, origin.xy()),
530 initial_smart_selection
531 .end
532 .as_absolute_point(size, origin.xy()),
533 )
534 .unwrap_or(false)
535 {
536 selection_state.should_use_smart_end = true
537 }
538 true
539 }
540 
541 // Returns the current selection in absolute coordinates.
542 fn get_current_selection_absolute(&self) -> Option<Selection> {
543 let (Some(origin), Some(size)) = (self.origin, self.size) else {
544 return None;
545 };
546 let selection = self
547 .selectable_area_state
548 .selection
549 .lock()
550 .expect("Should not be poisoned.");
551 let (Some(start), Some(end)) = (selection.start(), selection.end()) else {
552 return None;
553 };
554 
555 Some(Selection {
556 start: start.as_absolute_point(size, origin.xy()),
557 end: end.as_absolute_point(size, origin.xy()),
558 is_rect: selection.unit.into(),
559 })
560 }
561 
562 fn get_current_selection_text_fragments(&self) -> Option<Vec<SelectionFragment>> {
563 let updated_selection = self.get_current_selection_absolute()?;
564 let selectable_child_ref = self.child.as_selectable_element()?;
565 
566 // Order selected text fragments
567 selectable_child_ref.get_selection(
568 updated_selection.start,
569 updated_selection.end,
570 updated_selection.is_rect,
571 )
572 }
573 
574 fn is_current_selection_empty(&self) -> bool {
575 self.get_current_selection_text_fragments()
576 .unwrap_or_default()
577 .is_empty()
578 }
579 
580 fn invoke_selection_handler(&mut self, ctx: &mut EventContext, app: &AppContext) {
581 let text_fragments = self.get_current_selection_text_fragments();
582 let update_args = SelectionUpdateArgs {
583 // If `text_fragments` is `None`, we still need to invoke the selection_handler accordingly.
584 // Otherwise, clicking away from text within an AIBlock won't clear the underlying selected_text state.
585 selection: text_fragments.map(order_and_concatenate_fragments),
586 };
587 (self.selection_handler)(update_args, ctx, app);
588 ctx.notify();
589 }
590}
591 
592/// Determine if the mouse is over the element
593fn is_mouse_in(
594 origin: Option<Point>,
595 size: Option<Vector2F>,
596 ctx: &EventContext,
597 position: Vector2F,
598) -> bool {
599 let Some(origin) = origin else {
600 log::warn!("self.origin was None in `SelectableArea::is_mouse_in`");
601 return false;
602 };
603 let Some(size) = size else {
604 log::warn!("self.size() was None in `SelectableArea::is_mouse_in`");
605 return false;
606 };
607 
608 ctx.visible_rect(origin, size)
609 .is_some_and(|bound| bound.contains_point(position))
610}
611 
612fn order_and_concatenate_fragments(mut selection_fragments: Vec<SelectionFragment>) -> String {
613 selection_fragments.sort_by(|a, b| {
614 if a.origin.y() == b.origin.y() {
615 a.origin.x().total_cmp(&b.origin.x())
616 } else {
617 a.origin.y().total_cmp(&b.origin.y())
618 }
619 });
620 
621 selection_fragments
622 .iter()
623 .map(|s| s.text.as_str())
624 .collect::<Vec<&str>>()
625 .concat()
626}
627 
628impl Element for SelectableArea {
629 fn layout(
630 &mut self,
631 constraint: SizeConstraint,
632 ctx: &mut LayoutContext,
633 app: &AppContext,
634 ) -> Vector2F {
635 let child_constraint = SizeConstraint {
636 min: (constraint.min).max(Vector2F::zero()),
637 max: (constraint.max).max(Vector2F::zero()),
638 };
639 let child_size = self.child.layout(child_constraint, ctx, app);
640 let size = child_size;
641 self.size = Some(size);
642 size
643 }
644 
645 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
646 self.child.after_layout(ctx, app);
647 }
648 
649 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
650 self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index()));
651 ctx.current_selection = self.get_current_selection_absolute();
652 self.child.paint(origin, ctx, app);
653 ctx.current_selection = None;
654 }
655 
656 fn dispatch_event(
657 &mut self,
658 event: &DispatchedEvent,
659 ctx: &mut EventContext,
660 app: &AppContext,
661 ) -> bool {
662 // Only dispatch to the child if we're not in the middle of a non-empty selection.
663 // Nested `SelectableArea` elements should pick up on mouse events if said events
664 // are not being used to create a non-trivial selection in this `SelectableArea`.
665 // Do not handle the event with this element if the child handles it, as doing so
666 // could result in nested `SelectableArea` elements being unnecessarily cleared.
667 let should_dispatch_to_child =
668 !self.selectable_area_state.is_selecting() || self.is_current_selection_empty();
669 if should_dispatch_to_child {
670 let handled = self.child.as_mut().dispatch_event(event, ctx, app);
671 if handled {
672 return true;
673 }
674 }
675 
676 match event.raw_event() {
677 Event::LeftMouseDown {
678 position,
679 click_count,
680 modifiers,
681 ..
682 } => {
683 let selection_started =
684 self.on_mouse_down(*position, modifiers, *click_count, ctx, app);
685 
686 // Invoking the selection handler is necessary to notify parent views that we've
687 // cleared the internal selection state of the `SelectableArea`.
688 self.invoke_selection_handler(ctx, app);
689 selection_started
690 }
691 Event::LeftMouseDragged { position, .. } => {
692 if !self.selectable_area_state.is_selecting() {
693 return false;
694 }
695 let (Some(origin), Some(size)) = (self.origin, self.size) else {
696 return false;
697 };
698 
699 let selection_updated = self.update_selection(*position);
700 if !selection_updated {
701 return false;
702 }
703 if let Some(selection_updated_handler) = self.selection_updated_handler.as_mut() {
704 selection_updated_handler(ctx, app);
705 }
706 
707 // Materialize and cache the selected text if SelectableArea is about to go off-screen.
708 // Since origin isn't available when SelectableArea is off-screen, we aren't able to
709 // materialize the selection on mouse up if that's the case. As a workaround,
710 // we cache it here ahead of time.
711 if origin.y() < 0.
712 || origin.y() + size.y() > app.windows().active_display_bounds().height()
713 {
714 self.invoke_selection_handler(ctx, app)
715 }
716 
717 // Returning true ensures that this SelectableArea's ongoing selections won't
718 // conflict with parent or child SelectableAreas in the element tree.
719 ctx.notify();
720 true
721 }
722 Event::LeftMouseUp { position, .. } => {
723 self.selectable_area_state
724 .selection
725 .lock()
726 .expect("Should not be poisoned.")
727 .is_selecting = false;
728 self.invoke_selection_handler(ctx, app);
729 
730 // If the mouse is inside this element no other element needs to handle this event
731 // because this is the "lowest level" element so we return `true`. If the mouse is
732 // outside this element we return `false` so other elements can handle the event
733 // as well. We need to handle `LeftMouseUp` in either case to support selections
734 // across elements. Note that this behavior may need to change in the future.
735 is_mouse_in(self.origin, self.size, ctx, *position)
736 }
737 Event::RightMouseDown { position, .. } => self.on_right_mouse_down(*position, ctx),
738 _ => false,
739 }
740 }
741 
742 fn size(&self) -> Option<Vector2F> {
743 self.size
744 }
745 
746 fn origin(&self) -> Option<Point> {
747 self.origin
748 }
749}
750