1use std::{fmt, sync::Arc};
2
3use parking_lot::{Mutex, RwLock};
4use zng_app::{event::CommandScope, widget::node::Z_INDEX};
5use zng_ext_clipboard::{CLIPBOARD, COPY_CMD};
6use zng_ext_font::CaretIndex;
7use zng_ext_input::focus::{FOCUS, FOCUS_CHANGED_EVENT};
8use zng_ext_window::WINDOWS;
9use zng_wgt::prelude::*;
10
11use crate::{
12 RICH_TEXT_FOCUSED_Z_VAR,
13 cmd::{SELECT_ALL_CMD, SELECT_CMD, TextSelectOp},
14};
15
16use super::{RICH_TEXT, RICH_TEXT_NOTIFY, RichCaretInfo, RichText, TEXT};
17
18pub(crate) fn rich_text_node(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
19 let enabled = enabled.into_var();
20 let child = rich_text_focus_change_broadcast(child);
21 let child = rich_text_cmds(child);
22 let child = rich_text_component(child, "rich_text");
23
24 let mut ctx = None;
25 let mut dispatch = None;
26 match_node(child, move |child, op| match op {
27 UiNodeOp::Init => {
28 WIDGET.sub_var(&enabled);
29 if enabled.get() && TEXT.try_rich().is_none() {
30 ctx = Some(Arc::new(RwLock::new(RichText {
31 root_id: WIDGET.id(),
32 caret: RichCaretInfo {
33 index: None,
34 selection_index: None,
35 },
36 selection_started_by_alt: false,
37 })));
38 dispatch = Some(Arc::new(RwLock::new(vec![])));
39
40 RICH_TEXT.with_context(&mut ctx, || child.init());
41 }
42 }
43 UiNodeOp::Event { update } => {
44 if ctx.is_some() {
45 RICH_TEXT.with_context(&mut ctx, || {
46 RICH_TEXT_NOTIFY.with_context(&mut dispatch, || {
47 child.event(update);
48
49 let mut tree = None;
50 let mut requests = std::mem::take(&mut *RICH_TEXT_NOTIFY.write());
51 while !requests.is_empty() {
52 for mut update in requests.drain(..) {
53 if update.delivery_list_mut().has_pending_search() {
54 if tree.is_none() {
55 tree = Some(WINDOW.info());
56 }
57 update.delivery_list_mut().fulfill_search(tree.iter());
58 }
59 if update.delivery_list().enter_widget(WIDGET.id()) {
60 child.event(&update);
61 } else {
62 tracing::error!("RichText notify_leaf update does not target an widget inside the rich context");
63 }
64 }
65 requests.extend(RICH_TEXT_NOTIFY.write().drain(..));
66 }
67 });
68 });
69 }
70 }
71 UiNodeOp::Update { updates } => {
72 if enabled.is_new() {
73 WIDGET.reinit();
74 } else if ctx.is_some() {
75 RICH_TEXT.with_context(&mut ctx, || child.update(updates));
76 }
77 }
78 UiNodeOp::Deinit => {
79 if ctx.is_some() {
80 RICH_TEXT.with_context(&mut ctx, || child.deinit());
81 ctx = None;
82 dispatch = None;
83 }
84 }
85 op => {
86 if ctx.is_some() {
87 RICH_TEXT.with_context(&mut ctx, || child.op(op));
88 }
89 }
90 })
91}
92
93fn rich_text_cmds(child: impl IntoUiNode) -> UiNode {
94 #[derive(Default)]
95 struct Cmds {
96 copy: CommandHandle,
98 select: CommandHandle,
101 select_all: CommandHandle,
102 }
103 let mut cmds = Cmds::default();
104 match_node(child, move |child, op| match op {
105 UiNodeOp::Init => {
106 if TEXT.try_rich().is_some() {
107 let id = WIDGET.id();
108 cmds.copy = COPY_CMD.scoped(id).subscribe(true);
110 cmds.select = SELECT_CMD.scoped(id).subscribe(true);
112 cmds.select_all = SELECT_ALL_CMD.scoped(id).subscribe(true);
114 }
115 }
116 UiNodeOp::Deinit => {
117 cmds = Cmds::default();
118 }
119 UiNodeOp::Event { update } => {
120 let ctx = match TEXT.try_rich() {
121 Some(c) => c,
122 None => return, };
124 if let Some(args) = COPY_CMD.event().on_unhandled(update) {
125 if args.param.is_none()
126 && let CommandScope::Widget(scope_id) = args.scope
127 && (ctx.root_id == scope_id || ctx.leaf_info(scope_id).is_some())
128 {
129 args.propagation().stop();
131
132 let mut txt = String::new();
133
134 for leaf in ctx.selection() {
135 let rich_copy = RichTextCopyParam::default();
136 let mut update = COPY_CMD.scoped(leaf.id()).new_update_param(rich_copy.clone());
137 update.delivery_list_mut().fulfill_search([leaf.tree()].into_iter());
138
139 child.event(&update);
140
141 if let Some(t_frag) = rich_copy.into_text() {
142 let line_info = leaf.rich_text_line_info();
143 if line_info.starts_new_line && !line_info.is_wrap_start && !txt.is_empty() {
144 txt.push('\n');
145 }
146
147 txt.push_str(&t_frag);
148 }
149 }
150
151 let _ = CLIPBOARD.set_text(txt);
152 }
153 } else if let Some(args) = SELECT_CMD.scoped(ctx.root_id).on_unhandled(update) {
154 args.propagation().stop();
155 if let Some(leaf) = ctx.caret_index_info().or_else(|| ctx.leaves().next()) {
156 let mut update = SELECT_CMD.scoped(leaf.id()).new_update();
157 update.delivery_list_mut().fulfill_search([leaf.tree()].into_iter());
158 child.event(&update);
159 }
160 } else if let Some(args) = SELECT_ALL_CMD.scoped(ctx.root_id).on_unhandled(update) {
161 args.propagation().stop();
162 if let Some(leaf) = ctx.caret_index_info().or_else(|| ctx.leaves().next()) {
163 let mut update = SELECT_ALL_CMD.scoped(leaf.id()).new_update();
164 update.delivery_list_mut().fulfill_search([leaf.tree()].into_iter());
165 child.event(&update);
166 }
167 }
168 }
169 _ => {}
170 })
171}
172fn rich_text_focus_change_broadcast(child: impl IntoUiNode) -> UiNode {
174 match_node(child, move |c, op| {
175 if let UiNodeOp::Event { update } = op
176 && let Some(args) = FOCUS_CHANGED_EVENT.on(update)
177 {
178 let ctx = match TEXT.try_rich() {
179 Some(c) => c,
180 None => return, };
182 if args.prev_focus.iter().chain(args.new_focus.iter()).any(|p| p.contains(ctx.root_id)) {
183 let mut extended_list = UpdateDeliveryList::new_any();
184 for leaf in ctx.leaves() {
185 extended_list.insert_wgt(&leaf);
186 }
187 args.delivery_list(&mut extended_list);
188 debug_assert!(!extended_list.has_pending_search()); c.event(&update.custom(extended_list));
190 }
191 }
192 })
193}
194
195pub fn rich_text_component(child: impl IntoUiNode, kind: &'static str) -> UiNode {
204 let mut focus_within = false;
205 let mut prev_index = ZIndex::DEFAULT;
206 let mut index_update = None;
207 match_node(child, move |c, op| match op {
208 UiNodeOp::Init => {
209 c.init();
210
211 if TEXT.try_rich().is_some() {
212 WIDGET.sub_event(&FOCUS_CHANGED_EVENT).sub_var(&RICH_TEXT_FOCUSED_Z_VAR);
213 prev_index = Z_INDEX.get();
214 }
215 }
216 UiNodeOp::Deinit => {
217 focus_within = false;
218 }
219 UiNodeOp::Info { info } => {
220 if let Some(r) = TEXT.try_rich() {
221 let c = match kind {
222 "rich_text" => {
223 if r.root_id == WIDGET.id() {
224 RichTextComponent::Root
225 } else {
226 RichTextComponent::Branch
227 }
228 }
229 kind => RichTextComponent::Leaf { kind },
230 };
231 info.set_meta(*RICH_TEXT_COMPONENT_ID, c);
232 }
233 }
234 UiNodeOp::Event { update } => {
235 if let Some(args) = FOCUS_CHANGED_EVENT.on(update) {
236 let new_is_focus_within = args.is_focus_within(WIDGET.id());
237 if focus_within != new_is_focus_within {
238 focus_within = new_is_focus_within;
239
240 if TEXT.try_rich().is_some() {
241 index_update = Some(focus_within);
242 WIDGET.update(); }
244 }
245 }
246 }
247 UiNodeOp::Update { updates } => {
248 c.update(updates);
249
250 if let Some(apply) = index_update.take() {
251 if apply {
252 prev_index = Z_INDEX.get();
253 if let Some(i) = RICH_TEXT_FOCUSED_Z_VAR.get() {
254 Z_INDEX.set(i);
255 }
256 } else if RICH_TEXT_FOCUSED_Z_VAR.get().is_some() {
257 Z_INDEX.set(prev_index);
258 }
259 }
260 if let Some(idx) = RICH_TEXT_FOCUSED_Z_VAR.get_new()
261 && focus_within
262 {
263 Z_INDEX.set(idx.unwrap_or(prev_index));
264 }
265 }
266 _ => {}
267 })
268}
269
270impl RichText {
271 pub fn root_info(&self) -> Option<WidgetInfo> {
275 WINDOWS.widget_info(self.root_id)
276 }
277
278 pub fn leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
280 self.root_info().into_iter().flat_map(|w| rich_text_leaves_static(&w))
281 }
282
283 pub fn leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
285 self.root_info().into_iter().flat_map(|w| rich_text_leaves_rev_static(&w))
286 }
287
288 pub fn selection(&self) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
292 let (root, a, b) = match (self.caret.index, self.caret.selection_index) {
293 (Some(a), Some(b)) => (self.root_info(), a, b),
294 _ => (None, self.root_id, self.root_id),
295 };
296 OptKnownLenIter {
297 known_len_iter: root.into_iter().flat_map(move |w| rich_text_selection_static(&w, a, b)),
298 }
299 }
300
301 pub fn selection_rev(&self) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
305 let (root, a, b) = match (self.caret.index, self.caret.selection_index) {
306 (Some(a), Some(b)) => (self.root_info(), a, b),
307 _ => (None, self.root_id, self.root_id),
308 };
309 OptKnownLenIter {
310 known_len_iter: root.into_iter().flat_map(move |w| rich_text_selection_rev_static(&w, a, b)),
311 }
312 }
313
314 pub fn caret_index_info(&self) -> Option<WidgetInfo> {
316 self.leaf_info(self.caret.index?)
317 }
318
319 pub fn caret_selection_index_info(&self) -> Option<WidgetInfo> {
321 self.leaf_info(self.caret.selection_index?)
322 }
323
324 pub fn leaf_info(&self, id: WidgetId) -> Option<WidgetInfo> {
326 let root = self.root_info()?;
327 let wgt = root.tree().get(id)?;
328 if !matches!(wgt.rich_text_component(), Some(RichTextComponent::Leaf { .. })) {
329 return None;
330 }
331 if !wgt.is_descendant(&root) {
332 return None;
333 }
334 Some(wgt)
335 }
336}
337impl RichCaretInfo {
338 pub fn update_selection(
352 &mut self,
353 new_index: &WidgetInfo,
354 new_selection_index: Option<&WidgetInfo>,
355 skip_end_points: bool,
356 skip_focus: bool,
357 ) {
358 let root = new_index.rich_text_root().unwrap();
359 let old_index = self
360 .index
361 .and_then(|id| new_index.tree().get(id))
362 .unwrap_or_else(|| new_index.clone());
363 let old_selection_index = self.selection_index.and_then(|id| new_index.tree().get(id));
364
365 self.index = Some(new_index.id());
366 self.selection_index = new_selection_index.map(|w| w.id());
367
368 match (&old_selection_index, new_selection_index) {
369 (None, None) => self.continue_focus(skip_focus, new_index, &root),
370 (None, Some(new_sel)) => {
371 let (a, b) = match new_index.cmp_sibling_in(new_sel, &root).unwrap() {
373 std::cmp::Ordering::Less => (new_index, new_sel),
374 std::cmp::Ordering::Greater => (new_sel, new_index),
375 std::cmp::Ordering::Equal => {
376 return self.continue_focus(skip_focus, new_index, &root);
378 }
379 };
380 if !skip_end_points {
381 self.continue_select_lesser(a, a == new_index);
382 }
383 let middle_op = TextSelectOp::local_select_all();
384 for middle in a.rich_text_next().take_while(|n| n != b) {
385 notify_leaf_select_op(middle.id(), middle_op.clone());
386 }
387 if !skip_end_points {
388 self.continue_select_greater(b, b == new_index);
389 }
390
391 self.continue_focus(skip_focus, new_index, &root);
392 }
393 (Some(old_sel), None) => {
394 let (a, b) = match old_index.cmp_sibling_in(old_sel, &root).unwrap() {
396 std::cmp::Ordering::Less => (&old_index, old_sel),
397 std::cmp::Ordering::Greater => (old_sel, &old_index),
398 std::cmp::Ordering::Equal => {
399 if !skip_end_points {
401 notify_leaf_select_op(old_sel.id(), TextSelectOp::local_clear_selection());
402 }
403 return self.continue_focus(skip_focus, new_index, &root);
404 }
405 };
406 let op = TextSelectOp::local_clear_selection();
407 if !skip_end_points {
408 notify_leaf_select_op(a.id(), op.clone());
409 }
410 for middle in a.rich_text_next().take_while(|n| n != b) {
411 notify_leaf_select_op(middle.id(), op.clone());
412 }
413 if !skip_end_points {
414 notify_leaf_select_op(b.id(), op);
415 }
416
417 self.continue_focus(skip_focus, new_index, &root);
418 }
419 (Some(old_sel), Some(new_sel)) => {
420 let (old_a, old_b) = match old_index.cmp_sibling_in(old_sel, &root).unwrap() {
423 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => (&old_index, old_sel),
424 std::cmp::Ordering::Greater => (old_sel, &old_index),
425 };
426 let (new_a, new_b) = match new_index.cmp_sibling_in(new_sel, &root).unwrap() {
427 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => (new_index, new_sel),
428 std::cmp::Ordering::Greater => (new_sel, new_index),
429 };
430
431 let min_a = match old_a.cmp_sibling_in(new_a, &root).unwrap() {
432 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => old_a,
433 std::cmp::Ordering::Greater => new_a,
434 };
435 let max_b = match old_b.cmp_sibling_in(new_b, &root).unwrap() {
436 std::cmp::Ordering::Less => new_b,
437 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => old_b,
438 };
439
440 fn inclusive_range_contains(a: &WidgetInfo, b: &WidgetInfo, q: &WidgetInfo, root: &WidgetInfo) -> bool {
441 match a.cmp_sibling_in(q, root).unwrap() {
442 std::cmp::Ordering::Less => match b.cmp_sibling_in(q, root).unwrap() {
444 std::cmp::Ordering::Less => false,
446 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
448 },
449 std::cmp::Ordering::Equal => true,
451 std::cmp::Ordering::Greater => false,
453 }
454 }
455
456 for wgt in min_a.rich_text_self_and_next() {
461 if &wgt == new_a {
462 if !skip_end_points && new_a != new_b {
463 self.continue_select_lesser(new_a, new_a == new_index);
464 }
465 } else if &wgt == new_b {
466 if !skip_end_points && new_a != new_b {
467 self.continue_select_greater(new_b, new_b == new_index);
468 }
469 } else {
470 let is_old = inclusive_range_contains(old_a, old_b, &wgt, &root);
471 let is_new = inclusive_range_contains(new_a, new_b, &wgt, &root);
472
473 match (is_old, is_new) {
474 (true, true) => {
475 if &wgt == old_a || &wgt == old_b {
476 notify_leaf_select_op(wgt.id(), TextSelectOp::local_select_all())
478 }
479 }
480 (true, false) => {
481 notify_leaf_select_op(wgt.id(), TextSelectOp::local_clear_selection());
482 }
483 (false, true) => notify_leaf_select_op(wgt.id(), TextSelectOp::local_select_all()),
484 (false, false) => {}
485 }
486 }
487
488 if &wgt == max_b {
489 break;
490 }
491 }
492
493 self.continue_focus(skip_focus, new_index, &root);
494 }
495 }
496 }
497 fn continue_select_lesser(&self, a: &WidgetInfo, a_is_caret: bool) {
498 notify_leaf_select_op(
499 a.id(),
500 TextSelectOp::new(move || {
501 let len = TEXT.resolved().segmented_text.text().len();
502 let len = CaretIndex { index: len, line: 0 }; let mut ctx = TEXT.resolve_caret();
504 if a_is_caret {
505 ctx.selection_index = Some(len);
506 } else {
507 ctx.index = Some(len);
508 }
509 ctx.index_version += 1;
510 }),
511 );
512 }
513 fn continue_select_greater(&self, b: &WidgetInfo, b_is_caret: bool) {
514 notify_leaf_select_op(
515 b.id(),
516 TextSelectOp::new(move || {
517 let mut ctx = TEXT.resolve_caret();
518 if b_is_caret {
519 ctx.selection_index = Some(CaretIndex::ZERO);
520 } else {
521 ctx.index = Some(CaretIndex::ZERO);
522 }
523 ctx.index_version += 1;
524 }),
525 );
526 }
527 fn continue_focus(&self, skip_focus: bool, new_index: &WidgetInfo, root: &WidgetInfo) {
528 if !skip_focus && FOCUS.is_focus_within(root.id()).get() {
529 FOCUS.focus_widget(new_index.id(), false);
530 }
531 }
532}
533
534pub(crate) fn notify_leaf_select_op(leaf_id: WidgetId, op: TextSelectOp) {
535 RICH_TEXT_NOTIFY.write().push(SELECT_CMD.scoped(leaf_id).new_update_param(op));
536}
537
538pub trait RichTextWidgetInfoExt {
540 fn rich_text_root(&self) -> Option<WidgetInfo>;
542
543 fn rich_text_component(&self) -> Option<RichTextComponent>;
545
546 fn rich_text_leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
548 fn rich_text_leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
550
551 fn rich_text_selection(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static;
555 fn rich_text_selection_rev(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static;
559
560 fn rich_text_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
562 fn rich_text_self_and_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
564 fn rich_text_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
566 fn rich_text_self_and_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
568
569 fn rich_text_line_info(&self) -> RichLineInfo;
571
572 fn rich_text_nearest_leaf(&self, window_point: PxPoint) -> Option<WidgetInfo>;
574 fn rich_text_nearest_leaf_filtered(
579 &self,
580 window_point: PxPoint,
581 filter: impl FnMut(&WidgetInfo, PxRect, usize, usize) -> bool,
582 ) -> Option<WidgetInfo>;
583}
584impl RichTextWidgetInfoExt for WidgetInfo {
585 fn rich_text_root(&self) -> Option<WidgetInfo> {
586 self.self_and_ancestors()
587 .find(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Root)))
588 }
589
590 fn rich_text_component(&self) -> Option<RichTextComponent> {
591 self.meta().copy(*RICH_TEXT_COMPONENT_ID)
592 }
593
594 fn rich_text_leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
595 rich_text_leaves_static(self)
596 }
597 fn rich_text_leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
598 rich_text_leaves_rev_static(self)
599 }
600
601 fn rich_text_selection(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
602 rich_text_selection_static(self, a, b)
603 }
604 fn rich_text_selection_rev(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
605 rich_text_selection_rev_static(self, a, b)
606 }
607
608 fn rich_text_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
609 let me = self.clone();
610 self.rich_text_root()
611 .into_iter()
612 .flat_map(move |w| me.prev_siblings_in(&w))
613 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
614 }
615
616 fn rich_text_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
617 let me = self.clone();
618 self.rich_text_root()
619 .into_iter()
620 .flat_map(move |w| me.next_siblings_in(&w))
621 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
622 }
623
624 fn rich_text_self_and_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
625 let me = self.clone();
626 self.rich_text_root()
627 .into_iter()
628 .flat_map(move |w| me.self_and_prev_siblings_in(&w))
629 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
630 }
631
632 fn rich_text_self_and_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
633 let me = self.clone();
634 self.rich_text_root()
635 .into_iter()
636 .flat_map(move |w| me.self_and_next_siblings_in(&w))
637 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
638 }
639
640 fn rich_text_line_info(&self) -> RichLineInfo {
641 let (prev_min, prev_max) = match self.rich_text_prev().next() {
642 Some(p) => {
643 let bounds = p.bounds_info();
644 let inner_bounds = bounds.inner_bounds();
645 if let Some(inline) = bounds.inline() {
646 let mut last = inline.rows[inline.rows.len() - 1];
647 last.origin += inner_bounds.origin.to_vector();
648 (last.min_y(), last.max_y())
649 } else {
650 (inner_bounds.min_y(), inner_bounds.max_y())
651 }
652 }
653 None => (Px::MIN, Px::MIN),
654 };
655
656 let bounds = self.bounds_info();
657 let inner_bounds = bounds.inner_bounds();
658 let (min, max, wraps, inlined) = if let Some(inline) = bounds.inline() {
659 let mut first = inline.rows[0];
660 first.origin += inner_bounds.origin.to_vector();
661 (first.min_y(), first.max_y(), inline.rows.len() > 1, true)
662 } else {
663 (inner_bounds.min_y(), inner_bounds.max_y(), false, false)
664 };
665
666 let starts = !lines_overlap_strict(prev_min, prev_max, min, max);
667
668 let mut is_wrap_start = false;
669 if inlined {
670 let mut child_id = self.id();
671 for parent in self.ancestors() {
672 if parent.first_child().unwrap().id() != child_id {
673 is_wrap_start = true; break;
675 }
676 if parent.bounds_info().inline().is_none() {
677 break;
678 }
679 child_id = parent.id();
680 }
681 }
682
683 RichLineInfo {
684 starts_new_line: starts,
685 is_wrap_start,
686 ends_in_new_line: wraps,
687 }
688 }
689
690 fn rich_text_nearest_leaf(&self, window_point: PxPoint) -> Option<WidgetInfo> {
691 self.rich_text_nearest_leaf_filtered(window_point, |_, _, _, _| true)
692 }
693 fn rich_text_nearest_leaf_filtered(
694 &self,
695 window_point: PxPoint,
696 mut filter: impl FnMut(&WidgetInfo, PxRect, usize, usize) -> bool,
697 ) -> Option<WidgetInfo> {
698 let root_size = self.inner_border_size();
699 let search_radius = root_size.width.max(root_size.height);
700
701 self.nearest_rect_filtered(window_point, search_radius, |w, rect, i, len| {
702 matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })) && filter(w, rect, i, len)
703 })
704 }
705}
706fn lines_overlap_strict(y_min1: Px, y_max1: Px, y_min2: Px, y_max2: Px) -> bool {
707 let (a_min, a_max) = if y_min1 <= y_max1 { (y_min1, y_max1) } else { (y_max1, y_min1) };
708 let (b_min, b_max) = if y_min2 <= y_max2 { (y_min2, y_max2) } else { (y_max2, y_min2) };
709
710 a_min < b_max && b_min < a_max
711}
712
713#[derive(Debug, Clone, PartialEq, Eq)]
715#[non_exhaustive]
716pub struct RichLineInfo {
717 pub starts_new_line: bool,
726
727 pub is_wrap_start: bool,
729
730 pub ends_in_new_line: bool,
734}
735
736fn rich_text_leaves_static(wgt: &WidgetInfo) -> impl Iterator<Item = WidgetInfo> + use<> {
739 wgt.descendants()
740 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
741}
742fn rich_text_leaves_rev_static(wgt: &WidgetInfo) -> impl Iterator<Item = WidgetInfo> + use<> {
743 wgt.descendants()
744 .tree_rev()
745 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
746}
747fn rich_text_selection_static(wgt: &WidgetInfo, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + use<> {
748 let mut ai = usize::MAX;
749 let mut bi = usize::MAX;
750
751 for (i, leaf) in wgt.rich_text_leaves().enumerate() {
752 let id = leaf.id();
753 if id == a {
754 ai = i;
755 }
756 if id == b {
757 bi = i;
758 }
759 if ai != usize::MAX && bi != usize::MAX {
760 break;
761 }
762 }
763
764 if ai > bi {
765 std::mem::swap(&mut ai, &mut bi);
766 }
767
768 let (skip, take) = if ai != usize::MAX && bi != usize::MAX {
769 (ai, bi - ai + 1)
770 } else {
771 (0, 0)
772 };
773
774 KnownLenIter {
775 take: rich_text_leaves_static(wgt).skip(skip).take(take),
776 len: take,
777 }
778}
779fn rich_text_selection_rev_static(wgt: &WidgetInfo, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + use<> {
780 let mut ai = usize::MAX;
781 let mut bi = usize::MAX;
782
783 for (i, leaf) in wgt.rich_text_leaves_rev().enumerate() {
784 let id = leaf.id();
785 if id == a {
786 ai = i;
787 } else if id == b {
788 bi = i;
789 }
790 if ai != usize::MAX && bi != usize::MAX {
791 break;
792 }
793 }
794
795 if ai > bi {
796 std::mem::swap(&mut ai, &mut bi);
797 }
798
799 let (skip, take) = if ai != usize::MAX && bi != usize::MAX {
800 (ai, bi - ai + 1)
801 } else {
802 (0, 0)
803 };
804
805 KnownLenIter {
806 take: rich_text_leaves_rev_static(wgt).skip(skip).take(take),
807 len: take,
808 }
809}
810
811#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
813pub enum RichTextComponent {
814 Root,
816 Branch,
818 Leaf {
820 kind: &'static str,
824 },
825}
826
827static_id! {
828 static ref RICH_TEXT_COMPONENT_ID: StateId<RichTextComponent>;
829}
830
831struct KnownLenIter<I> {
832 take: I,
833 len: usize,
834}
835impl<I: Iterator<Item = WidgetInfo>> Iterator for KnownLenIter<I> {
836 type Item = WidgetInfo;
837
838 fn next(&mut self) -> Option<Self::Item> {
839 match self.take.next() {
840 Some(r) => {
841 self.len -= 1;
842 Some(r)
843 }
844 None => None,
845 }
846 }
847
848 fn size_hint(&self) -> (usize, Option<usize>) {
849 (self.len, Some(self.len))
850 }
851}
852impl<I: Iterator<Item = WidgetInfo>> ExactSizeIterator for KnownLenIter<I> {}
853
854struct OptKnownLenIter<I> {
855 known_len_iter: I,
856}
857impl<I: Iterator<Item = WidgetInfo>> Iterator for OptKnownLenIter<I> {
858 type Item = WidgetInfo;
859
860 fn next(&mut self) -> Option<Self::Item> {
861 self.known_len_iter.next()
862 }
863
864 fn size_hint(&self) -> (usize, Option<usize>) {
865 self.known_len_iter.size_hint()
867 }
868}
869impl<I: Iterator<Item = WidgetInfo>> ExactSizeIterator for OptKnownLenIter<I> {}
870
871#[derive(Clone, Default)]
874pub struct RichTextCopyParam(Arc<Mutex<Option<Txt>>>);
875impl RichTextCopyParam {
876 pub fn set_text(&self, txt: impl Into<Txt>) {
878 *self.0.lock() = Some(txt.into());
879 }
880
881 pub fn into_text(self) -> Option<Txt> {
883 match Arc::try_unwrap(self.0) {
884 Ok(m) => m.into_inner(),
885 Err(c) => c.lock().clone(),
886 }
887 }
888}
889impl fmt::Debug for RichTextCopyParam {
890 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
891 if let Some(t) = self.0.try_lock() {
892 f.debug_tuple("RichTextCopyParam").field(&*t).finish()
893 } else {
894 f.debug_tuple("RichTextCopyParam").finish_non_exhaustive()
895 }
896 }
897}