150 lines
4.7 KiB
Go
150 lines
4.7 KiB
Go
package mujica
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"mujicaui/internal"
|
|
"mujicaui/theme"
|
|
)
|
|
|
|
// AgendaViewOptions configure an AgendaView.
|
|
type AgendaViewOptions struct {
|
|
// Location is the zone days are cut in; required.
|
|
Location *time.Location
|
|
// Version must change when events change in place.
|
|
Version int
|
|
// From is the first day listed; zero means today.
|
|
From Date
|
|
// Days is how many days are listed; zero means 30.
|
|
Days int
|
|
// Today is marked; zero uses the system clock.
|
|
Today Date
|
|
Hour12 bool
|
|
Disabled bool
|
|
Label string
|
|
}
|
|
|
|
// agendaRow is a day heading (ev < 0) or event.
|
|
type agendaRow struct {
|
|
day Date
|
|
ev int
|
|
}
|
|
|
|
// AgendaView lists events by day, building only visible rows.
|
|
func AgendaView(c *ui.Context, events []CalendarEvent, opts AgendaViewOptions) CalendarViewResult {
|
|
loc := eventLocation("AgendaView", opts.Location)
|
|
if opts.Days < 0 {
|
|
panic("mujica: AgendaView Days is negative")
|
|
}
|
|
days := opts.Days
|
|
if days == 0 {
|
|
days = 30
|
|
}
|
|
eventsCheck("AgendaView", events)
|
|
k := Tokens(c)
|
|
d := Density(c)
|
|
today := datetimeToday(c, opts.Today)
|
|
from := opts.From
|
|
if from.IsZero() {
|
|
from = today
|
|
}
|
|
datetimeCheckDate("AgendaView From", from)
|
|
label := opts.Label
|
|
if label == "" {
|
|
label = msg(c, "agenda_view.label")
|
|
}
|
|
r := CalendarViewResult{}
|
|
root := ui.Column(c).Grow(1).MinHeight(0).Role(ui.RoleGroup).Label(label).Disabled(opts.Disabled)
|
|
r.Element = root
|
|
rows := eventsDerived(root, "agenda-rows", events, opts.Version, eventDayStart(from, loc), eventDayStart(from.AddDays(days), loc),
|
|
func(hits []int) []agendaRow { return agendaRows(events, hits, from, days, loc) })
|
|
state := ui.Local(root, "agenda-list", func() *ui.ListState { return &ui.ListState{} })
|
|
st := *state
|
|
st.Key = func(i int) any { return rows[i] }
|
|
st.Header = func(i int) bool { return rows[i].ev < 0 }
|
|
st.Label = func(i int) string {
|
|
if rows[i].ev < 0 {
|
|
return datetimeDayLabel(c, rows[i].day)
|
|
}
|
|
return events[rows[i].ev].Title
|
|
}
|
|
root.Children(func() {
|
|
list := ui.List(c, st, len(rows), func(i int) {
|
|
row := rows[i]
|
|
if row.ev < 0 {
|
|
h := ui.Row(c).MinHeight(d.ControlHeight()).AlignItems(ui.Center).Gap(d.Unit() * 2).PaddingX(d.Unit() * 3).Background(k.Background).Role(ui.RoleColumnHeader)
|
|
h.Children(func() {
|
|
t := ui.Text(c, datetimeDayLabel(c, row.day)).Font(theme.SerifFont).TextColor(k.Text)
|
|
if row.day == today {
|
|
t.Bold().TextColor(k.AccentText)
|
|
ui.Text(c, msg(c, "agenda_view.today")).FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.AccentText)
|
|
}
|
|
})
|
|
h.DrawOver(func(p *ui.Painter, rr ui.Rect) { internal.Rule(p, rr.X, rr.X+rr.W, rr.Y+rr.H-1, k.Ornament) })
|
|
return
|
|
}
|
|
if agendaItem(c, events[row.ev], loc, opts.Hour12) && !root.IsDisabled() {
|
|
r.opened = events[row.ev].ID
|
|
}
|
|
}).Grow(1).MinHeight(d.ControlHeight() * 4).FocusGroup(ui.Vertical)
|
|
list.Children(func() {
|
|
if len(rows) == 0 {
|
|
ui.Text(c, fmt.Sprintf(msg(c, "agenda_view.empty"), days)).TextColor(k.TextMuted).Padding(d.Unit() * 3)
|
|
}
|
|
})
|
|
})
|
|
return r
|
|
}
|
|
|
|
// agendaRows flattens the span into headed days.
|
|
func agendaRows(events []CalendarEvent, hits []int, from Date, days int, loc *time.Location) []agendaRow {
|
|
buckets := make([][]int, days)
|
|
for _, h := range hits {
|
|
ev := events[h]
|
|
first := max(0, monthDays(ev.Start.In(loc), from)*-1)
|
|
last := min(days-1, -monthDays(ev.End.Add(-time.Nanosecond).In(loc), from))
|
|
for i := first; i <= last; i++ {
|
|
buckets[i] = append(buckets[i], h)
|
|
}
|
|
}
|
|
var out []agendaRow
|
|
for i, b := range buckets {
|
|
if len(b) > 0 {
|
|
out = append(out, agendaRow{from.AddDays(i), -1})
|
|
for _, h := range b {
|
|
out = append(out, agendaRow{from.AddDays(i), h})
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// agendaItem is one event row; it reports a click.
|
|
func agendaItem(c *ui.Context, ev CalendarEvent, loc *time.Location, hour12 bool) bool {
|
|
k := Tokens(c)
|
|
d := Density(c)
|
|
when := eventTimeText(c, ev, loc, hour12)
|
|
b := ui.ButtonBase(c).MinHeight(d.ControlHeight()).Justify(ui.Start).Gap(d.Unit() * 3).PaddingX(d.Unit() * 3).Label(ev.Title + ", " + when)
|
|
internal.Ring(b, k.Focus, theme.ControlRadius)
|
|
if b.Hovered() {
|
|
b.Background(k.SurfaceHover)
|
|
}
|
|
col := eventColor(k, ev.Color)
|
|
b.Children(func() {
|
|
ui.Text(c, when).Width(d.ControlHeight() * 3.5).Shrink(0).FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted).FontFeatures("tnum")
|
|
ui.Box(c).Size(8, 8).Shrink(0).Draw(func(p *ui.Painter, r ui.Rect) {
|
|
p.FillPath(internal.Diamond(r.X+r.W/2, r.Y+r.H/2, r.W), col)
|
|
})
|
|
ui.Column(c).Grow(1).Shrink(1).Children(func() {
|
|
ui.Text(c, ev.Title).TextColor(k.Text).SingleLine().Ellipsis("…")
|
|
if ev.Location != "" {
|
|
ui.Text(c, ev.Location).FontSize(FontSize(c, theme.CaptionSize)).TextColor(k.TextMuted).SingleLine().Ellipsis("…")
|
|
}
|
|
})
|
|
})
|
|
return b.Clicked()
|
|
}
|