add(project): initial commit
This commit is contained in:
parent
f34565bf49
commit
cf17a4ec59
11 changed files with 628 additions and 0 deletions
25
internal/components/dropdown.go
Normal file
25
internal/components/dropdown.go
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
type Dropdown struct {
|
||||
Editor widget.Editor
|
||||
Clicks []*widget.Clickable
|
||||
Options []string
|
||||
Filtered []string
|
||||
Show bool
|
||||
}
|
||||
|
||||
func NewDropdown(options []string) *Dropdown {
|
||||
return &Dropdown{
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Dropdown) Layout(gtx layout.Context, th *material.Theme, hint string) (layout.Dimensions, string) {
|
||||
return layout.Dimensions{}, ""
|
||||
}
|
||||
89
internal/components/icon_button.go
Normal file
89
internal/components/icon_button.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
type ButtonState int
|
||||
|
||||
const (
|
||||
NormalButton ButtonState = iota
|
||||
SelectedButton
|
||||
UnselectedButton
|
||||
)
|
||||
|
||||
type iconAndTextButton struct {
|
||||
theme *material.Theme
|
||||
button *widget.Clickable
|
||||
icon *widget.Icon
|
||||
word string
|
||||
axis layout.Axis
|
||||
state ButtonState
|
||||
}
|
||||
|
||||
func (b iconAndTextButton) Layout(gtx layout.Context) layout.Dimensions {
|
||||
btn := material.ButtonLayout(b.theme, b.button)
|
||||
switch b.state {
|
||||
case NormalButton:
|
||||
btn.Background = b.theme.Palette.Bg
|
||||
case SelectedButton:
|
||||
btn.Background = b.theme.Palette.ContrastBg
|
||||
case UnselectedButton:
|
||||
btn.Background = b.theme.Palette.Bg
|
||||
}
|
||||
|
||||
return btn.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.UniformInset(unit.Dp(12)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
iconAndLabel := layout.Flex{Axis: b.axis, Alignment: layout.Middle}
|
||||
var bottomSpacer, textIconSpacer unit.Dp
|
||||
|
||||
if b.axis == layout.Horizontal {
|
||||
bottomSpacer = unit.Dp(0)
|
||||
textIconSpacer = unit.Dp(2)
|
||||
} else {
|
||||
bottomSpacer = unit.Dp(2)
|
||||
textIconSpacer = unit.Dp(0)
|
||||
}
|
||||
|
||||
layIcon := layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{Bottom: bottomSpacer, Right: textIconSpacer}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
var d layout.Dimensions
|
||||
if b.icon != nil {
|
||||
size := gtx.Dp(unit.Dp(24))
|
||||
|
||||
if b.axis == layout.Horizontal {
|
||||
size = gtx.Dp(unit.Dp(56)) - 2*gtx.Dp(unit.Dp(16))
|
||||
}
|
||||
|
||||
gtx.Constraints = layout.Exact(image.Pt(size, size))
|
||||
if b.state == SelectedButton {
|
||||
d = b.icon.Layout(gtx, b.theme.Palette.ContrastFg)
|
||||
} else {
|
||||
d = b.icon.Layout(gtx, b.theme.Palette.Fg)
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
})
|
||||
|
||||
layLabel := layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{Top: bottomSpacer, Right: textIconSpacer}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
||||
l := material.Body1(b.theme, b.word)
|
||||
if b.state == SelectedButton {
|
||||
l.Color = b.theme.Palette.ContrastFg
|
||||
} else {
|
||||
l.Color = b.theme.Palette.Fg
|
||||
}
|
||||
return l.Layout(gtx)
|
||||
})
|
||||
})
|
||||
|
||||
return iconAndLabel.Layout(gtx, layIcon, layLabel)
|
||||
})
|
||||
})
|
||||
}
|
||||
194
internal/components/navigation_menu.go
Normal file
194
internal/components/navigation_menu.go
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue