101 lines
3.2 KiB
Go
101 lines
3.2 KiB
Go
package mujica
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
)
|
|
|
|
// DurationPickerOptions configure a DurationPicker.
|
|
type DurationPickerOptions struct {
|
|
// Seconds adds a seconds field.
|
|
Seconds bool
|
|
// Min and Max bound the duration; zero Max means 99:59:59.
|
|
Min, Max time.Duration
|
|
Disabled bool
|
|
Label string
|
|
}
|
|
|
|
// DurationPicker edits hours, minutes and seconds within a range.
|
|
func DurationPicker(c *ui.Context, value *time.Duration, opts DurationPickerOptions) ChoiceResult {
|
|
hi := opts.Max
|
|
if hi == 0 {
|
|
hi = 99*time.Hour + 59*time.Minute + 59*time.Second
|
|
}
|
|
unit := time.Minute
|
|
if opts.Seconds {
|
|
unit = time.Second
|
|
}
|
|
switch {
|
|
case opts.Min < 0 || hi < opts.Min:
|
|
panic(fmt.Sprintf("mujica: DurationPicker range %v..%v is not valid", opts.Min, hi))
|
|
case *value < 0 || *value%unit != 0:
|
|
panic(fmt.Sprintf("mujica: DurationPicker value %v is negative or finer than the fields", *value))
|
|
}
|
|
d := Density(c)
|
|
label := opts.Label
|
|
if label == "" {
|
|
label = msg(c, "duration_picker.label")
|
|
}
|
|
col := ui.Column(c).Gap(d.Unit()).Role(ui.RoleGroup).Label(label).Disabled(opts.Disabled)
|
|
draft := ui.Local(col, "duration-draft", func() [3]float64 { return durationParts(*value) })
|
|
seen := ui.Local(col, "duration-seen", func() time.Duration { return *value })
|
|
if *seen != *value {
|
|
*draft, *seen = durationParts(*value), *value
|
|
}
|
|
changed, edited := false, false
|
|
col.Children(func() {
|
|
ui.Row(c).Gap(d.Unit() * 2).AlignItems(ui.Center).Children(func() {
|
|
fields := []struct {
|
|
key string
|
|
max float64
|
|
}{{"duration_picker.hours", math.Floor(hi.Hours())}, {"duration_picker.minutes", 59}, {"duration_picker.seconds", 59}}
|
|
for i, f := range fields {
|
|
if i == 2 && !opts.Seconds {
|
|
break
|
|
}
|
|
ui.Row(c).Gap(d.Unit()).AlignItems(ui.Center).Children(func() {
|
|
ui.Box(c).Width(d.ControlHeight() * 3.2).Children(func() {
|
|
r := NumberInput(c, &draft[i], NumberInputOptions{Min: 0, Max: f.max, Label: msg(c, f.key), Disabled: opts.Disabled})
|
|
edited = edited || r.Changed()
|
|
})
|
|
ui.Text(c, msg(c, f.key+"_unit")).TextColor(Tokens(c).TextMuted)
|
|
})
|
|
}
|
|
})
|
|
total := time.Duration(draft[0])*time.Hour + time.Duration(draft[1])*time.Minute + time.Duration(draft[2])*time.Second
|
|
problem := ""
|
|
if total < opts.Min || total > hi {
|
|
problem = fmt.Sprintf(msg(c, "duration_picker.range"), DurationText(c, opts.Min), DurationText(c, hi))
|
|
}
|
|
if edited && problem == "" && total != *value && !col.IsDisabled() {
|
|
slog.Debug("mujica: duration_picker change", "from", *value, "to", total)
|
|
*value, *seen, changed = total, total, true
|
|
}
|
|
datetimeError(c, problem)
|
|
})
|
|
return ChoiceResult{Element: col, changed: changed}
|
|
}
|
|
|
|
func durationParts(v time.Duration) [3]float64 {
|
|
return [3]float64{float64(v / time.Hour), float64(v % time.Hour / time.Minute), float64(v % time.Minute / time.Second)}
|
|
}
|
|
|
|
// DurationText words a duration, as "1 h 30 min".
|
|
func DurationText(c *ui.Context, v time.Duration) string {
|
|
p := durationParts(v)
|
|
s := ""
|
|
for i, key := range []string{"duration_picker.hours_unit", "duration_picker.minutes_unit", "duration_picker.seconds_unit"} {
|
|
if p[i] > 0 || i == 1 && s == "" && p[2] == 0 {
|
|
if s != "" {
|
|
s += " "
|
|
}
|
|
s += fmt.Sprintf("%d %s", int(p[i]), msg(c, key))
|
|
}
|
|
}
|
|
return s
|
|
}
|