1use crate::{
2 makepad_derive_widget::*,
3 makepad_draw::*,
4 widget::*,
5};
6
7live_design!{
8 link widgets;
9 use link::theme::*;
10 use makepad_draw::shader::std::*;
11 use crate::button::Button;
12
13 pub DrawFlowBlock = {{DrawFlowBlock}} {}
14 pub TextFlowBase = {{TextFlow}} {
15 font_size: 8,
18 flow: RightWrap,
20 }
21
22 pub TextFlowLinkBase = {{TextFlowLink}} {
23 link = {
24 draw_text = {
25 color: #1a0dab
27 }
28 }
29 }
30
31 pub TextFlowLink = <TextFlowLinkBase> {
32 color: #xa,
33 color_hover: #xf,
34 color_down: #x3,
35
36 margin:{right:5}
37
38 animator: {
39 hover = {
40 default: off,
41 off = {
42 redraw: true,
43 from: {all: Forward {duration: 0.01}}
44 apply: {
45 hovered: 0.0,
46 down: 0.0,
47 }
48 }
49
50 on = {
51 redraw: true,
52 from: {
53 all: Forward {duration: 0.1}
54 down: Forward {duration: 0.01}
55 }
56 apply: {
57 hovered: [{time: 0.0, value: 1.0}],
58 down: [{time: 0.0, value: 1.0}],
59 }
60 }
61
62 down = {
63 redraw: true,
64 from: {all: Forward {duration: 0.01}}
65 apply: {
66 hovered: [{time: 0.0, value: 1.0}],
67 down: [{time: 0.0, value: 1.0}],
68 }
69 }
70 }
71 }
72 }
73
74 pub TextFlow = <TextFlowBase> {
75 width: Fill, height: Fit,
76 flow: RightWrap,
77 width:Fill,
78 height:Fit,
79 padding: 0
80
81 font_size: (THEME_FONT_SIZE_P),
82 font_color: (THEME_COLOR_TEXT),
83
84 draw_normal: {
85 text_style: <THEME_FONT_REGULAR> {
86 font_size: (THEME_FONT_SIZE_P)
87 }
88 color: (THEME_COLOR_TEXT)
89 }
90
91 draw_italic: {
92 text_style: <THEME_FONT_ITALIC> {
93 font_size: (THEME_FONT_SIZE_P)
94 }
95 color: (THEME_COLOR_TEXT)
96 }
97
98 draw_bold: {
99 text_style: <THEME_FONT_BOLD> {
100 font_size: (THEME_FONT_SIZE_P)
101 }
102 color: (THEME_COLOR_TEXT)
103 }
104
105 draw_bold_italic: {
106 text_style: <THEME_FONT_BOLD_ITALIC> {
107 font_size: (THEME_FONT_SIZE_P)
108 }
109 color: (THEME_COLOR_TEXT)
110 }
111
112 draw_fixed: {
113 text_style: <THEME_FONT_CODE> {
114 font_size: (THEME_FONT_SIZE_P)
115 }
116 color: (THEME_COLOR_TEXT)
117 }
118
119 code_layout: {
120 flow: RightWrap,
121 padding: <THEME_MSPACE_2> { left: (THEME_SPACE_3), right: (THEME_SPACE_3) }
122 }
123 code_walk: { width: Fill, height: Fit }
124
125 quote_layout: {
126 flow: RightWrap,
127 padding: <THEME_MSPACE_2> { left: (THEME_SPACE_3), right: (THEME_SPACE_3) }
128 }
129 quote_walk: { width: Fill, height: Fit, }
130
131 list_item_layout: {
132 flow: RightWrap,
133 padding: <THEME_MSPACE_1> {}
134 }
135 list_item_walk: {
136 height: Fit, width: Fill,
137 }
138
139 inline_code_padding: <THEME_MSPACE_1> {},
140 inline_code_margin: <THEME_MSPACE_1> {},
141
142 sep_walk: {
143 width: Fill, height: 4.
144 margin: <THEME_MSPACE_V_1> {}
145 }
146
147 link = <TextFlowLink> {}
148
149 draw_block:{
150 line_color: (THEME_COLOR_TEXT)
151 sep_color: (THEME_COLOR_SHADOW)
152 quote_bg_color: (THEME_COLOR_BG_HIGHLIGHT)
153 quote_fg_color: (THEME_COLOR_TEXT)
154 code_color: (THEME_COLOR_BG_HIGHLIGHT)
155 fn pixel(self) -> vec4 {
156 let sdf = Sdf2d::viewport(self.pos * self.rect_size);
157 match self.block_type {
158 FlowBlockType::Quote => {
159 sdf.box(
160 0.,
161 0.,
162 self.rect_size.x,
163 self.rect_size.y,
164 2.
165 );
166 sdf.fill(self.quote_bg_color)
167 sdf.box(
168 THEME_SPACE_1,
169 THEME_SPACE_1,
170 THEME_SPACE_1,
171 self.rect_size.y - THEME_SPACE_2,
172 1.5
173 );
174 sdf.fill(self.quote_fg_color);
175 return sdf.result;
176 }
177 FlowBlockType::Sep => {
178 sdf.box(
179 0.,
180 1.,
181 self.rect_size.x-1,
182 self.rect_size.y-2.,
183 2.
184 );
185 sdf.fill(self.sep_color);
186 return sdf.result;
187 }
188 FlowBlockType::Code => {
189 sdf.box(
190 0.,
191 0.,
192 self.rect_size.x,
193 self.rect_size.y,
194 2.
195 );
196 sdf.fill(self.code_color);
197 return sdf.result;
198 }
199 FlowBlockType::InlineCode => {
200 sdf.box(
201 1.,
202 1.,
203 self.rect_size.x-2.,
204 self.rect_size.y-2.,
205 2.
206 );
207 sdf.fill(self.code_color);
208 return sdf.result;
209 }
210 FlowBlockType::Underline => {
211 sdf.box(
212 0.,
213 self.rect_size.y-2,
214 self.rect_size.x,
215 2.0,
216 0.5
217 );
218 sdf.fill(self.line_color);
219 return sdf.result;
220 }
221 FlowBlockType::Strikethrough => {
222 sdf.box(
223 0.,
224 self.rect_size.y * 0.45,
225 self.rect_size.x,
226 2.0,
227 0.5
228 );
229 sdf.fill(self.line_color);
230 return sdf.result;
231 }
232 }
233 return #f00
234 }
235 }
236 }
237}
238
239#[derive(Live, LiveHook)]
240#[live_ignore]
241#[repr(u32)]
242pub enum FlowBlockType {
243 #[pick] Quote = shader_enum(1),
244 Sep = shader_enum(2),
245 Code = shader_enum(3),
246 InlineCode = shader_enum(4),
247 Underline = shader_enum(5),
248 Strikethrough = shader_enum(6)
249}
250
251#[derive(Live, LiveHook, LiveRegister)]
252#[repr(C)]
253pub struct DrawFlowBlock {
254 #[deref] draw_super: DrawQuad,
255 #[live] pub line_color: Vec4,
256 #[live] pub sep_color: Vec4,
257 #[live] pub code_color: Vec4,
258 #[live] pub quote_bg_color: Vec4,
259 #[live] pub quote_fg_color: Vec4,
260 #[live] pub block_type: FlowBlockType
261}
262
263#[derive(Default)]
264pub struct StackCounter(usize);
265impl StackCounter{
266 pub fn push(&mut self){
267 self.0 += 1;
268 }
269 pub fn pop(&mut self){
270 if self.0 > 0{
271 self.0 -=1;
272 }
273 }
274 pub fn clear(&mut self){
275 self.0 = 0
276 }
277 pub fn value(&self)->usize{
278 self.0
279 }
280}
281
282#[derive(Live, Widget)]
284pub struct TextFlow {
285 #[live] pub draw_normal: DrawText,
286 #[live] pub draw_italic: DrawText,
287 #[live] pub draw_bold: DrawText,
288 #[live] pub draw_bold_italic: DrawText,
289 #[live] pub draw_fixed: DrawText,
290 #[live] pub draw_block: DrawFlowBlock,
291
292 #[live] pub font_size: f32,
294 #[live] pub font_color: Vec4,
296 #[walk] walk: Walk,
297
298 #[rust] area_stack: SmallVec<[Area;4]>,
299 #[rust] pub font_sizes: SmallVec<[f32;8]>,
300 #[rust] pub font_colors: SmallVec<[Vec4;8]>,
301 #[rust] pub combine_spaces: SmallVec<[bool;4]>,
304 #[rust] pub ignore_newlines: SmallVec<[bool;4]>,
305 #[rust] pub bold: StackCounter,
306 #[rust] pub italic: StackCounter,
307 #[rust] pub fixed: StackCounter,
308 #[rust] pub underline: StackCounter,
309 #[rust] pub strikethrough: StackCounter,
310 #[rust] pub inline_code: StackCounter,
311
312 #[rust] pub item_counter: u64,
313 #[rust] pub first_thing_on_a_line: bool,
314
315 #[rust] pub areas_tracker: RectAreasTracker,
316
317 #[layout] layout: Layout,
318
319 #[live] quote_layout: Layout,
320 #[live] quote_walk: Walk,
321 #[live] code_layout: Layout,
322 #[live] code_walk: Walk,
323 #[live] sep_walk: Walk,
324 #[live] list_item_layout: Layout,
325 #[live] list_item_walk: Walk,
326 #[live] pub inline_code_padding: Padding,
327 #[live] pub inline_code_margin: Margin,
328 #[live(Margin{top:0.5,bottom:0.5,left:0.0,right:0.0})] pub heading_margin: Margin,
329 #[live(Margin{top:0.5,bottom:0.5,left:0.0,right:0.0})] pub paragraph_margin: Margin,
330
331
332
333 #[redraw] #[rust] area:Area,
334 #[rust] draw_state: DrawStateWrap<DrawState>,
335 #[rust(Some(Default::default()))] items: Option<ComponentMap<LiveId,(WidgetRef, LiveId)>>,
336 #[rust] templates: ComponentMap<LiveId, LivePtr>,
337}
338
339impl LiveHook for TextFlow{
340 fn apply_value_instance(&mut self, cx: &mut Cx, apply: &mut Apply, index: usize, nodes: &[LiveNode]) -> usize {
342 let id = nodes[index].id;
343 match apply.from {
344 ApplyFrom::NewFromDoc {file_id} | ApplyFrom::UpdateFromDoc {file_id,..} => {
345 if nodes[index].origin.has_prop_type(LivePropType::Instance) {
346 let live_ptr = cx.live_registry.borrow().file_id_index_to_live_ptr(file_id, index);
347 self.templates.insert(id, live_ptr);
348 for (node, templ_id) in self.items.as_mut().unwrap().values_mut() {
350 if *templ_id == id {
351 node.apply(cx, apply, index, nodes);
352 }
353 }
354 }
355 else {
356 cx.apply_error_no_matching_field(live_error_origin!(), index, nodes);
357 }
358 }
359 _ => ()
360 }
361 nodes.skip_node(index)
362 }
363}
364
365#[derive(Default)]
366pub struct RectAreasTracker{
367 pub areas: SmallVec<[Area;4]>,
368 pos: usize,
369 stack: SmallVec<[usize;2]>,
370}
371
372impl RectAreasTracker{
373 fn clear_stack(&mut self){
374 self.pos = 0;
375 self.areas.clear();
376 self.stack.clear();
377 }
378
379 pub fn push_tracker(&mut self){
380 self.stack.push(self.pos);
381 }
382
383 pub fn pop_tracker(&mut self)->(usize, usize){
385 return (self.stack.pop().unwrap(), self.pos)
386 }
387
388 pub fn track_rect(&mut self, cx:&mut Cx2d, rect:Rect){
389 if self.stack.len() >0{
390 if self.pos >= self.areas.len(){
391 self.areas.push(Area::Empty);
392 }
393 cx.add_aligned_rect_area(&mut self.areas[self.pos], rect);
394 self.pos += 1;
395 }
396 }
397}
398
399#[derive(Clone)]
400enum DrawState {
401 Begin,
402 Drawing,
403}
404
405impl Widget for TextFlow {
406 fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk:Walk)->DrawStep{
407 if self.draw_state.begin(cx, DrawState::Begin) {
409 self.begin(cx, walk);
410 return DrawStep::make_step()
411 }
412 if let Some(_) = self.draw_state.get() {
413 self.end(cx);
414 self.draw_state.end();
415 }
416 DrawStep::done()
417 }
418 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
429 for (id,(entry,_)) in self.items.as_mut().unwrap().iter_mut(){
430
431 scope.with_id(*id, |scope| {
432 entry.handle_event(cx, event, scope);
433 });
434 }
435 }
436}
437
438impl TextFlow{
439 pub fn begin(&mut self, cx: &mut Cx2d, walk:Walk){
440 cx.begin_turtle(walk, self.layout);
444 self.draw_state.set(DrawState::Drawing);
445 self.draw_block.append_to_draw_call(cx);
446 self.clear_stacks();
447 }
448
449 fn clear_stacks(&mut self){
450 self.item_counter = 0;
451 self.areas_tracker.clear_stack();
452 self.bold.clear();
453 self.italic.clear();
454 self.fixed.clear();
455 self.underline.clear();
456 self.strikethrough.clear();
457 self.inline_code.clear();
458 self.font_sizes.clear();
460 self.font_colors.clear();
461 self.area_stack.clear();
462 self.combine_spaces.clear();
464 self.ignore_newlines.clear();
465 self.first_thing_on_a_line = true;
466 }
467
468
469 pub fn push_size_rel_scale(&mut self, scale: f64){
470 self.font_sizes.push(
471 self.font_sizes.last().unwrap_or(&self.font_size) * (scale as f32)
472 );
473 }
474
475 pub fn push_size_abs_scale(&mut self, scale: f64){
476 self.font_sizes.push(
477 self.font_size * (scale as f32)
478 );
479 }
480
481 pub fn end(&mut self, cx: &mut Cx2d){
482 cx.end_turtle_with_area(&mut self.area);
484 self.items.as_mut().unwrap().retain_visible();
485 }
486
487 pub fn begin_code(&mut self, cx:&mut Cx2d){
488 self.draw_block.block_type = FlowBlockType::Code;
490 self.draw_block.begin(cx, self.code_walk, self.code_layout);
491 self.area_stack.push(self.draw_block.draw_vars.area);
492 self.first_thing_on_a_line = true;
493 }
494
495 pub fn end_code(&mut self, cx:&mut Cx2d){
496 self.draw_block.draw_vars.area = self.area_stack.pop().unwrap();
498 self.draw_block.end(cx);
499 }
500
501 pub fn begin_list_item(&mut self, cx:&mut Cx2d, dot:&str, pad:f64){
502 let fs = self.font_sizes.last().unwrap_or(&self.font_size);
504 self.draw_normal.text_style.font_size = *fs as _;
505 let fc = self.font_colors.last().unwrap_or(&self.font_color);
506 self.draw_normal.color = *fc;
507 let pad = self.draw_normal.text_style.font_size as f64 * pad;
508 cx.begin_turtle(self.list_item_walk, Layout{
511 padding:Padding{
512 left: self.list_item_layout.padding.left + pad,
513 ..self.list_item_layout.padding
514 },
515 ..self.list_item_layout
516 });
517 let marker_len = dot.chars().count();
521 let pos = match marker_len {
522 1 => {
523 cx.turtle().pos() - dvec2(pad, 0.0)
524 },
525 _ => {
526 let pad = pad + self.draw_normal.text_style.font_size as f64 * (marker_len - 2) as f64;
529 cx.turtle().pos() - dvec2(pad, 0.0)
530 }
531 };
532 self.draw_normal.draw_abs(cx, pos, dot);
533
534 self.area_stack.push(self.draw_block.draw_vars.area);
535 }
536
537 pub fn end_list_item(&mut self, cx:&mut Cx2d){
538 cx.end_turtle();
539 self.first_thing_on_a_line = true;
540 }
541
542 pub fn new_line_collapsed(&mut self, cx:&mut Cx2d){
543 cx.turtle_new_line();
545 self.first_thing_on_a_line = true;
546 }
547
548 pub fn new_line_collapsed_with_spacing(&mut self, cx:&mut Cx2d, spacing: f64){
549 cx.turtle_new_line_with_spacing(spacing);
551 self.first_thing_on_a_line = true;
552 }
553
554 pub fn sep(&mut self, cx:&mut Cx2d){
555 self.draw_block.block_type = FlowBlockType::Sep;
556 self.draw_block.draw_walk(cx, self.sep_walk);
557 }
558
559 pub fn begin_quote(&mut self, cx:&mut Cx2d){
560 self.draw_block.block_type = FlowBlockType::Quote;
562 self.draw_block.begin(cx, self.quote_walk, self.quote_layout);
563 self.area_stack.push(self.draw_block.draw_vars.area);
564 }
565
566 pub fn end_quote(&mut self, cx:&mut Cx2d){
567 self.draw_block.draw_vars.area = self.area_stack.pop().unwrap();
568 self.draw_block.end(cx);
569 }
570 pub fn draw_item_counted(&mut self, cx: &mut Cx2d, template: LiveId,)->LiveId{
583 let entry_id = self.new_counted_id();
584 self.item_with(cx, entry_id, template, |cx, item, tf|{
585 item.draw_all(cx, &mut Scope::with_data(tf));
586 });
587 entry_id
588 }
589
590 pub fn new_counted_id(&mut self)->LiveId{
591 self.item_counter += 1;
592 LiveId(self.item_counter)
593 }
594
595 pub fn draw_item(&mut self, cx: &mut Cx2d, entry_id: LiveId, template: LiveId){
596 self.item_with(cx, entry_id, template, |cx, item, tf|{
597 item.draw_all(cx, &mut Scope::with_data(tf));
598 });
599 }
600
601 pub fn draw_item_counted_ref(&mut self, cx: &mut Cx2d, template: LiveId,)->WidgetRef{
602 let entry_id = self.new_counted_id();
603 self.item_with(cx, entry_id, template, |cx, item, tf|{
604 item.draw_all(cx, &mut Scope::with_data(tf));
605 item.clone()
606 })
607 }
608
609 pub fn draw_item_ref(&mut self, cx: &mut Cx2d, entry_id: LiveId, template: LiveId)->WidgetRef{
610 self.item_with(cx, entry_id, template, |cx, item, tf|{
611 item.draw_all(cx, &mut Scope::with_data(tf));
612 item.clone()
613 })
614 }
615
616 pub fn item_with<F,R:Default>(&mut self, cx: &mut Cx2d, entry_id:LiveId, template: LiveId, f:F)->R
617 where F:FnOnce(&mut Cx2d, &WidgetRef, &mut TextFlow)->R{
618 let mut items = self.items.take().unwrap();
619 let r = if let Some(ptr) = self.templates.get(&template) {
620 let entry = items.get_or_insert(cx, entry_id, | cx | {
621 (WidgetRef::new_from_ptr(cx, Some(*ptr)), template)
622 });
623 f(cx, &entry.0, self)
624 }else{
625 R::default()
626 };
627 self.items = Some(items);
628 r
629 }
630
631
632 pub fn item(&mut self, cx: &mut Cx, entry_id: LiveId, template: LiveId) -> WidgetRef {
633 if let Some(ptr) = self.templates.get(&template) {
634 let entry = self.items.as_mut().unwrap().get_or_insert(cx, entry_id, | cx | {
635 (WidgetRef::new_from_ptr(cx, Some(*ptr)), template)
636 });
637 return entry.0.clone()
638 }
639 WidgetRef::empty()
640 }
641
642
643 pub fn item_counted(&mut self, cx: &mut Cx, template: LiveId) -> WidgetRef {
644 let entry_id = self.new_counted_id();
645 if let Some(ptr) = self.templates.get(&template) {
646 let entry = self.items.as_mut().unwrap().get_or_insert(cx, entry_id, | cx | {
647 (WidgetRef::new_from_ptr(cx, Some(*ptr)), template)
648 });
649 return entry.0.clone()
650 }
651 WidgetRef::empty()
652 }
653
654 pub fn existing_item(&mut self, entry_id: LiveId) -> WidgetRef {
655 if let Some(item) = self.items.as_mut().unwrap().get(&entry_id){
656 item.0.clone()
657 }
658 else{
659 WidgetRef::empty()
660 }
661 }
662
663 pub fn clear_items(&mut self){
664 self.items.as_mut().unwrap().clear();
665 }
666
667
668 pub fn item_with_scope(&mut self, cx: &mut Cx, scope: &mut Scope, entry_id: LiveId, template: LiveId) -> Option<WidgetRef> {
669 if let Some(ptr) = self.templates.get(&template) {
670 let entry = self.items.as_mut().unwrap().get_or_insert(cx, entry_id, | cx | {
671 (WidgetRef::new_from_ptr_with_scope(cx, Some(*ptr), scope), template)
672 });
673 return Some(entry.0.clone())
674 }
675 None
676 }
677
678 pub fn draw_text(&mut self, cx:&mut Cx2d, text:&str){
679 if let Some(DrawState::Drawing) = self.draw_state.get(){
680
681 if (text == " " || text == "") && self.first_thing_on_a_line{
682 return
683 }
684 let text = if self.first_thing_on_a_line{
685 text.trim_start().trim_end_matches("\n")
686 }
687 else{
688 text.trim_end_matches("\n")
689 };
690
691 let dt = if self.fixed.value() > 0{
692 &mut self.draw_fixed
693 }
694 else if self.bold.value() > 0{
695 if self.italic.value() > 0{
696 &mut self.draw_bold_italic
697 }
698 else{
699 &mut self.draw_bold
700 }
701 }
702 else if self.italic.value() > 0{
703 &mut self.draw_italic
704 }
705 else{
706 &mut self.draw_normal
707 };
708 let font_size = self.font_sizes.last().unwrap_or(&self.font_size);
709 let font_color = self.font_colors.last().unwrap_or(&self.font_color);
710 dt.text_style.font_size = *font_size as _;
712 dt.color = *font_color;
713 let areas_tracker = &mut self.areas_tracker;
719 if self.inline_code.value() > 0{
720 let db = &mut self.draw_block;
721 db.block_type = FlowBlockType::InlineCode;
722 if !self.first_thing_on_a_line{
723 let rect = TextFlow::walk_margin(cx, self.inline_code_margin.left);
724 areas_tracker.track_rect(cx, rect);
725 }
726 dt.draw_walk_resumable_with(cx, text, |cx, mut rect|{
727 rect.pos -= self.inline_code_padding.left_top();
728 rect.size += self.inline_code_padding.size();
729 db.draw_abs(cx, rect);
730 areas_tracker.track_rect(cx, rect);
731 });
732 let rect = TextFlow::walk_margin(cx, self.inline_code_margin.right);
733 areas_tracker.track_rect(cx, rect);
734 }
735 else if self.strikethrough.value() > 0{
736 let db = &mut self.draw_block;
737 db.line_color = *font_color;
738 db.block_type = FlowBlockType::Strikethrough;
739 dt.draw_walk_resumable_with(cx, text, |cx, rect|{
740 db.draw_abs(cx, rect);
741 areas_tracker.track_rect(cx, rect);
742 });
743 }
744 else if self.underline.value() > 0{
745 let db = &mut self.draw_block;
746 db.line_color = *font_color;
747 db.block_type = FlowBlockType::Underline;
748 dt.draw_walk_resumable_with(cx, text, |cx, rect|{
749 db.draw_abs(cx, rect);
750 areas_tracker.track_rect(cx, rect);
751 });
752 }
753 else{
754 dt.draw_walk_resumable_with(cx, text, |cx, rect|{
755 areas_tracker.track_rect(cx, rect);
756 });
757 }
758 }
759 self.first_thing_on_a_line = false;
760
761 }
762
763 pub fn walk_margin(cx:&mut Cx2d, margin:f64)->Rect{
764 cx.walk_turtle(Walk{
765 width: Size::Fixed(margin),
766 height: Size::Fixed(0.0),
767 ..Default::default()
768 })
769 }
770
771 pub fn draw_link(&mut self, cx:&mut Cx2d, template:LiveId, data:impl ActionTrait + PartialEq, label:&str){
772 let entry_id = self.new_counted_id();
773 self.item_with(cx, entry_id, template, |cx, item, tf|{
774 item.set_text(cx, label);
775 item.set_action_data(data);
776 item.draw_all(cx, &mut Scope::with_data(tf));
777 })
778 }
779}
780
781#[derive(Debug, Clone, DefaultNone)]
782pub enum TextFlowLinkAction {
783 Clicked {
784 key_modifiers: KeyModifiers,
785 },
786 None,
787}
788
789#[derive(Live, Widget)]
790struct TextFlowLink {
791 #[animator] animator: Animator,
792
793 #[redraw] #[area] area: Area,
796
797 #[live(true)] click_on_down: bool,
800 #[rust] drawn_areas: SmallVec<[Area; 2]>,
801 #[live(true)] grab_key_focus: bool,
802 #[live] margin: Margin,
803 #[live] hovered: f32,
804 #[live] down: f32,
805
806 #[live] color: Option<Vec4>,
808 #[live] color_hover: Option<Vec4>,
810 #[live] color_down: Option<Vec4>,
812
813 #[live] pub text: ArcStringMut,
814
815 #[action_data] #[rust] action_data: WidgetActionData,
816}
817
818impl LiveHook for TextFlowLink {}
819
820impl Widget for TextFlowLink {
821 fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
822 if self.animator_handle_event(cx, event).must_redraw() {
823 if let Some(tf) = scope.data.get_mut::<TextFlow>() {
824 tf.redraw(cx);
825 } else {
826 self.drawn_areas.iter().for_each(|area| area.redraw(cx));
827 }
828 }
829
830 for area in self.drawn_areas.clone().into_iter() {
831 match event.hits(cx, area) {
832 Hit::FingerDown(fe) if fe.is_primary_hit() => {
833 if self.grab_key_focus {
834 cx.set_key_focus(self.area());
835 }
836 self.animator_play(cx, id!(hover.down));
837 if self.click_on_down{
838 cx.widget_action_with_data(
839 &self.action_data,
840 self.widget_uid(),
841 &scope.path,
842 TextFlowLinkAction::Clicked {
843 key_modifiers: fe.modifiers,
844 },
845 );
846 }
847 }
848 Hit::FingerHoverIn(_) => {
849 cx.set_cursor(MouseCursor::Hand);
850 self.animator_play(cx, id!(hover.on));
851 }
852 Hit::FingerHoverOut(_) => {
853 self.animator_play(cx, id!(hover.off));
854 }
855 Hit::FingerUp(fe) if fe.is_primary_hit() => {
856 if fe.is_over {
857 if !self.click_on_down{
858 cx.widget_action_with_data(
859 &self.action_data,
860 self.widget_uid(),
861 &scope.path,
862 TextFlowLinkAction::Clicked {
863 key_modifiers: fe.modifiers,
864 },
865 );
866 }
867
868 if fe.device.has_hovers() {
869 self.animator_play(cx, id!(hover.on));
870 } else {
871 self.animator_play(cx, id!(hover.off));
872 }
873 } else {
874 self.animator_play(cx, id!(hover.off));
875 }
876 }
877 _ => (),
878 }
879 }
880 }
881
882 fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, _walk: Walk) -> DrawStep {
883 let Some(tf) = scope.data.get_mut::<TextFlow>() else {
884 return DrawStep::done();
885 };
886
887 tf.underline.push();
889 tf.areas_tracker.push_tracker();
890 let mut pushed_color = false;
891 if self.hovered > 0.0 {
892 if let Some(color) = self.color_hover {
893 tf.font_colors.push(color);
894 pushed_color = true;
895 }
896 } else if self.down > 0.0 {
897 if let Some(color) = self.color_down {
898 tf.font_colors.push(color);
899 pushed_color = true;
900 }
901 } else {
902 if let Some(color) = self.color {
903 tf.font_colors.push(color);
904 pushed_color = true;
905 }
906 }
907 TextFlow::walk_margin(cx, self.margin.left);
908 tf.draw_text(cx, self.text.as_ref());
909 TextFlow::walk_margin(cx, self.margin.right);
910
911 if pushed_color {
912 tf.font_colors.pop();
913 }
914 tf.underline.pop();
915
916 let (start, end) = tf.areas_tracker.pop_tracker();
917
918 if self.drawn_areas.len() == end-start{
919 for i in 0..end-start{
920 self.drawn_areas[i] = cx.update_area_refs( self.drawn_areas[i],
921 tf.areas_tracker.areas[i+start]);
922 }
923 }
924 else{
925 self.drawn_areas = SmallVec::from(
926 &tf.areas_tracker.areas[start..end]
927 );
928 }
929
930 DrawStep::done()
931 }
932
933 fn text(&self) -> String {
934 self.text.as_ref().to_string()
935 }
936
937 fn set_text(&mut self, cx:&mut Cx, v: &str) {
938 self.text.as_mut_empty().push_str(v);
939 self.redraw(cx);
940 }
941}
942