diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-13 16:39:55 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-13 16:39:55 +0100 |
| commit | 1ef099127f0265fd36fe4b1565ecb735b64cfca3 (patch) | |
| tree | 54b7a354467dd48d4f37814cf274241a23996fc0 | |
| parent | 4a1cce078d7d27dcc9218c40a552948759d9cc5f (diff) | |
| download | quickshell-1ef099127f0265fd36fe4b1565ecb735b64cfca3.tar.gz quickshell-1ef099127f0265fd36fe4b1565ecb735b64cfca3.zip | |
feat(fonts): deck-it's text, clock sizes and clock fonts
The Fonts page gets a deck-it section: deck-it's text size (the
override.deckit deck-it already read, with no row until now), and the
dim cover's clock time and date, following it at 8 and 1.6 times unless
given their own size (up to 400px). A Families section names the
clock's fonts, deckit-clock and deckit-date, saved under "family" in
fonts.json; deck-it reads both itself. The name field updates the draft
on every keystroke, since a click on Save takes no focus and an
editingFinished would never come.
The clock font fields leave the deck-it Dashboard page, which wrote
[tablet] keys deck-it no longer reads.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | desktop/modules/deckit/DeckItDashboardModule.qml | 3 | ||||
| -rw-r--r-- | desktop/modules/deckit/DeckItDashboardPage.qml | 6 | ||||
| -rw-r--r-- | desktop/modules/fonts/Fonts.js | 35 | ||||
| -rw-r--r-- | desktop/modules/fonts/FontsModule.qml | 5 | ||||
| -rw-r--r-- | desktop/modules/fonts/FontsPage.qml | 71 | ||||
| -rw-r--r-- | desktop/modules/fonts/README.md | 10 | ||||
| -rw-r--r-- | desktop/modules/fonts/test_fonts.js | 13 |
7 files changed, 118 insertions, 25 deletions
diff --git a/desktop/modules/deckit/DeckItDashboardModule.qml b/desktop/modules/deckit/DeckItDashboardModule.qml index 78a1e5a..ca033f8 100644 --- a/desktop/modules/deckit/DeckItDashboardModule.qml +++ b/desktop/modules/deckit/DeckItDashboardModule.qml @@ -22,11 +22,10 @@ Module { group: "deck-it" dirty: DeckIt.store.dirty keywords: ["deck-it", "tablet", "dashboard", "modules", "panels", "actions", "icons", "hardware", - "network", "battery", "cpu", "gpu", "sensor", "dim", "brightness", "backlight", "clock", "font", "date"] + "network", "battery", "cpu", "gpu", "sensor", "dim", "brightness", "backlight"] readonly property var fields: [ ["modules", "", "Dashboard parts"], ["dashboard", "actions", "Dashboard actions"], ["tablet", "dim_after", "Dim after"], ["tablet", "dim_brightness", "Dim brightness"], - ["tablet", "clock_font", "Clock font"], ["tablet", "clock_font_date", "Date in clock font"], ["hardware", "net", "Network interface"], ["hardware", "battery", "Battery"], ["hardware", "cpu_sensor", "CPU sensor"], ["hardware", "gpu", "GPU"], ["hardware", "gpu_mem_total", "GPU memory"]] diff --git a/desktop/modules/deckit/DeckItDashboardPage.qml b/desktop/modules/deckit/DeckItDashboardPage.qml index 20581a8..14aba95 100644 --- a/desktop/modules/deckit/DeckItDashboardPage.qml +++ b/desktop/modules/deckit/DeckItDashboardPage.qml @@ -74,12 +74,6 @@ Column { hint: "Seconds without a touch before the tablet's backlight goes down; a touch brings it back. 0 turns it off." } Setting { store: col.st; section: "tablet"; key: "dim_brightness"; label: "Dim brightness"; kind: "int"; min: 0; max: 255 placeholder: col.st.placeholder("tablet", "dim_brightness"); hint: "Backlight while dimmed, 0 to 255." } - Setting { store: col.st; section: "tablet"; key: "clock_font"; label: "Clock font" - placeholder: col.st.placeholder("tablet", "clock_font") || "page font" - hint: "A font family installed on the PC (fc-list), for the clock on the dim cover. Empty keeps the page font." } - BoolSetting { store: col.st; section: "tablet"; key: "clock_font_date"; label: "Date in clock font" - fallback: col.def("tablet", "clock_font_date") ?? true - hint: "the date under the clock uses the clock font too" } Heading { text: "Hardware" } Small { text: "Each is auto, or a fixed value such as wlan0, BAT1 or coretemp." } diff --git a/desktop/modules/fonts/Fonts.js b/desktop/modules/fonts/Fonts.js index 5b7d303..d42c784 100644 --- a/desktop/modules/fonts/Fonts.js +++ b/desktop/modules/fonts/Fonts.js @@ -12,9 +12,10 @@ // Pure helpers for the fonts page, tested by test_fonts.js under node. // -// The model is fonts.json: {base, override}. base is one pixel size for the -// whole desktop; override maps a system ("qt") or one entry of a section -// ("rofi:kf-launcher-entry") to its own pixel size. Everything is stored in +// The model is fonts.json: {base, override, family}. base is one pixel size +// for the whole desktop; override maps a system ("qt") or one entry of a +// section ("rofi:kf-launcher-entry") to its own pixel size; family maps a +// font slot no other config holds (FAMILIES) to an installed family. Everything is stored in // px and converted where a system wants points: at scale 1 Wayland's logical // DPI is 96 (gtk-xft-dpi reads 98304, 96 * 1024), so px = pt * 96 / 72. @@ -30,6 +31,14 @@ const SYSTEMS = [ { id: "gtk", label: "GTK", unit: "pt", where: "gtk-3.0 and gtk-4.0 settings.ini, .gtkrc-2.0, gsettings", when: "at once for GTK 3 and 4" }, { id: "kitty", label: "kitty", unit: "pt", where: "kitty.conf, through an include", when: "at once" }, { id: "groupbar", label: "Hyprland groupbar", unit: "px", where: "keystone.lua, group:groupbar:font_size", when: "at once" }, + { id: "deckit", label: "deck-it", unit: "px", where: "deck-it's theme.css, from fonts.json", when: "on the tablet within 5 s" }, +]; + +// deck-it's dim cover clock, sized from deck-it's text as deck.css designs +// it (8rem, 1.6rem); an override is "deckit:clock" or "deckit:date". +const DECKIT_CLOCK = [ + { id: "clock", label: "Clock time", ratio: 8 }, + { id: "date", label: "Clock date", ratio: 1.6 }, ]; // A section is a set of designed sizes that scale together: rofi's fonts, @@ -40,6 +49,12 @@ const SECTIONS = [ { id: "hyprlock", label: "hyprlock", unit: "pt", when: "next lock" }, ]; +// Font families set here, read by their program from fonts.json itself. +const FAMILIES = [ + { id: "deckit-clock", label: "deck-it clock", where: "the time on the tablet's dim cover" }, + { id: "deckit-date", label: "deck-it date", where: "the date under it" }, +]; + // A system's or a section's size in px. function size(m, id) { const o = m.override[id]; @@ -62,13 +77,21 @@ function withOverride(m, id, px) { const o = {}; for (const k in m.override) if (k !== id) o[k] = m.override[k]; if (px !== null) o[id] = px; - return { base: m.base, override: o }; + return { base: m.base, override: o, family: m.family }; +} + +// The model with family id set to name, or unset when name is empty. +function withFamily(m, id, name) { + const f = {}; + for (const k in m.family) if (k !== id) f[k] = m.family[k]; + if (name.trim() !== "") f[id] = name.trim(); + return { base: m.base, override: m.override, family: f }; } function parseModel(text) { try { const j = JSON.parse(text); - if (typeof j.base === "number") return { base: j.base, override: j.override || {} }; + if (typeof j.base === "number") return { base: j.base, override: j.override || {}, family: j.family || {} }; } catch (e) {} return null; } @@ -78,7 +101,7 @@ function parseModel(text) { // from it is an override at its current size. Kept to 0.01px: at 0.1, an // 11pt body stored as 14.7px scaled rofi's 72pt glyph to 72.2. function seed(now) { - const m = { base: 16, override: {} }; + const m = { base: 16, override: {}, family: {} }; for (const id in now) if (now[id] !== null && Math.abs(now[id] - 16) > 0.05) m.override[id] = Math.round(now[id] * 100) / 100; return m; } diff --git a/desktop/modules/fonts/FontsModule.qml b/desktop/modules/fonts/FontsModule.qml index 016d85d..76f6ae9 100644 --- a/desktop/modules/fonts/FontsModule.qml +++ b/desktop/modules/fonts/FontsModule.qml @@ -33,7 +33,7 @@ Module { settings: true group: "Appearance" dirty: fm.touched - keywords: ["font", "size", "text", "px", "pt", "qt6ct", "gtk", "kitty", "rofi", "hyprlock", "groupbar", "scale"] + keywords: ["font", "size", "text", "px", "pt", "qt6ct", "gtk", "kitty", "rofi", "hyprlock", "groupbar", "scale", "family", "deck-it", "clock", "date", "tablet"] readonly property string home: Quickshell.env("HOME") readonly property string config: Quickshell.env("XDG_CONFIG_HOME") || fm.home + "/.config" @@ -76,7 +76,7 @@ Module { } readonly property var saved: { fm.version; return F.parseModel(fm.texts.model ?? "") ?? F.seed(fm.now()); } - property var draft: ({ base: 16, override: {} }) + property var draft: ({ base: 16, override: {}, family: {} }) // A reload replaces the draft only while it holds no edits. onSavedChanged: if (!fm.touched) fm.draft = fm.saved property bool touched: false @@ -85,6 +85,7 @@ Module { function setBase(px) { fm.touched = true; fm.draft = { base: px, override: fm.draft.override }; } function setOverride(id, px) { fm.touched = true; fm.draft = F.withOverride(fm.draft, id, px); } + function setFamily(id, name) { fm.touched = true; fm.draft = F.withFamily(fm.draft, id, name); } function discard() { fm.touched = false; fm.draft = fm.saved; fm.error = ""; } function refresh() { diff --git a/desktop/modules/fonts/FontsPage.qml b/desktop/modules/fonts/FontsPage.qml index fd2e672..1a58b11 100644 --- a/desktop/modules/fonts/FontsPage.qml +++ b/desktop/modules/fonts/FontsPage.qml @@ -11,6 +11,7 @@ import QtQuick import "../.." +import "../appearance" as A import "Fonts.js" as F // The base size, then a row per system and per section entry: what it is, @@ -27,9 +28,9 @@ Column { spacing: 8 - function clamp(px) { return Math.max(page.minPx, Math.min(page.maxPx, px)); } + function clamp(px, max) { return Math.max(page.minPx, Math.min(max, px)); } // A step of 1 in the row's own unit, so a pt row moves by whole points. - function step(px, unit, d) { return page.clamp(unit === "pt" ? F.toPx(Math.round(F.toPt(px)) + d) : Math.round(px) + d); } + function step(px, unit, d, max) { return page.clamp(unit === "pt" ? F.toPx(Math.round(F.toPt(px)) + d) : Math.round(px) + d, max); } component Small: Text { color: Theme.subtext @@ -60,13 +61,14 @@ Column { property real px: 16 property string unit: "px" property bool enabled: true + property real max: page.maxPx signal changed(real px) spacing: 6 opacity: st.enabled ? 1 : 0.45 - Button { text: "−"; enabled: st.enabled; onClicked: st.changed(page.step(st.px, st.unit, -1)) } + Button { text: "−"; enabled: st.enabled; onClicked: st.changed(page.step(st.px, st.unit, -1, st.max)) } Unit { text: F.round(st.px) + " px"; own: st.unit === "px" } Unit { text: F.round(F.toPt(st.px)) + " pt"; own: st.unit === "pt" } - Button { text: "+"; enabled: st.enabled; onClicked: st.changed(page.step(st.px, st.unit, 1)) } + Button { text: "+"; enabled: st.enabled; onClicked: st.changed(page.step(st.px, st.unit, 1, st.max)) } } // A system or a section entry. id is its override key; inherited is the @@ -79,6 +81,7 @@ Column { property string unit: "px" required property real inherited property string follows: "base" + property real max: page.maxPx readonly property bool own: page.m.override[sr.key] !== undefined readonly property real px: sr.own ? page.m.override[sr.key] : sr.inherited objectName: "font:" + sr.key @@ -116,6 +119,7 @@ Column { anchors.verticalCenter: parent.verticalCenter px: sr.px unit: sr.unit + max: sr.max enabled: sr.own onChanged: px => page.fonts.setOverride(sr.key, px) } @@ -147,7 +151,7 @@ Column { Heading { text: "Systems" } Repeater { - model: F.SYSTEMS.filter(s => s.id !== "groupbar") + model: F.SYSTEMS.filter(s => s.id !== "groupbar" && s.id !== "deckit") SizeRow { required property var modelData key: modelData.id @@ -166,6 +170,26 @@ Column { inherited: page.m.base } + Heading { text: "deck-it" } + SizeRow { + key: "deckit" + title: "deck-it text" + detail: "The tablet page's rem. deck-it reads fonts.json itself; seen on the tablet within 5 s." + inherited: page.m.base + } + Repeater { + model: F.DECKIT_CLOCK + SizeRow { + required property var modelData + key: "deckit:" + modelData.id + title: modelData.label + detail: "On the dim cover, designed at " + modelData.ratio + "× deck-it's text." + follows: "deck-it" + max: 400 + inherited: F.size(page.m, "deckit") * modelData.ratio + } + } + // rofi and hyprlock: the section's size, which is its body text, then // each entry at its designed ratio to the body. Repeater { @@ -209,4 +233,41 @@ Column { } } } + + // Families no other config holds; the program reads fonts.json itself. + Heading { text: "Families" } + Small { + x: 8 + width: parent.width - 16 + text: "A family installed on this PC, as fc-list names it. Empty keeps the program's own font." + } + Repeater { + model: F.FAMILIES + Item { + id: fr + required property var modelData + objectName: "font:" + fr.modelData.id + width: page.width + height: 48 + Column { + anchors { left: parent.left; leftMargin: 8; right: field.left; rightMargin: 12; verticalCenter: parent.verticalCenter } + Text { + text: fr.modelData.label + color: Theme.text + font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 } + } + Small { width: parent.width; text: fr.modelData.where } + } + A.Field { + id: field + anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter } + width: 240 + value: page.m.family[fr.modelData.id] ?? "" + placeholder: "program's font" + // Every keystroke, not editingFinished: a click on Save takes no + // focus, so a name typed and saved would never reach the draft. + onValueChanged: if (value.trim() !== (page.m.family[fr.modelData.id] ?? "")) page.fonts.setFamily(fr.modelData.id, value) + } + } + } } diff --git a/desktop/modules/fonts/README.md b/desktop/modules/fonts/README.md index b887060..c3e2232 100644 --- a/desktop/modules/fonts/README.md +++ b/desktop/modules/fonts/README.md @@ -4,7 +4,8 @@ One pixel size for the desktop, in the settings window under Appearance > Fonts, and an override wherever a system needs its own. Saved as `~/.config/keystone/fonts.json`: - { "base": 16, "override": { "qt": 18.67, "rofi:kf-launcher-entry": 22 } } + { "base": 16, "override": { "qt": 18.67, "rofi:kf-launcher-entry": 22 }, + "family": { "deckit-clock": "Some Font" } } Everything is stored in px. At scale 1 Wayland's logical DPI is 96 (`gtk-xft-dpi` reads 98304, 96 * 1024), so a point is 4/3 of a pixel and @@ -17,6 +18,7 @@ Everything is stored in px. At scale 1 Wayland's logical DPI is 96 | GTK | gtk-3.0 and gtk-4.0 `settings.ini`, `.gtkrc-2.0`, gsettings `font-name` | pt | at once for GTK 3 and 4 | | kitty | `~/.config/keystone/kitty-fonts.conf`, included last by `kitty.conf`; SIGUSR1 reloads | pt | at once | | Hyprland groupbar | `group:groupbar:font_size` line in `keystone.lua` | px | at once | +| deck-it | reads `fonts.json` itself into its `theme.css`: `deckit`, and the dim cover's `deckit:clock` and `deckit:date` (8 and 1.6 times `deckit` unless set) | px | on the tablet within 5 s | | rofi | `~/.config/keystone/rofi-fonts.rasi`, imported by udt's `common.rasi` | pt | next open | | hyprlock | `~/.config/keystone/hyprlock-fonts.conf`, sourced by `hyprlock.conf` | pt | next lock | @@ -39,7 +41,11 @@ With no `fonts.json`, the page starts from what the files say: base 16 px, and an override for every system at its current size, so the first save changes nothing. -Not handled: font families; the hard-coded `pixelSize` values in the shells +`family` names a font for a slot no other config holds, read by its +program from `fonts.json` itself: `deckit-clock` and `deckit-date`, the +time and the date on deck-it's dim cover. Empty leaves the program's own. + +Not handled: the families of the systems above; the hard-coded `pixelSize` values in the shells (21 of them), which do not follow `Theme.fontSize`; and a `keystone.lua` save from this page and the Options page at the same moment, which can drop one line. diff --git a/desktop/modules/fonts/test_fonts.js b/desktop/modules/fonts/test_fonts.js index 947bd42..773da64 100644 --- a/desktop/modules/fonts/test_fonts.js +++ b/desktop/modules/fonts/test_fonts.js @@ -17,7 +17,7 @@ const fs = require("fs"); const assert = require("assert"); const src = fs.readFileSync(__dirname + "/Fonts.js", "utf8").replace(/^\.pragma library$/m, ""); -const F = new Function(src + "\nreturn { size, entryPx, withOverride, parseModel, seed, qtPt, gtkPt, kittyPt, " +const F = new Function(src + "\nreturn { DECKIT_CLOCK, size, entryPx, withOverride, withFamily, parseModel, seed, qtPt, gtkPt, kittyPt, " + "rofiEntries, hyprlockEntries, setQt, setGtk, gtkFamily, kittyFile, rofiFile, hyprlockFile };")(); const qt = '[Fonts]\nfixed="Inconsolata Nerd Font Mono,14,-1,5,400"\ngeneral="Noto Sans,14,-1,5,400,Regular"\n'; @@ -36,11 +36,16 @@ assert.strictEqual(F.setGtk(gtk2, 12), 'gtk-font-name="Noto Sans, 12"\n'); // The first save changes nothing: what differs from 16px is an override. const m = F.seed({ quickshell: 16, qt: 14 * 4 / 3, gtk: 10 * 4 / 3, kitty: 20 }); -assert.deepStrictEqual(m, { base: 16, override: { qt: 18.67, gtk: 13.33, kitty: 20 } }); +assert.deepStrictEqual(m, { base: 16, override: { qt: 18.67, gtk: 13.33, kitty: 20 }, family: {} }); assert.strictEqual(F.size(m, "quickshell"), 16); assert.strictEqual(F.size(F.withOverride(m, "qt", null), "qt"), 16); assert.strictEqual(F.parseModel("nope"), null); assert.deepStrictEqual(F.parseModel(JSON.stringify(m)), m); +// A family survives a size edit; an empty one is dropped. +const fm = F.withOverride(F.withFamily(m, "deckit-date", " Sedgwick Ave "), "qt", 20); +assert.deepStrictEqual(fm.family, { "deckit-date": "Sedgwick Ave" }); +assert.deepStrictEqual(F.withFamily(fm, "deckit-date", "").family, {}); +assert.deepStrictEqual(F.parseModel('{"base": 16}').family, {}); const rasi = ['* { font: var(kf-common-font, "Noto Sans 11"); }', 'entry { font: var(kf-launcher-entry, "Noto Sans Bold 15"); }', @@ -64,4 +69,8 @@ const sr = F.seed({ rofi: 11 * 4 / 3 }); assert.ok(F.rofiFile(sr, [{ name: "kf-a", family: "M", pt: 11 }, { name: "kf-b", family: "M", pt: 72 }]).includes('kf-b: "M 72"')); assert.strictEqual(F.kittyFile(m).split("\n")[1], "font_size 15"); +// The clock follows deck-it's text at its designed ratio. +const d = { base: 16, override: { deckit: 20 }, family: {} }; +assert.strictEqual(F.size(d, "deckit") * F.DECKIT_CLOCK[0].ratio, 160); + console.log("ok"); |
