Files
MujicaUI/time_range_picker.go
T

103 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package mujica
import (
"fmt"
"log/slog"
"github.com/egoist/mygo/ui"
"mujicaui/theme"
)
// TimeRange is a start and an end time of day. End before Start means it ends the next day.
type TimeRange struct {
Start, End TimeOfDay
}
// Overnight reports whether r crosses midnight.
func (r TimeRange) Overnight() bool { return r.End.Compare(r.Start) < 0 }
// TimeRangePickerOptions configure a TimeRangePicker.
type TimeRangePickerOptions struct {
Seconds bool
Hour12 bool
// MinuteStep and SecondStep must divide 60; zero means 1.
MinuteStep, SecondStep int
// Min and Max bound both ends; a zero Max means 23:59:59.
Min, Max TimeOfDay
// AllowOvernight accepts an end before the start, meaning the next day.
AllowOvernight bool
Disabled bool
// Label names the pair for assistive technology.
Label string
}
// TimeRangePicker edits a start and an end time. It writes *value only when both ends are valid
// and the end follows the start, or crosses midnight with AllowOvernight.
func TimeRangePicker(c *ui.Context, value *TimeRange, opts TimeRangePickerOptions) DateTimeResult {
cfg := timePickerConfig("TimeRangePicker", TimePickerOptions{Seconds: opts.Seconds, Hour12: opts.Hour12,
MinuteStep: opts.MinuteStep, SecondStep: opts.SecondStep, Min: opts.Min, Max: opts.Max})
if !value.Start.Valid() || !value.End.Valid() {
panic(fmt.Sprintf("mujica: TimeRangePicker value %v is not a pair of times", *value))
}
if value.Overnight() && !opts.AllowOvernight {
panic(fmt.Sprintf("mujica: TimeRangePicker value %v crosses midnight without AllowOvernight", *value))
}
k := Tokens(c)
d := Density(c)
col := ui.Column(c).Gap(d.Unit()).AlignItems(ui.Start).Disabled(opts.Disabled).Role(ui.RoleGroup)
if opts.Label != "" {
col.Label(opts.Label)
}
draft := ui.Local(col, "time-range-draft", func() TimeRange { return *value })
seen := ui.Local(col, "time-range-seen", func() TimeRange { return *value })
problem := ui.Local(col, "time-range-error", func() string { return "" })
if *seen != *value {
*draft, *seen, *problem = *value, *value, ""
}
changed := false
col.Children(func() {
edited := false
ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() {
start, end := cfg, cfg
start.label, end.label = msg(c, "time_range_picker.start"), msg(c, "time_range_picker.end")
start.invalid, end.invalid = *problem != "", *problem != ""
if _, e := timeEditor(c, &draft.Start, start); e {
edited = true
}
ui.Text(c, "–").TextColor(k.TextMuted).Role(ui.RoleNone)
if _, e := timeEditor(c, &draft.End, end); e {
edited = true
}
if draft.Overnight() && opts.AllowOvernight {
ui.Text(c, msg(c, "time_range_picker.next_day")).FontSize(theme.CaptionSize).TextColor(k.AccentText)
}
})
if edited {
*problem = timeRangeProblem(c, *draft, cfg, opts.AllowOvernight)
if *problem == "" && *draft != *value {
slog.Debug("mujica: time_range_picker change", "from", *value, "to", *draft)
*value, *seen, changed = *draft, *draft, true
}
}
datetimeError(c, *problem)
})
return DateTimeResult{Element: col, changed: changed}
}
// timeRangeProblem explains why r cannot be written, or returns "".
func timeRangeProblem(c *ui.Context, r TimeRange, cfg timeConfig, overnight bool) string {
for _, t := range []TimeOfDay{r.Start, r.End} {
if p := timePickerProblem(c, t, cfg); p != "" {
return p
}
}
switch {
case r.Start == r.End:
return msg(c, "time_range_picker.same")
case r.Overnight() && !overnight:
return msg(c, "time_range_picker.order")
}
return ""
}