From d6a1f70823c5913faaa1426771da0c0b7cbfab1c Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sun, 22 Oct 2023 11:00:50 -0700 Subject: [PATCH] re-implement MultiHyperlink with manual layout for now --- ui/widgets/multihyperlink.go | 136 +++++++++++++++++++++++++++++++++-- 1 file changed, 131 insertions(+), 5 deletions(-) diff --git a/ui/widgets/multihyperlink.go b/ui/widgets/multihyperlink.go index 2bbd4f6..2770316 100644 --- a/ui/widgets/multihyperlink.go +++ b/ui/widgets/multihyperlink.go @@ -2,6 +2,8 @@ package widgets import ( "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -11,7 +13,14 @@ type MultiHyperlink struct { Segments []MultiHyperlinkSegment OnTapped func(string) - provider *widget.RichText + minSegWidthCached float32 + minHeightCached float32 + separatorWCached float32 + + // TODO: Once https://github.com/fyne-io/fyne/issues/4336 is resolved, + // we can switch to the much cleaner RichText implementation + //provider *widget.RichText + content *fyne.Container } type MultiHyperlinkSegment struct { @@ -21,13 +30,119 @@ type MultiHyperlinkSegment struct { func NewMultiHyperlink() *MultiHyperlink { c := &MultiHyperlink{ - provider: widget.NewRichText(), + //provider: widget.NewRichText(), + content: container.NewWithoutLayout(), } c.ExtendBaseWidget(c) - c.provider.Wrapping = fyne.TextTruncate + //c.provider.Wrapping = fyne.TextTruncate return c } +func (c *MultiHyperlink) getMinSegWidth() float32 { + if c.minSegWidthCached == 0 { + c.minSegWidthCached = fyne.MeasureText(", W", theme.TextSize(), fyne.TextStyle{}).Width + } + return c.minSegWidthCached +} + +func (c *MultiHyperlink) getSeparatorWidth() float32 { + if c.separatorWCached == 0 { + c.separatorWCached = fyne.MeasureText(",", theme.TextSize(), fyne.TextStyle{}).Width + } + return c.separatorWCached +} + +func (c *MultiHyperlink) layoutObjects() { + if len(c.Segments) == 0 { + c.content.RemoveAll() + return + } + x := float32(0) + width := c.Size().Width + l := len(c.content.Objects) + + var i int // at end of loop should be index of last seg that was laid out for display + var seg MultiHyperlinkSegment + for i, seg = range c.Segments { + if i > 0 { + // check if we have enough room to show another segment + if x+c.getMinSegWidth() > width { + i -= 1 // we are not showing this segment + break + } + } + + appendingSegments := 2*i >= l + var obj fyne.CanvasObject + if !appendingSegments { + obj = c.content.Objects[2*i] + } else if i > 0 { + c.content.Objects = append(c.content.Objects, c.newSeparatorLabel()) + } + if seg.LinkID == "" { + obj = c.updateOrReplaceLabel(obj, seg.Text) + } else { + obj = c.updateOrReplaceHyperlink(obj, seg.Text) + } + if appendingSegments { + c.content.Objects = append(c.content.Objects, obj) + } else { + c.content.Objects[2*i] = obj + } + + if i > 0 { + // move and resize separator + obj = c.content.Objects[2*i-1] + ms := obj.MinSize() + obj.Resize(ms) + obj.Move(fyne.NewPos(x-ms.Width+c.getSeparatorWidth()+1, 0)) // this is really ugly + x -= theme.Padding() * 2 + } + // move and resize text object + textW := fyne.MeasureText(seg.Text, theme.TextSize(), fyne.TextStyle{}).Width + theme.Padding()*2 + theme.InnerPadding() + obj = c.content.Objects[2*i] + ms := obj.MinSize() + w := fyne.Min(width-x, textW) + obj.Resize(fyne.NewSize(w, ms.Height)) + obj.Move(fyne.NewPos(x, 0)) + x += w + } + + i += 1 + c.content.Objects = c.content.Objects[:2*i-1] +} + +func (c *MultiHyperlink) updateOrReplaceLabel(obj fyne.CanvasObject, text string) fyne.CanvasObject { + if obj != nil { + if label, ok := obj.(*widget.Label); ok { + label.Text = text + return label + } + } + l := widget.NewLabel(text) + l.Wrapping = fyne.TextTruncate + return l +} + +func (c *MultiHyperlink) updateOrReplaceHyperlink(obj fyne.CanvasObject, text string) fyne.CanvasObject { + if obj != nil { + if link, ok := obj.(*widget.Hyperlink); ok { + link.Text = text + return link + } + } + l := widget.NewHyperlink(text, nil) + l.Wrapping = fyne.TextTruncate + return l +} + +func (c *MultiHyperlink) newSeparatorLabel() *widget.Label { + return widget.NewLabel(", ") +} + +/*** + * RichText implementation + func (c *MultiHyperlink) syncSegments() { l := len(c.provider.Segments) for i, seg := range c.Segments { @@ -82,15 +197,26 @@ func (c *MultiHyperlink) updateOrReplaceHyperlinkSegment(seg widget.RichTextSegm return &widget.HyperlinkSegment{Text: text} // TODO: OnTapped } +*/ + func (c *MultiHyperlink) onSegmentTapped(linkID string) { // TODO } +func (c *MultiHyperlink) MinSize() fyne.Size { + if c.minHeightCached == 0 { + c.minHeightCached = widget.NewLabel("").MinSize().Height + } + return fyne.NewSize(1, c.minHeightCached) +} + func (c *MultiHyperlink) Refresh() { - c.syncSegments() + //c.syncSegments() + c.layoutObjects() c.BaseWidget.Refresh() } func (c *MultiHyperlink) CreateRenderer() fyne.WidgetRenderer { - return widget.NewSimpleRenderer(c.provider) + return widget.NewSimpleRenderer(c.content) + //return widget.NewSimpleRenderer(c.provider) }