date_picker.go 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. package gfyne
  2. import (
  3. "fmt"
  4. "strconv"
  5. "time"
  6. "fyne.io/fyne/v2"
  7. "fyne.io/fyne/v2/container"
  8. "fyne.io/fyne/v2/layout"
  9. "fyne.io/fyne/v2/theme"
  10. "fyne.io/fyne/v2/widget"
  11. )
  12. var datePickerMonths = []string{
  13. "一月",
  14. "二月",
  15. "三月",
  16. "四月",
  17. "五月",
  18. "六月",
  19. "七月",
  20. "八月",
  21. "九月",
  22. "十月",
  23. "十一月",
  24. "十二月",
  25. }
  26. func daysInMonth(t time.Time, o int) int {
  27. // get first day of the given month, add a month, and go one day back
  28. return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).AddDate(0, 1+o, -1).Day()
  29. }
  30. func firstWeekdayOfMonth(t time.Time) time.Weekday {
  31. return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).Weekday()
  32. }
  33. func lastWeekdayOfMonth(t time.Time) time.Weekday {
  34. // get first day of the given month, add a month, and go back one day
  35. return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location()).AddDate(0, 1, -1).Weekday()
  36. }
  37. func timeJumpMonth(when time.Time, offset int) time.Time {
  38. if offset == 0 {
  39. return when
  40. }
  41. // adjust day when higher than days in the destination month
  42. dstDays := daysInMonth(when, offset)
  43. if when.Day() > dstDays {
  44. when = time.Date(
  45. when.Year(),
  46. when.Month(),
  47. dstDays,
  48. when.Hour(),
  49. when.Minute(),
  50. when.Second(),
  51. when.Nanosecond(),
  52. when.Location(),
  53. )
  54. }
  55. return when.AddDate(0, offset, 0)
  56. }
  57. func timeJumpYearMonth(when time.Time, year int, month int) time.Time {
  58. dst := time.Date(
  59. year,
  60. time.Month(month),
  61. 1,
  62. when.Hour(),
  63. when.Minute(),
  64. when.Second(),
  65. when.Nanosecond(),
  66. when.Location(),
  67. )
  68. // adjust day when higher than days in the destination month
  69. dstDays := daysInMonth(dst, 0)
  70. dstDay := when.Day()
  71. if dstDay > dstDays {
  72. dstDay = dstDays
  73. }
  74. return time.Date(
  75. dst.Year(),
  76. dst.Month(),
  77. dstDay,
  78. when.Hour(),
  79. when.Minute(),
  80. when.Second(),
  81. when.Nanosecond(),
  82. when.Location(),
  83. )
  84. }
  85. // weeks start on Monday, why is it otherwise called "the weekend"?!
  86. func adjustWeekday(d time.Weekday, weekStart time.Weekday) int {
  87. if weekStart == 0 {
  88. return int(d)
  89. }
  90. return ((7 - int(weekStart)) + int(d)) % 7
  91. }
  92. // offsets correspond to time.Weekday, hence starting on Monday
  93. var weekdayLabels = []*widget.Label{
  94. widget.NewLabelWithStyle("周日", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
  95. widget.NewLabelWithStyle("周一", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
  96. widget.NewLabelWithStyle("周二", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
  97. widget.NewLabelWithStyle("周三", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
  98. widget.NewLabelWithStyle("周四", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
  99. widget.NewLabelWithStyle("周五", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
  100. widget.NewLabelWithStyle("周六", fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
  101. }
  102. func updateGrid(grid *fyne.Container, when time.Time, weekStart time.Weekday, updateWhen func(time.Time), updateSelects func(time.Time)) {
  103. objs := []fyne.CanvasObject{}
  104. // row of weekdays at the top
  105. for n := weekStart; n < 7; n++ {
  106. objs = append(objs, weekdayLabels[n])
  107. }
  108. for n := 0; n < int(weekStart); n++ {
  109. objs = append(objs, weekdayLabels[n])
  110. }
  111. firstWeekday := adjustWeekday(firstWeekdayOfMonth(when), weekStart)
  112. lastWeekday := adjustWeekday(lastWeekdayOfMonth(when), weekStart)
  113. days := daysInMonth(when, 0)
  114. daysPrevMonth := daysInMonth(when, -1)
  115. // empty fields for days of the previous month that cut into the first week
  116. for n := 1; n <= firstWeekday; n++ {
  117. day := daysPrevMonth - firstWeekday + n
  118. button := widget.NewButton(fmt.Sprintf("%d", day), func() {
  119. when = time.Date(
  120. when.Year(),
  121. when.Month(),
  122. 1,
  123. when.Hour(),
  124. when.Minute(),
  125. when.Second(),
  126. when.Nanosecond(),
  127. when.Location(),
  128. ).AddDate(0, -1, day-1)
  129. updateWhen(when)
  130. updateSelects(when)
  131. })
  132. button.Importance = widget.LowImportance
  133. objs = append(objs, button)
  134. }
  135. var buttons []*widget.Button
  136. for n := 1; n <= days; n++ {
  137. var button *widget.Button
  138. var day = n
  139. button = widget.NewButton(fmt.Sprintf("%02d", n), func() {
  140. when = time.Date(
  141. when.Year(),
  142. when.Month(),
  143. day,
  144. when.Hour(),
  145. when.Minute(),
  146. when.Second(),
  147. when.Nanosecond(),
  148. when.Location(),
  149. )
  150. updateWhen(when)
  151. // reset importance of all buttons
  152. for _, b := range buttons {
  153. b.Importance = widget.MediumImportance
  154. }
  155. // only highlight selected day
  156. button.Importance = widget.HighImportance
  157. grid.Refresh()
  158. })
  159. // initially highlight a given day
  160. if n == when.Day() {
  161. button.Importance = widget.HighImportance
  162. }
  163. buttons = append(buttons, button)
  164. objs = append(objs, button)
  165. }
  166. // empty fields for days after the previous month
  167. for n := 1; lastWeekday < 7 && n < 7-(lastWeekday%7); n++ {
  168. day := n
  169. button := widget.NewButton(fmt.Sprintf("%d", day), func() {
  170. when = time.Date(
  171. when.Year(),
  172. when.Month(),
  173. 1,
  174. when.Hour(),
  175. when.Minute(),
  176. when.Second(),
  177. when.Nanosecond(),
  178. when.Location(),
  179. ).AddDate(0, 1, day-1)
  180. updateWhen(when)
  181. updateSelects(when)
  182. })
  183. button.Importance = widget.LowImportance
  184. objs = append(objs, button)
  185. }
  186. // add up to another empty row to compensate for months with a high first weekday
  187. for n := len(objs); n < 7*7; n++ {
  188. objs = append(objs, widget.NewLabel(""))
  189. }
  190. grid.Objects = objs
  191. grid.Refresh()
  192. }
  193. func findMonth(month string) int {
  194. for n := 0; n < len(datePickerMonths); n++ {
  195. if datePickerMonths[n] == month {
  196. return n + 1
  197. }
  198. }
  199. return 0
  200. }
  201. func NewDatePicker(when time.Time, weekStart time.Weekday, fn func(time.Time, bool)) fyne.CanvasObject {
  202. var updateSelects func(time.Time)
  203. grid := container.New(layout.NewGridLayoutWithColumns(7))
  204. updateWhen := func(t time.Time) {
  205. when = t
  206. }
  207. monthSelect := widget.NewSelect(datePickerMonths, func(selected string) {
  208. i := findMonth(selected)
  209. if i == 0 {
  210. return
  211. }
  212. when = timeJumpYearMonth(when, when.Year(), i)
  213. updateGrid(grid, when, weekStart, updateWhen, updateSelects)
  214. })
  215. monthSelect.Selected = when.Month().String()
  216. years := []string{}
  217. // inverted years, most recent on top for easy selection
  218. for n := when.Year() + 10; n >= when.Year()-100; n-- {
  219. years = append(years, fmt.Sprintf("%d", n))
  220. }
  221. yearSelect := widget.NewSelect(years, func(selected string) {
  222. i, err := strconv.ParseInt(selected, 10, 64)
  223. if err != nil {
  224. return
  225. }
  226. when = timeJumpYearMonth(when, int(i), int(when.Month()))
  227. updateGrid(grid, when, weekStart, updateWhen, updateSelects)
  228. })
  229. yearSelect.Selected = fmt.Sprintf("%d", when.Year())
  230. updateSelects = func(t time.Time) {
  231. // directly assign instead of setter methods to avoid multiple updates
  232. monthSelect.Selected = t.Month().String()
  233. monthSelect.Refresh()
  234. yearSelect.Selected = fmt.Sprintf("%d", t.Year())
  235. yearSelect.Refresh()
  236. updateGrid(grid, t, weekStart, updateWhen, updateSelects)
  237. }
  238. prevMonthButton := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() {
  239. when = timeJumpMonth(when, -1)
  240. updateSelects(when)
  241. })
  242. nextMonthButton := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() {
  243. when = timeJumpMonth(when, 1)
  244. updateSelects(when)
  245. })
  246. top := container.New(
  247. // previous and next button left and right
  248. layout.NewBorderLayout(nil, nil, prevMonthButton, nextMonthButton),
  249. prevMonthButton,
  250. nextMonthButton,
  251. // month and year dropdowns centered in the middle
  252. container.New(
  253. layout.NewHBoxLayout(),
  254. layout.NewSpacer(),
  255. monthSelect,
  256. yearSelect,
  257. layout.NewSpacer(),
  258. ),
  259. )
  260. updateGrid(grid, when, weekStart, updateWhen, updateSelects)
  261. hours := []string{}
  262. for n := 0; n < 23; n++ {
  263. hours = append(hours, fmt.Sprintf("%02d", n))
  264. }
  265. hourInput := widget.NewSelectEntry(hours)
  266. minutes := []string{}
  267. for n := 0; n < 59; n++ {
  268. minutes = append(minutes, fmt.Sprintf("%02d", n))
  269. }
  270. minuteInput := widget.NewSelectEntry(minutes)
  271. controlButtons := container.New(
  272. layout.NewHBoxLayout(),
  273. widget.NewButton("现在", func() {
  274. when = time.Now()
  275. hourInput.SetText(when.Format("15"))
  276. minuteInput.SetText(when.Format("04"))
  277. updateSelects(when)
  278. fn(when, true)
  279. }),
  280. widget.NewButton("取消", func() {
  281. fn(when, false)
  282. }),
  283. widget.NewButton("确认", func() {
  284. fn(when, true)
  285. }),
  286. )
  287. hourInput.SetText(when.Format("15"))
  288. hourInput.OnChanged = func(str string) {
  289. t, err := time.Parse("15", str)
  290. if err != nil {
  291. fyne.LogError("invalid hour", err)
  292. return
  293. }
  294. when = time.Date(
  295. when.Year(),
  296. when.Month(),
  297. when.Day(),
  298. t.Hour(),
  299. when.Minute(),
  300. 0,
  301. 0,
  302. when.Location(),
  303. )
  304. }
  305. minuteInput.SetText(when.Format("04"))
  306. minuteInput.OnChanged = func(str string) {
  307. i, err := strconv.ParseInt(str, 10, 64)
  308. if err != nil {
  309. fyne.LogError("failed to parse minte value", err)
  310. return
  311. }
  312. if i < 0 || i > 59 {
  313. fyne.LogError("minute value out of range", err)
  314. return
  315. }
  316. when = time.Date(
  317. when.Year(),
  318. when.Month(),
  319. when.Day(),
  320. when.Hour(),
  321. int(i),
  322. 0,
  323. 0,
  324. when.Location(),
  325. )
  326. }
  327. timeForm := widget.NewForm(
  328. widget.NewFormItem("Time",
  329. container.NewHBox(hourInput, widget.NewLabel(":"), minuteInput),
  330. ),
  331. )
  332. bottom := container.New(
  333. layout.NewBorderLayout(nil, nil, nil, controlButtons),
  334. controlButtons,
  335. timeForm,
  336. )
  337. return container.New(
  338. layout.NewBorderLayout(top, bottom, nil, nil),
  339. top,
  340. grid,
  341. bottom,
  342. )
  343. }