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