1use std::{borrow::Cow, 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::{
8 focus::{FOCUS, FOCUS_CHANGED_EVENT},
9 mouse::MOUSE_INPUT_EVENT,
10 touch::{TOUCH_INPUT_EVENT, TOUCH_TAP_EVENT},
11};
12use zng_ext_window::WINDOWS;
13use zng_wgt::prelude::*;
14use zng_wgt_scroll::SCROLL;
15
16use crate::{
17 RICH_TEXT_FOCUSED_Z_VAR, TEXT_SELECTABLE_VAR,
18 cmd::{SELECT_ALL_CMD, SELECT_CMD, TextSelectOp},
19};
20
21use super::{RICH_TEXT, RichCaretInfo, RichText, TEXT};
22
23pub(crate) fn rich_text_node(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
24 let enabled = enabled.into_var();
25 let child = rich_text_events_broadcast(child);
26 let child = rich_text_cmds(child);
27 let child = rich_text_component(child, "rich_text");
28
29 let mut ctx = None;
30 match_node(child, move |child, op| match op {
31 UiNodeOp::Init => {
32 WIDGET.sub_var(&enabled);
33 if enabled.get() && TEXT.try_rich().is_none() {
34 ctx = Some(Arc::new(RwLock::new(RichText {
35 root_id: WIDGET.id(),
36 caret: RichCaretInfo {
37 index: None,
38 selection_index: None,
39 },
40 selection_started_by_alt: false,
41 })));
42
43 RICH_TEXT.with_context(&mut ctx, || child.init());
44 }
45 }
46 UiNodeOp::Update { updates } => {
47 if enabled.is_new() {
48 WIDGET.reinit();
49 } else if ctx.is_some() {
50 RICH_TEXT.with_context(&mut ctx, || child.update(updates));
51 }
52 }
53 UiNodeOp::Deinit => {
54 if ctx.is_some() {
55 RICH_TEXT.with_context(&mut ctx, || child.deinit());
56 ctx = None;
57 }
58 }
59 op => {
60 if ctx.is_some() {
61 RICH_TEXT.with_context(&mut ctx, || child.op(op));
62 }
63 }
64 })
65}
66
67fn rich_text_cmds(child: impl IntoUiNode) -> UiNode {
68 #[derive(Default)]
69 struct Cmds {
70 copy: CommandHandle,
72 select: CommandHandle,
75 select_all: CommandHandle,
76 }
77 let mut _cmds = Cmds::default();
78 match_node(child, move |_, op| match op {
79 UiNodeOp::Init if TEXT.try_rich().is_some() => {
80 let id = WIDGET.id();
81 _cmds.copy = COPY_CMD.scoped(id).subscribe(true);
83 _cmds.select = SELECT_CMD.scoped(id).subscribe(true);
85 _cmds.select_all = SELECT_ALL_CMD.scoped(id).subscribe(true);
87 }
88 UiNodeOp::Deinit => {
89 _cmds = Cmds::default();
90 }
91 UiNodeOp::Update { .. } => {
92 let ctx = match TEXT.try_rich() {
93 Some(c) => c,
94 None => return, };
96 COPY_CMD.event().each_update(false, |args| {
97 if args.param.is_none()
98 && let CommandScope::Widget(scope_id) = args.scope
99 && (ctx.root_id == scope_id || ctx.leaf_info(scope_id).is_some())
100 {
101 args.propagation.stop();
103
104 let leaf_texts: Vec<_> = ctx
105 .selection()
106 .map(|leaf| {
107 let rich_copy = RichTextCopyParam::default();
108 COPY_CMD.scoped(leaf.id()).notify_param(rich_copy.clone());
109 (rich_copy, leaf)
110 })
111 .collect();
112
113 if !leaf_texts.is_empty() {
114 UPDATES.once_next_update("rich-text-copy", move || {
116 let mut txt = String::new();
117
118 for (leaf_text, leaf) in leaf_texts {
119 if let Some(t_frag) = leaf_text.into_text() {
120 let line_info = leaf.rich_text_line_info();
121 if line_info.starts_new_line && !line_info.is_wrap_start && !txt.is_empty() {
122 txt.push('\n');
123 }
124
125 txt.push_str(&t_frag);
126 }
127 }
128
129 let _ = CLIPBOARD.set_text(txt);
130 });
131 }
132 }
133 });
134
135 SELECT_CMD.scoped(ctx.root_id).each_update(true, false, |args| {
137 args.propagation.stop();
138 if let Some(leaf) = ctx.caret_index_info().or_else(|| ctx.leaves().next()) {
139 SELECT_CMD.scoped(leaf.id()).notify_param(args.param.clone());
140 }
141 });
142 SELECT_ALL_CMD.scoped(ctx.root_id).latest_update(true, false, |args| {
143 args.propagation.stop();
144 if let Some(leaf) = ctx.caret_index_info().or_else(|| ctx.leaves().next()) {
145 SELECT_ALL_CMD.scoped(leaf.id()).notify_param(args.param.clone());
146 }
147 });
148 }
149 _ => {}
150 })
151}
152fn rich_text_events_broadcast(child: impl IntoUiNode) -> UiNode {
156 let mut handles = VarHandles::dummy();
157 match_node(child, move |c, op| {
158 match op {
159 UiNodeOp::Init if TEXT.try_rich().is_some() => {
160 WIDGET.sub_var(&TEXT_SELECTABLE_VAR);
161 if TEXT_SELECTABLE_VAR.get() {
162 let id = WIDGET.id();
163 handles.push(MOUSE_INPUT_EVENT.subscribe(UpdateOp::Update, id));
164 handles.push(TOUCH_INPUT_EVENT.subscribe(UpdateOp::Update, id));
165 handles.push(TOUCH_TAP_EVENT.subscribe(UpdateOp::Update, id));
166 }
167 }
168 UiNodeOp::Deinit => {
169 handles = VarHandles::dummy();
170 }
171 UiNodeOp::Update { updates } if let Some(ctx) = TEXT.try_rich() => {
172 let selectable = if let Some(sub) = TEXT_SELECTABLE_VAR.get_new() {
173 if sub {
174 let id = WIDGET.id();
175 handles.push(MOUSE_INPUT_EVENT.subscribe(UpdateOp::Update, id));
176 handles.push(TOUCH_INPUT_EVENT.subscribe(UpdateOp::Update, id));
177 handles.push(TOUCH_TAP_EVENT.subscribe(UpdateOp::Update, id));
178 } else {
179 handles.clear();
180 }
181 sub
182 } else {
183 TEXT_SELECTABLE_VAR.get()
184 };
185 if !selectable {
186 return;
187 }
188
189 if ctx.caret.selection_index.is_some() {
191 let extend_propagation = FOCUS_CHANGED_EVENT.any_update(true, |args| {
192 args.prev_focus.iter().chain(args.new_focus.iter()).any(|p| p.contains(ctx.root_id))
193 });
194 if extend_propagation {
195 if let Cow::Owned(updates) = updates.clone_insert_all(ctx.selection()) {
196 drop(ctx);
197 c.update(&updates);
198 }
199 return;
200 }
201 }
202
203 let extend_propagation = MOUSE_INPUT_EVENT.any_update(true, |args| {
206 args.is_primary() && args.is_mouse_down() && args.target.contains(ctx.root_id)
207 }) || TOUCH_INPUT_EVENT.any_update(true, |args| args.target.contains(ctx.root_id))
208 || TOUCH_TAP_EVENT.any_update(true, |args| args.target.contains(ctx.root_id));
209 if extend_propagation && let Cow::Owned(updates) = updates.clone_insert_any(ctx.leaves()) {
210 drop(ctx);
211 c.update(&updates);
212 }
213 }
214 _ => (),
215 }
216 })
217}
218
219pub fn rich_text_component(child: impl IntoUiNode, kind: &'static str) -> UiNode {
228 let mut focus_within = false;
229 let mut prev_index = ZIndex::DEFAULT;
230 let mut index_update = None;
231 match_node(child, move |c, op| match op {
232 UiNodeOp::Init => {
233 c.init();
234
235 if TEXT.try_rich().is_some() {
236 WIDGET.sub_event(&FOCUS_CHANGED_EVENT).sub_var(&RICH_TEXT_FOCUSED_Z_VAR);
237 prev_index = Z_INDEX.get();
238 }
239 }
240 UiNodeOp::Deinit => {
241 focus_within = false;
242 }
243 UiNodeOp::Info { info } if let Some(r) = TEXT.try_rich() => {
244 let c = match kind {
245 "rich_text" => {
246 if r.root_id == WIDGET.id() {
247 RichTextComponent::Root
248 } else {
249 RichTextComponent::Branch
250 }
251 }
252 kind => RichTextComponent::Leaf { kind },
253 };
254 info.set_meta(*RICH_TEXT_COMPONENT_ID, c);
255 }
256 UiNodeOp::Update { updates } => {
257 FOCUS_CHANGED_EVENT.each_update(true, |args| {
258 let new_is_focus_within = args.is_focus_within(WIDGET.id());
259 if focus_within != new_is_focus_within {
260 focus_within = new_is_focus_within;
261
262 if TEXT.try_rich().is_some() {
263 index_update = Some(focus_within);
264 }
265 }
266 });
267
268 c.update(updates);
269
270 if let Some(apply) = index_update.take() {
271 if apply {
272 prev_index = Z_INDEX.get();
273 if let Some(i) = RICH_TEXT_FOCUSED_Z_VAR.get() {
274 Z_INDEX.set(i);
275 }
276 } else if RICH_TEXT_FOCUSED_Z_VAR.get().is_some() {
277 Z_INDEX.set(prev_index);
278 }
279 }
280 if let Some(idx) = RICH_TEXT_FOCUSED_Z_VAR.get_new()
281 && focus_within
282 {
283 Z_INDEX.set(idx.unwrap_or(prev_index));
284 }
285 }
286 _ => {}
287 })
288}
289
290impl RichText {
291 pub fn root_info(&self) -> Option<WidgetInfo> {
295 WINDOWS.widget_info(self.root_id)
296 }
297
298 pub fn leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
300 self.root_info().into_iter().flat_map(|w| rich_text_leaves_static(&w))
301 }
302
303 pub fn leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
305 self.root_info().into_iter().flat_map(|w| rich_text_leaves_rev_static(&w))
306 }
307
308 pub fn selection(&self) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
312 let (root, a, b) = match (self.caret.index, self.caret.selection_index) {
313 (Some(a), Some(b)) => (self.root_info(), a, b),
314 _ => (None, self.root_id, self.root_id),
315 };
316 OptKnownLenIter {
317 known_len_iter: root.into_iter().flat_map(move |w| rich_text_selection_static(&w, a, b)),
318 }
319 }
320
321 pub fn selection_rev(&self) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
325 let (root, a, b) = match (self.caret.index, self.caret.selection_index) {
326 (Some(a), Some(b)) => (self.root_info(), a, b),
327 _ => (None, self.root_id, self.root_id),
328 };
329 OptKnownLenIter {
330 known_len_iter: root.into_iter().flat_map(move |w| rich_text_selection_rev_static(&w, a, b)),
331 }
332 }
333
334 pub fn caret_index_info(&self) -> Option<WidgetInfo> {
336 self.leaf_info(self.caret.index?)
337 }
338
339 pub fn caret_selection_index_info(&self) -> Option<WidgetInfo> {
341 self.leaf_info(self.caret.selection_index?)
342 }
343
344 pub fn leaf_info(&self, id: WidgetId) -> Option<WidgetInfo> {
346 let root = self.root_info()?;
347 let wgt = root.tree().get(id)?;
348 if !matches!(wgt.rich_text_component(), Some(RichTextComponent::Leaf { .. })) {
349 return None;
350 }
351 if !wgt.is_descendant(&root) {
352 return None;
353 }
354 Some(wgt)
355 }
356}
357impl RichCaretInfo {
358 pub fn update_selection(
372 &mut self,
373 new_index: &WidgetInfo,
374 new_selection_index: Option<&WidgetInfo>,
375 skip_end_points: bool,
376 skip_focus: bool,
377 ) {
378 let root = new_index.rich_text_root().unwrap();
379 let old_index = self
380 .index
381 .and_then(|id| new_index.tree().get(id))
382 .unwrap_or_else(|| new_index.clone());
383 let old_selection_index = self.selection_index.and_then(|id| new_index.tree().get(id));
384
385 self.index = Some(new_index.id());
386 self.selection_index = new_selection_index.map(|w| w.id());
387
388 match (&old_selection_index, new_selection_index) {
389 (None, None) => self.continue_focus(skip_focus, new_index, &root),
390 (None, Some(new_sel)) => {
391 let (a, b) = match new_index.cmp_sibling_in(new_sel, &root).unwrap() {
393 std::cmp::Ordering::Less => (new_index, new_sel),
394 std::cmp::Ordering::Greater => (new_sel, new_index),
395 std::cmp::Ordering::Equal => {
396 return self.continue_focus(skip_focus, new_index, &root);
398 }
399 };
400 if !skip_end_points {
401 self.continue_select_lesser(a, a == new_index);
402 }
403 let middle_op = TextSelectOp::local_select_all();
404 for middle in a.rich_text_next().take_while(|n| n != b) {
405 notify_leaf_select_op(middle.id(), middle_op.clone());
406 }
407 if !skip_end_points {
408 self.continue_select_greater(b, b == new_index);
409 }
410
411 self.continue_focus(skip_focus, new_index, &root);
412 }
413 (Some(old_sel), None) => {
414 let (a, b) = match old_index.cmp_sibling_in(old_sel, &root).unwrap() {
416 std::cmp::Ordering::Less => (&old_index, old_sel),
417 std::cmp::Ordering::Greater => (old_sel, &old_index),
418 std::cmp::Ordering::Equal => {
419 if !skip_end_points {
421 notify_leaf_select_op(old_sel.id(), TextSelectOp::local_clear_selection());
422 }
423 return self.continue_focus(skip_focus, new_index, &root);
424 }
425 };
426 let op = TextSelectOp::local_clear_selection();
427 if !skip_end_points {
428 notify_leaf_select_op(a.id(), op.clone());
429 }
430 for middle in a.rich_text_next().take_while(|n| n != b) {
431 notify_leaf_select_op(middle.id(), op.clone());
432 }
433 if !skip_end_points {
434 notify_leaf_select_op(b.id(), op);
435 }
436
437 self.continue_focus(skip_focus, new_index, &root);
438 }
439 (Some(old_sel), Some(new_sel)) => {
440 let (old_a, old_b) = match old_index.cmp_sibling_in(old_sel, &root).unwrap() {
443 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => (&old_index, old_sel),
444 std::cmp::Ordering::Greater => (old_sel, &old_index),
445 };
446 let (new_a, new_b) = match new_index.cmp_sibling_in(new_sel, &root).unwrap() {
447 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => (new_index, new_sel),
448 std::cmp::Ordering::Greater => (new_sel, new_index),
449 };
450
451 let min_a = match old_a.cmp_sibling_in(new_a, &root).unwrap() {
452 std::cmp::Ordering::Less | std::cmp::Ordering::Equal => old_a,
453 std::cmp::Ordering::Greater => new_a,
454 };
455 let max_b = match old_b.cmp_sibling_in(new_b, &root).unwrap() {
456 std::cmp::Ordering::Less => new_b,
457 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => old_b,
458 };
459
460 fn inclusive_range_contains(a: &WidgetInfo, b: &WidgetInfo, q: &WidgetInfo, root: &WidgetInfo) -> bool {
461 match a.cmp_sibling_in(q, root).unwrap() {
462 std::cmp::Ordering::Less => match b.cmp_sibling_in(q, root).unwrap() {
464 std::cmp::Ordering::Less => false,
466 std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => true,
468 },
469 std::cmp::Ordering::Equal => true,
471 std::cmp::Ordering::Greater => false,
473 }
474 }
475
476 for wgt in min_a.rich_text_self_and_next() {
481 if &wgt == new_a {
482 if !skip_end_points && new_a != new_b {
483 self.continue_select_lesser(new_a, new_a == new_index);
484 }
485 } else if &wgt == new_b {
486 if !skip_end_points && new_a != new_b {
487 self.continue_select_greater(new_b, new_b == new_index);
488 }
489 } else {
490 let is_old = inclusive_range_contains(old_a, old_b, &wgt, &root);
491 let is_new = inclusive_range_contains(new_a, new_b, &wgt, &root);
492
493 match (is_old, is_new) {
494 (true, true) => {
495 if &wgt == old_a || &wgt == old_b {
496 notify_leaf_select_op(wgt.id(), TextSelectOp::local_select_all())
498 }
499 }
500 (true, false) => {
501 notify_leaf_select_op(wgt.id(), TextSelectOp::local_clear_selection());
502 }
503 (false, true) => notify_leaf_select_op(wgt.id(), TextSelectOp::local_select_all()),
504 (false, false) => {}
505 }
506 }
507
508 if &wgt == max_b {
509 break;
510 }
511 }
512
513 self.continue_focus(skip_focus, new_index, &root);
514 }
515 }
516 }
517 fn continue_select_lesser(&self, a: &WidgetInfo, a_is_caret: bool) {
518 notify_leaf_select_op(
519 a.id(),
520 TextSelectOp::new(move || {
521 let len = TEXT.resolved().segmented_text.text().len();
522 let len = CaretIndex { index: len, line: 0 }; let mut ctx = TEXT.resolve_caret();
524 if a_is_caret {
525 ctx.selection_index = Some(len);
526 } else {
527 ctx.index = Some(len);
528 }
529 ctx.index_version += 1;
530 }),
531 );
532 }
533 fn continue_select_greater(&self, b: &WidgetInfo, b_is_caret: bool) {
534 notify_leaf_select_op(
535 b.id(),
536 TextSelectOp::new(move || {
537 let mut ctx = TEXT.resolve_caret();
538 if b_is_caret {
539 ctx.selection_index = Some(CaretIndex::ZERO);
540 } else {
541 ctx.index = Some(CaretIndex::ZERO);
542 }
543 ctx.index_version += 1;
544 }),
545 );
546 }
547 fn continue_focus(&self, skip_focus: bool, new_index: &WidgetInfo, root: &WidgetInfo) {
548 if !skip_focus && FOCUS.is_focus_within(root.id()).get() {
549 FOCUS.focus_widget(new_index.id(), false);
551 SCROLL.ignore_next_scroll_to_focused();
554 }
555 }
556}
557
558pub(crate) fn notify_leaf_select_op(leaf_id: WidgetId, op: TextSelectOp) {
559 SELECT_CMD.scoped(leaf_id).notify_param(op);
560}
561
562pub trait RichTextWidgetInfoExt {
564 fn rich_text_root(&self) -> Option<WidgetInfo>;
566
567 fn rich_text_component(&self) -> Option<RichTextComponent>;
569
570 fn rich_text_leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
572 fn rich_text_leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
574
575 fn rich_text_selection(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static;
579 fn rich_text_selection_rev(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static;
583
584 fn rich_text_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
586 fn rich_text_self_and_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
588 fn rich_text_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
590 fn rich_text_self_and_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static;
592
593 fn rich_text_line_info(&self) -> RichLineInfo;
595
596 fn rich_text_nearest_leaf(&self, window_point: PxPoint) -> Option<WidgetInfo>;
598 fn rich_text_nearest_leaf_filtered(
603 &self,
604 window_point: PxPoint,
605 filter: impl FnMut(&WidgetInfo, PxRect, usize, usize) -> bool,
606 ) -> Option<WidgetInfo>;
607}
608impl RichTextWidgetInfoExt for WidgetInfo {
609 fn rich_text_root(&self) -> Option<WidgetInfo> {
610 self.self_and_ancestors()
611 .find(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Root)))
612 }
613
614 fn rich_text_component(&self) -> Option<RichTextComponent> {
615 self.meta().copy(*RICH_TEXT_COMPONENT_ID)
616 }
617
618 fn rich_text_leaves(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
619 rich_text_leaves_static(self)
620 }
621 fn rich_text_leaves_rev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
622 rich_text_leaves_rev_static(self)
623 }
624
625 fn rich_text_selection(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
626 rich_text_selection_static(self, a, b)
627 }
628 fn rich_text_selection_rev(&self, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + 'static {
629 rich_text_selection_rev_static(self, a, b)
630 }
631
632 fn rich_text_prev(&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.prev_siblings_in(&w))
637 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
638 }
639
640 fn rich_text_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
641 let me = self.clone();
642 self.rich_text_root()
643 .into_iter()
644 .flat_map(move |w| me.next_siblings_in(&w))
645 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
646 }
647
648 fn rich_text_self_and_prev(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
649 let me = self.clone();
650 self.rich_text_root()
651 .into_iter()
652 .flat_map(move |w| me.self_and_prev_siblings_in(&w))
653 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
654 }
655
656 fn rich_text_self_and_next(&self) -> impl Iterator<Item = WidgetInfo> + 'static {
657 let me = self.clone();
658 self.rich_text_root()
659 .into_iter()
660 .flat_map(move |w| me.self_and_next_siblings_in(&w))
661 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
662 }
663
664 fn rich_text_line_info(&self) -> RichLineInfo {
665 let (prev_min, prev_max) = match self.rich_text_prev().next() {
666 Some(p) => {
667 let bounds = p.bounds_info();
668 let inner_bounds = bounds.inner_bounds();
669 if let Some(inline) = bounds.inline() {
670 let mut last = inline.rows[inline.rows.len() - 1];
671 last.origin += inner_bounds.origin.to_vector();
672 (last.min_y(), last.max_y())
673 } else {
674 (inner_bounds.min_y(), inner_bounds.max_y())
675 }
676 }
677 None => (Px::MIN, Px::MIN),
678 };
679
680 let bounds = self.bounds_info();
681 let inner_bounds = bounds.inner_bounds();
682 let (min, max, wraps, inlined) = if let Some(inline) = bounds.inline() {
683 let mut first = inline.rows[0];
684 first.origin += inner_bounds.origin.to_vector();
685 (first.min_y(), first.max_y(), inline.rows.len() > 1, true)
686 } else {
687 (inner_bounds.min_y(), inner_bounds.max_y(), false, false)
688 };
689
690 let starts = !lines_overlap_strict(prev_min, prev_max, min, max);
691
692 let mut is_wrap_start = false;
693 if inlined {
694 let mut child_id = self.id();
695 for parent in self.ancestors() {
696 if parent.first_child().unwrap().id() != child_id {
697 is_wrap_start = true; break;
699 }
700 if parent.bounds_info().inline().is_none() {
701 break;
702 }
703 child_id = parent.id();
704 }
705 }
706
707 RichLineInfo {
708 starts_new_line: starts,
709 is_wrap_start,
710 ends_in_new_line: wraps,
711 }
712 }
713
714 fn rich_text_nearest_leaf(&self, window_point: PxPoint) -> Option<WidgetInfo> {
715 self.rich_text_nearest_leaf_filtered(window_point, |_, _, _, _| true)
716 }
717 fn rich_text_nearest_leaf_filtered(
718 &self,
719 window_point: PxPoint,
720 mut filter: impl FnMut(&WidgetInfo, PxRect, usize, usize) -> bool,
721 ) -> Option<WidgetInfo> {
722 let root_size = self.inner_border_size();
723 let search_radius = root_size.width.max(root_size.height);
724
725 self.nearest_rect_filtered(window_point, search_radius, |w, rect, i, len| {
726 matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })) && filter(w, rect, i, len)
727 })
728 }
729}
730fn lines_overlap_strict(y_min1: Px, y_max1: Px, y_min2: Px, y_max2: Px) -> bool {
731 let (a_min, a_max) = if y_min1 <= y_max1 { (y_min1, y_max1) } else { (y_max1, y_min1) };
732 let (b_min, b_max) = if y_min2 <= y_max2 { (y_min2, y_max2) } else { (y_max2, y_min2) };
733
734 a_min < b_max && b_min < a_max
735}
736
737#[derive(Debug, Clone, PartialEq, Eq)]
739#[non_exhaustive]
740pub struct RichLineInfo {
741 pub starts_new_line: bool,
750
751 pub is_wrap_start: bool,
753
754 pub ends_in_new_line: bool,
758}
759
760fn rich_text_leaves_static(wgt: &WidgetInfo) -> impl Iterator<Item = WidgetInfo> + use<> {
763 wgt.descendants()
764 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
765}
766fn rich_text_leaves_rev_static(wgt: &WidgetInfo) -> impl Iterator<Item = WidgetInfo> + use<> {
767 wgt.descendants()
768 .tree_rev()
769 .filter(|w| matches!(w.rich_text_component(), Some(RichTextComponent::Leaf { .. })))
770}
771fn rich_text_selection_static(wgt: &WidgetInfo, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + use<> {
772 let mut ai = usize::MAX;
773 let mut bi = usize::MAX;
774
775 for (i, leaf) in wgt.rich_text_leaves().enumerate() {
776 let id = leaf.id();
777 if id == a {
778 ai = i;
779 }
780 if id == b {
781 bi = i;
782 }
783 if ai != usize::MAX && bi != usize::MAX {
784 break;
785 }
786 }
787
788 if ai > bi {
789 std::mem::swap(&mut ai, &mut bi);
790 }
791
792 let (skip, take) = if ai != usize::MAX && bi != usize::MAX {
793 (ai, bi - ai + 1)
794 } else {
795 (0, 0)
796 };
797
798 KnownLenIter {
799 take: rich_text_leaves_static(wgt).skip(skip).take(take),
800 len: take,
801 }
802}
803fn rich_text_selection_rev_static(wgt: &WidgetInfo, a: WidgetId, b: WidgetId) -> impl ExactSizeIterator<Item = WidgetInfo> + use<> {
804 let mut ai = usize::MAX;
805 let mut bi = usize::MAX;
806
807 for (i, leaf) in wgt.rich_text_leaves_rev().enumerate() {
808 let id = leaf.id();
809 if id == a {
810 ai = i;
811 } else if id == b {
812 bi = i;
813 }
814 if ai != usize::MAX && bi != usize::MAX {
815 break;
816 }
817 }
818
819 if ai > bi {
820 std::mem::swap(&mut ai, &mut bi);
821 }
822
823 let (skip, take) = if ai != usize::MAX && bi != usize::MAX {
824 (ai, bi - ai + 1)
825 } else {
826 (0, 0)
827 };
828
829 KnownLenIter {
830 take: rich_text_leaves_rev_static(wgt).skip(skip).take(take),
831 len: take,
832 }
833}
834
835#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
837pub enum RichTextComponent {
838 Root,
840 Branch,
842 Leaf {
844 kind: &'static str,
848 },
849}
850
851static_id! {
852 static ref RICH_TEXT_COMPONENT_ID: StateId<RichTextComponent>;
853}
854
855struct KnownLenIter<I> {
856 take: I,
857 len: usize,
858}
859impl<I: Iterator<Item = WidgetInfo>> Iterator for KnownLenIter<I> {
860 type Item = WidgetInfo;
861
862 fn next(&mut self) -> Option<Self::Item> {
863 match self.take.next() {
864 Some(r) => {
865 self.len -= 1;
866 Some(r)
867 }
868 None => None,
869 }
870 }
871
872 fn size_hint(&self) -> (usize, Option<usize>) {
873 (self.len, Some(self.len))
874 }
875}
876impl<I: Iterator<Item = WidgetInfo>> ExactSizeIterator for KnownLenIter<I> {}
877
878struct OptKnownLenIter<I> {
879 known_len_iter: I,
880}
881impl<I: Iterator<Item = WidgetInfo>> Iterator for OptKnownLenIter<I> {
882 type Item = WidgetInfo;
883
884 fn next(&mut self) -> Option<Self::Item> {
885 self.known_len_iter.next()
886 }
887
888 fn size_hint(&self) -> (usize, Option<usize>) {
889 self.known_len_iter.size_hint()
891 }
892}
893impl<I: Iterator<Item = WidgetInfo>> ExactSizeIterator for OptKnownLenIter<I> {}
894
895#[derive(Clone, Default)]
898pub struct RichTextCopyParam(Arc<Mutex<Option<Txt>>>);
899impl RichTextCopyParam {
900 pub fn set_text(&self, txt: impl Into<Txt>) {
902 *self.0.lock() = Some(txt.into());
903 }
904
905 pub fn into_text(self) -> Option<Txt> {
907 match Arc::try_unwrap(self.0) {
908 Ok(m) => m.into_inner(),
909 Err(c) => c.lock().clone(),
910 }
911 }
912}
913impl fmt::Debug for RichTextCopyParam {
914 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
915 if let Some(t) = self.0.try_lock() {
916 f.debug_tuple("RichTextCopyParam").field(&*t).finish()
917 } else {
918 f.debug_tuple("RichTextCopyParam").finish_non_exhaustive()
919 }
920 }
921}