Calendar: components 421-440
This commit is contained in:
110 files changed
+7335
-23
No files matched your search
+168
@@ -0,0 +1,168 @@
|
||||
package mujica
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/egoist/mygo/ui"
|
||||
|
||||
"mujicaui/internal"
|
||||
"mujicaui/theme"
|
||||
)
|
||||
|
||||
// YearViewOptions configure a YearView.
|
||||
type YearViewOptions struct {
|
||||
// Location is the zone days are cut in; required.
|
||||
Location *time.Location
|
||||
// Version must change when events change in place.
|
||||
Version int
|
||||
WeekStart WeekStart
|
||||
// Today is marked; zero uses the system clock.
|
||||
Today Date
|
||||
Disabled bool
|
||||
Label string
|
||||
}
|
||||
|
||||
// YearView shows twelve months with busy-day dots.
|
||||
func YearView(c *ui.Context, year *int, events []CalendarEvent, opts YearViewOptions) CalendarViewResult {
|
||||
loc := eventLocation("YearView", opts.Location)
|
||||
if *year < 1 {
|
||||
panic(fmt.Sprintf("mujica: YearView year %d is not valid", *year))
|
||||
}
|
||||
eventsCheck("YearView", events)
|
||||
d := Density(c)
|
||||
today := datetimeToday(c, opts.Today)
|
||||
first := Date{*year, time.January, 1}
|
||||
label := opts.Label
|
||||
if label == "" {
|
||||
label = strconv.Itoa(*year)
|
||||
}
|
||||
r := CalendarViewResult{}
|
||||
root := ui.Column(c).Gap(d.Unit() * 3).Role(ui.RoleGroup).Label(label).Disabled(opts.Disabled)
|
||||
r.Element = root
|
||||
counts := eventsDerived(root, "year-view-counts", events, opts.Version, eventDayStart(first, loc), eventDayStart(first.AddMonths(12), loc),
|
||||
func(hits []int) map[Date]int {
|
||||
m := map[Date]int{}
|
||||
for _, h := range hits {
|
||||
ev := events[h]
|
||||
for day := DateOf(ev.Start.In(loc)); eventDayStart(day, loc).Before(ev.End); day = day.AddDays(1) {
|
||||
m[day]++
|
||||
}
|
||||
}
|
||||
return m
|
||||
})
|
||||
cursor := ui.Local(root, "year-view-cursor", func() Date { return Date{} })
|
||||
if cursor.Year != *year {
|
||||
*cursor = Date{*year, today.Month, min(today.Day, 28)}
|
||||
}
|
||||
root.Children(func() {
|
||||
calendarViewHeader(c, strconv.Itoa(*year), msg(c, "calendar.prev_year"), msg(c, "calendar.next_year"), func(dir int) {
|
||||
*year += dir
|
||||
})
|
||||
grid := ui.Row(c).Wrap().AlignItems(ui.Start).Gap(d.Unit() * 4).FocusGroup(ui.Horizontal | ui.Vertical)
|
||||
moved := false
|
||||
if !grid.IsDisabled() {
|
||||
for key, days := range map[ui.Key]int{ui.KeyLeft: -1, ui.KeyRight: 1, ui.KeyUp: -7, ui.KeyDown: 7} {
|
||||
if grid.Shortcut(0, key) {
|
||||
*cursor, moved = cursor.AddDays(days), true
|
||||
}
|
||||
}
|
||||
for key, months := range map[ui.Key]int{ui.KeyPageUp: -1, ui.KeyPageDown: 1} {
|
||||
if grid.Shortcut(0, key) {
|
||||
*cursor, moved = cursor.AddMonths(months), true
|
||||
}
|
||||
}
|
||||
if moved && cursor.Year != *year {
|
||||
*year = cursor.Year
|
||||
}
|
||||
}
|
||||
grid.Children(func() {
|
||||
for m := time.January; m <= time.December; m++ {
|
||||
if day, ok := yearMonth(c, *year, m, counts, today, *cursor, moved, opts.WeekStart); ok && !grid.IsDisabled() {
|
||||
r.day, *cursor = day, day
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// yearMonth draws one small month and reports a day clicked.
|
||||
func yearMonth(c *ui.Context, year int, m time.Month, counts map[Date]int, today, cursor Date, focus bool, ws WeekStart) (Date, bool) {
|
||||
k := Tokens(c)
|
||||
cell := theme.MinHitSize
|
||||
first := calendarFirstWeekday(c, ws)
|
||||
start := Date{year, m, 1}
|
||||
lead := (int(start.Weekday()) - int(first) + 7) % 7
|
||||
var picked Date
|
||||
ui.Column(c).Width(cell * 7).Gap(2).Children(func() {
|
||||
ui.Text(c, datetimeMonthName(c, m)).Font(theme.SerifFont).TextColor(k.Text).Bold()
|
||||
ui.Row(c).Children(func() {
|
||||
for i := range 7 {
|
||||
wd := (int(first) + i) % 7
|
||||
ui.Text(c, msg(c, "calendar.weekday_"+strconv.Itoa(wd))).Width(cell).TextAlign(ui.Center).
|
||||
FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted)
|
||||
}
|
||||
})
|
||||
last := start.AddMonths(1).AddDays(-1).Day
|
||||
for w := 0; w*7 < lead+last; w++ {
|
||||
ui.Row(c).Children(func() {
|
||||
for i := range 7 {
|
||||
n := w*7 + i - lead + 1
|
||||
if n < 1 || n > last {
|
||||
ui.Box(c).Size(cell, cell)
|
||||
continue
|
||||
}
|
||||
day := Date{year, m, n}
|
||||
busy := counts[day]
|
||||
text := datetimeDayLabel(c, day)
|
||||
if busy > 0 {
|
||||
text += ", " + calendarCount(c, "year_view.events", busy)
|
||||
}
|
||||
b := ui.ButtonBase(c).Size(cell, cell).Radius(theme.ControlRadius).Label(text).Column()
|
||||
internal.Ring(b, k.Focus, theme.ControlRadius)
|
||||
if day == cursor && focus {
|
||||
b.Focus()
|
||||
}
|
||||
switch {
|
||||
case day == today:
|
||||
b.Background(k.Selection)
|
||||
case b.Hovered():
|
||||
b.Background(k.SurfaceHover)
|
||||
}
|
||||
dots := yearDots(busy)
|
||||
b.Draw(func(p *ui.Painter, r ui.Rect) {
|
||||
x0 := r.X + r.W/2 - float32(dots*5-2)/2
|
||||
for j := range dots {
|
||||
p.Fill(ui.Rect{X: x0 + float32(j)*5, Y: r.Y + r.H - 6, W: 3, H: 3}, k.AccentText, 1.5)
|
||||
}
|
||||
})
|
||||
b.Children(func() {
|
||||
t := ui.Text(c, strconv.Itoa(n)).FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.Text).FontFeatures("tnum")
|
||||
if day == today {
|
||||
t.Bold().TextColor(k.AccentText)
|
||||
}
|
||||
})
|
||||
if b.Clicked() {
|
||||
picked = day
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
return picked, !picked.IsZero()
|
||||
}
|
||||
|
||||
// yearDots maps an event count to one to three dots.
|
||||
func yearDots(n int) int {
|
||||
switch {
|
||||
case n == 0:
|
||||
return 0
|
||||
case n <= 2:
|
||||
return 1
|
||||
case n <= 5:
|
||||
return 2
|
||||
}
|
||||
return 3
|
||||
}
|
||||
Reference in new issue
Block a user