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