go-simple-ui/internal/components/navigation_menu.go

194 lines
4.9 KiB
Go

package components
import (
"fmt"
"image/color"
"simple_ui/internal/utils"
"time"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"golang.org/x/exp/shiny/materialdesign/icons"
)
type NavigationMenu struct {
theme *material.Theme
contractButton *widget.Clickable
items []MenuItem
contracted bool
selected int
anim utils.Animation
}
type MenuItem struct {
Label string
Button *widget.Clickable
Icon *widget.Icon
OnClick func()
}
func NewNavigationMenu(theme *material.Theme) *NavigationMenu {
nm := &NavigationMenu{
theme: theme,
contractButton: &widget.Clickable{},
items: make([]MenuItem, 0),
}
nm.anim.Start(0, 0, 0, utils.EasingInOutSine)
return nm
}
func (nm *NavigationMenu) ToggleContracted() {
nm.contracted = !nm.contracted
var end float32
if nm.contracted {
end = 1
} else {
end = 0
}
nm.anim.Start(nm.anim.Current(), end, 250*time.Millisecond, utils.EasingInOutSine)
fmt.Printf("Navigation menu toggled: %v\n", nm.contracted)
}
func (nm *NavigationMenu) IsContracted() bool {
return nm.contracted
}
func (nm *NavigationMenu) AddItem(label string, icon *widget.Icon, onClick func()) *widget.Clickable {
btn := &widget.Clickable{}
nm.items = append(nm.items, MenuItem{
Label: label,
Button: btn,
Icon: icon,
OnClick: onClick,
})
return btn
}
func (nm *NavigationMenu) SetSelected(index int) {
if index >= 0 && index < len(nm.items) {
nm.selected = index
}
}
func (nm *NavigationMenu) HandleClicks(gtx layout.Context) {
for i, item := range nm.items {
if item.Button.Clicked(gtx) {
nm.selected = i
if item.OnClick != nil {
item.OnClick()
}
}
}
}
func (nm *NavigationMenu) Layout(gtx layout.Context) layout.Dimensions {
// Animate contraction/expansion
value := nm.anim.Update(gtx)
contractedWidth := float32(gtx.Dp(unit.Dp(100)))
expandedWidth := float32(gtx.Dp(unit.Dp(240)))
width := int(expandedWidth + (contractedWidth-expandedWidth)*value)
gtx.Constraints.Min.X = width
gtx.Constraints.Max.X = width
// Add background color
backgroundColor := color.NRGBA{R: 205, G: 205, B: 205, A: 255} // Blue
return layout.Background{}.Layout(gtx,
func(gtx layout.Context) layout.Dimensions {
paint.FillShape(gtx.Ops, backgroundColor, clip.Rect{Max: gtx.Constraints.Min}.Op())
return layout.Dimensions{Size: gtx.Constraints.Min}
},
func(gtx layout.Context) layout.Dimensions {
return layout.Flex{
Axis: layout.Vertical,
}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.UniformInset(unit.Dp(8)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{
Axis: layout.Horizontal,
Alignment: layout.Middle,
}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
if nm.contracted {
return layout.Dimensions{}
}
return material.H6(nm.theme, "Simple Weather").Layout(gtx)
}),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return layout.Spacer{Width: unit.Dp(16)}.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
contractedIcon, err := widget.NewIcon(icons.NavigationChevronLeft)
if err != nil {
return layout.Dimensions{}
}
if nm.IsContracted() {
contractedIcon, err = widget.NewIcon(icons.NavigationChevronRight)
if err != nil {
return layout.Dimensions{}
}
}
if nm.contractButton.Clicked(gtx) {
nm.ToggleContracted()
}
contractButton := material.IconButton(nm.theme, nm.contractButton, contractedIcon, "Toggle menu")
contractButton.Background = nm.theme.Palette.Bg
contractButton.Color = nm.theme.Palette.Fg
return contractButton.Layout(gtx)
}),
)
})
}),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{
Axis: layout.Vertical,
}.Layout(gtx, nm.menuItems()...)
}),
)
},
)
}
func (nm *NavigationMenu) menuItems() []layout.FlexChild {
children := make([]layout.FlexChild, len(nm.items))
for i, item := range nm.items {
i, item := i, item
children[i] = layout.Rigid(func(gtx layout.Context) layout.Dimensions {
var state ButtonState
if i == nm.selected {
state = SelectedButton
} else {
state = UnselectedButton
}
axis := layout.Horizontal
if nm.IsContracted() {
axis = layout.Vertical
}
btn := &iconAndTextButton{
theme: nm.theme,
button: item.Button,
icon: item.Icon,
word: item.Label,
state: state,
axis: axis,
}
return layout.Inset{Top: unit.Dp(2), Bottom: unit.Dp(2), Left: unit.Dp(4), Right: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return btn.Layout(gtx)
})
})
}
return children
}