package tracker import ( "image" "image/color" "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" ) type Menu struct { Visible bool clickable widget.Clickable tags []bool clicks []int hover int } type MenuStyle struct { Menu *Menu Title string IconColor color.NRGBA TextColor color.NRGBA ShortCutColor color.NRGBA FontSize unit.Value IconSize unit.Value HoverColor color.NRGBA } type MenuItem struct { IconBytes []byte Text string ShortcutText string Disabled bool } func (m *Menu) Clicked() (int, bool) { if len(m.clicks) == 0 { return 0, false } first := m.clicks[0] for i := 1; i < len(m.clicks); i++ { m.clicks[i-1] = m.clicks[i] } m.clicks = m.clicks[:len(m.clicks)-1] return first, true } func (m *MenuStyle) Layout(gtx C, items ...MenuItem) D { contents := func(gtx C) D { flexChildren := make([]layout.FlexChild, len(items)) for i, item := range items { // make sure we have a tag for every item for len(m.Menu.tags) <= i { m.Menu.tags = append(m.Menu.tags, false) } // handle pointer events for this item for _, ev := range gtx.Events(&m.Menu.tags[i]) { e, ok := ev.(pointer.Event) if !ok { continue } switch e.Type { case pointer.Press: m.Menu.clicks = append(m.Menu.clicks, i) m.Menu.Visible = false case pointer.Enter: m.Menu.hover = i + 1 case pointer.Leave: if m.Menu.hover == i+1 { m.Menu.hover = 0 } } } // layout contents for this item i2 := i // avoid loop variable getting updated in closure item2 := item flexChildren[i] = layout.Rigid(func(gtx C) D { defer op.Save(gtx.Ops).Load() var macro op.MacroOp if i2 == m.Menu.hover-1 && !item2.Disabled { macro = op.Record(gtx.Ops) } icon := widgetForIcon(item2.IconBytes) if !item2.Disabled { icon.Color = m.IconColor } else { icon.Color = mediumEmphasisTextColor } iconInset := layout.Inset{Left: unit.Dp(12), Right: unit.Dp(6)} textLabel := LabelStyle{Text: item2.Text, FontSize: m.FontSize, Color: m.TextColor} if item2.Disabled { textLabel.Color = mediumEmphasisTextColor } shortcutLabel := LabelStyle{Text: item2.ShortcutText, FontSize: m.FontSize, Color: m.ShortCutColor} shortcutInset := layout.Inset{Left: unit.Dp(12), Right: unit.Dp(12), Bottom: unit.Dp(2), Top: unit.Dp(2)} dims := layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, layout.Rigid(func(gtx C) D { return iconInset.Layout(gtx, func(gtx C) D { return icon.Layout(gtx, m.IconSize) }) }), layout.Rigid(textLabel.Layout), layout.Flexed(1, func(gtx C) D { return D{Size: image.Pt(gtx.Constraints.Max.X, 1)} }), layout.Rigid(func(gtx C) D { return shortcutInset.Layout(gtx, shortcutLabel.Layout) }), ) if i2 == m.Menu.hover-1 && !item2.Disabled { recording := macro.Stop() paint.FillShape(gtx.Ops, m.HoverColor, clip.Rect{ Max: image.Pt(dims.Size.X, dims.Size.Y), }.Op()) recording.Add(gtx.Ops) } if !item2.Disabled { rect := image.Rect(0, 0, dims.Size.X, dims.Size.Y) pointer.Rect(rect).Add(gtx.Ops) pointer.InputOp{Tag: &m.Menu.tags[i2], Types: pointer.Press | pointer.Enter | pointer.Leave, }.Add(gtx.Ops) } return dims }) } return layout.Flex{Axis: layout.Vertical}.Layout(gtx, flexChildren...) } popup := Popup(&m.Menu.Visible) popup.NE = unit.Dp(0) popup.ShadowN = unit.Dp(0) popup.NW = unit.Dp(0) return popup.Layout(gtx, contents) } func PopupMenu(th *material.Theme, menu *Menu) MenuStyle { return MenuStyle{ Menu: menu, IconColor: white, TextColor: white, ShortCutColor: mediumEmphasisTextColor, FontSize: unit.Dp(16), IconSize: unit.Dp(16), HoverColor: menuHoverColor, } }