makepad_widgets/
text_flow.rs

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        // ok so we can use one drawtext
16        // change to italic, change bold (SDF), strikethrough
17        font_size: 8,
18        // font_color: (THEME_COLOR_TEXT),
19        flow: RightWrap,
20    }
21    
22    pub TextFlowLinkBase = {{TextFlowLink}} {
23        link = {
24            draw_text = {
25                // other blue hyperlink colors: #1a0dab, // #0969da  // #0c50d1
26                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// this widget has a retained and an immediate mode api
283#[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    /// The default font size used for all text if not otherwise specified.
293    #[live] pub font_size: f32,
294    /// The default font color used for all text if not otherwise specified.
295    #[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 font: SmallVec<[Font;2]>,
302    //#[rust] pub top_drop: SmallVec<[f64;4]>,
303    #[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    // hook the apply flow to collect our templates and apply to instanced childnodes
341    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                    // lets apply this thing over all our childnodes with that template
349                    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    // this returns the range in the area vec    
384    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        //self.draw_text.draw_walk(cx, walk.with_add_padding(self.padding), self.align, self.text.as_ref());
408        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    /*
419    fn text(&self)->String{
420        "".into()
421        //self.text.as_ref().to_string()
422    }
423    
424    fn set_text(&mut self, _v:&str){
425        //self.text.as_mut_empty().push_str(v);
426    }*/
427    
428    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        // lets begin a turtle 
441        // well know if we have a known width to wrap
442        // if we dont we just dont wrap
443        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.clear();
459        self.font_sizes.clear();
460        self.font_colors.clear();
461        self.area_stack.clear();
462        //self.top_drop.clear();
463        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        // lets end the turtle with how far we walked
483        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        // alright we are going to push a block with a layout and a walk
489        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        // check if we need to use a widget
497        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        // alright we are going to push a block with a layout and a walk
503        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        // we should add the line spacing of the turlte
509        
510        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        // lets draw the 'marker' at -x 
518        // lets get the turtle position and abs draw 
519        
520        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                // This calculation takes into account when numbers have more than one digit
527                // making sure they are properly aligned.
528                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        // if all we emitted is a single whitespace
544        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        // if all we emitted is a single whitespace
550        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        // alright we are going to push a block with a layout and a walk
561        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    /*
571    pub fn counted_item(&mut self, cx: &mut Cx, template: LiveId) -> Option<WidgetRef> {
572        self.item_counter += 1;
573        if let Some(ptr) = self.templates.get(&template) {
574            let entry = self.items.as_mut().unwrap().get_or_insert(cx, (LiveId(self.item_counter), template), | cx | {
575                WidgetRef::new_from_ptr(cx, Some(*ptr))
576            });
577            return Some(entry.clone())
578        }
579        None 
580    }*/
581    
582    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.top_drop = *self.top_drop.last().unwrap_or(&1.2);
711            dt.text_style.font_size = *font_size as _;
712            dt.color = *font_color;
713            //dt.ignore_newlines = *self.ignore_newlines.last().unwrap_or(&true);
714            //dt.combine_spaces = *self.combine_spaces.last().unwrap_or(&true);
715            //if let Some(font) = self.font
716            // the turtle is at pos X so we walk it.
717           
718            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    // TODO: this is unusued; just here to invalidly satisfy the area provider.
794    //       I'm not sure how to implement `fn area()` given that it has multiple area rects.
795    #[redraw] #[area] area: Area,
796    
797    // TODO: remove these if they're unneeded
798    //#[walk] walk: Walk,
799    #[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    /// The default font color for the link when not hovered on or down.
807    #[live] color: Option<Vec4>,
808    /// The font color used when the link is hovered on.
809    #[live] color_hover: Option<Vec4>,
810    /// The font color used when the link is down.
811    #[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        // Here: the text flow has already began drawing, so we just need to draw the text.
888        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/*
943#[derive(Clone)]
944pub struct HtmlId(pub HtmlString);
945impl HtmlId{
946    pub fn new(rc:&Rc<String>, start:usize, end:usize)->Self{
947        Self(HtmlString(rc.clone(), start, end))
948    }
949    pub fn as_id(&self)->LiveId{
950        LiveId::from_str(&self.0.0[self.0.1..self.0.2])
951    }
952}
953
954impl fmt::Debug for HtmlId{
955    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
956        write!(f, "{}", self.0.as_str())
957    }
958}
959*/
960
961// HTML Dom tree-flat vector