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 }