From 0c8914060e58654e26e5dc939cc33b30e886c973 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Wed, 28 Feb 2024 08:32:36 -0800 Subject: [PATCH] add CaptionedImage widget --- ui/widgets/captionedimage.go | 113 +++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 ui/widgets/captionedimage.go diff --git a/ui/widgets/captionedimage.go b/ui/widgets/captionedimage.go new file mode 100644 index 0000000..0c9f4e3 --- /dev/null +++ b/ui/widgets/captionedimage.go @@ -0,0 +1,113 @@ +package widgets + +import ( + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/widget" +) + +type Aspectable interface { + fyne.CanvasObject + Aspect() float32 +} + +type CaptionedImage struct { + widget.BaseWidget + + Content Aspectable + Caption fyne.CanvasObject +} + +var _ fyne.Widget = (*CaptionedImage)(nil) + +func NewCaptionedImage(content Aspectable, caption fyne.CanvasObject) *CaptionedImage { + c := &CaptionedImage{Content: content, Caption: caption} + c.ExtendBaseWidget(c) + return c +} + +func (c *CaptionedImage) CreateRenderer() fyne.WidgetRenderer { + return &captionedImageRenderer{ + ci: c, + } +} + +type captionedImageRenderer struct { + ci *CaptionedImage +} + +var _ fyne.WidgetRenderer = (*captionedImageRenderer)(nil) + +func (*captionedImageRenderer) Destroy() { + // intentionally blank +} + +func (c *captionedImageRenderer) Layout(s fyne.Size) { + content := c.ci.Content + aspect := content.Aspect() + caption := c.ci.Caption + var captionHeight float32 + if caption != nil { + captionHeight = caption.MinSize().Height + } + + // max height that could be allocated to Content + contentMaxedHeight := s.Height + if caption != nil { + contentMaxedHeight -= captionHeight + } + + // aspect ratio of Content if it were maxed out + maxedAspect := s.Width / contentMaxedHeight + if maxedAspect > aspect { + // Content will use full height, but not full width + contentW := contentMaxedHeight * aspect + content.Resize(fyne.NewSize(contentW, contentMaxedHeight)) + contentX := (s.Width - contentW) / 2 + content.Move(fyne.NewPos(contentX, 0)) + if caption != nil { + caption.Resize(fyne.NewSize(s.Width, captionHeight)) + caption.Move(fyne.NewPos(0, contentMaxedHeight)) + } + return + } + + // Content will not use full height + // Positioning of Content and Caption will be adjusted to center + contentH := s.Width / aspect + yStart := (s.Height - contentH - captionHeight) / 2 + content.Resize(fyne.NewSize(s.Width, contentH)) + content.Move(fyne.NewPos(0, yStart)) + if caption != nil { + caption.Resize(fyne.NewSize(s.Width, captionHeight)) + caption.Move(fyne.NewPos(0, yStart+contentH)) + } +} + +func (c *captionedImageRenderer) MinSize() fyne.Size { + objSize := c.ci.Content.MinSize() + if c.ci.Caption == nil { + return objSize + } + cptSize := c.ci.Caption.MinSize() + return fyne.NewSize( + fyne.Max(objSize.Width, cptSize.Width), + objSize.Height+cptSize.Height, + ) +} + +func (c *captionedImageRenderer) Objects() []fyne.CanvasObject { + if c.ci.Caption != nil { + return []fyne.CanvasObject{c.ci.Content, c.ci.Caption} + } + return []fyne.CanvasObject{c.ci.Content} +} + +func (c *captionedImageRenderer) Refresh() { + c.ci.Content.Refresh() + if cap := c.ci.Caption; cap != nil { + cap.Refresh() + } + // aspect may have changed on Content, + // so we need to re-layout + c.Layout(c.ci.Size()) +}