diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-18 14:31:39 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-18 14:31:39 +0200 |
| commit | 28a3edc57047c2d8176e1cc726308c70f34ebc2e (patch) | |
| tree | b4783aa6b042f317af2331b3330d667406506438 | |
| parent | 719e1d98d61aa0614db86a9cab79402b304c4852 (diff) | |
| download | conky-theme-udt-28a3edc57047c2d8176e1cc726308c70f34ebc2e.tar.gz conky-theme-udt-28a3edc57047c2d8176e1cc726308c70f34ebc2e.zip | |
feat(now): current-conditions card replaces the OWM weather card
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
| -rw-r--r-- | dashboard.lua | 2 | ||||
| -rw-r--r-- | widgets/now.lua | 51 | ||||
| -rw-r--r-- | widgets/weather.lua | 202 |
3 files changed, 52 insertions, 203 deletions
diff --git a/dashboard.lua b/dashboard.lua index 53b0ab8..579c08a 100644 --- a/dashboard.lua +++ b/dashboard.lua @@ -21,7 +21,7 @@ local COLS, ROWS = 16, 12 local layout = { { widget = 'clock', col = 1, row = 1.5, w = 1.5, h = 5 }, -- Under the clock, same column. - { widget = 'weather', col = 1, row = 7, w = 3, h = 5 }, + { widget = 'now', col = 1, row = 7, w = 3, h = 5 }, -- In the board's open middle column, left of immich. { widget = 'calendar', col = 2.5, row = 1, w = 3, h = 6 }, -- Clustered near weather and clock diff --git a/widgets/now.lua b/widgets/now.lua new file mode 100644 index 0000000..8fc6843 --- /dev/null +++ b/widgets/now.lua @@ -0,0 +1,51 @@ +-- Now: the current conditions. Replaces the OpenWeatherMap weather card; the +-- daylight arc it carried moved to the sky card. +-- +-- One of the forecast cards adapted from the Conky-Mirage theme by +-- alexsson-xexpanderx (see lib/weather.lua). + +local card = require 'lib.card' +local data = require 'lib.data' +local weather = require 'lib.weather' + +local M = {} + +function M.draw(cr, rect, colors) + local inner = card.card(cr, rect, colors) + local w = weather.parse(data.slurp(weather.CACHE)) + if not w then + -- Closed explicitly: this branch reruns every frame until a fetch lands, + -- so an unclosed handle would accumulate one per draw. + local envf = io.open((os.getenv('HOME') or '') .. '/.config/udt/weather.env', 'r') + local hint = 'set ~/.config/udt/weather.env' + if envf then + envf:close() + hint = 'bin/weather-fetch.sh' + end + card.notice(cr, inner, colors, 'WEATHER', 'no weather data', hint) + return + end + + local c, today, now = w.current, w.days[1] or {}, os.time() + local rows = { + { 'FEELS LIKE', weather.deg(c.feels) }, + { 'HIGH / LOW', weather.deg(today.max) .. ' / ' .. weather.deg(today.min) }, + { 'RAIN', weather.num(today.precip, '%') }, + { 'UPDATED', weather.age_str(c.dt, now), + weather.is_stale(c.dt, now) and colors.warning or nil }, + } + local y, S = card.standard(cr, inner, colors, { + icon = weather.icon(c.code, c.is_day), title = 'WEATHER', + big = weather.deg(c.temp), rows = rows, share = 0.85, + }) + + -- The condition in words, in the prose face, in what the rows left. + local size = S * 0.36 + if y + size * 1.4 <= inner.y + inner.h then + card.font(cr, card.FONT_UI, size, false) + card.rgba(cr, colors.body) + card.text(cr, inner.x, y + size * 1.4, weather.describe(c.code)) + end +end + +return M diff --git a/widgets/weather.lua b/widgets/weather.lua deleted file mode 100644 index a0c7b3a..0000000 --- a/widgets/weather.lua +++ /dev/null @@ -1,202 +0,0 @@ --- Weather: current conditions over a sunrise-to-sunset arc. --- --- Shape follows idea1.png, which splits this across two cards; this merges --- them into one vertical cell. Every colour comes from the palette, every size --- derives from the rect, so the card composes on either monitor. - -local card = require 'lib.card' -local weather = require 'lib.weather' - -local M = {} - -local CACHE = (os.getenv('XDG_CACHE_HOME') or (os.getenv('HOME') .. '/.cache')) - .. '/udt/weather.json' - --- The daylight arc: a curve, its baseline, the sun, and the two times. --- --- The dot is placed by evaluating the curve at t rather than by computing a --- point on a circle: the curve IS the path, so evaluating it keeps the dot on --- the arc if the control points are ever adjusted. -local function draw_arc(cr, x, y, w, h, colors, w_data, now) - -- Every dimension derives from the band, not fixed pixels: the time labels - -- take a fraction of the height, the curve's room is what they leave, and the - -- dot and edge pad follow the same proportions so the arc composes at any - -- size the cell gives it. - local label_h = math.max(10, math.min(16, h * 0.16)) - local pad = math.max(4, w * 0.012) - local x0, x1 = x + pad, x + w - pad - -- Room under the curve for the time labels. - local base = y + h - label_h * 1.5 - local top = y + h * 0.08 - - -- Cubic Bezier control points, chosen so the curve peaks near the middle at - -- about two thirds of the band height. - local p0 = { x0, base } - local c1 = { x0 + (x1 - x0) * 0.22, top - h * 0.14 } - local c2 = { x1 - (x1 - x0) * 0.22, top - h * 0.14 } - local p3 = { x1, base } - - -- The baseline: the horizon the sun rises from and sets into. - card.rgba(cr, colors.rule, 0.6) - cairo_set_line_width(cr, 1) - cairo_move_to(cr, x0, base) - cairo_line_to(cr, x1, base) - cairo_stroke(cr) - - -- The arc itself. - card.rgba(cr, colors.rule, 0.9) - cairo_set_line_width(cr, 1.5) - cairo_move_to(cr, p0[1], p0[2]) - cairo_curve_to(cr, c1[1], c1[2], c2[1], c2[2], p3[1], p3[2]) - cairo_stroke(cr) - - local t = weather.sun_t(now, w_data.sunrise, w_data.sunset) or 0 - local day = weather.is_day(now, w_data.sunrise, w_data.sunset) - - -- Evaluate the cubic at t. - local function bez(a, b, c, d) - local u = 1 - t - return u * u * u * a + 3 * u * u * t * b + 3 * u * t * t * c + t * t * t * d - end - local sx = bez(p0[1], c1[1], c2[1], p3[1]) - local sy = bez(p0[2], c1[2], c2[2], p3[2]) - - -- At night the dot is parked at an end, so it takes the dim colour: a bright - -- dot sitting on the horizon would read as a sun that is still up. - card.rgba(cr, day and colors.body or colors.label, 1) - cairo_arc(cr, sx, sy, math.max(3, math.min(9, h * 0.045)), 0, math.pi * 2) - cairo_fill(cr) - - -- The times, each behind its own glyph, at the ends of the arc. - card.font(cr, card.FONT_MONO, label_h, false) - card.rgba(cr, colors.label) - local ty = y + h - label_h * 0.15 - card.text(cr, x0, ty, - weather.ICON.sunrise .. ' ' .. os.date('%H:%M', w_data.sunrise)) - card.text_right(cr, x1, ty, - weather.ICON.sunset .. ' ' .. os.date('%H:%M', w_data.sunset)) -end - -function M.draw(cr, rect, colors) - local inner = card.card(cr, rect, colors) - local now = os.time() - - local src = nil - local f = io.open(CACHE, 'r') - if f then src = f:read('*a'); f:close() end - - local w = weather.parse(src) - if not w then - -- Closed explicitly: an unclosed handle here would accumulate one per - -- draw (every update_interval), since this whole branch reruns each - -- frame until a fetch succeeds. - local envf = io.open(os.getenv('HOME') .. '/.config/udt/weather.env', 'r') - local hint = 'waiting for first fetch' - if not envf then - hint = 'set ~/.config/udt/weather.env' - else - envf:close() - end - card.notice(cr, inner, colors, 'WEATHER', 'no weather data', hint) - return - end - - -- The card is much taller than idea1's, so the bands are placed against the - -- rect rather than at fixed offsets: content sits in a block at the top, the - -- arc gets a bounded band pinned to the bottom, and the slack falls between - -- them. Letting the arc take all the leftover height instead drew a 794px - -- cathedral arch on this monitor, with the content crammed into the top - -- fifth. Scaled, not fixed, so the 1920 monitor gets the same proportions. - local y = inner.y - - -- Type sizes scale with the card, within limits that keep them legible on a - -- small cell and stop them ballooning on a tall one. - -- Sizes scale off the WIDTH, not the height: the widget sits in a 1x1 cell - -- whose height changes with the grid, and scaling off height made the type - -- shrink to a whisper beside the clock's numerals when the cell got shorter. - -- Width is the stable dimension here, and it is what constrains the text - -- anyway, since the stat rows run edge to edge. - local function clamp(v, lo, hi) return math.max(lo, math.min(hi, v)) end - local temp_size = clamp(inner.w * 0.135, 40, 84) - local glyph_size = clamp(temp_size * 0.95, 38, 78) - local label_size = clamp(inner.w * 0.030, 12, 19) - local desc_size = clamp(inner.w * 0.038, 15, 24) - local row_size = clamp(inner.w * 0.032, 13, 20) - local row_step = row_size * 2.0 - - -- Header, mirrored to match the other cards: the condition glyph large on - -- the left, the big temperature and the rest of the text against the right - -- edge. text_right takes the RIGHT edge as its x, and measures ink rather - -- than advance, which is what you want for aligning to an edge. - local glyph = weather.icon(w.id, now, w.sunrise, w.sunset) - card.font(cr, card.FONT_MONO, glyph_size, false) - card.rgba(cr, colors.highlight) - card.text(cr, inner.x, y + glyph_size * 0.95, glyph) - - card.font(cr, card.FONT_HEAVY, temp_size, false) - card.rgba(cr, colors.body) - card.text_right(cr, inner.x + inner.w, y + temp_size * 0.86, - string.format('%d\u{00B0}', math.floor(w.temp + 0.5))) - - card.font(cr, card.FONT_MONO, label_size, false) - card.rgba(cr, colors.label) - local city = (w.city or ''):upper() - if weather.is_stale(w.dt, now) then - city = city .. ' stale ' .. weather.age_str(w.dt, now) - end - card.text_right(cr, inner.x + inner.w, y + temp_size * 1.28, city) - - -- Condition, in OWM's own words, first letter capitalised. - card.font(cr, card.FONT_UI, desc_size, false) - card.rgba(cr, colors.body) - local desc = w.description or '' - desc = desc:sub(1, 1):upper() .. desc:sub(2) - local dy = y + temp_size * 1.95 - card.text_right(cr, inner.x + inner.w, dy, desc) - - -- Rule. - local ry = dy + desc_size * 1.4 - card.rgba(cr, colors.rule, 0.8) - cairo_set_line_width(cr, 1) - cairo_move_to(cr, inner.x, ry) - cairo_line_to(cr, inner.x + inner.w, ry) - cairo_stroke(cr) - - -- Stats: label left, value right. - local kmh = weather.kmh(w.wind_speed) - local wind_txt = kmh - and string.format('%s %d km/h', weather.arrow(w.wind_deg), math.floor(kmh + 0.5)) - or '--' - local rows = { - { 'FEELS LIKE', w.feels_like and string.format('%d\u{00B0}', math.floor(w.feels_like + 0.5)) or '--' }, - { 'HUMIDITY', w.humidity and (w.humidity .. '%') or '--' }, - { 'WIND', wind_txt }, - } - local sy = ry + row_step - for _, row in ipairs(rows) do - card.font(cr, card.FONT_MONO, row_size, false) - card.rgba(cr, colors.label) - card.text(cr, inner.x, sy, row[1]) - card.rgba(cr, colors.value) - card.text_right(cr, inner.x + inner.w, sy, row[2]) - sy = sy + row_step - end - - -- The arc takes what is genuinely left below the stats, with no minimum: a - -- floor here meant a short cell pulled the curve up THROUGH the stat rows, - -- drawing the sunrise time over the word WIND. Below the height where it - -- can be read it is dropped entirely, because no arc beats a broken one. - -- This is what lets one widget sit in a 2x2 or a 2x3 cell unchanged. - local bottom = inner.y + inner.h - local arc_y = sy + row_step * 0.6 - -- The cap is a proportion of the card's width, not a fixed 240px: an arch - -- taller than about 0.6 of its own width reads as a cathedral door rather - -- than a day's arc, and the ceiling has to follow a card that is resized. - local arc_h = math.min(bottom - arc_y, inner.h * 0.34, inner.w * 0.6) - if arc_h >= 70 then - draw_arc(cr, inner.x, arc_y, inner.w, arc_h, colors, w, now) - end -end - - -return M |
