package gioui import ( "image" "image/color" "gioui.org/io/event" "gioui.org/io/pointer" "gioui.org/layout" "gioui.org/op" "gioui.org/op/clip" "gioui.org/op/paint" "gioui.org/text" "gioui.org/unit" "gioui.org/widget" "gioui.org/widget/material" "github.com/vsariola/sointu/tracker" ) type Menu struct { Visible bool tags []bool clicks []int hover int list layout.List scrollBar ScrollBar } type MenuStyle struct { Menu *Menu Title string IconColor color.NRGBA TextColor color.NRGBA ShortCutColor color.NRGBA FontSize unit.Sp IconSize unit.Dp HoverColor color.NRGBA Shaper *text.Shaper } type MenuItem struct { IconBytes []byte Text string ShortcutText string Doer tracker.Action } 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 { 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, ok := gtx.Event(pointer.Filter{ Target: &m.Menu.tags[i], Kinds: pointer.Press | pointer.Enter | pointer.Leave, }) if !ok { break } e, ok := ev.(pointer.Event) if !ok { continue } switch e.Kind { case pointer.Press: item.Doer.Do() 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 } } } } m.Menu.list.Axis = layout.Vertical m.Menu.scrollBar.Axis = layout.Vertical return layout.Stack{Alignment: layout.SE}.Layout(gtx, layout.Expanded(func(gtx C) D { return m.Menu.list.Layout(gtx, len(items), func(gtx C, i int) D { defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() var macro op.MacroOp item := &items[i] if i == m.Menu.hover-1 && item.Doer.Allowed() { macro = op.Record(gtx.Ops) } icon := widgetForIcon(item.IconBytes) iconColor := m.IconColor if !item.Doer.Allowed() { iconColor = mediumEmphasisTextColor } iconInset := layout.Inset{Left: unit.Dp(12), Right: unit.Dp(6)} textLabel := LabelStyle{Text: item.Text, FontSize: m.FontSize, Color: m.TextColor, Shaper: m.Shaper} if !item.Doer.Allowed() { textLabel.Color = mediumEmphasisTextColor } shortcutLabel := LabelStyle{Text: item.ShortcutText, FontSize: m.FontSize, Color: m.ShortCutColor, Shaper: m.Shaper} 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 { p := gtx.Dp(unit.Dp(m.IconSize)) gtx.Constraints.Min = image.Pt(p, p) return icon.Layout(gtx, iconColor) }) }), 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 i == m.Menu.hover-1 && item.Doer.Allowed() { 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 item.Doer.Allowed() { rect := image.Rect(0, 0, dims.Size.X, dims.Size.Y) area := clip.Rect(rect).Push(gtx.Ops) event.Op(gtx.Ops, &m.Menu.tags[i]) area.Pop() } return dims }) }), layout.Expanded(func(gtx C) D { return m.Menu.scrollBar.Layout(gtx, unit.Dp(10), len(items), &m.Menu.list.Position) }), ) } 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(menu *Menu, shaper *text.Shaper) MenuStyle { return MenuStyle{ Menu: menu, IconColor: white, TextColor: white, ShortCutColor: mediumEmphasisTextColor, FontSize: unit.Sp(16), IconSize: unit.Dp(16), HoverColor: menuHoverColor, Shaper: shaper, } } func (tr *Tracker) layoutMenu(gtx C, title string, clickable *widget.Clickable, menu *Menu, width unit.Dp, items ...MenuItem) layout.Widget { for clickable.Clicked(gtx) { menu.Visible = true } m := PopupMenu(menu, tr.Theme.Shaper) return func(gtx C) D { defer op.Offset(image.Point{}).Push(gtx.Ops).Pop() titleBtn := material.Button(tr.Theme, clickable, title) titleBtn.Color = white titleBtn.Background = transparent titleBtn.CornerRadius = unit.Dp(0) dims := titleBtn.Layout(gtx) op.Offset(image.Pt(0, dims.Size.Y)).Add(gtx.Ops) gtx.Constraints.Max.X = gtx.Dp(width) gtx.Constraints.Max.Y = gtx.Dp(unit.Dp(300)) m.Layout(gtx, items...) return dims } }