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, 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 match_node(child, move |child, op| match op {
26 UiNodeOp::Init => {
27 WIDGET.sub_var(&enabled);
28 if enabled.get() && TEXT.try_rich().is_none() {
29 ctx = Some(Arc::new(RwLock::new(RichText {
30 root_id: WIDGET.id(),
31 caret: RichCaretInfo {
32 index: None,
33 selection_index: None,
34 },
35 selection_started_by_alt: false,
36 })));
37
38 RICH_TEXT.with_context(&mut ctx, || child.init());
39 }
40 }
41 UiNodeOp::Update { updates } => {
42 if enabled.is_new() {
43 WIDGET.reinit();
44 } else if ctx.is_some() {
45 RICH_TEXT.with_context(&mut ctx, || child.update(updates));
46 }
47 }
48 UiNodeOp::Deinit => {
49 if ctx.is_some() {
50 RICH_TEXT.with_context(&mut ctx, || child.deinit());
51 ctx = None;
52 }
53 }
54 op => {
55 if ctx.is_some() {
56 RICH_TEXT.with_context(&mut ctx, || child.op(op));
57 }
58 }
59 })
60}
61
62fn rich_text_cmds(child: impl IntoUiNode) -> UiNode {
63 #[derive(Default)]
64 struct Cmds {
65 copy: CommandHandle,
67 select: CommandHandle,
70 select_all: CommandHandle,
71 }
72 let mut _cmds = Cmds::default();
73 match_node(child, move |_, op| match op {
74 UiNodeOp::Init => {
75 if TEXT.try_rich().is_some() {
76 let id = WIDGET.id();
77 _cmds.copy = COPY_CMD.scoped(id).subscribe(true);
79 _cmds.select = SELECT_CMD.scoped(id).subscribe(true);
81 _cmds.select_all = SELECT_ALL_CMD.scoped(id).subscribe(true);
83 }
84 }
85 UiNodeOp::Deinit => {
86 _cmds = Cmds::default();
87 }
88 UiNodeOp::Update { .. } => {
89 let ctx = match TEXT.try_rich() {
90 Some(c) => c,
91 None => return, };
93 COPY_CMD.event().each_update(false, |args| {
94 if args.param.is_none()
95 && let CommandScope::Widget(scope_id) = args.scope
96 && (ctx.root_id == scope_id || ctx.leaf_info(scope_id).is_some())
97 {
98 args.propagation.stop();
100
101 let leaf_texts: Vec<_> = ctx
102 .selection()
103 .map(|leaf| {
104 let rich_copy = RichTextCopyParam::default();
105 COPY_CMD.scoped(leaf.id()).notify_param(rich_copy.clone());
106 (rich_copy, leaf)
107 })
108 .collect();
109
110 if !leaf_texts.is_empty() {
111 UPDATES.once_next_update("rich-text-copy", move || {
113 let mut txt = String::new();
114
115 for (leaf_text, leaf) in leaf_texts {
116 if let Some(t_frag) = leaf_text.into_text() {
117 let line_info = leaf.rich_text_line_info();
118 if line_info.starts_new_line && !line_info.is_wrap_start && !txt.is_empty() {
119 txt.push('\n');
120 }
121
122 txt.push_str(&t_frag);
123 }
124 }
125
126 let _ = CLIPBOARD.set_text(txt);
127 });
128 }
129 }
130 });
131
132 SELECT_CMD.scoped(ctx.root_id).each_update(true, false, |args| {
133 args.propagation.stop();
134 if let Some(leaf) = ctx.caret_index_info().or_else(|| ctx.leaves().next()) {
135 SELECT_CMD.scoped(leaf.id()).notify_param(args.param.clone());
136 }
137 });
138 SELECT_ALL_CMD.scoped(ctx.root_id).latest_update(true, false, |args| {
139 args.propagation.stop();
140 if let Some(leaf) = ctx.caret_index_info().or_else(|| ctx.leaves().next()) {
141 SELECT_ALL_CMD.scoped(leaf.id()).notify_param(args.param.clone());
142 }
143 });
144 }
145 _ => {}
146 })
147}
148fn rich_text_focus_change_broadcast(child: impl IntoUiNode) -> UiNode {
150 match_node(child, move |c, op| {
151 if let UiNodeOp::Update { .. } = op {
152 FOCUS_CHANGED_EVENT.each_update(true, |args| {
153 let ctx = match TEXT.try_rich() {
154 Some(c) => c,
155 None => return, };
157 if args.prev_focus.iter().chain(args.new_focus.iter()).any(|p| p.contains(ctx.root_id)) {
158 let mut extended_list = UpdateDeliveryList::new();
160 for leaf in ctx.leaves() {
161 extended_list.insert_wgt(&leaf);
162 }
163 drop(ctx);
164 let updates = WidgetUpdates::new(extended_list);
165 c.node().update(&updates);
166 }
167 });
168 }
169 })
170}
171
172pub fn rich_text_component(child: impl IntoUiNode, kind: &'static str) -> UiNode {
181 let mut focus_within = false;
182 let mut prev_index = ZIndex::DEFAULT;
183 let mut index_update = None;
184 match_node(child, move |c, op| match op {
185 UiNodeOp::Init => {
186 c.init();
187
188 if TEXT.try_rich().is_some() {
189 WIDGET.sub_event(&FOCUS_CHANGED_EVENT).sub_var(&RICH_TEXT_FOCUSED_Z_VAR);
190 prev_index = Z_INDEX.get();
191 }
192 }
193 UiNodeOp::Deinit => {
194 focus_within = false;
195 }
196 UiNodeOp::Info { info } => {
197 if let Some(r) = TEXT.try_rich() {
198 let c = match kind {
199 "rich_text" => {
200 if r.root_id == WIDGET.id() {
201 RichTextComponent::Root
202 } else {
203 RichTextComponent::Branch
204 }
205 }
206 kind => RichTextComponent::Leaf { kind },
207 };
208 info.set_meta(*RICH_TEXT_COMPONENT_ID, c);
209 }
210 }
211 UiNodeOp::Update { updates } => {
212 FOCUS_CHANGED_EVENT.each_update(true, |args| {
213 let new_is_focus_within = args.is_focus_within(WIDGET.id());
214 if focus_within != new_is_focus_within {
215 focus_within = new_is_focus_within;
216
217 if TEXT.try_rich().is_some() {
218 index_update = Some(focus_within);
219 WIDGET.update(); }
221 }
222 });
223
224 c.update(updates);
225
226 if let Some(apply) = index_update.take() {
227 if apply {
228 prev_index = Z_INDEX.get();
229 if let Some(i) = RICH_TEXT_FOCUSED_Z_VAR.get() {
230 Z_INDEX.set(i);
231 }
232 } else if RICH_TEXT_FOCUSED_Z_VAR.get().is_some() {
233 Z_INDEX.set(prev_index);
234 }
235 }
236 if let Some(idx) = RICH_TEXT_FOCUSED_Z_VAR.get_new()
237 && focus_within
238 {
239 Z_INDEX.set(idx.unwrap_or(prev_index));
240 }
241 }
242 _ => {}
243 })
244}
245
246impl RichText {
247 pub fn root_info(&self) -> Option<WidgetInfo> {
251 WINDOWS.widget_info(self.root_id)
252 }
253
254 pub fn leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
256 self.root_info().into_iter().flat_map(|w| rich_text_leaves_static(&w))
257 }
258
259 pub fn leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
261 self.root_info().into_iter().flat_map(|w| rich_text_leaves_rev_static(&w))
262 }
263
264 pub fn selection(&self) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
268 let (root, a, b) = match (self.caret.index, self.caret.selection_index) {
269 (Some(a), Some(b)) => (self.root_info(), a, b),
270 _ => (None, self.root_id, self.root_id),
271 };
272 OptKnownLenIter {
273 known_len_iter: root.into_iter().flat_map(move |w| rich_text_selection_static(&w, a, b)),
274 }
275 }
276
277 pub fn selection_rev(&self) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
281 let (root, a, b) = match (self.caret.index, self.caret.selection_index) {
282 (Some(a), Some(b)) => (self.root_info(), a, b),
283 _ => (None, self.root_id, self.root_id),
284 };
285 OptKnownLenIter {
286 known_len_iter: root.into_iter().flat_map(move |w| rich_text_selection_rev_static(&w, a, b)),
287 }
288 }
289
290 pub fn caret_index_info(&self) -> Option<WidgetInfo> {
292 self.leaf_info(self.caret.index?)
293 }
294
295 pub fn caret_selection_index_info(&self) -> Option<WidgetInfo> {
297 self.leaf_info(self.caret.selection_index?)
298 }
299
300 pub fn leaf_info(&self, id: WidgetId) -> Option<WidgetInfo> {
302 let root = self.root_info()?;
303 let wgt = root.tree().get(id)?;
304 if !matches!(wgt.rich_text_component(), Some(RichTextComponent::Leaf { .. })) {
305 return None;
306 }
307 if !wgt.is_descendant(&root) {
308 return None;
309 }
310 Some(wgt)
311 }
312}
313impl RichCaretInfo {
314 pub fn update_selection(
328 &mut self,
329 new_index: &WidgetInfo,
330 new_selection_index: Option<&WidgetInfo>,
331 skip_end_points: bool,
332 skip_focus: bool,
333 ) {
334 let root = new_index.rich_text_root().unwrap();
335 let old_index = self
336 .index
337 .and_then(|id| new_index.tree().get(id))
338 .unwrap_or_else(|| new_index.clone());
339 let old_selection_index = self.selection_index.and_then(|id| new_index.tree().get(id));
340
341 self.index = Some(new_index.id());
342 self.selection_index = new_selection_index.map(|w| w.id());
343
344 match (&old_selection_index, new_selection_index) {
345 (None, None) => self.continue_focus(skip_focus, new_index, &root),
346 (None, Some(new_sel)) => {
347 let (a, b) = match new_index.cmp_sibling_in(new_sel, &root).unwrap() {
349 std::cmp::Ordering::Less => (new_index, new_sel),
350 std::cmp::Ordering::Greater => (new_sel, new_index),
351 std::cmp::Ordering::Equal => {
352 return self.continue_focus(skip_focus, new_index, &root);
354 }
355 };
356 if !skip_end_points {
357 self.continue_select_lesser(a, a == new_index);
358 }
359 let middle_op = TextSelectOp::local_select_all();
360 for middle in a.rich_text_next().take_while(|n| n != b) {
361 notify_leaf_select_op(middle.id(), middle_op.clone());
362 }
363 if !skip_end_points {
364 self.continue_select_greater(b, b == new_index);
365 }
366
367 self.continue_focus(skip_focus, new_index, &root);
368 }
369 (Some(old_sel), None) => {
370 let (a, b) = match old_index.cmp_sibling_in(old_sel, &root).unwrap() {
372 std::cmp::Ordering::Less => (&old_index, old_sel),
373 std::cmp::Ordering::Greater => (old_sel, &old_index),
374 std::cmp::Ordering::Equal => {
375 if !skip_end_points {
377 notify_leaf_select_op(old_sel.id(), TextSelectOp::local_clear_selection());
378 }
379 return self.continue_focus(skip_focus, new_index, &root);
380 }
381 };
382 let op = TextSelectOp::local_clear_selection();
383 if !skip_end_points {
384 notify_leaf_select_op(a.id(), op.clone());
385 }
386 for middle in a.rich_text_next().take_while(|n| n != b) {
387 notify_leaf_select_op(middle.id(), op.clone());
388 }
389 if !skip_end_points {
390 notify_leaf_select_op(b.id(), op);
391 }
392
393 self.continue_focus(skip_focus, new_index, &root);
394 }
395 (Some(old_sel), Some(new_sel)) => {
396 let (old_a, old_b) = match old_index.cmp_sibling_in(old_sel, &root).unwrap() {
399 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => (&old_index, old_sel),
400 std::cmp::Ordering::Greater => (old_sel, &old_index),
401 };
402 let (new_a, new_b) = match new_index.cmp_sibling_in(new_sel, &root).unwrap() {
403 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => (new_index, new_sel),
404 std::cmp::Ordering::Greater => (new_sel, new_index),
405 };
406
407 let min_a = match old_a.cmp_sibling_in(new_a, &root).unwrap() {
408 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => old_a,
409 std::cmp::Ordering::Greater => new_a,
410 };
411 let max_b = match old_b.cmp_sibling_in(new_b, &root).unwrap() {
412 std::cmp::Ordering::Less => new_b,
413 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => old_b,
414 };
415
416 fn inclusive_range_contains(a: &WidgetInfo, b: &WidgetInfo, q: &WidgetInfo, root: &WidgetInfo) -> bool {
417 match a.cmp_sibling_in(q, root).unwrap() {
418 std::cmp::Ordering::Less => match b.cmp_sibling_in(q, root).unwrap() {
420 std::cmp::Ordering::Less => false,
422 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
424 },
425 std::cmp::Ordering::Equal => true,
427 std::cmp::Ordering::Greater => false,
429 }
430 }
431
432 for wgt in min_a.rich_text_self_and_next() {
437 if &wgt == new_a {
438 if !skip_end_points && new_a != new_b {
439 self.continue_select_lesser(new_a, new_a == new_index);
440 }
441 } else if &wgt == new_b {
442 if !skip_end_points && new_a != new_b {
443 self.continue_select_greater(new_b, new_b == new_index);
444 }
445 } else {
446 let is_old = inclusive_range_contains(old_a, old_b, &wgt, &root);
447 let is_new = inclusive_range_contains(new_a, new_b, &wgt, &root);
448
449 match (is_old, is_new) {
450 (true, true) => {
451 if &wgt == old_a || &wgt == old_b {
452 notify_leaf_select_op(wgt.id(), TextSelectOp::local_select_all())
454 }
455 }
456 (true, false) => {
457 notify_leaf_select_op(wgt.id(), TextSelectOp::local_clear_selection());
458 }
459 (false, true) => notify_leaf_select_op(wgt.id(), TextSelectOp::local_select_all()),
460 (false, false) => {}
461 }
462 }
463
464 if &wgt == max_b {
465 break;
466 }
467 }
468
469 self.continue_focus(skip_focus, new_index, &root);
470 }
471 }
472 }
473 fn continue_select_lesser(&self, a: &WidgetInfo, a_is_caret: bool) {
474 notify_leaf_select_op(
475 a.id(),
476 TextSelectOp::new(move || {
477 let len = TEXT.resolved().segmented_text.text().len();
478 let len = CaretIndex { index: len, line: 0 }; let mut ctx = TEXT.resolve_caret();
480 if a_is_caret {
481 ctx.selection_index = Some(len);
482 } else {
483 ctx.index = Some(len);
484 }
485 ctx.index_version += 1;
486 }),
487 );
488 }
489 fn continue_select_greater(&self, b: &WidgetInfo, b_is_caret: bool) {
490 notify_leaf_select_op(
491 b.id(),
492 TextSelectOp::new(move || {
493 let mut ctx = TEXT.resolve_caret();
494 if b_is_caret {
495 ctx.selection_index = Some(CaretIndex::ZERO);
496 } else {
497 ctx.index = Some(CaretIndex::ZERO);
498 }
499 ctx.index_version += 1;
500 }),
501 );
502 }
503 fn continue_focus(&self, skip_focus: bool, new_index: &WidgetInfo, root: &WidgetInfo) {
504 if !skip_focus && FOCUS.is_focus_within(root.id()).get() {
505 FOCUS.focus_widget(new_index.id(), false);
506 }
507 }
508}
509
510pub(crate) fn notify_leaf_select_op(leaf_id: WidgetId, op: TextSelectOp) {
511 SELECT_CMD.scoped(leaf_id).notify_param(op);
512}
513
514pub trait RichTextWidgetInfoExt {
516 fn rich_text_root(&self) -> Option<WidgetInfo>;
518
519 fn rich_text_component(&self) -> Option<RichTextComponent>;
521
522 fn rich_text_leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
524 fn rich_text_leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
526
527 fn rich_text_selection(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static;
531 fn rich_text_selection_rev(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static;
535
536 fn rich_text_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
538 fn rich_text_self_and_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
540 fn rich_text_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
542 fn rich_text_self_and_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
544
545 fn rich_text_line_info(&self) -> RichLineInfo;
547
548 fn rich_text_nearest_leaf(&self, window_point: PxPoint) -> Option<WidgetInfo>;
550 fn rich_text_nearest_leaf_filtered(
555 &self,
556 window_point: PxPoint,
557 filter: impl FnMut(&WidgetInfo, PxRect, usize, usize) -> bool,
558 ) -> Option<WidgetInfo>;
559}
560impl RichTextWidgetInfoExt for WidgetInfo {
561 fn rich_text_root(&self) -> Option<WidgetInfo> {
562 self.self_and_ancestors()
563 .find(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Root)))
564 }
565
566 fn rich_text_component(&self) -> Option<RichTextComponent> {
567 self.meta().copy(*RICH_TEXT_COMPONENT_ID)
568 }
569
570 fn rich_text_leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
571 rich_text_leaves_static(self)
572 }
573 fn rich_text_leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
574 rich_text_leaves_rev_static(self)
575 }
576
577 fn rich_text_selection(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
578 rich_text_selection_static(self, a, b)
579 }
580 fn rich_text_selection_rev(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
581 rich_text_selection_rev_static(self, a, b)
582 }
583
584 fn rich_text_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
585 let me = self.clone();
586 self.rich_text_root()
587 .into_iter()
588 .flat_map(move |w| me.prev_siblings_in(&w))
589 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
590 }
591
592 fn rich_text_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
593 let me = self.clone();
594 self.rich_text_root()
595 .into_iter()
596 .flat_map(move |w| me.next_siblings_in(&w))
597 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
598 }
599
600 fn rich_text_self_and_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
601 let me = self.clone();
602 self.rich_text_root()
603 .into_iter()
604 .flat_map(move |w| me.self_and_prev_siblings_in(&w))
605 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
606 }
607
608 fn rich_text_self_and_next(&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.self_and_next_siblings_in(&w))
613 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
614 }
615
616 fn rich_text_line_info(&self) -> RichLineInfo {
617 let (prev_min, prev_max) = match self.rich_text_prev().next() {
618 Some(p) => {
619 let bounds = p.bounds_info();
620 let inner_bounds = bounds.inner_bounds();
621 if let Some(inline) = bounds.inline() {
622 let mut last = inline.rows[inline.rows.len() - 1];
623 last.origin += inner_bounds.origin.to_vector();
624 (last.min_y(), last.max_y())
625 } else {
626 (inner_bounds.min_y(), inner_bounds.max_y())
627 }
628 }
629 None => (Px::MIN, Px::MIN),
630 };
631
632 let bounds = self.bounds_info();
633 let inner_bounds = bounds.inner_bounds();
634 let (min, max, wraps, inlined) = if let Some(inline) = bounds.inline() {
635 let mut first = inline.rows[0];
636 first.origin += inner_bounds.origin.to_vector();
637 (first.min_y(), first.max_y(), inline.rows.len() > 1, true)
638 } else {
639 (inner_bounds.min_y(), inner_bounds.max_y(), false, false)
640 };
641
642 let starts = !lines_overlap_strict(prev_min, prev_max, min, max);
643
644 let mut is_wrap_start = false;
645 if inlined {
646 let mut child_id = self.id();
647 for parent in self.ancestors() {
648 if parent.first_child().unwrap().id() != child_id {
649 is_wrap_start = true; break;
651 }
652 if parent.bounds_info().inline().is_none() {
653 break;
654 }
655 child_id = parent.id();
656 }
657 }
658
659 RichLineInfo {
660 starts_new_line: starts,
661 is_wrap_start,
662 ends_in_new_line: wraps,
663 }
664 }
665
666 fn rich_text_nearest_leaf(&self, window_point: PxPoint) -> Option<WidgetInfo> {
667 self.rich_text_nearest_leaf_filtered(window_point, |_, _, _, _| true)
668 }
669 fn rich_text_nearest_leaf_filtered(
670 &self,
671 window_point: PxPoint,
672 mut filter: impl FnMut(&WidgetInfo, PxRect, usize, usize) -> bool,
673 ) -> Option<WidgetInfo> {
674 let root_size = self.inner_border_size();
675 let search_radius = root_size.width.max(root_size.height);
676
677 self.nearest_rect_filtered(window_point, search_radius, |w, rect, i, len| {
678 matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })) && filter(w, rect, i, len)
679 })
680 }
681}
682fn lines_overlap_strict(y_min1: Px, y_max1: Px, y_min2: Px, y_max2: Px) -> bool {
683 let (a_min, a_max) = if y_min1 <= y_max1 { (y_min1, y_max1) } else { (y_max1, y_min1) };
684 let (b_min, b_max) = if y_min2 <= y_max2 { (y_min2, y_max2) } else { (y_max2, y_min2) };
685
686 a_min < b_max && b_min < a_max
687}
688
689#[derive(Debug, Clone, PartialEq, Eq)]
691#[non_exhaustive]
692pub struct RichLineInfo {
693 pub starts_new_line: bool,
702
703 pub is_wrap_start: bool,
705
706 pub ends_in_new_line: bool,
710}
711
712fn rich_text_leaves_static(wgt: &WidgetInfo) -> impl Iterator<Item = WidgetInfo> + use<> {
715 wgt.descendants()
716 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
717}
718fn rich_text_leaves_rev_static(wgt: &WidgetInfo) -> impl Iterator<Item = WidgetInfo> + use<> {
719 wgt.descendants()
720 .tree_rev()
721 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
722}
723fn rich_text_selection_static(wgt: &WidgetInfo, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + use<> {
724 let mut ai = usize::MAX;
725 let mut bi = usize::MAX;
726
727 for (i, leaf) in wgt.rich_text_leaves().enumerate() {
728 let id = leaf.id();
729 if id == a {
730 ai = i;
731 }
732 if id == b {
733 bi = i;
734 }
735 if ai != usize::MAX && bi != usize::MAX {
736 break;
737 }
738 }
739
740 if ai > bi {
741 std::mem::swap(&mut ai, &mut bi);
742 }
743
744 let (skip, take) = if ai != usize::MAX && bi != usize::MAX {
745 (ai, bi - ai + 1)
746 } else {
747 (0, 0)
748 };
749
750 KnownLenIter {
751 take: rich_text_leaves_static(wgt).skip(skip).take(take),
752 len: take,
753 }
754}
755fn rich_text_selection_rev_static(wgt: &WidgetInfo, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + use<> {
756 let mut ai = usize::MAX;
757 let mut bi = usize::MAX;
758
759 for (i, leaf) in wgt.rich_text_leaves_rev().enumerate() {
760 let id = leaf.id();
761 if id == a {
762 ai = i;
763 } else if id == b {
764 bi = i;
765 }
766 if ai != usize::MAX && bi != usize::MAX {
767 break;
768 }
769 }
770
771 if ai > bi {
772 std::mem::swap(&mut ai, &mut bi);
773 }
774
775 let (skip, take) = if ai != usize::MAX && bi != usize::MAX {
776 (ai, bi - ai + 1)
777 } else {
778 (0, 0)
779 };
780
781 KnownLenIter {
782 take: rich_text_leaves_rev_static(wgt).skip(skip).take(take),
783 len: take,
784 }
785}
786
787#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
789pub enum RichTextComponent {
790 Root,
792 Branch,
794 Leaf {
796 kind: &'static str,
800 },
801}
802
803static_id! {
804 static ref RICH_TEXT_COMPONENT_ID: StateId<RichTextComponent>;
805}
806
807struct KnownLenIter<I> {
808 take: I,
809 len: usize,
810}
811impl<I: Iterator<Item = WidgetInfo>> Iterator for KnownLenIter<I> {
812 type Item = WidgetInfo;
813
814 fn next(&mut self) -> Option<Self::Item> {
815 match self.take.next() {
816 Some(r) => {
817 self.len -= 1;
818 Some(r)
819 }
820 None => None,
821 }
822 }
823
824 fn size_hint(&self) -> (usize, Option<usize>) {
825 (self.len, Some(self.len))
826 }
827}
828impl<I: Iterator<Item = WidgetInfo>> ExactSizeIterator for KnownLenIter<I> {}
829
830struct OptKnownLenIter<I> {
831 known_len_iter: I,
832}
833impl<I: Iterator<Item = WidgetInfo>> Iterator for OptKnownLenIter<I> {
834 type Item = WidgetInfo;
835
836 fn next(&mut self) -> Option<Self::Item> {
837 self.known_len_iter.next()
838 }
839
840 fn size_hint(&self) -> (usize, Option<usize>) {
841 self.known_len_iter.size_hint()
843 }
844}
845impl<I: Iterator<Item = WidgetInfo>> ExactSizeIterator for OptKnownLenIter<I> {}
846
847#[derive(Clone, Default)]
850pub struct RichTextCopyParam(Arc<Mutex<Option<Txt>>>);
851impl RichTextCopyParam {
852 pub fn set_text(&self, txt: impl Into<Txt>) {
854 *self.0.lock() = Some(txt.into());
855 }
856
857 pub fn into_text(self) -> Option<Txt> {
859 match Arc::try_unwrap(self.0) {
860 Ok(m) => m.into_inner(),
861 Err(c) => c.lock().clone(),
862 }
863 }
864}
865impl fmt::Debug for RichTextCopyParam {
866 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
867 if let Some(t) = self.0.try_lock() {
868 f.debug_tuple("RichTextCopyParam").field(&*t).finish()
869 } else {
870 f.debug_tuple("RichTextCopyParam").finish_non_exhaustive()
871 }
872 }
873}