Skip to content

Commit 353dadf

Browse files
Color Timer Clock: update
1 parent dccab43 commit 353dadf

11 files changed

Lines changed: 501 additions & 271 deletions

File tree

cinnamon-color-timer-clock-desklet@curbsoftware/LICENSE

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
World Clock Desklet is free software: you can redistribute it and/or modify it
1+
Color Timer Clock Desklet is free software: you can redistribute it and/or modify it
22
under the terms of the GNU General Public License as published by the
33
Free Software Foundation, either version 2 of the License, or (at your
44
option) any later version.
55

66
Copyright (C) 2026 CurbSoftware
77

8-
World Clock Desklet is derived from TimeAndDate@nightflame, which is
9-
distributed under the GNU General Public License version 2.
8+
Its responsive sizing helpers are derived from World Clock Desklet and remain
9+
under the GNU General Public License version 2 or later.
1010

1111
The complete text of the GNU General Public License version 2 follows.
1212

cinnamon-color-timer-clock-desklet@curbsoftware/README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
A Cinnamon desklet with three cards (**Clock**, **Timer** and **Chronometer**) whose background colors follow a schedule you define. Each card's color ramps between (time, color) stops: the clock by time of day wrapping over midnight, the timer by time remaining, the chronometer by time elapsed.
44

5-
![Color Timer Clock desklet](screenshot.png)
5+
![Color Timer Clock desklet](screenshots/color-timer-clock-desklet.webp)
6+
7+
The settings window:
8+
9+
![Color Timer Clock settings](screenshots/color-timer-clock-config.webp)
610

711
## Why this exists
812

@@ -16,37 +20,38 @@ One glance tells me the state of things. That's the whole idea.
1620

1721
## Features
1822

19-
- Up to three side-by-side cards; show or hide each one
23+
- Up to three responsive cards. Narrow widths wrap cards onto centered rows and add enough height instead of hiding or clipping cards
2024
- Per-card color schedules with smooth (interpolated) or stepped transitions
2125
- Clock card with an optional IANA timezone
2226
- Timer with on-card play/pause, reset and ±60 s buttons; survives restarts and expires correctly even after downtime
2327
- Optional desktop notification when the timer finishes
2428
- Optional notifications when the clock or chronometer reaches a schedule stop
2529
- Chronometer with pause/resume; the accumulated time survives restarts, and an optional hundredths display (20 updates/s while running)
2630
- Text and border colors adapt to the card background for readability (WCAG contrast ratio)
27-
- Tooltips on every card control
28-
- A per-card refresh button to reload settings without interrupting a running timer or chronometer
29-
- A next-colour swatch on each card that previews the upcoming schedule colour
31+
- Keyboard focus, pressed states, accessible names and tooltips on every card control
32+
- One **Reload color schedules** desklet menu action that does not interrupt a running timer or chronometer
33+
- A labelled **Next** preview chip on each card, with a 20 px double-ring colour sample and the upcoming stop details in its tooltip
34+
- A clear setup message when every card is disabled
3035
- One-click "Reset all schedules to defaults"
3136

3237
## Configuration
3338

34-
Right-click the desklet **Configure**
39+
Right-click the desklet and choose **Configure**.
3540

36-
- **Color schedules**: a list of (time, color) stops per card. Colors are hex (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`) or a name (`red`, `blue`, …). Duplicate times keep the last row; invalid rows are dropped with a log line
41+
- **Color schedules**: a list of (time, color) stops per card. Colors are hex (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`) or a name such as `red` or `blue`. Duplicate times keep the last row; invalid rows are dropped with a log line
3742
- **Smooth color transitions**: blend gradually between stops, or hold each color and jump at the next stop
3843
- **Show a notification when the timer finishes**: pops a desktop notification when the countdown reaches zero
3944
- **Notify (per schedule row)**: tick a clock or chronometer schedule row's Notify checkbox to pop a notification when that time is reached
4045
- **Timer minutes / seconds**: the default duration the timer restarts from
41-
- **Refresh (per card)**: the small button in each card's top-right corner reloads that card's settings without restarting a running timer or chronometer
42-
- **Next-colour swatch (per card)**: the small circle in each card's bottom-right corner previews the next schedule colour and snaps to it at the breakpoint
46+
- **Reload color schedules**: the desklet menu action reloads all three schedules without restarting a running timer or chronometer
47+
- **Next colour (per card)**: the labelled footer chip previews the next schedule colour. Hover its colour sample for the stop time and exact colour value
4348
- **Show hundredths of a second**: adds a `.ss` fraction to the chronometer while it runs
4449
- **Time / date format**: `strftime` patterns for the clock card
4550
- **Maximum font sizes, card spacing, desklet width / height**: cards shrink text to fit
4651

4752
## Notes
4853

49-
Colors interpolate piecewise-linearly in RGB, so a redblue ramp passes through purple. The clock schedule wraps over midnight; timer and chronometer schedules hold the last stop's color beyond it. Cards share the desklet width and each needs about 130 px. When the width cannot fit them all, the chronometer and then the timer hide automatically (a note is logged) and return when the desklet is widened.
54+
Colors interpolate piecewise-linearly in RGB, so a red to blue ramp passes through purple. The clock schedule wraps over midnight; timer and chronometer schedules hold the last stop's color beyond it. Cards share the desklet width and wrap onto more rows when each card would otherwise become too narrow. Enabled cards never disappear because of desklet width.
5055

5156
UUID: `cinnamon-color-timer-clock-desklet@curbsoftware`
5257

cinnamon-color-timer-clock-desklet@curbsoftware/files/cinnamon-color-timer-clock-desklet@curbsoftware/cardActions.js

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -261,19 +261,17 @@ function thresholdsCrossed(prev, cur, times, wrap) {
261261
}
262262

263263
/**
264-
* nextColor:
264+
* nextStop:
265265
* @stops (array): normalised stops [{t, rgba}], any order
266266
* @pos (number): query position in seconds
267267
* @opts (object): { wrap, reverse }. wrap treats the position as circular
268268
* (clock); reverse walks the schedule backwards (timer counting down).
269269
*
270-
* Returns (array): the colour of the next stop in the direction of travel, or
271-
* null when there are no stops. A forward schedule returns the first stop
272-
* above @pos (wrapping to the first past the end when @opts.wrap, else holding
273-
* the last stop); a reverse schedule returns the first stop below @pos,
274-
* falling back to the lowest stop at or past the end.
270+
* Returns (object): a fresh {t, rgba} for the next stop in the direction of
271+
* travel, or null when no stop remains. Wrapped schedules always have a next
272+
* stop. This gives the desklet both the preview colour and its schedule time.
275273
*/
276-
function nextColor(stops, pos, opts) {
274+
function nextStop(stops, pos, opts) {
277275
opts = opts || {};
278276

279277
let list = [];
@@ -294,24 +292,27 @@ function nextColor(stops, pos, opts) {
294292
if (opts.wrap)
295293
q = ((q % DAY_SECONDS) + DAY_SECONDS) % DAY_SECONDS;
296294

297-
if (list.length === 1)
298-
return list[0].rgba.slice();
299-
300295
if (opts.reverse) {
301296
for (let i = list.length - 1; i >= 0; i--) {
302297
if (list[i].t < q)
303-
return list[i].rgba.slice();
298+
return { t: list[i].t, rgba: list[i].rgba.slice() };
304299
}
305-
return list[0].rgba.slice();
300+
return null;
306301
}
307302

308303
for (let i = 0; i < list.length; i++) {
309304
if (list[i].t > q)
310-
return list[i].rgba.slice();
305+
return { t: list[i].t, rgba: list[i].rgba.slice() };
311306
}
312307
if (opts.wrap)
313-
return list[0].rgba.slice();
314-
return list[list.length - 1].rgba.slice();
308+
return { t: list[0].t, rgba: list[0].rgba.slice() };
309+
return null;
310+
}
311+
312+
/* Compatibility helper for callers that only need the preview colour. */
313+
function nextColor(stops, pos, opts) {
314+
let stop = nextStop(stops, pos, opts);
315+
return stop ? stop.rgba : null;
315316
}
316317

317318
/* ------------------------------------------------------------------ *
@@ -571,37 +572,59 @@ var CARD_LAYOUT = {
571572
dateEm: 0.58,
572573
labelEm: 0.56,
573574
addMaxPt: 16,
574-
/* A card cannot shrink below its control pill; fitKinds() hides cards
575-
* rather than let the grid overflow the container below this. */
576-
minCardWidth: 120
575+
/* Timer controls and the labelled next-colour chip share one footer. */
576+
minCardWidth: 250,
577+
/* Title, value, subtitle and control row need this much card height. */
578+
minCardHeight: 124
577579
};
578580

579581
/**
580-
* fitKinds:
581-
* @kinds (array): visible card kinds from the settings checkboxes, in
582-
* build order (clock, timer, chrono)
582+
* computeResponsiveGrid:
583+
* @cardCount (number): count of enabled cards
583584
* @width (number): desklet width setting (px)
584585
* @spacing (number): card spacing setting (px, used as per-card margin)
585586
*
586-
* Returns (array): the kinds that fit. Cards drop from the end - the
587-
* chronometer first, then the timer - because the clock is the anchor;
588-
* one card always survives so the desklet never renders empty. Widening
589-
* restores the hidden cards on the next rebuild; the settings checkboxes
590-
* remain the source of truth.
587+
* Returns (object): { rows, cols }. Every enabled card gets a cell. Narrow
588+
* widths reduce the column count and add rows instead of hiding cards.
591589
*/
592-
function fitKinds(kinds, width, spacing) {
593-
let list = Array.isArray(kinds) ? kinds.slice() : [];
590+
function computeResponsiveGrid(cardCount, width, spacing) {
591+
let count = parseInt(cardCount, 10);
592+
if (!Number.isFinite(count) || count < 1)
593+
return { rows: 0, cols: 0 };
594594
let w = Number(width);
595595
if (!Number.isFinite(w) || w <= 0)
596596
w = 840;
597597
let gap = Number(spacing);
598598
if (!Number.isFinite(gap) || gap < 0)
599599
gap = 0;
600+
const available = Math.max(1, w - 2 * CARD_LAYOUT.containerPad);
600601
const min = CARD_LAYOUT.minCardWidth + 2 * gap;
601-
while (list.length > 1 &&
602-
list.length * min + 2 * CARD_LAYOUT.containerPad > w)
603-
list.pop();
604-
return list;
602+
const cols = Math.max(1, Math.min(count, Math.floor(available / min)));
603+
return { rows: Math.ceil(count / cols), cols: cols };
604+
}
605+
606+
/**
607+
* computeResponsiveHeight:
608+
* @rows (number): responsive row count
609+
* @height (number): configured desklet height
610+
* @spacing (number): per-card margin
611+
*
612+
* Returns (number): configured height or safe row-dependent minimum,
613+
* whichever is larger.
614+
*/
615+
function computeResponsiveHeight(rows, height, spacing) {
616+
let count = parseInt(rows, 10);
617+
if (!Number.isFinite(count) || count < 1)
618+
count = 1;
619+
let base = Number(height);
620+
if (!Number.isFinite(base) || base <= 0)
621+
base = 260;
622+
let gap = Number(spacing);
623+
if (!Number.isFinite(gap) || gap < 0)
624+
gap = 0;
625+
const safe = count * (CARD_LAYOUT.minCardHeight + 2 * gap) +
626+
2 * CARD_LAYOUT.containerPad;
627+
return Math.max(base, safe);
605628
}
606629

607630
function _toPositiveInt(value, fallback) {

0 commit comments

Comments
 (0)