aboutsummaryrefslogtreecommitdiffstats
path: root/widgets/weather.lua
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-18 14:31:39 +0200
committerDanilo M. <danix@danix.xyz>2026-10-18 14:31:39 +0200
commit28a3edc57047c2d8176e1cc726308c70f34ebc2e (patch)
treeb4783aa6b042f317af2331b3330d667406506438 /widgets/weather.lua
parent719e1d98d61aa0614db86a9cab79402b304c4852 (diff)
downloadconky-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>
Diffstat (limited to 'widgets/weather.lua')
-rw-r--r--widgets/weather.lua202
1 files changed, 0 insertions, 202 deletions
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