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" var _ tomo.Object = new(Calendar) // Calendar is an object that can display a date and allow the user to change // it. It can display one month at a time. // // Sub-components: // - CalendarGrid organizes the days into a grid. // - CalendarWeekdayHeader appears at the top of each grid column, and shows // the day of the week that column represents. // - CalendarDay appears within the grid for each day of the current month. // // CalendarDay tags: // - [weekend] The day is a weekend. // - [weekday] The day is a weekday. type Calendar struct { box tomo.ContainerBox grid tomo.ContainerBox time time.Time monthLabel *Label on struct { valueChange event.FuncBroadcaster } } // NewCalendar creates a new calendar with the specified date. func NewCalendar (tm time.Time) *Calendar { calendar := &Calendar { box: tomo.NewContainerBox(), time: tm, } calendar.box.SetRole(tomo.R("objects", "Calendar")) calendar.box.SetAttr(tomo.ALayout(layouts.ContractVertical)) prevButton := NewButton("") prevButton.SetIcon(tomo.IconGoPrevious) prevButton.OnClick(func () { calendar.prevMonth() calendar.on.valueChange.Broadcast() }) nextButton := NewButton("") nextButton.SetIcon(tomo.IconGoNext) nextButton.OnClick(func () { calendar.nextMonth() calendar.on.valueChange.Broadcast() }) calendar.monthLabel = NewLabel("") calendar.monthLabel.SetAlign(tomo.AlignMiddle, tomo.AlignMiddle) calendar.grid = tomo.NewContainerBox() calendar.grid.SetRole(tomo.R("objects", "CalendarGrid")) calendar.grid.SetAttr(tomo.ALayout(layouts.NewGrid ( true, true, true, true, true, true, true)())) calendar.box.Add(NewContainer ( layouts.Row { false, true, false }, prevButton, calendar.monthLabel, nextButton)) calendar.box.Add(calendar.grid) calendar.box.OnScroll(calendar.handleScroll) calendar.refresh() return calendar } // GetBox returns the underlying box. func (this *Calendar) GetBox () tomo.Box { return this.box } // Value returns the time this calendar is displaying. func (this *Calendar) Value () time.Time { return this.time } // SetValue sets the date the calendar will display. func (this *Calendar) SetValue (tm time.Time) { if this.time == tm { return } this.time = tm this.refresh() } // OnValueChange sets a function to be called when the user changes the date on // the calendar. func (this *Calendar) OnValueChange (callback func ()) { this.on.valueChange.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.SetAttr(tomo.AAlign(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")) day.SetTag("weekend", true) } else { day.SetRole(tomo.R("objects", "CalendarDay")) day.SetTag("weekday", true) } this.grid.Add(day) } else { this.grid.Add(tomo.NewBox()) } } } func (this *Calendar) handleScroll (x, y float64) bool { if y < 0 { this.prevMonth() } else { this.nextMonth() } return true } 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 }