1use std::{fmt, num::Wrapping, ops, sync::Arc};
4
5use super::text_properties::*;
6use atomic::{Atomic, Ordering};
7use parking_lot::RwLock;
8use zng_app::render::FontSynthesis;
9use zng_app_context::{MappedRwLockWriteGuardOwned, RwLockReadGuardOwned, RwLockWriteGuardOwned};
10use zng_ext_font::{CaretIndex, FontFaceList, FontList, SegmentedText, ShapedLine, ShapedText, TextOverflowInfo};
11use zng_ext_input::{
12 focus::FOCUS_CHANGED_EVENT,
13 keyboard::{KEY_INPUT_EVENT, Key, KeyState},
14 mouse::MOUSE_INPUT_EVENT,
15 touch::{TOUCH_INPUT_EVENT, TOUCH_LONG_PRESS_EVENT},
16};
17use zng_ext_window::WINDOW_Ext as _;
18use zng_view_api::{mouse::ButtonState, touch::TouchPhase};
19use zng_wgt::prelude::*;
20use zng_wgt_data::{DATA, DataNoteHandle};
21use zng_wgt_layer::{
22 AnchorMode, AnchorTransform,
23 popup::{ContextCapture, POPUP, PopupState},
24};
25
26mod rich;
27pub use rich::*;
28
29mod resolve;
30pub use resolve::*;
31
32mod layout;
33pub use layout::*;
34
35mod render;
36pub use render::*;
37
38mod caret;
39pub use caret::*;
40
41#[derive(Clone, Debug)]
43#[non_exhaustive]
44pub struct RichCaretInfo {
45 pub index: Option<WidgetId>,
49 pub selection_index: Option<WidgetId>,
53}
54
55#[derive(Clone)]
57#[non_exhaustive]
58pub struct CaretInfo {
59 pub opacity: Var<Factor>,
66
67 pub index: Option<CaretIndex>,
71
72 pub selection_index: Option<CaretIndex>,
74
75 pub initial_selection: Option<(ops::Range<CaretIndex>, bool)>,
78
79 pub index_version: Wrapping<u8>,
86
87 pub used_retained_x: bool,
91
92 pub skip_next_scroll: bool,
96}
97impl fmt::Debug for CaretInfo {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 f.debug_struct("CaretInfo")
100 .field("opacity", &self.opacity.get_debug(false))
101 .field("index", &self.index)
102 .field("index_version", &self.index_version)
103 .field("used_retained_x", &self.used_retained_x)
104 .finish()
105 }
106}
107impl CaretInfo {
108 pub fn set_index(&mut self, index: CaretIndex) {
110 self.index = Some(index);
111 self.index_version += 1;
112 }
113
114 pub fn set_selection(&mut self, start: CaretIndex, end: CaretIndex) {
118 self.selection_index = Some(start);
119 self.set_index(end);
120 }
121
122 pub fn clear_selection(&mut self) {
124 self.selection_index = None;
125 self.initial_selection = None;
126 self.index_version += 1;
127 }
128
129 pub fn set_char_index(&mut self, index: usize) {
133 if let Some(i) = &mut self.index {
134 i.index = index;
135 } else {
136 self.index = Some(CaretIndex { index, line: 0 });
137 }
138 self.index_version += 1;
139 }
140
141 pub fn set_char_selection(&mut self, start: usize, end: usize) {
147 if let Some(s) = &mut self.selection_index {
148 s.index = start;
149 } else {
150 self.selection_index = Some(CaretIndex { index: start, line: 0 });
151 }
152 self.set_char_index(end);
153 }
154
155 pub fn selection_range(&self) -> Option<ops::Range<CaretIndex>> {
160 let a = self.index?;
161 let b = self.selection_index?;
162
163 use std::cmp::Ordering;
164 match a.index.cmp(&b.index) {
165 Ordering::Less => Some(a..b),
166 Ordering::Equal => None,
167 Ordering::Greater => Some(b..a),
168 }
169 }
170
171 pub fn selection_char_range(&self) -> Option<ops::Range<usize>> {
176 self.selection_range().map(|r| r.start.index..r.end.index)
177 }
178}
179
180#[derive(Clone)]
182#[non_exhaustive]
183pub struct ImePreview {
184 pub txt: Txt,
186
187 pub prev_caret: CaretIndex,
189 pub prev_selection: Option<CaretIndex>,
193}
194
195pub struct TEXT;
200
201impl TEXT {
202 pub fn try_rich(&self) -> Option<RwLockReadGuardOwned<RichText>> {
204 if RICH_TEXT.is_default() {
205 None
206 } else {
207 Some(RICH_TEXT.read_recursive())
208 }
209 }
210
211 pub fn try_resolved(&self) -> Option<RwLockReadGuardOwned<ResolvedText>> {
215 if RESOLVED_TEXT.is_default() {
216 None
217 } else {
218 Some(RESOLVED_TEXT.read_recursive())
219 }
220 }
221
222 pub fn rich(&self) -> RwLockReadGuardOwned<RichText> {
230 RICH_TEXT.read_recursive()
231 }
232
233 pub fn resolved(&self) -> RwLockReadGuardOwned<ResolvedText> {
239 RESOLVED_TEXT.read_recursive()
240 }
241
242 pub fn try_laidout(&self) -> Option<RwLockReadGuardOwned<LaidoutText>> {
246 if LAIDOUT_TEXT.is_default() {
247 None
248 } else {
249 Some(LAIDOUT_TEXT.read_recursive())
250 }
251 }
252
253 pub fn laidout(&self) -> RwLockReadGuardOwned<LaidoutText> {
259 LAIDOUT_TEXT.read_recursive()
260 }
261
262 pub fn resolve_caret(&self) -> MappedRwLockWriteGuardOwned<ResolvedText, CaretInfo> {
270 RwLockWriteGuardOwned::map(self.resolve(), |ctx| &mut ctx.caret)
271 }
272
273 pub fn resolve_rich_caret(&self) -> MappedRwLockWriteGuardOwned<RichText, RichCaretInfo> {
283 RwLockWriteGuardOwned::map(RICH_TEXT.write(), |ctx| &mut ctx.caret)
284 }
285
286 pub fn set_caret_retained_x(&self, x: Px) {
291 self.layout().caret_retained_x = x;
292 }
293
294 pub fn set_underlines(&self, underlines: Vec<(PxPoint, Px)>) {
299 let mut l = self.layout();
300 if l.underlines != underlines {
301 WIDGET.render();
302 }
303 l.underlines = underlines;
304 }
305
306 pub(crate) fn resolve(&self) -> RwLockWriteGuardOwned<ResolvedText> {
307 RESOLVED_TEXT.write()
308 }
309
310 fn layout(&self) -> RwLockWriteGuardOwned<LaidoutText> {
311 LAIDOUT_TEXT.write()
312 }
313
314 pub(crate) fn take_rich_selection_started_by_alt(&self) -> bool {
315 std::mem::take(&mut RICH_TEXT.write().selection_started_by_alt)
316 }
317
318 pub(crate) fn flag_rich_selection_started_by_alt(&self) {
319 RICH_TEXT.write().selection_started_by_alt = true;
320 }
321}
322
323#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, bytemuck::NoUninit)]
327#[repr(u8)]
328pub enum SelectionBy {
329 Command = 0,
331 Keyboard = 1,
333 Mouse = 2,
335 Touch = 3,
337}
338impl SelectionBy {
339 pub fn matches_interactive_mode(self, mode: InteractiveCaretMode) -> bool {
341 match mode {
342 InteractiveCaretMode::TouchOnly => matches!(self, SelectionBy::Touch),
343 InteractiveCaretMode::Enabled => true,
344 InteractiveCaretMode::Disabled => false,
345 }
346 }
347}
348
349#[non_exhaustive]
353pub struct ResolvedText {
354 pub txt: Var<Txt>,
356 pub ime_preview: Option<ImePreview>,
359
360 pub segmented_text: SegmentedText,
362 pub faces: FontFaceList,
364 pub synthesis: FontSynthesis,
366
367 pub pending_layout: PendingLayout,
372
373 pub pending_edit: bool,
375
376 pub caret: CaretInfo,
378
379 pub selection_by: SelectionBy,
381
382 pub selection_toolbar_is_open: bool,
384}
385impl fmt::Debug for ResolvedText {
386 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387 f.debug_struct("ResolvedText")
388 .field("segmented_text", &self.segmented_text)
389 .field("faces", &self.faces)
390 .field("synthesis", &self.synthesis)
391 .field("pending_layout", &self.pending_layout)
392 .field("pending_edit", &self.pending_edit)
393 .field("caret", &self.caret)
394 .field("selection_by", &self.selection_by)
395 .field("selection_toolbar_is_open", &self.selection_toolbar_is_open)
396 .finish_non_exhaustive()
397 }
398}
399impl ResolvedText {
400 fn no_context() -> Self {
401 panic!("no `ResolvedText` in context, only available inside `resolve_text`")
402 }
403}
404
405#[derive(Debug, Clone)]
407#[non_exhaustive]
408pub struct RenderInfo {
409 pub transform: PxTransform,
411 pub scale_factor: Factor,
413}
414impl Default for RenderInfo {
415 fn default() -> Self {
417 Self {
418 transform: PxTransform::identity(),
419 scale_factor: 1.fct(),
420 }
421 }
422}
423
424#[derive(Debug)]
428#[non_exhaustive]
429pub struct LaidoutText {
430 pub fonts: FontList,
434
435 pub shaped_text: ShapedText,
437
438 pub overflow: Option<TextOverflowInfo>,
440
441 pub overflow_suffix: Option<ShapedText>,
443
444 pub shaped_text_version: u32,
446
447 pub overlines: Vec<(PxPoint, Px)>,
453
454 pub overline_thickness: Px,
456
457 pub strikethroughs: Vec<(PxPoint, Px)>,
463 pub strikethrough_thickness: Px,
465
466 pub underlines: Vec<(PxPoint, Px)>,
473 pub underline_thickness: Px,
475
476 pub ime_underlines: Vec<(PxPoint, Px)>,
482 pub ime_underline_thickness: Px,
484
485 pub caret_origin: Option<PxPoint>,
487
488 pub caret_selection_origin: Option<PxPoint>,
490
491 pub caret_retained_x: Px,
493
494 pub render_info: RenderInfo,
496
497 pub viewport: PxSize,
499}
500impl LaidoutText {
501 fn no_context() -> Self {
502 panic!("no `LaidoutText` in context, only available inside `layout_text`")
503 }
504}
505
506#[non_exhaustive]
510pub struct RichText {
511 pub root_id: WidgetId,
513
514 pub caret: RichCaretInfo,
516
517 selection_started_by_alt: bool,
518}
519impl RichText {
520 fn no_context() -> Self {
521 panic!("no `RichText` in context, only available inside `rich_text`")
522 }
523}
524
525context_local! {
526 static RICH_TEXT: RwLock<RichText> = RwLock::new(RichText::no_context());
528 static RESOLVED_TEXT: RwLock<ResolvedText> = RwLock::new(ResolvedText::no_context());
530 static LAIDOUT_TEXT: RwLock<LaidoutText> = RwLock::new(LaidoutText::no_context());
532}
533
534bitflags! {
535 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
537 pub struct PendingLayout: u8 {
538 const UNDERLINE = 0b0000_0001;
540 const STRIKETHROUGH = 0b0000_0010;
542 const OVERLINE = 0b0000_0100;
544 const CARET = 0b0000_1000;
546 const OVERFLOW = 0b0001_0000;
548 const RESHAPE_LINES = 0b0111_1111;
550 const RESHAPE = 0b1111_1111;
552 }
553}
554
555pub fn line_placeholder(width: impl IntoVar<Length>) -> UiNode {
561 let child = layout_text(FillUiNode);
562 let child = resolve_text(child, " ");
563 zng_wgt_size_offset::width(child, width)
564}
565
566pub(super) fn get_caret_index(child: impl IntoUiNode, index: impl IntoVar<Option<CaretIndex>>) -> UiNode {
567 let index = index.into_var();
568 match_node(child, move |c, op| {
569 let mut u = false;
570 match op {
571 UiNodeOp::Init => {
572 c.init();
573 index.set(TEXT.resolved().caret.index);
574 }
575 UiNodeOp::Deinit => {
576 index.set(None);
577 }
578 UiNodeOp::Update { updates } => {
579 c.update(updates);
580 u = true;
581 }
582 _ => {}
583 }
584 if u {
585 let t = TEXT.resolved();
586 let idx = t.caret.index;
587 if !t.pending_edit && index.get() != idx {
588 index.set(idx);
589 }
590 }
591 })
592}
593
594pub(super) fn get_caret_status(child: impl IntoUiNode, status: impl IntoVar<CaretStatus>) -> UiNode {
595 let status = status.into_var();
596 match_node(child, move |c, op| {
597 let mut u = false;
598 match op {
599 UiNodeOp::Init => {
600 c.init();
601 let t = TEXT.resolved();
602 status.set(match t.caret.index {
603 None => CaretStatus::none(),
604 Some(i) => CaretStatus::new(i.index, &t.segmented_text),
605 });
606 }
607 UiNodeOp::Deinit => {
608 status.set(CaretStatus::none());
609 }
610 UiNodeOp::Update { updates } => {
611 c.update(updates);
612 u = true;
613 }
614 _ => {}
615 }
616 if u {
617 let t = TEXT.resolved();
618 let idx = t.caret.index;
619 if !t.pending_edit && status.get().index() != idx.map(|ci| ci.index) {
620 status.set(match idx {
621 None => CaretStatus::none(),
622 Some(i) => CaretStatus::new(i.index, &t.segmented_text),
623 });
624 }
625 }
626 })
627}
628
629pub(super) fn get_lines_len(child: impl IntoUiNode, len: impl IntoVar<usize>) -> UiNode {
630 let len = len.into_var();
631 match_node(child, move |c, op| match op {
632 UiNodeOp::Deinit => {
633 len.set(0usize);
634 }
635 UiNodeOp::Layout { wl, final_size } => {
636 *final_size = c.layout(wl);
637 let t = TEXT.laidout();
638 let l = t.shaped_text.lines_len();
639 if l != len.get() {
640 len.set(t.shaped_text.lines_len());
641 }
642 }
643 _ => {}
644 })
645}
646
647pub(super) fn get_lines_wrap_count(child: impl IntoUiNode, lines: impl IntoVar<super::LinesWrapCount>) -> UiNode {
648 let lines = lines.into_var();
649 let mut version = 0;
650 match_node(child, move |c, op| match op {
651 UiNodeOp::Deinit => {
652 lines.set(super::LinesWrapCount::NoWrap(0));
653 }
654 UiNodeOp::Layout { wl, final_size } => {
655 *final_size = c.layout(wl);
656 let t = TEXT.laidout();
657 if t.shaped_text_version != version {
658 version = t.shaped_text_version;
659 if let Some(update) = lines.with(|l| lines_wrap_count(l, &t.shaped_text)) {
660 lines.set(update);
661 }
662 }
663 }
664 _ => {}
665 })
666}
667fn lines_wrap_count(prev: &super::LinesWrapCount, txt: &ShapedText) -> Option<super::LinesWrapCount> {
669 match prev {
670 super::LinesWrapCount::NoWrap(len) => {
671 let mut counter = lines_wrap_counter(txt);
672 let mut l = 0;
673 for c in &mut counter {
674 if c != 1 {
675 let mut wrap = vec![1; l];
677 wrap.push(c);
678 wrap.extend(&mut counter);
679 return Some(super::LinesWrapCount::Wrap(wrap));
680 }
681 l += 1;
682 }
683 if l != *len {
684 Some(super::LinesWrapCount::NoWrap(l))
686 } else {
687 None
688 }
689 }
690 super::LinesWrapCount::Wrap(counts) => {
691 let mut prev_counts = counts.iter();
694 let mut new_counts = lines_wrap_counter(txt);
695 let mut eq_l = 0;
696 let mut eq_wrap = false;
697 for c in &mut new_counts {
698 if prev_counts.next() == Some(&c) {
699 eq_l += 1;
700 eq_wrap |= c != 1;
701 } else if eq_wrap || c != 1 {
702 let mut wrap = counts[..eq_l].to_vec();
704 wrap.push(c);
705 wrap.extend(&mut new_counts);
706 return Some(super::LinesWrapCount::Wrap(wrap));
707 } else {
708 let mut l = eq_l + 1; for c in &mut new_counts {
711 if c != 1 {
712 let mut wrap = vec![1; l];
714 wrap.push(c);
715 wrap.extend(&mut new_counts);
716 return Some(super::LinesWrapCount::Wrap(wrap));
717 }
718 l += 1;
719 }
720 return Some(super::LinesWrapCount::NoWrap(l));
722 }
723 }
724 if prev_counts.next().is_some() {
725 Some(super::LinesWrapCount::Wrap(counts[..eq_l].to_vec()))
726 } else {
727 None
728 }
729 }
730 }
731}
732fn lines_wrap_counter(txt: &ShapedText) -> impl Iterator<Item = u32> + '_ {
733 struct Counter<I> {
734 lines: I,
735 count: u32,
736 }
737 impl<'a, I: Iterator<Item = ShapedLine<'a>>> Iterator for Counter<I> {
738 type Item = u32;
739
740 fn next(&mut self) -> Option<u32> {
741 loop {
742 let line = self.lines.next()?;
743 if line.ended_by_wrap() {
744 self.count += 1;
745 continue;
746 }
747
748 let c = self.count;
749 self.count = 1;
750
751 return Some(c);
752 }
753 }
754 }
755 Counter {
756 lines: txt.lines(),
757 count: 1,
758 }
759}
760
761pub(super) fn parse_text<T>(child: impl IntoUiNode, value: impl IntoVar<T>) -> UiNode
762where
763 T: super::TxtParseValue,
764{
765 let value = value.into_var();
766
767 let error = var(Txt::from_static(""));
768 let mut _error_note = DataNoteHandle::dummy();
769
770 #[derive(Clone, Copy, bytemuck::NoUninit)]
771 #[repr(u8)]
772 enum State {
773 Sync,
774 Requested,
775 Pending,
776 }
777 let state = Arc::new(Atomic::new(State::Sync));
778
779 match_node(child, move |_, op| match op {
780 UiNodeOp::Init => {
781 let ctx = TEXT.resolved();
782
783 ctx.txt.set_from_map(&value, |val| val.to_txt());
785
786 let live = TXT_PARSE_LIVE_VAR.current_context();
794 let is_pending = TXT_PARSE_PENDING_VAR.current_context();
795 let cmd_handle = Arc::new(super::cmd::PARSE_CMD.scoped(WIDGET.id()).subscribe(false));
796
797 let binding = ctx.txt.bind_filter_map_bidi(
798 &value,
799 clmv!(state, error, is_pending, cmd_handle, |txt| {
800 if live.get() || matches!(state.load(Ordering::Relaxed), State::Requested) {
801 if !matches!(state.swap(State::Sync, Ordering::Relaxed), State::Sync) {
804 is_pending.set(false);
806 cmd_handle.enabled().set(false);
807 }
808
809 match T::from_txt(txt) {
811 Ok(val) => {
812 error.set(Txt::from_static(""));
813 Some(val)
814 }
815 Err(e) => {
816 error.set(e);
817 None
818 }
819 }
820 } else {
821 if !matches!(state.swap(State::Pending, Ordering::Relaxed), State::Pending) {
824 is_pending.set(true);
826 cmd_handle.enabled().set(true);
827 }
828
829 None
831 }
832 }),
833 clmv!(state, error, |val| {
834 error.set(Txt::from_static(""));
837
838 if !matches!(state.swap(State::Sync, Ordering::Relaxed), State::Sync) {
839 is_pending.set(false);
840 cmd_handle.enabled().set(false);
841 }
842
843 Some(val.to_txt())
844 }),
845 );
846
847 WIDGET.sub_var(&TXT_PARSE_LIVE_VAR).sub_var(&error).push_var_handles(binding);
850 }
851 UiNodeOp::Deinit => {
852 _error_note = DataNoteHandle::dummy();
853 }
854 UiNodeOp::Update { .. } => {
855 if matches!(state.load(Ordering::Relaxed), State::Pending) {
856 super::cmd::PARSE_CMD.scoped(WIDGET.id()).each_update(true, false, |args| {
857 state.store(State::Requested, Ordering::Relaxed);
860 TEXT.resolved().txt.update();
861 args.propagation.stop();
862 });
863 }
864
865 if let Some(true) = TXT_PARSE_LIVE_VAR.get_new()
866 && matches!(state.load(Ordering::Relaxed), State::Pending)
867 {
868 TEXT.resolved().txt.update();
871 }
872
873 if let Some(error) = error.get_new() {
874 _error_note = if error.is_empty() {
877 DataNoteHandle::dummy()
878 } else {
879 DATA.invalidate(error)
880 };
881 }
882 }
883 _ => {}
884 })
885}
886
887pub(super) fn on_change_stop(child: impl IntoUiNode, handler: Handler<ChangeStopArgs>) -> UiNode {
888 let mut handler = handler.into_wgt_runner();
889 let mut pending = None;
890 match_node(child, move |c, op| match op {
891 UiNodeOp::Deinit => {
892 handler.deinit();
893 }
894 UiNodeOp::Update { updates } => {
895 if TEXT.resolved().txt.is_new() {
896 let deadline = TIMERS.deadline(CHANGE_STOP_DELAY_VAR.get());
897 deadline.subscribe(UpdateOp::Update, WIDGET.id()).perm();
898 pending = Some(deadline);
899 } else if let Some(p) = &pending
900 && p.get().has_elapsed()
901 {
902 pending = None;
903
904 handler.event(&ChangeStopArgs {
905 cause: ChangeStopCause::DelayElapsed,
906 });
907 }
908
909 c.update(updates);
910 handler.update();
911
912 if pending.is_none() {
913 return;
914 }
915
916 KEY_INPUT_EVENT.each_update(false, |args| {
917 if let KeyState::Pressed = args.state
918 && let Key::Enter = &args.key
919 && !ACCEPTS_ENTER_VAR.get()
920 {
921 pending = None;
922 handler.event(&ChangeStopArgs {
923 cause: ChangeStopCause::Enter,
924 });
925 }
926 });
927 FOCUS_CHANGED_EVENT.each_update(true, |args| {
928 let target = WIDGET.id();
929 if args.is_blur(target) {
930 pending = None;
931 handler.event(&ChangeStopArgs {
932 cause: ChangeStopCause::Blur,
933 });
934 }
935 });
936 }
937 _ => {}
938 })
939}
940
941pub fn selection_toolbar_node(child: impl IntoUiNode) -> UiNode {
943 use super::node::*;
944
945 let mut selection_range = None;
946 let mut popup_state = None::<Var<PopupState>>;
947 match_node(child, move |c, op| {
948 let mut open = false;
949 let mut open_long_press = false;
950 let mut close = false;
951 match op {
952 UiNodeOp::Init => {
953 WIDGET.sub_var(&SELECTION_TOOLBAR_FN_VAR);
954 }
955 UiNodeOp::Deinit => {
956 close = true;
957 }
958 UiNodeOp::Update { updates } => {
959 if SELECTION_TOOLBAR_FN_VAR.is_new() {
960 close = true;
961 }
962 if let Some(state) = popup_state.as_ref().and_then(|s| s.get_new()) {
963 let is_open = !matches!(state, PopupState::Closed);
964 let mut r_txt = TEXT.resolve();
965 if r_txt.selection_toolbar_is_open != is_open {
966 r_txt.selection_toolbar_is_open = is_open;
967 WIDGET.layout().render();
968
969 if !is_open {
970 popup_state = None;
971 }
972 }
973 }
974
975 c.update(updates);
976
977 let open_id = || {
978 if let Some(popup_state) = &popup_state
979 && let PopupState::Open(id) = popup_state.get()
980 {
981 return Some(id);
982 }
983 None
984 };
985
986 MOUSE_INPUT_EVENT.each_update(true, |args| {
987 if open_id().map(|id| !args.target.contains(id)).unwrap_or(false) {
988 close = true;
989 }
990 if args.state == ButtonState::Released {
991 open = true;
992 }
993 });
994
995 if TOUCH_LONG_PRESS_EVENT.has_update(true) {
996 open = true;
997 open_long_press = true;
998 }
999 if KEY_INPUT_EVENT.has_update(true) {
1000 close = true;
1001 }
1002
1003 FOCUS_CHANGED_EVENT.each_update(true, |args| {
1004 if args.is_blur(WIDGET.id())
1005 && open_id()
1006 .map(|id| args.new_focus.as_ref().map(|p| !p.contains(id)).unwrap_or(true))
1007 .unwrap_or(false)
1008 {
1009 close = true;
1010 }
1011 });
1012 TOUCH_INPUT_EVENT.each_update(true, |args| {
1013 if matches!(args.phase, TouchPhase::Start | TouchPhase::Move)
1014 && open_id().map(|id| !args.target.contains(id)).unwrap_or(false)
1015 {
1016 close = true;
1017 }
1018 });
1019
1020 if popup_state.is_some() {
1021 let r_txt = TEXT.resolved();
1022 if selection_range != r_txt.caret.selection_range() {
1023 close = true;
1024 }
1025 }
1026 }
1027 _ => {}
1028 }
1029 if close && let Some(state) = &popup_state.take() {
1030 selection_range = None;
1031 POPUP.close(state);
1032 TEXT.resolve().selection_toolbar_is_open = false;
1033 WIDGET.layout().render();
1034 }
1035 if open {
1036 let r_txt = TEXT.resolved();
1037
1038 let range = r_txt.caret.selection_range();
1039 if open_long_press || range.is_some() {
1040 selection_range = range;
1041
1042 let toolbar_fn = SELECTION_TOOLBAR_FN_VAR.get();
1043 if let Some(node) = toolbar_fn.call_checked(SelectionToolbarArgs {
1044 anchor_id: WIDGET.id(),
1045 is_touch: matches!(r_txt.selection_by, SelectionBy::Touch),
1046 }) {
1047 let (node, _) = node.init_widget();
1048
1049 let mut translate = PxVector::zero();
1050 let transform_key = FrameValueKey::new_unique();
1051 let node = match_widget(node, move |c, op| match op {
1052 UiNodeOp::Init => {
1053 c.init();
1054 if let Some(mut wgt) = c.node().as_widget() {
1055 wgt.with_context(WidgetUpdateMode::Bubble, || WIDGET.sub_var_layout(&SELECTION_TOOLBAR_ANCHOR_VAR));
1056 }
1057 }
1058 UiNodeOp::Layout { wl, final_size } => {
1059 let r_txt = TEXT.resolved();
1060
1061 let range = if open_long_press {
1062 Some(r_txt.caret.selection_range().unwrap_or_else(|| {
1063 let i = r_txt.caret.index.unwrap_or(CaretIndex::ZERO);
1064 i..i
1065 }))
1066 } else {
1067 r_txt.caret.selection_range()
1068 };
1069
1070 if let Some(range) = range {
1071 let l_txt = TEXT.laidout();
1072 let r_txt = r_txt.segmented_text.text();
1073
1074 let mut bounds = PxBox::new(PxPoint::splat(Px::MAX), PxPoint::splat(Px::MIN));
1075 for line_rect in l_txt.shaped_text.highlight_rects(range, r_txt) {
1076 let line_box = line_rect.to_box2d();
1077 bounds.min = bounds.min.min(line_box.min);
1078 bounds.max = bounds.max.max(line_box.max);
1079 }
1080 let selection_bounds = bounds.to_rect();
1081
1082 *final_size = c.layout(wl);
1083
1084 let offset = SELECTION_TOOLBAR_ANCHOR_VAR.get();
1085
1086 fn layout_offset(size: PxSize, point: Point) -> PxVector {
1087 LAYOUT
1088 .with_constraints(PxConstraints2d::new_exact_size(size), || point.layout())
1089 .to_vector()
1090 }
1091 let place = layout_offset(selection_bounds.size, offset.place);
1092 let origin = layout_offset(*final_size, offset.origin);
1093
1094 translate = selection_bounds.origin.to_vector() + place - origin;
1095 } else {
1096 wl.collapse();
1098 *final_size = PxSize::zero();
1099 };
1100 }
1101 UiNodeOp::Render { frame } => {
1102 let l_txt = TEXT.laidout();
1103 let transform = l_txt.render_info.transform.then_translate(translate.cast());
1104 let transform = adjust_viewport_bound(transform, c.node());
1105
1106 frame.push_reference_frame(transform_key.into(), FrameValue::Value(transform), true, false, |frame| {
1107 c.render(frame)
1108 });
1109 }
1110 UiNodeOp::RenderUpdate { update } => {
1111 let l_txt = TEXT.laidout();
1112 let transform = l_txt.render_info.transform.then_translate(translate.cast());
1113 let transform = adjust_viewport_bound(transform, c.node());
1114
1115 update.with_transform(transform_key.update(transform, true), false, |update| c.render_update(update));
1116 }
1117 _ => {}
1118 });
1119
1120 let capture = ContextCapture::CaptureBlend {
1122 filter: CaptureFilter::Exclude({
1123 let mut exclude = ContextValueSet::new();
1124 super::Text::context_vars_set(&mut exclude);
1125
1126 let mut allow = ContextValueSet::new();
1127 super::LangMix::<()>::context_vars_set(&mut allow);
1128 exclude.remove_all(&allow);
1129 exclude.remove(&SELECTION_TOOLBAR_ANCHOR_VAR);
1130
1131 exclude
1132 }),
1133 over: false,
1134 };
1135
1136 let mut anchor_mode = AnchorMode::tooltip();
1137 anchor_mode.transform = AnchorTransform::None;
1138 let state = POPUP.open_config(node, anchor_mode, capture);
1139 state.subscribe(UpdateOp::Update, WIDGET.id()).perm();
1140 popup_state = Some(state);
1141 drop(r_txt);
1142 TEXT.resolve().selection_toolbar_is_open = true;
1143 WIDGET.layout().render();
1144 }
1145 };
1146 }
1147 })
1148}
1149fn adjust_viewport_bound(transform: PxTransform, widget: &mut UiNode) -> PxTransform {
1150 let window_bounds = WINDOW.vars().actual_size_px().get();
1151 let wgt_bounds = PxBox::from(match widget.as_widget() {
1152 Some(mut w) => w.with_context(WidgetUpdateMode::Ignore, || WIDGET.bounds().outer_size()),
1153 None => PxSize::zero(),
1154 });
1155 let wgt_bounds = transform.outer_transformed(wgt_bounds).unwrap_or_default();
1156
1157 let x_underflow = -wgt_bounds.min.x.min(Px(0));
1158 let x_overflow = (wgt_bounds.max.x - window_bounds.width).max(Px(0));
1159 let y_underflow = -wgt_bounds.min.y.min(Px(0));
1160 let y_overflow = (wgt_bounds.max.y - window_bounds.height).max(Px(0));
1161
1162 let x = x_underflow - x_overflow;
1163 let y = y_underflow - y_overflow;
1164
1165 let correction = PxVector::new(x, y);
1166
1167 transform.then_translate(correction.cast())
1168}