Files
8bitMyGoUI/datetime/grid.go
T
ZacharyZhang-NYandClaude Opus 5.5 03e1f91706 Upgrade MyGo to v0.5.5
Elements are now pass-scoped value handles: tests that read bounds
after a frame bind a ui.Handle. pixel tells frames apart by the build
pass's root, since the Tester clock no longer moves on its own.

Fix the regressions the upgrade brought:
- Clipboard reads in input handlers go through c.Services().
- MyGo notes an overlay's opener after building it, so AutoFocus
  inside lost it; pixel.AutoFocus focuses through a Handle, and
  archtest bans the raw call. Dialogs hand the focus back again.
- Elements around a press stay hovered: Tooltip tells a press-given
  focus by focus arriving under the pointer.
- The web host moves the Tester clock with real time, at most 500 ms
  a call, and gives components the real clock.

The gallery's source index also accepts CRLF checkouts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-17 01:31:08 -04:00

122 lines
4.2 KiB
Go

package datetime
import (
"fmt"
"strconv"
"time"
"github.com/egoist/mygo/ui"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/icon"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/input"
"git.zacharyzhang.com/ZacharyZhang-NY/8bitMyGoUI/pixel"
)
// cell is a day's square in dots.
const cell = pixel.LineHeight
// look is how one day of a month grid draws.
type look struct{ chosen, between bool }
// addMonths moves t n months, keeping the day inside.
func addMonths(t time.Time, n int) time.Time {
y, m, d := t.Date()
first := time.Date(y, m+time.Month(n), 1, 0, 0, 0, 0, t.Location())
last := first.AddDate(0, 1, -1).Day()
return onDay(t, time.Date(first.Year(), first.Month(), min(d, last), 0, 0, 0, 0, t.Location()))
}
// month moves *cur by keys; clicks, Enter and Space pick.
func month(c *ui.Context, text string, cur *time.Time, disabled, focus bool, mark func(time.Time) look) (e, grid ui.Element, picked bool) {
k := pixel.K(c)
e = ui.Column(c).Gap(k.D(2)).Role(ui.RoleGroup).Label(text)
e.Children(func() {
ui.Row(c).Width(k.D(7 * cell)).AlignItems(ui.Center).Children(func() {
if input.IconButton(c, icon.ChevronLeft, k.Msg("prev"), input.IconButtonOptions{Size: input.Small, Disabled: disabled}).Clicked {
*cur = addMonths(*cur, -1)
}
ui.Row(c).Grow(1).Justify(ui.Center).Children(func() {
pixel.Text(c, fmt.Sprintf("%s %d", monthName(k, cur.Month()), cur.Year()), pixel.TextOptions{})
})
if input.IconButton(c, icon.ChevronRight, k.Msg("next"), input.IconButtonOptions{Size: input.Small, Disabled: disabled}).Clicked {
*cur = addMonths(*cur, 1)
}
})
names := weekdays(k)
ui.Box(c).Size(k.D(7*cell), k.D(pixel.LineHeight)).Role(ui.RoleNone).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
for i, n := range names {
// Micro letters leave room between two-letter names.
if pixel.MicroOK(n) {
g.Micro(i*cell+(cell-pixel.MicroWidth(n))/2, 6, n, pixel.Faint, 1)
} else {
g.Text(i*cell+(cell-pixel.Measure(n))/2, 0, n, pixel.Faint, 1)
}
}
})
shell, base, st := pixel.Shell(c, func() ui.Element {
return ui.Box(c).Focusable().Role(ui.RoleTable).Label(text).Disabled(disabled)
})
grid = base
if focus {
pixel.AutoFocus(c, base)
}
y, m, d := cur.Date()
for _, mv := range []struct {
key ui.Key
to time.Time
}{
{ui.KeyLeft, cur.AddDate(0, 0, -1)}, {ui.KeyRight, cur.AddDate(0, 0, 1)},
{ui.KeyUp, cur.AddDate(0, 0, -7)}, {ui.KeyDown, cur.AddDate(0, 0, 7)},
{ui.KeyPageUp, addMonths(*cur, -1)}, {ui.KeyPageDown, addMonths(*cur, 1)},
{ui.KeyHome, cur.AddDate(0, 0, 1-d)}, {ui.KeyEnd, addMonths(cur.AddDate(0, 0, 1-d), 1).AddDate(0, 0, -1)},
} {
if pixel.Shortcut(base, 0, mv.key) {
*cur = mv.to
}
}
enter, space := pixel.Shortcut(base, 0, ui.KeyEnter), pixel.Shortcut(base, 0, ui.KeySpace)
picked = enter || space
first := time.Date(y, m, 1, 0, 0, 0, 0, cur.Location())
first = first.AddDate(0, 0, -int(first.Weekday()))
// Enter and Space are taken, so clicks come from pointers.
if base.Clicked() {
px, py, _ := base.PointerPosition()
if col, row := int(px/k.D(cell)), int(py/k.D(cell)); px >= 0 && py >= 0 && col < 7 && row < 6 {
*cur, picked = onDay(*cur, first.AddDate(0, 0, row*7+col)), true
}
}
base.Description(cur.Format("2006-01-02"))
now, shown := today(c, k), cur.Month()
shell.Size(k.D(7*cell), k.D(6*cell)).Draw(func(p *ui.Painter, r ui.Rect) {
g := k.Canvas(p, r)
for i := range 42 {
day, x, y := first.AddDate(0, 0, i), i%7*cell, i/7*cell
lk, ink := mark(day), pixel.Ink
if day.Month() != shown {
ink = pixel.Faint
}
if lk.between {
g.Veil(x, y, cell, cell, pixel.Accent, 4)
}
if lk.chosen {
g.Frame(x, y, cell, cell, pixel.Sunken, pixel.Well)
}
n := strconv.Itoa(day.Day())
g.Text(x+(cell-pixel.Measure(n))/2, y, n, ink, 1)
if sameDay(day, now) {
g.HLine(x+3, y+cell-2, cell-6, pixel.Accent)
}
if st.Focus && sameDay(day, *cur) {
g.Rect(x, y, cell, 1, pixel.Accent)
g.Rect(x, y+cell-1, cell, 1, pixel.Accent)
g.Rect(x, y, 1, cell, pixel.Accent)
g.Rect(x+cell-1, y, 1, cell, pixel.Accent)
}
}
g.Finish(st)
})
})
return e, grid, picked
}