167 lines
3.9 KiB
Go
167 lines
3.9 KiB
Go
package objects
|
|
|
|
import "fmt"
|
|
import "time"
|
|
import "git.tebibyte.media/tomo/tomo"
|
|
import "git.tebibyte.media/tomo/tomo/event"
|
|
import "git.tebibyte.media/tomo/objects/layouts"
|
|
|
|
// Calendar is an object that can display a date and allow the user to change
|
|
// it. It can display one month at a time.
|
|
type Calendar struct {
|
|
tomo.ContainerBox
|
|
|
|
grid tomo.ContainerBox
|
|
time time.Time
|
|
monthLabel *Label
|
|
|
|
on struct {
|
|
edit event.FuncBroadcaster
|
|
}
|
|
}
|
|
|
|
// NewCalendar creates a new calendar with the specified date.
|
|
func NewCalendar (tm time.Time) *Calendar {
|
|
calendar := &Calendar {
|
|
ContainerBox: tomo.NewContainerBox(),
|
|
time: tm,
|
|
}
|
|
calendar.SetRole(tomo.R("objects", "Calendar", ""))
|
|
calendar.SetLayout(layouts.ContractVertical)
|
|
|
|
prevButton := NewButton("")
|
|
prevButton.SetIcon(tomo.IconGoPrevious)
|
|
prevButton.OnClick(func () {
|
|
calendar.prevMonth()
|
|
calendar.on.edit.Broadcast()
|
|
})
|
|
nextButton := NewButton("")
|
|
nextButton.SetIcon(tomo.IconGoNext)
|
|
nextButton.OnClick(func () {
|
|
calendar.nextMonth()
|
|
calendar.on.edit.Broadcast()
|
|
})
|
|
calendar.monthLabel = NewLabel("")
|
|
calendar.monthLabel.SetAlign(tomo.AlignMiddle, tomo.AlignMiddle)
|
|
|
|
calendar.grid = tomo.NewContainerBox()
|
|
calendar.grid.SetRole(tomo.R("objects", "CalendarGrid", ""))
|
|
calendar.grid.SetLayout(layouts.NewGrid (
|
|
true, true, true, true, true, true, true)())
|
|
calendar.Add(NewInnerContainer (
|
|
layouts.Row { false, true, false },
|
|
prevButton, calendar.monthLabel, nextButton))
|
|
calendar.Add(calendar.grid)
|
|
|
|
calendar.OnScroll(calendar.handleScroll)
|
|
calendar.CaptureScroll(true)
|
|
|
|
calendar.refresh()
|
|
return calendar
|
|
}
|
|
|
|
// SetTime sets the date the calendar will display.
|
|
func (this *Calendar) SetTime (tm time.Time) {
|
|
if this.time == tm { return }
|
|
this.time = tm
|
|
this.refresh()
|
|
}
|
|
|
|
// OnEdit sets a function to be called when the user changes the date on the
|
|
// calendar.
|
|
func (this *Calendar) OnEdit (callback func ()) {
|
|
this.on.edit.Connect(callback)
|
|
}
|
|
|
|
func (this *Calendar) prevMonth () {
|
|
this.time = firstOfMonth(this.time.Add(24 * time.Hour * -20))
|
|
this.refresh()
|
|
}
|
|
|
|
func (this *Calendar) nextMonth () {
|
|
this.time = firstOfMonth(this.time.Add(24 * time.Hour * 40))
|
|
this.refresh()
|
|
}
|
|
|
|
var weekdayAbbreviations = []string {
|
|
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
|
|
}
|
|
|
|
func (this *Calendar) refresh () {
|
|
this.monthLabel.SetText(this.time.Format("2006 January"))
|
|
|
|
this.grid.Clear()
|
|
for _, day := range weekdayAbbreviations {
|
|
dayLabel := tomo.NewTextBox()
|
|
dayLabel.SetRole(tomo.R("objects", "CalendarWeekdayHeader", ""))
|
|
dayLabel.SetText(day)
|
|
dayLabel.SetAlign(tomo.AlignMiddle, tomo.AlignMiddle)
|
|
this.grid.Add(dayLabel)
|
|
}
|
|
|
|
dayIter := 0 - int(firstOfMonth(this.time).Weekday())
|
|
if dayIter <= -6 {
|
|
dayIter = 1
|
|
}
|
|
weekday := 0
|
|
totalDays := daysInMonth(this.time)
|
|
for ; dayIter <= totalDays; dayIter ++ {
|
|
weekday = (weekday + 1) % 7
|
|
if dayIter > 0 {
|
|
day := tomo.NewTextBox()
|
|
day.SetText(fmt.Sprint(dayIter))
|
|
if weekday == 1 || weekday == 0 {
|
|
day.SetRole(tomo.R("objects", "CalendarDay", "weekend"))
|
|
} else {
|
|
day.SetRole(tomo.R("objects", "CalendarDay", "weekday"))
|
|
}
|
|
this.grid.Add(day)
|
|
} else {
|
|
this.grid.Add(tomo.NewBox())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (this *Calendar) handleScroll (x, y float64) {
|
|
if y < 0 {
|
|
this.prevMonth()
|
|
} else {
|
|
this.nextMonth()
|
|
}
|
|
}
|
|
|
|
func firstOfMonth (tm time.Time) time.Time {
|
|
return time.Date(tm.Year(), tm.Month(), 0, 0, 0, 0, 0, time.Local)
|
|
}
|
|
|
|
func daysInMonth (tm time.Time) (days int) {
|
|
year := tm.Year()
|
|
month := tm.Month()
|
|
switch month {
|
|
case 1: days = 31
|
|
case 2:
|
|
// betcha didn't know this about leap years
|
|
if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
|
|
days = 29
|
|
} else {
|
|
days = 28
|
|
}
|
|
case 3: days = 31
|
|
case 4: days = 30
|
|
case 5: days = 31
|
|
case 6: days = 30
|
|
case 7: days = 31
|
|
case 8: days = 31
|
|
case 9: days = 30
|
|
case 10: days = 31
|
|
case 11: days = 30
|
|
case 12: days = 31
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func canonMonth (tm time.Time) int {
|
|
return int(tm.Month() - 1) + tm.Year() * 12
|
|
}
|