From 40b9322079b5f877e2b7d1e4ba0b4a20f9b8240f Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 21 Oct 2023 09:25:30 -0700 Subject: [PATCH] Fix #261: Show next-up sort icon when hovering over sortable list header --- ui/theme/themedrectangle.go | 12 +++++- ui/widgets/listheader.go | 76 +++++++++++++++++++++++++++---------- 2 files changed, 66 insertions(+), 22 deletions(-) diff --git a/ui/theme/themedrectangle.go b/ui/theme/themedrectangle.go index fcc0c34..843f592 100644 --- a/ui/theme/themedrectangle.go +++ b/ui/theme/themedrectangle.go @@ -1,17 +1,22 @@ package theme import ( + "image/color" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/widget" ) +const translucentAlpha = 155 + type ThemedRectangle struct { widget.BaseWidget rect *canvas.Rectangle ColorName fyne.ThemeColorName + Translucent bool BorderWidth float32 BorderColorName fyne.ThemeColorName } @@ -29,7 +34,12 @@ func NewThemedRectangle(colorName fyne.ThemeColorName) *ThemedRectangle { func (t *ThemedRectangle) Refresh() { settings := fyne.CurrentApp().Settings() theme := settings.Theme() - t.rect.FillColor = theme.Color(t.ColorName, settings.ThemeVariant()) + fc := theme.Color(t.ColorName, settings.ThemeVariant()) + if t.Translucent { + r, g, b, _ := fc.RGBA() + fc = color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: translucentAlpha} + } + t.rect.FillColor = fc t.rect.StrokeWidth = t.BorderWidth t.rect.StrokeColor = theme.Color(t.BorderColorName, settings.ThemeVariant()) t.BaseWidget.Refresh() diff --git a/ui/widgets/listheader.go b/ui/widgets/listheader.go index bbd7551..3ad8911 100644 --- a/ui/widgets/listheader.go +++ b/ui/widgets/listheader.go @@ -9,6 +9,7 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" + "fyne.io/fyne/v2/driver/desktop" "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" @@ -101,6 +102,7 @@ func (l *ListHeader) SetSorting(sort ListHeaderSort) { } l.sort = sort for i, c := range l.columnsContainer.Objects { + c.(*colHeader).hovered = false // reset hovered status if i == sort.ColNumber { c.(*colHeader).Sort = sort.Type } else { @@ -166,9 +168,10 @@ type colHeader struct { sortDisabled *bool columnCfg ListColumn + hovered bool label *widget.RichText - sortIcon *widget.Icon + sortIconContainer *fyne.Container sortIconNegSpacer fyne.CanvasObject container *fyne.Container } @@ -182,7 +185,9 @@ func newColHeader(columnCfg ListColumn, sortDisabled *bool) *colHeader { TextStyle: fyne.TextStyle{Bold: true}, Alignment: columnCfg.Alignment, } - c.sortIcon = widget.NewIcon(theme.MenuDropDownIcon()) + r := myTheme.NewThemedRectangle(myTheme.ColorNameListHeader) + r.Translucent = true + c.sortIconContainer = container.NewMax(widget.NewIcon(theme.MenuDropDownIcon()), r) // hack to remove extra icon space // should be hidden whenever sortIcon is hidden c.sortIconNegSpacer = util.NewHSpace(0) @@ -190,20 +195,30 @@ func newColHeader(columnCfg ListColumn, sortDisabled *bool) *colHeader { return c } +var _ desktop.Hoverable = (*colHeader)(nil) + +func (c *colHeader) MouseIn(*desktop.MouseEvent) { + if !*c.sortDisabled { + c.hovered = true + c.Refresh() + } +} + +func (c *colHeader) MouseOut() { + c.hovered = false + c.Refresh() +} + +func (c *colHeader) MouseMoved(*desktop.MouseEvent) { + // intentionally left blank +} + func (c *colHeader) Tapped(*fyne.PointEvent) { if *c.sortDisabled { return } - switch c.Sort { - case SortNone: - c.Sort = SortAscending - case SortAscending: - c.Sort = SortDescending - case SortDescending: - c.Sort = SortNone - default: - log.Println("notReached colHeader.Tapped") - } + c.hovered = false // stop showing the next-up sort icon + c.Sort = nextSortType(c.Sort) c.Refresh() if c.OnSortChanged != nil { c.OnSortChanged(c.Sort) @@ -217,17 +232,22 @@ func (c *colHeader) TappedSecondary(e *fyne.PointEvent) { } func (c *colHeader) Refresh() { - if c.Sort == SortDescending { - c.sortIcon.Resource = theme.MenuDropDownIcon() - } else { - c.sortIcon.Resource = theme.MenuDropUpIcon() + sort := c.Sort + if c.hovered { + sort = nextSortType(sort) } + if sort == SortDescending { + c.sortIconContainer.Objects[0].(*widget.Icon).Resource = theme.MenuDropDownIcon() + } else { + c.sortIconContainer.Objects[0].(*widget.Icon).Resource = theme.MenuDropUpIcon() + } + c.sortIconContainer.Objects[1].(*myTheme.ThemedRectangle).Hidden = !c.hovered - if c.Sort > 0 && c.sortIcon.Hidden { - c.sortIcon.Show() + if sort > 0 && c.sortIconContainer.Hidden { + c.sortIconContainer.Hidden = false c.container.Add(c.sortIconNegSpacer) - } else if (c.Sort == SortNone || *c.sortDisabled) && !c.sortIcon.Hidden { - c.sortIcon.Hide() + } else if (sort == SortNone || *c.sortDisabled) && !c.sortIconContainer.Hidden { + c.sortIconContainer.Hidden = true c.container.Remove(c.sortIconNegSpacer) } @@ -241,7 +261,7 @@ func (c *colHeader) CreateRenderer() fyne.WidgetRenderer { c.container.Add(layout.NewSpacer()) } c.container.Add(c.label) - c.container.Add(c.sortIcon) + c.container.Add(c.sortIconContainer) c.container.Add(c.sortIconNegSpacer) if c.columnCfg.Alignment == fyne.TextAlignCenter { c.container.Add(layout.NewSpacer()) @@ -249,3 +269,17 @@ func (c *colHeader) CreateRenderer() fyne.WidgetRenderer { } return widget.NewSimpleRenderer(c.container) } + +func nextSortType(in SortType) SortType { + switch in { + case SortNone: + return SortAscending + case SortAscending: + return SortDescending + case SortDescending: + return SortNone + default: + log.Println("Unreachable") + return SortNone + } +}