Files
MujicaUI/datetime_grid.go
T

183 lines
5.4 KiB
Go

package mujica
import (
"github.com/egoist/mygo/ui"
"mujicaui/internal"
"mujicaui/theme"
)
// datetimeGridConfig describes a page of month or year cells, addressed by ordinal.
type datetimeGridConfig struct {
// pageOf returns the first ordinal and count of the page holding o.
pageOf func(o int) (start, n int)
cols int
page int
title func(start int) string
prev string
next string
text func(o int) string
label func(o int) string
chosen func(o int) bool
current func(o int) bool
enabled func(o int) bool
// muted greys cells of the page that belong to a neighbor page.
muted func(o, start int) bool
autoFocus bool
}
// datetimeGrid draws the page of *cursor and returns the ordinal picked, if any.
func datetimeGrid(c *ui.Context, cursor *int, cfg datetimeGridConfig) (picked int, ok bool) {
k := Tokens(c)
d := Density(c)
root := ui.Column(c).Gap(d.Unit() * 2).Shrink(0).Width(d.ControlHeight() * 7)
keyed := ui.Local(root, "datetime-grid-keys", func() bool { return false })
root.Children(func() {
start, _ := cfg.pageOf(*cursor)
ui.Row(c).AlignItems(ui.Center).Gap(d.Unit()).BorderWidth(0, 0, theme.BorderWidth, 0).BorderColor(k.Border).
PaddingY(d.Unit()).Children(func() {
if datetimeIconButton(c, "chevron-left", cfg.prev).Clicked() {
*cursor, *keyed = *cursor-cfg.page, false
}
ui.Text(c, cfg.title(start)).Font(theme.SerifFont).FontSize(theme.BodySize + 2).Bold().
TextColor(k.Text).Grow(1).TextAlign(ui.Center).SingleLine()
if datetimeIconButton(c, "chevron-right", cfg.next).Clicked() {
*cursor, *keyed = *cursor+cfg.page, false
}
})
cells := ui.Column(c).Gap(d.Unit()).FocusGroup(ui.Horizontal | ui.Vertical)
moved := true
switch {
case cells.Shortcut(0, ui.KeyLeft):
*cursor--
case cells.Shortcut(0, ui.KeyRight):
*cursor++
case cells.Shortcut(0, ui.KeyUp):
*cursor -= cfg.cols
case cells.Shortcut(0, ui.KeyDown):
*cursor += cfg.cols
case cells.Shortcut(0, ui.KeyPageUp):
*cursor -= cfg.page
case cells.Shortcut(0, ui.KeyPageDown):
*cursor += cfg.page
case cells.Shortcut(0, ui.KeyHome):
*cursor, _ = cfg.pageOf(*cursor)
case cells.Shortcut(0, ui.KeyEnd):
s, n := cfg.pageOf(*cursor)
*cursor = s + n - 1
default:
moved = false
}
if moved {
*keyed = true
}
start, n := cfg.pageOf(*cursor)
w := d.ControlHeight() * 7 / float32(cfg.cols)
cells.Children(func() {
for r := 0; r < n; r += cfg.cols {
ui.Row(c).Children(func() {
for o := start + r; o < start+min(r+cfg.cols, n); o++ {
if datetimeGridCell(c, o, start, *cursor, cfg, w, moved, *keyed) {
picked, ok = o, true
*cursor, *keyed = o, false
}
}
})
}
})
})
return picked, ok
}
// datetimeGridCell draws one month or year cell and reports whether it was picked.
func datetimeGridCell(c *ui.Context, o, start, cursor int, cfg datetimeGridConfig, w float32, focus, keyed bool) bool {
k := Tokens(c)
d := Density(c)
enabled, chosen := cfg.enabled(o), cfg.chosen(o)
sel := o - 1
if chosen {
sel = o
}
cell := ui.RadioBase(c, &sel, o).Size(w, d.ControlHeight()).Center().Radius(theme.ControlRadius).
Label(cfg.label(o)).Disabled(!enabled).FocusRing(false)
if o == cursor && enabled {
if focus {
cell.Focus()
} else if cfg.autoFocus {
cell.AutoFocus()
}
}
fg := k.Text
if cfg.muted(o, start) {
fg = k.TextMuted
}
switch {
case chosen:
cell.Background(k.Accent)
fg = k.OnAccent
if cell.Hovered() {
cell.Background(k.AccentHover)
}
case cell.Pressed():
cell.Background(k.Selection)
case cell.Hovered():
cell.Background(k.SurfaceHover)
}
current := cfg.current(o)
ring := cell.FocusVisible() || cell.Focused() && keyed
cell.DrawOver(func(p *ui.Painter, r ui.Rect) {
if current {
p.FillPath(internal.Diamond(r.X+r.W/2, r.Y+r.H-6, 5), fg)
}
if ring {
internal.FocusRing(p, r, theme.ControlRadius, k.Focus)
}
})
cell.Children(func() {
t := ui.Text(c, cfg.text(o)).TextColor(fg).FontFeatures("tnum").SingleLine()
if current {
t.Bold()
}
if !enabled {
t.Strikethrough()
}
})
return cell.Clicked() && enabled
}
// datetimeGridField is the field and popup shared by MonthPicker and YearPicker.
type datetimeGridField struct {
text, placeholder, label string
clearable, hasValue bool
disabled bool
// home is the ordinal the popup opens on.
home int
}
// datetimeGridPicker draws f and its popup grid; it returns the column, a picked ordinal and whether Clear was pressed.
func datetimeGridPicker(c *ui.Context, f datetimeGridField, cfg datetimeGridConfig) (col *ui.Element, picked int, ok, cleared bool) {
d := Density(c)
col = ui.Column(c).Width(d.ControlHeight() * 6).Gap(d.Unit()).AlignItems(ui.Stretch).Disabled(f.disabled)
open := ui.Local(col, "datetime-grid-open", func() bool { return false })
cursor := ui.Local(col, "datetime-grid-cursor", func() int { return 0 })
col.Children(func() {
field := datetimeField(c, ui.Row(c), false)
field.Children(func() {
t := datetimeTrigger(c, f.text, f.placeholder, "calendar").Label(f.label).Description(f.text)
if t.Clicked() || !*open && t.Shortcut(0, ui.KeyDown) {
*open, *cursor = !*open, f.home
}
if f.clearable && f.hasValue && datetimeIconButton(c, "x", msg(c, "calendar.clear")).Clicked() {
cleared = true
}
})
datetimePopup(c, field, open, func() {
cfg.autoFocus = true
if picked, ok = datetimeGrid(c, cursor, cfg); ok {
*open = false
}
})
})
return col, picked, ok, cleared
}