diff options
| -rw-r--r-- | README.md | 15 | ||||
| -rw-r--r-- | widgets/calendar.lua | 91 |
2 files changed, 92 insertions, 14 deletions
@@ -154,16 +154,25 @@ takes its calendar's colour; today takes a filled pill, coloured by its own calendar when something is on and `highlight` otherwise, with the number over it in the heading colour because a calendar colour loses too much contrast against the fill. Past days in the month dim: the grid is mostly about what is -still coming. Below the grid, one row per appointment, start time left in the -calendar's colour and title right in the body colour. Colour-coding the whole +still coming. Below the grid, one row per appointment: date and start time +left in the calendar's colour, title right in the body colour. The date is +day/month with no year, since the window is a week and the grid above already +names the month. Colour-coding the whole row makes three calendars read as three unrelated cards, so the time carries the code and the title stays readable. All-day events read `all day`, never `00:00`, because midnight is a legitimate start time. +A legend of the calendar colours is pinned along the card's bottom, in name +order so it does not reshuffle between frames, and the event rows end above it +rather than running under it. It lists every calendar khal declared a colour +for, including ones with nothing on this week: it is a colour key, not a second +list of what is coming. + Events come from a cache file written by `bin/calendar-sample.sh`, which shells out to `khal`. The card shows as many rows as the cell affords and ends with `+N more` when the week does not fit, since a card that silently drops the rest -of the week defeats its own purpose. +of the week defeats its own purpose. A cell too short for even one row draws +the grid alone, without the legend, rather than a clipped half-row. **Calendar colours come from khal's own config**, parsed out of the `[calendars]` section by the sampler, then mapped onto the board's palette diff --git a/widgets/calendar.lua b/widgets/calendar.lua index 82e10d3..6194447 100644 --- a/widgets/calendar.lua +++ b/widgets/calendar.lua @@ -41,11 +41,15 @@ local function cal_colour(name, colors, khal_colors) return colors[role] or colors.value end --- 'Dentist' at 13:00 -> '13:00', an all-day event -> 'all day'. --- Never '00:00' for an all-day event: midnight is a legitimate start time and --- the two must not read the same. +-- 'Dentist' on 24 September at 13:00 -> '24/09 13:00', an all-day event -> +-- '26/09 all day'. Never '00:00' for an all-day event: midnight is a +-- legitimate start time and the two must not read the same. +-- +-- Day/month, no year: the grid above already names the month, and the window +-- is a week, so a year would be three characters of the row spent on the one +-- field that cannot vary. local function when(e) - return e.start or 'all day' + return string.format('%02d/%02d %s', e.d, e.m, e.start or 'all day') end -- The failure face: the sampler has not run. Named, as the other cached cards @@ -66,6 +70,33 @@ local function draw_empty(cr, inner, colors, label_f, note, hint) card.text(cr, inner.x, inner.y + S + S * label_f * 2.6 + S * 0.72, hint) end +-- The legend: one coloured dot and name per calendar, along the card's bottom. +-- Wraps to a second line when the names do not fit, and drops any that still +-- do not: a legend spilling off the card is worse than a short one, since the +-- colours are also readable from the grid above. +local function draw_legend(cr, inner, y, size, names, colors, khal_colors) + card.font(cr, card.FONT_MONO, size, false) + local r = size * 0.30 + local x = inner.x + local right = inner.x + inner.w + for _, name in ipairs(names) do + local w = r * 2 + size * 0.45 + card.advance(cr, name) + if x + w > right then + -- Next line, if the card has one to give. + x = inner.x + y = y + size * 1.5 + if y > inner.y + inner.h then return end + end + card.rgba(cr, cal_colour(name, colors, khal_colors)) + cairo_new_path(cr) + cairo_arc(cr, x + r, y - size * 0.32, r, 0, math.pi * 2) + cairo_fill(cr) + card.rgba(cr, colors.label, 0.85) + card.text(cr, x + r * 2 + size * 0.45, y, name) + x = x + w + size * 0.9 + end +end + function M.draw(cr, rect, colors) local inner = card.card(cr, rect, colors) local LABEL_F = 0.30 @@ -113,7 +144,9 @@ function M.draw(cr, rect, colors) card.text(cr, inner.x, inner.y + label_size, 'CALENDAR') -- === Month grid ========================================================= - local gy = inner.y + big_size * 1.5 + -- 2.3, not 1.5: the weekday row sat close enough to the header that the + -- initials read as part of it rather than as the top of the grid. + local gy = inner.y + big_size * 2.3 local row_h = day_size * 1.55 -- Weekday initials. Monday-first, matching khal's firstweekday = 0. @@ -173,10 +206,25 @@ function M.draw(cr, rect, colors) -- === Event table ======================================================== -- Everything below the grid, however much that is. A card too short for even -- one row draws the grid alone rather than a clipped half-row. - local avail = (inner.y + inner.h) - grid_bottom local ev_size = clamp(day_size * 0.78, 7, 18) local ev_step = ev_size * 1.75 - if avail < ev_step then return end + + -- The legend is pinned to the card's bottom, so the event rows end above it + -- rather than running under it. Only calendars that khal declared a colour + -- for appear, in the config's own order, which is why the names are sorted + -- rather than taken from a hash walk: pairs() would reorder the legend + -- between frames. + local legend = {} + for name in pairs(cal.colors) do legend[#legend + 1] = name end + table.sort(legend) + + local legend_size = ev_size * 0.82 + local legend_h = #legend > 0 and legend_size * 2.0 or 0 + local floor_y = inner.y + inner.h - legend_h + + local avail = floor_y - grid_bottom + if avail < ev_step then legend = {}; legend_h = 0; floor_y = inner.y + inner.h end + if (floor_y - grid_bottom) < ev_step then return end local ey = grid_bottom + ev_size * 1.1 @@ -184,6 +232,10 @@ function M.draw(cr, rect, colors) card.font(cr, card.FONT_UI, ev_size, false) card.rgba(cr, colors.label, 0.8) card.text(cr, inner.x, ey, 'nothing this week') + if #legend > 0 then + draw_legend(cr, inner, inner.y + inner.h - legend_size * 0.5, legend_size, + legend, colors, cal.colors) + end return end @@ -211,14 +263,26 @@ function M.draw(cr, rect, colors) -- A card that silently drops the rest of the week is worse than one showing -- fewer events and admitting it: the whole point of the card is knowing -- what is coming. - local room = math.floor(((inner.y + inner.h) - ey + ev_step) / ev_step) + -- + -- Counted from the same test the drawing loop uses (a row at `ey` fits when + -- ey <= floor_y), not an independent division: the two disagreeing by one + -- row is what made the overflow line vanish exactly when it was needed, + -- since the loop cut the row the count had already spent. + local room = 0 + local probe = ey + while probe <= floor_y do room = room + 1; probe = probe + ev_step end + local shown = #cal.events - if room < shown then shown = math.max(room - 1, 1) end + -- The overflow line costs a row, so it is only worth taking when it reports + -- more than the row it displaces. + if room < shown then + shown = (room >= 2) and (room - 1) or room + end local hidden = #cal.events - shown for i = 1, shown do local e = cal.events[i] - if ey + ev_step > inner.y + inner.h then break end + if ey > floor_y then break end local colour = cal_colour(e.calendar, colors, cal.colors) card.font(cr, card.FONT_MONO, ev_size, false) @@ -237,11 +301,16 @@ function M.draw(cr, rect, colors) -- The overflow line, in the label colour so it reads as chrome rather than -- as another appointment. - if hidden > 0 and ey + ev_step <= inner.y + inner.h + ev_step then + if hidden > 0 and ey <= floor_y then card.font(cr, card.FONT_UI, ev_size * 0.92, false) card.rgba(cr, colors.label, 0.8) card.text(cr, inner.x, ey, string.format('+%d more', hidden)) end + + if #legend > 0 then + draw_legend(cr, inner, inner.y + inner.h - legend_size * 0.5, legend_size, + legend, colors, cal.colors) + end end return M |
