194 lines
4.9 KiB
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
|
|
}
|