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 }