| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- package gfyne
- import (
- "fmt"
- "strconv"
- "time"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/container"
- "fyne.io/fyne/v2/layout"
- "fyne.io/fyne/v2/theme"
- "fyne.io/fyne/v2/widget"
- )
- var datePickerMonths = []string{
- "一月",
- "二月",
- "三月",
- "四月",
- "五月",
- "六月",
- "七月",
- "八月",
- "九月",
- "十月",
- "十一月",
- "十二月",
- }
- func daysInMonth(t time.Time, o int) int {
- // get first day of the given month, add a month, and go one day back
- return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).AddDate(0, 1+o, -1).Day()
- }
- func firstWeekdayOfMonth(t time.Time) time.Weekday {
- return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).Weekday()
- }
- func lastWeekdayOfMonth(t time.Time) time.Weekday {
- // get first day of the given month, add a month, and go back one day
- return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).AddDate(0, 1, -1).Weekday()
- }
- func timeJumpMonth(when time.Time, offset int) time.Time {
- if offset == 0 {
- return when
- }
- // adjust day when higher than days in the destination month
- dstDays := daysInMonth(when, offset)
- if when.Day() > dstDays {
- when = time.Date(
- when.Year(),
- when.Month(),
- dstDays,
- when.Hour(),
- when.Minute(),
- when.Second(),
- when.Nanosecond(),
- when.Location(),
- )
- }
- return when.AddDate(0, offset, 0)
- }
- func timeJumpYearMonth(when time.Time, year int, month int) time.Time {
- dst := time.Date(
- year,
- time.Month(month),
- 1,
- when.Hour(),
- when.Minute(),
- when.Second(),
- when.Nanosecond(),
- when.Location(),
- )
- // adjust day when higher than days in the destination month
- dstDays := daysInMonth(dst, 0)
- dstDay := when.Day()
- if dstDay > dstDays {
- dstDay = dstDays
- }
- return time.Date(
- dst.Year(),
- dst.Month(),
- dstDay,
- when.Hour(),
- when.Minute(),
- when.Second(),
- when.Nanosecond(),
- when.Location(),
- )
- }
- // weeks start on Monday, why is it otherwise called "the weekend"?!
- func adjustWeekday(d time.Weekday, weekStart time.Weekday) int {
- if weekStart == 0 {
- return int(d)
- }
- return ((7 - int(weekStart)) + int(d)) % 7
- }
- // offsets correspond to time.Weekday, hence starting on Monday
- var weekdayLabels = []*widget.Label{
- widget.NewLabelWithStyle("Sun", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
- widget.NewLabelWithStyle("Mon", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
- widget.NewLabelWithStyle("Tue", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
- widget.NewLabelWithStyle("Wed", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
- widget.NewLabelWithStyle("Thu", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
- widget.NewLabelWithStyle("Fri", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
- widget.NewLabelWithStyle("Sat", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
- }
- func updateGrid(grid *fyne.Container, when time.Time, weekStart time.Weekday, updateWhen func(time.Time), updateSelects func(time.Time)) {
- objs := []fyne.CanvasObject{}
- // row of weekdays at the top
- for n := weekStart; n < 7; n++ {
- objs = append(objs, weekdayLabels[n])
- }
- for n := 0; n < int(weekStart); n++ {
- objs = append(objs, weekdayLabels[n])
- }
- firstWeekday := adjustWeekday(firstWeekdayOfMonth(when), weekStart)
- lastWeekday := adjustWeekday(lastWeekdayOfMonth(when), weekStart)
- days := daysInMonth(when, 0)
- daysPrevMonth := daysInMonth(when, -1)
- // empty fields for days of the previous month that cut into the first week
- for n := 1; n <= firstWeekday; n++ {
- day := daysPrevMonth - firstWeekday + n
- button := widget.NewButton(fmt.Sprintf("%d", day), func() {
- when = time.Date(
- when.Year(),
- when.Month(),
- 1,
- when.Hour(),
- when.Minute(),
- when.Second(),
- when.Nanosecond(),
- when.Location(),
- ).AddDate(0, -1, day-1)
- updateWhen(when)
- updateSelects(when)
- })
- button.Importance = widget.LowImportance
- objs = append(objs, button)
- }
- var buttons []*widget.Button
- for n := 1; n <= days; n++ {
- var button *widget.Button
- var day = n
- button = widget.NewButton(fmt.Sprintf("%02d", n), func() {
- when = time.Date(
- when.Year(),
- when.Month(),
- day,
- when.Hour(),
- when.Minute(),
- when.Second(),
- when.Nanosecond(),
- when.Location(),
- )
- updateWhen(when)
- // reset importance of all buttons
- for _, b := range buttons {
- b.Importance = widget.MediumImportance
- }
- // only highlight selected day
- button.Importance = widget.HighImportance
- grid.Refresh()
- })
- // initially highlight a given day
- if n == when.Day() {
- button.Importance = widget.HighImportance
- }
- buttons = append(buttons, button)
- objs = append(objs, button)
- }
- // empty fields for days after the previous month
- for n := 1; lastWeekday < 7 && n < 7-(lastWeekday%7); n++ {
- day := n
- button := widget.NewButton(fmt.Sprintf("%d", day), func() {
- when = time.Date(
- when.Year(),
- when.Month(),
- 1,
- when.Hour(),
- when.Minute(),
- when.Second(),
- when.Nanosecond(),
- when.Location(),
- ).AddDate(0, 1, day-1)
- updateWhen(when)
- updateSelects(when)
- })
- button.Importance = widget.LowImportance
- objs = append(objs, button)
- }
- // add up to another empty row to compensate for months with a high first weekday
- for n := len(objs); n < 7*7; n++ {
- objs = append(objs, widget.NewLabel(""))
- }
- grid.Objects = objs
- grid.Refresh()
- }
- func findMonth(month string) int {
- for n := 0; n < len(datePickerMonths); n++ {
- if datePickerMonths[n] == month {
- return n + 1
- }
- }
- return 0
- }
- func NewDatePicker(when time.Time, weekStart time.Weekday, fn func(time.Time, bool)) fyne.CanvasObject {
- var updateSelects func(time.Time)
- grid := container.New(layout.NewGridLayoutWithColumns(7))
- updateWhen := func(t time.Time) {
- when = t
- }
- monthSelect := widget.NewSelect(datePickerMonths, func(selected string) {
- i := findMonth(selected)
- if i == 0 {
- return
- }
- when = timeJumpYearMonth(when, when.Year(), i)
- updateGrid(grid, when, weekStart, updateWhen, updateSelects)
- })
- monthSelect.Selected = when.Month().String()
- years := []string{}
- // inverted years, most recent on top for easy selection
- for n := when.Year() + 10; n >= when.Year()-100; n-- {
- years = append(years, fmt.Sprintf("%d", n))
- }
- yearSelect := widget.NewSelect(years, func(selected string) {
- i, err := strconv.ParseInt(selected, 10, 64)
- if err != nil {
- return
- }
- when = timeJumpYearMonth(when, int(i), int(when.Month()))
- updateGrid(grid, when, weekStart, updateWhen, updateSelects)
- })
- yearSelect.Selected = fmt.Sprintf("%d", when.Year())
- updateSelects = func(t time.Time) {
- // directly assign instead of setter methods to avoid multiple updates
- monthSelect.Selected = t.Month().String()
- monthSelect.Refresh()
- yearSelect.Selected = fmt.Sprintf("%d", t.Year())
- yearSelect.Refresh()
- updateGrid(grid, t, weekStart, updateWhen, updateSelects)
- }
- prevMonthButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() {
- when = timeJumpMonth(when, -1)
- updateSelects(when)
- })
- nextMonthButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() {
- when = timeJumpMonth(when, 1)
- updateSelects(when)
- })
- top := container.New(
- // previous and next button left and right
- layout.NewBorderLayout(nil, nil, prevMonthButton, nextMonthButton),
- prevMonthButton,
- nextMonthButton,
- // month and year dropdowns centered in the middle
- container.New(
- layout.NewHBoxLayout(),
- layout.NewSpacer(),
- monthSelect,
- yearSelect,
- layout.NewSpacer(),
- ),
- )
- updateGrid(grid, when, weekStart, updateWhen, updateSelects)
- hours := []string{}
- for n := 0; n < 23; n++ {
- hours = append(hours, fmt.Sprintf("%02d", n))
- }
- hourInput := widget.NewSelectEntry(hours)
- minutes := []string{}
- for n := 0; n < 59; n++ {
- minutes = append(minutes, fmt.Sprintf("%02d", n))
- }
- minuteInput := widget.NewSelectEntry(minutes)
- controlButtons := container.New(
- layout.NewHBoxLayout(),
- widget.NewButton("Now", func() {
- when = time.Now()
- hourInput.SetText(when.Format("15"))
- minuteInput.SetText(when.Format("04"))
- updateSelects(when)
- }),
- widget.NewButton("Cancel", func() {
- fn(when, false)
- }),
- widget.NewButton("Ok", func() {
- fn(when, true)
- }),
- )
- hourInput.SetText(when.Format("15"))
- hourInput.OnChanged = func(str string) {
- t, err := time.Parse("15", str)
- if err != nil {
- fyne.LogError("invalid hour", err)
- return
- }
- when = time.Date(
- when.Year(),
- when.Month(),
- when.Day(),
- t.Hour(),
- when.Minute(),
- 0,
- 0,
- when.Location(),
- )
- }
- minuteInput.SetText(when.Format("04"))
- minuteInput.OnChanged = func(str string) {
- i, err := strconv.ParseInt(str, 10, 64)
- if err != nil {
- fyne.LogError("failed to parse minte value", err)
- return
- }
- if i < 0 || i > 59 {
- fyne.LogError("minute value out of range", err)
- return
- }
- when = time.Date(
- when.Year(),
- when.Month(),
- when.Day(),
- when.Hour(),
- int(i),
- 0,
- 0,
- when.Location(),
- )
- }
- timeForm := widget.NewForm(
- widget.NewFormItem("Time",
- container.NewHBox(hourInput, widget.NewLabel(":"), minuteInput),
- ),
- )
- bottom := container.New(
- layout.NewBorderLayout(nil, nil, nil, controlButtons),
- controlButtons,
- timeForm,
- )
- return container.New(
- layout.NewBorderLayout(top, bottom, nil, nil),
- top,
- grid,
- bottom,
- )
- }
|