summaryrefslogtreecommitdiffstats
path: root/desktop/modules/fonts/FontsPage.qml
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-29 09:55:20 +0100
committerDanilo M. <danix@danix.xyz>2026-10-29 09:55:20 +0100
commit28beb8a9600b6e74cab454401cc94c664d90bfe5 (patch)
treec91a0731af8f3204e2c2b07a96a8bbfeaae68e35 /desktop/modules/fonts/FontsPage.qml
parent38cda58f0969fbdda2c33a38356ab43b2869b955 (diff)
downloadquickshell-28beb8a9600b6e74cab454401cc94c664d90bfe5.tar.gz
quickshell-28beb8a9600b6e74cab454401cc94c664d90bfe5.zip
fonts: one font size for the desktop, with per-system overridesarchive/companion
A settings page under Appearance > Fonts holds one pixel size for the whole desktop and an override wherever a system needs its own, saved as ~/.config/keystone/fonts.json, all in px. At scale 1 the logical DPI is 96, so a point is 4/3 px, and each system is written in its own unit: Theme.fontSize reads the json itself, so every shell follows live; qt5ct/qt6ct and the GTK settings files are edited in place, size only, plus gsettings; kitty, rofi and hyprlock read generated files under ~/.config/keystone that their configs include over designed fallbacks; the groupbar is a keystone.lua line. rofi and hyprlock are sections of designed sizes that scale together from their body text, the smallest entry, each keeping its ratio unless given its own size. The design is read from the fallbacks themselves, var(kf-..., "Family N") in the rofi files and $kf_... = N in hyprlock.conf, so a new entry appears on the page with no list to keep. With no fonts.json the page starts from what the files say, base 16 and an override per system at its current size, so the first save changes nothing; seeded to 0.01px, since 0.1 scaled rofi's 72pt glyph to 72.2. Each stepper shows px then pt in fixed columns, the system's own unit highlighted. Check: node desktop/modules/fonts/test_fonts.js Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'desktop/modules/fonts/FontsPage.qml')
-rw-r--r--desktop/modules/fonts/FontsPage.qml212
1 files changed, 212 insertions, 0 deletions
diff --git a/desktop/modules/fonts/FontsPage.qml b/desktop/modules/fonts/FontsPage.qml
new file mode 100644
index 0000000..fd2e672
--- /dev/null
+++ b/desktop/modules/fonts/FontsPage.qml
@@ -0,0 +1,212 @@
+// Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+import QtQuick
+import "../.."
+import "Fonts.js" as F
+
+// The base size, then a row per system and per section entry: what it is,
+// its size in its own unit and in px, and a switch to give it its own size
+// instead of following the base (or, inside rofi and hyprlock, its share of
+// the section's size). Edits are a draft until Save.
+Column {
+ id: page
+
+ required property QtObject fonts
+ readonly property var m: page.fonts.draft
+ readonly property real minPx: 8
+ readonly property real maxPx: 160
+
+ spacing: 8
+
+ function clamp(px) { return Math.max(page.minPx, Math.min(page.maxPx, 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); }
+
+ component Small: Text {
+ color: Theme.subtext
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 }
+ wrapMode: Text.Wrap
+ }
+
+ component Heading: Text {
+ topPadding: 10
+ color: Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 1; bold: true }
+ }
+
+ // One column of the stepper: px always first, pt second, so every row
+ // lines up; the unit the system's config is written in is the bright one.
+ component Unit: Text {
+ property bool own: false
+ width: 72
+ anchors.verticalCenter: parent.verticalCenter
+ horizontalAlignment: Text.AlignRight
+ color: own ? Theme.accent : Theme.subtext
+ font { family: Theme.iconFamily; pixelSize: Theme.fontSize - 3; bold: own }
+ }
+
+ // − px pt +, the system's own unit highlighted.
+ component Stepper: Row {
+ id: st
+ property real px: 16
+ property string unit: "px"
+ property bool enabled: true
+ 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)) }
+ 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)) }
+ }
+
+ // A system or a section entry. id is its override key; inherited is the
+ // size it has when it follows.
+ component SizeRow: Item {
+ id: sr
+ required property string key
+ required property string title
+ property string detail: ""
+ property string unit: "px"
+ required property real inherited
+ property string follows: "base"
+ 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
+ width: page.width
+ height: Math.max(48, texts.implicitHeight + 12)
+
+ Column {
+ id: texts
+ anchors { left: parent.left; leftMargin: 8; right: controls.left; rightMargin: 12; verticalCenter: parent.verticalCenter }
+ Text {
+ width: parent.width
+ text: sr.title
+ color: Theme.text
+ elide: Text.ElideRight
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 2 }
+ }
+ Small { width: parent.width; visible: sr.detail !== ""; text: sr.detail }
+ }
+
+ Row {
+ id: controls
+ anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter }
+ spacing: 12
+ Small {
+ anchors.verticalCenter: parent.verticalCenter
+ wrapMode: Text.NoWrap
+ text: sr.own ? "own size" : "follows " + sr.follows
+ }
+ Switch {
+ anchors.verticalCenter: parent.verticalCenter
+ checked: sr.own
+ onToggled: on => page.fonts.setOverride(sr.key, on ? F.round(sr.inherited) : null)
+ }
+ Stepper {
+ anchors.verticalCenter: parent.verticalCenter
+ px: sr.px
+ unit: sr.unit
+ enabled: sr.own
+ onChanged: px => page.fonts.setOverride(sr.key, px)
+ }
+ }
+ }
+
+ Small {
+ width: parent.width
+ text: "One size for the whole desktop, in pixels. At scale 1 a point is 4/3 of a pixel (96 dpi), so 16 px is 12 pt. "
+ + "A system with its own size keeps it when the base moves."
+ }
+
+ Item {
+ objectName: "font:base"
+ width: parent.width
+ height: 48
+ Text {
+ anchors { left: parent.left; leftMargin: 8; verticalCenter: parent.verticalCenter }
+ text: "Base size"
+ color: Theme.text
+ font { family: Theme.fontFamily; pixelSize: Theme.fontSize; bold: true }
+ }
+ Stepper {
+ anchors { right: parent.right; rightMargin: 8; verticalCenter: parent.verticalCenter }
+ px: page.m.base
+ onChanged: px => page.fonts.setBase(px)
+ }
+ }
+
+ Heading { text: "Systems" }
+ Repeater {
+ model: F.SYSTEMS.filter(s => s.id !== "groupbar")
+ SizeRow {
+ required property var modelData
+ key: modelData.id
+ title: modelData.label
+ detail: modelData.where + ". Seen " + modelData.when + "."
+ unit: modelData.unit
+ inherited: page.m.base
+ }
+ }
+
+ Heading { text: "Hyprland groupbar" }
+ SizeRow {
+ key: "groupbar"
+ title: "Group tab titles"
+ detail: "group:groupbar:font_size, a keystone.lua line. Seen at once. Its text_offset is tuned for 14 px."
+ inherited: page.m.base
+ }
+
+ // rofi and hyprlock: the section's size, which is its body text, then
+ // each entry at its designed ratio to the body.
+ Repeater {
+ model: F.SECTIONS
+ Column {
+ id: sec
+ required property var modelData
+ readonly property var entries: sec.modelData.id === "rofi" ? page.fonts.rofiEntries : page.fonts.hyprlockEntries
+ readonly property real body: sec.entries.length ? Math.min.apply(null, sec.entries.map(e => e.pt)) : 0
+ width: page.width
+ spacing: 8
+
+ Heading { text: sec.modelData.label }
+ Small {
+ x: 8
+ width: parent.width - 16
+ visible: sec.entries.length === 0
+ text: "No sizes found to scale: " + (sec.modelData.id === "rofi"
+ ? "no var(kf-..., \"...\") in ~/.config/rofi/udt."
+ : "no $kf_... = size in ~/.config/hypr/hyprlock.conf.")
+ }
+ SizeRow {
+ visible: sec.entries.length > 0
+ key: sec.modelData.id
+ title: sec.modelData.label + " body text"
+ detail: "Designed at " + sec.body + " pt; everything below keeps its ratio to it. Seen " + sec.modelData.when + "."
+ unit: "pt"
+ inherited: page.m.base
+ }
+ Repeater {
+ model: sec.entries
+ SizeRow {
+ required property var modelData
+ key: sec.modelData.id + ":" + modelData.name
+ title: modelData.name.replace(/^kf[-_]/, "")
+ detail: (modelData.family ? modelData.family + ", " : "") + "designed " + modelData.pt + " pt"
+ unit: "pt"
+ follows: sec.modelData.label
+ inherited: F.entryPx(page.m, sec.modelData.id, sec.entries, { name: "", pt: modelData.pt })
+ }
+ }
+ }
+ }
+}