EditableTextView.Changed is a method like every other result's. AlertDialogResult, PopconfirmResult and DateTimeResult spell Canceled the American way, matching EditableTextView and LoadingOverlayResult. Callers, Gallery and docs updated.
414 lines
15 KiB
Go
414 lines
15 KiB
Go
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"time"
|
||
|
||
"github.com/egoist/mygo/ui"
|
||
|
||
mujica "mujicaui"
|
||
"mujicaui/theme"
|
||
)
|
||
|
||
const dtCategory = "Date & Time"
|
||
|
||
var datetimePages = []page{
|
||
{ID: "051", Name: "Calendar", ZhName: "日历", Category: dtCategory, Demo: dtCalendarDemo,
|
||
Code: dtCalendarCode, Keys: "Tab enters the days; arrows move by day and week; PageUp/PageDown by month, with Shift by year; Home/End go to the first and last day; Enter or Space chooses."},
|
||
{ID: "052", Name: "DatePicker", ZhName: "日期选择器", Category: dtCategory, Demo: dtDatePickerDemo,
|
||
Code: dtDatePickerCode, Keys: "Type a date and press Enter, or leave the field, to commit; Down opens the calendar; in it arrows, PageUp/PageDown, Enter choose and Escape closes."},
|
||
{ID: "053", Name: "DateRangePicker", ZhName: "日期范围选择器", Category: dtCategory, Demo: dtDateRangeDemo,
|
||
Code: dtDateRangeCode, Keys: "Enter, Space or Down opens; Enter picks the start, then the end; Tab reaches Clear, Cancel and Confirm; Escape discards the draft."},
|
||
{ID: "054", Name: "TimePicker", ZhName: "时间选择器", Category: dtCategory, Demo: dtTimePickerDemo,
|
||
Code: dtTimePickerCode, Keys: "Tab enters the segments; Left/Right move between them; Up/Down step; digits type; A/P set the period; Alt+Down or the clock button opens the lists."},
|
||
{ID: "055", Name: "TimeRangePicker", ZhName: "时间范围选择器", Category: dtCategory, Demo: dtTimeRangeDemo,
|
||
Code: dtTimeRangeCode, Keys: "Each end works as a TimePicker; Tab moves from the start to the end."},
|
||
{ID: "056", Name: "DateTimePicker", ZhName: "日期时间选择器", Category: dtCategory, Demo: dtDateTimeDemo,
|
||
Code: dtDateTimeCode, Keys: "Enter, Space or Down opens; the calendar and time segments work as in DatePicker and TimePicker; Tab reaches Cancel and Confirm; Escape cancels."},
|
||
{ID: "057", Name: "MonthPicker", ZhName: "月份选择器", Category: dtCategory, Demo: dtMonthDemo,
|
||
Code: dtMonthCode, Keys: "Enter, Space or Down opens; arrows move by month and row; PageUp/PageDown by year; Enter chooses; Escape closes."},
|
||
{ID: "058", Name: "YearPicker", ZhName: "年份选择器", Category: dtCategory, Demo: dtYearDemo,
|
||
Code: dtYearCode, Keys: "Enter, Space or Down opens; arrows move by year and row; PageUp/PageDown by decade; Enter chooses; Escape closes."},
|
||
}
|
||
|
||
// dtSection lays out a captioned block of a demo.
|
||
func dtSection(c *ui.Context, title string, fn func()) {
|
||
k := mujica.Tokens(c)
|
||
ui.Column(c).Gap(8).Children(func() {
|
||
ui.Text(c, title).Font(theme.SerifFont).FontSize(mujica.FontSize(c, theme.H3Size)).TextColor(k.Text)
|
||
fn()
|
||
})
|
||
}
|
||
|
||
// dtStates lays labeled variants side by side.
|
||
func dtStates(c *ui.Context, fn func(state func(name string, body func()))) {
|
||
k := mujica.Tokens(c)
|
||
ui.Row(c).Gap(24).Wrap().AlignItems(ui.Start).Children(func() {
|
||
fn(func(name string, body func()) {
|
||
ui.Column(c).Gap(6).AlignItems(ui.Start).Children(func() {
|
||
ui.Text(c, name).FontSize(mujica.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
|
||
body()
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
func dtControls(c *ui.Context, fn func()) {
|
||
ui.Row(c).Gap(16).Wrap().AlignItems(ui.Center).Children(fn)
|
||
}
|
||
|
||
var dtWeekStarts = []string{"Locale", "Sunday", "Monday", "Saturday"}
|
||
|
||
func dtWeekStart(s string) mujica.WeekStart {
|
||
for i, n := range dtWeekStarts {
|
||
if n == s {
|
||
return mujica.WeekStart(i)
|
||
}
|
||
}
|
||
panic("gallery: unknown week start " + s)
|
||
}
|
||
|
||
func dtCalendarDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
day mujica.Date
|
||
weekStart string
|
||
weekends, bound, frozen bool
|
||
}
|
||
s := ui.Local(c.Root(), "051", func() state { return state{day: mujica.DateOf(time.Now()), weekStart: "Locale"} })
|
||
dtSection(c, "Calendar", func() {
|
||
dtControls(c, func() {
|
||
ui.Select(c, &s.weekStart, dtWeekStarts)
|
||
ui.Checkbox(c, &s.weekends, "Disable weekends")
|
||
ui.Checkbox(c, &s.bound, "Only this month")
|
||
ui.Checkbox(c, &s.frozen, "Disabled")
|
||
})
|
||
opts := mujica.CalendarOptions{WeekStart: dtWeekStart(s.weekStart), Disabled: s.frozen, Label: "Calendar demo"}
|
||
if s.weekends {
|
||
opts.DisableDate = func(d mujica.Date) bool { return d.Weekday() == time.Saturday || d.Weekday() == time.Sunday }
|
||
}
|
||
if s.bound {
|
||
now := mujica.DateOf(time.Now())
|
||
opts.Min = mujica.Date{Year: now.Year, Month: now.Month, Day: 1}
|
||
opts.Max = opts.Min.AddMonths(1).AddDays(-1)
|
||
}
|
||
if mujica.Calendar(c, &s.day, opts).Changed() {
|
||
log("Calendar changed: " + s.day.String())
|
||
}
|
||
})
|
||
}
|
||
|
||
func dtDatePickerDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
a, b, d mujica.Date
|
||
clearable, slash bool
|
||
}
|
||
s := ui.Local(c.Root(), "052", func() state {
|
||
t := mujica.DateOf(time.Now())
|
||
return state{a: t, d: t, clearable: true}
|
||
})
|
||
dtSection(c, "DatePicker", func() {
|
||
dtControls(c, func() {
|
||
ui.Checkbox(c, &s.clearable, "Clearable")
|
||
ui.Checkbox(c, &s.slash, "Layout 02/01/2006")
|
||
})
|
||
layout := ""
|
||
if s.slash {
|
||
layout = "02/01/2006"
|
||
}
|
||
now := mujica.DateOf(time.Now())
|
||
dtStates(c, func(state func(string, func())) {
|
||
state("Default", func() {
|
||
if mujica.DatePicker(c, &s.a, mujica.DatePickerOptions{Label: "Due date", Layout: layout, Clearable: s.clearable}).Changed() {
|
||
log("DatePicker changed: " + s.a.String())
|
||
}
|
||
})
|
||
state("Next 30 days only", func() {
|
||
opts := mujica.DatePickerOptions{Label: "Delivery", Layout: layout, Clearable: s.clearable, Min: now, Max: now.AddDays(30)}
|
||
if mujica.DatePicker(c, &s.b, opts).Changed() {
|
||
log("DatePicker (bounded) changed: " + s.b.String())
|
||
}
|
||
})
|
||
state("Read-only", func() {
|
||
mujica.DatePicker(c, &s.d, mujica.DatePickerOptions{Label: "Signed on", Layout: layout, ReadOnly: true})
|
||
})
|
||
state("Disabled", func() {
|
||
mujica.DatePicker(c, &s.d, mujica.DatePickerOptions{Label: "Locked", Layout: layout, Disabled: true})
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
func dtDateRangeDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
a mujica.DateRange
|
||
noTuesdays bool
|
||
frozen bool
|
||
fromThisWeek bool
|
||
}
|
||
s := ui.Local(c.Root(), "053", func() state { return state{} })
|
||
dtSection(c, "DateRangePicker", func() {
|
||
dtControls(c, func() {
|
||
ui.Checkbox(c, &s.noTuesdays, "Tuesdays unavailable")
|
||
ui.Checkbox(c, &s.fromThisWeek, "Start from today")
|
||
ui.Checkbox(c, &s.frozen, "Disabled")
|
||
})
|
||
opts := mujica.DateRangePickerOptions{Label: "Stay", Disabled: s.frozen}
|
||
if s.noTuesdays {
|
||
opts.DisableDate = func(d mujica.Date) bool { return d.Weekday() == time.Tuesday }
|
||
}
|
||
if s.fromThisWeek {
|
||
opts.Min = mujica.DateOf(time.Now())
|
||
}
|
||
r := mujica.DateRangePicker(c, &s.a, opts)
|
||
if r.Changed() {
|
||
log(fmt.Sprintf("DateRangePicker changed: %v – %v (%d days)", s.a.Start, s.a.End, s.a.Days()))
|
||
}
|
||
if r.Canceled() {
|
||
log("DateRangePicker canceled")
|
||
}
|
||
})
|
||
}
|
||
|
||
func dtTimePickerDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
a, b mujica.TimeOfDay
|
||
seconds, hour12 bool
|
||
step string
|
||
}
|
||
s := ui.Local(c.Root(), "054", func() state {
|
||
return state{a: mujica.TimeOfDay{Hour: 9, Minute: 30}, b: mujica.TimeOfDay{Hour: 10}, step: "1"}
|
||
})
|
||
dtSection(c, "TimePicker", func() {
|
||
dtControls(c, func() {
|
||
ui.Checkbox(c, &s.seconds, "Seconds")
|
||
ui.Checkbox(c, &s.hour12, "12-hour")
|
||
ui.Text(c, "Minute step")
|
||
ui.Select(c, &s.step, []string{"1", "5", "15", "30"})
|
||
})
|
||
step := 0
|
||
if _, err := fmt.Sscan(s.step, &step); err != nil {
|
||
panic(err)
|
||
}
|
||
base := mujica.TimePickerOptions{Seconds: s.seconds, Hour12: s.hour12, MinuteStep: step}
|
||
dtStates(c, func(state func(string, func())) {
|
||
state("Any time", func() {
|
||
o := base
|
||
o.Label = "Alarm"
|
||
if mujica.TimePicker(c, &s.a, o).Changed() {
|
||
log("TimePicker changed: " + s.a.String())
|
||
}
|
||
})
|
||
state("Office hours 09:00–18:00", func() {
|
||
o := base
|
||
o.Label, o.Min, o.Max = "Meeting", mujica.TimeOfDay{Hour: 9}, mujica.TimeOfDay{Hour: 18}
|
||
if mujica.TimePicker(c, &s.b, o).Changed() {
|
||
log("TimePicker (bounded) changed: " + s.b.String())
|
||
}
|
||
})
|
||
state("Read-only", func() {
|
||
o := base
|
||
o.Label, o.ReadOnly = "Logged at", true
|
||
mujica.TimePicker(c, &s.a, o)
|
||
})
|
||
state("Disabled", func() {
|
||
o := base
|
||
o.Label, o.Disabled = "Locked", true
|
||
mujica.TimePicker(c, &s.a, o)
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
func dtTimeRangeDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
v mujica.TimeRange
|
||
overnight bool
|
||
}
|
||
s := ui.Local(c.Root(), "055", func() state {
|
||
return state{v: mujica.TimeRange{Start: mujica.TimeOfDay{Hour: 9}, End: mujica.TimeOfDay{Hour: 17}}}
|
||
})
|
||
dtSection(c, "TimeRangePicker", func() {
|
||
dtControls(c, func() {
|
||
if ui.Checkbox(c, &s.overnight, "Allow overnight").Changed() && !s.overnight && s.v.Overnight() {
|
||
s.v = mujica.TimeRange{Start: s.v.End, End: s.v.Start}
|
||
log("TimeRangePicker swapped ends: overnight turned off")
|
||
}
|
||
})
|
||
opts := mujica.TimeRangePickerOptions{Label: "Opening hours", AllowOvernight: s.overnight, MinuteStep: 15}
|
||
if mujica.TimeRangePicker(c, &s.v, opts).Changed() {
|
||
log(fmt.Sprintf("TimeRangePicker changed: %v – %v overnight=%v", s.v.Start, s.v.End, s.v.Overnight()))
|
||
}
|
||
})
|
||
}
|
||
|
||
var dtZones = []string{"Local", "UTC", "Asia/Shanghai", "America/New_York", "Europe/London"}
|
||
|
||
func dtDateTimeDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
v time.Time
|
||
zone string
|
||
hour12 bool
|
||
}
|
||
s := ui.Local(c.Root(), "056", func() state { return state{zone: "Local"} })
|
||
dtSection(c, "DateTimePicker", func() {
|
||
dtControls(c, func() {
|
||
ui.Text(c, "Time zone")
|
||
ui.Select(c, &s.zone, dtZones)
|
||
ui.Checkbox(c, &s.hour12, "12-hour")
|
||
})
|
||
loc, err := time.LoadLocation(s.zone)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
r := mujica.DateTimePicker(c, &s.v, mujica.DateTimePickerOptions{Label: "Meeting", Location: loc, Hour12: s.hour12})
|
||
if r.Changed() {
|
||
log("DateTimePicker changed: " + s.v.Format(time.RFC3339))
|
||
}
|
||
if r.Canceled() {
|
||
log("DateTimePicker canceled")
|
||
}
|
||
k := mujica.Tokens(c)
|
||
ui.Text(c, "Try 2026-03-08 02:30 in America/New_York: the picker reports the DST gap.").
|
||
FontSize(mujica.FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
|
||
})
|
||
}
|
||
|
||
func dtMonthDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
v mujica.YearMonth
|
||
clearable, past bool
|
||
}
|
||
s := ui.Local(c.Root(), "057", func() state { return state{clearable: true} })
|
||
dtSection(c, "MonthPicker", func() {
|
||
dtControls(c, func() {
|
||
ui.Checkbox(c, &s.clearable, "Clearable")
|
||
ui.Checkbox(c, &s.past, "Past months only")
|
||
})
|
||
opts := mujica.MonthPickerOptions{Label: "Billing month", Clearable: s.clearable,
|
||
DisableMonth: func(m mujica.YearMonth) bool { return m.Month == time.February }}
|
||
if s.past {
|
||
now := time.Now()
|
||
opts.Max = mujica.YearMonth{Year: now.Year(), Month: now.Month()}
|
||
}
|
||
dtStates(c, func(state func(string, func())) {
|
||
state("February unavailable", func() {
|
||
if mujica.MonthPicker(c, &s.v, opts).Changed() {
|
||
log("MonthPicker changed: " + s.v.String())
|
||
}
|
||
})
|
||
state("Read-only", func() {
|
||
mujica.MonthPicker(c, &s.v, mujica.MonthPickerOptions{Label: "Closed month", ReadOnly: true})
|
||
})
|
||
state("Disabled", func() {
|
||
mujica.MonthPicker(c, &s.v, mujica.MonthPickerOptions{Label: "Locked", Disabled: true})
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
func dtYearDemo(c *ui.Context, log func(string)) {
|
||
type state struct {
|
||
v int
|
||
clearable bool
|
||
}
|
||
s := ui.Local(c.Root(), "058", func() state { return state{v: time.Now().Year(), clearable: true} })
|
||
dtSection(c, "YearPicker", func() {
|
||
dtControls(c, func() { ui.Checkbox(c, &s.clearable, "Clearable") })
|
||
opts := mujica.YearPickerOptions{Label: "Vintage", Min: 1990, Max: time.Now().Year(), Clearable: s.clearable,
|
||
DisableYear: func(y int) bool { return y == 2020 }}
|
||
dtStates(c, func(state func(string, func())) {
|
||
state("1990 to now, 2020 unavailable", func() {
|
||
if mujica.YearPicker(c, &s.v, opts).Changed() {
|
||
log(fmt.Sprintf("YearPicker changed: %d", s.v))
|
||
}
|
||
})
|
||
state("Read-only", func() {
|
||
mujica.YearPicker(c, &s.v, mujica.YearPickerOptions{Label: "Founded", ReadOnly: true})
|
||
})
|
||
state("Disabled", func() {
|
||
mujica.YearPicker(c, &s.v, mujica.YearPickerOptions{Label: "Locked", Disabled: true})
|
||
})
|
||
})
|
||
})
|
||
}
|
||
|
||
// Code snippets mirror example_datetime_test.go.
|
||
const (
|
||
dtCalendarCode = `day := mujica.Date{Year: 2026, Month: time.October, Day: 5}
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
if mujica.Calendar(c, &day, mujica.CalendarOptions{WeekStart: mujica.WeekStartMonday}).Changed() {
|
||
fmt.Println("chosen", day)
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
dtDatePickerCode = `due := mujica.Date{Year: 2026, Month: time.October, Day: 5}
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
opts := mujica.DatePickerOptions{Label: "Due date", Clearable: true,
|
||
Min: mujica.Date{Year: 2026, Month: time.January, Day: 1}}
|
||
if mujica.DatePicker(c, &due, opts).Changed() {
|
||
fmt.Println("due", due)
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
dtDateRangeCode = `var stay mujica.DateRange
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
r := mujica.DateRangePicker(c, &stay, mujica.DateRangePickerOptions{Label: "Stay"})
|
||
if r.Changed() {
|
||
fmt.Println("stay", stay.Start, stay.End, stay.Days())
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
dtTimePickerCode = `alarm := mujica.TimeOfDay{Hour: 7, Minute: 30}
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
opts := mujica.TimePickerOptions{Label: "Alarm", Hour12: true, MinuteStep: 5}
|
||
if mujica.TimePicker(c, &alarm, opts).Changed() {
|
||
fmt.Println("alarm", alarm)
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
dtTimeRangeCode = `shift := mujica.TimeRange{Start: mujica.TimeOfDay{Hour: 22}, End: mujica.TimeOfDay{Hour: 6}}
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
opts := mujica.TimeRangePickerOptions{Label: "Night shift", AllowOvernight: true}
|
||
if mujica.TimeRangePicker(c, &shift, opts).Changed() {
|
||
fmt.Println("shift", shift.Start, shift.End, shift.Overnight())
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
dtDateTimeCode = `tokyo, err := time.LoadLocation("Asia/Tokyo")
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
var meeting time.Time
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
opts := mujica.DateTimePickerOptions{Label: "Meeting", Location: tokyo, MinuteStep: 15}
|
||
if mujica.DateTimePicker(c, &meeting, opts).Changed() {
|
||
fmt.Println("meeting", meeting)
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
dtMonthCode = `var month mujica.YearMonth
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
opts := mujica.MonthPickerOptions{Label: "Billing month", Clearable: true}
|
||
if mujica.MonthPicker(c, &month, opts).Changed() {
|
||
fmt.Println("month", month)
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
dtYearCode = `year := 2026
|
||
view := func(c *ui.Context) {
|
||
mujica.Use(c, mujica.Settings{})
|
||
opts := mujica.YearPickerOptions{Label: "Vintage", Min: 1990, Max: 2030}
|
||
if mujica.YearPicker(c, &year, opts).Changed() {
|
||
fmt.Println("year", year)
|
||
}
|
||
}
|
||
ui.Render(view, 400, 300, 1)`
|
||
)
|