mirror of
https://github.com/arthur-pbty/arthur-os.git
synced 2026-08-03 20:28:21 +02:00
Themes now drive typography the same way they drive colors: one [font] base-size in shell.toml is the rem root, and every Style.font.<token> (caption, bodySmall, body, subtitle, title, heading, display, displayLarge, iconSmall, icon, iconLarge) derives from it via a fixed multiplier. Themes can also pin individual tokens for stylistic emphasis. base-size is clamped 11..13 until row-height tokens exist. Bar dimensions move to the same singleton: [bar] size-horizontal / size-vertical replace the hardcoded 26/28 in Bar.qml, exposed as Style.bar.sizeHorizontal / sizeVertical. Style.qml also resolves the fontconfig 'monospace' alias via fc-match and exposes Style.font.resolvedFamily so panels can display the concrete family. Watches ~/.config/fontconfig/fonts.conf so it tracks 'omarchy font set <name>'. The qs.Ui kit (PillButton, Dropdown, Toggle, TextField, etc.) and every first-party plugin (bar widgets, settings, menu, clipboard, emoji, polkit, notifications, osd, image-picker, dev-gallery) now bind to Style.font.* instead of pixel literals. Only three deliberate display-scale outliers remain: the notification empty-state glyph and the weather flyout's hero temperature pair, all commented. Background plugin's applyTheme IPC fast-path also pushes shell.toml to Style so theme swaps update typography and bar size without waiting for inotify debounce. Dev gallery (omarchy dev ui-preview) now ships a Typography section that renders the full scale and theme tokens live, and its summon command is fixed (omarchy-shell-ipc -> omarchy-shell).
192 lines
6.1 KiB
QML
192 lines
6.1 KiB
QML
import QtQuick
|
|
import QtQuick.Controls
|
|
import QtQuick.Layouts
|
|
import qs.Commons
|
|
import qs.Ui
|
|
|
|
// Generic schema-driven settings form. The host panel passes:
|
|
// - schema: array of { key, label, type, defaultValue?, options?, min?, max?, step?, description? }
|
|
// - entry: current widget entry object (set by the loader); fields override defaults
|
|
// - signal fieldChanged(string key, var value) — emitted on user edits
|
|
//
|
|
// Supported types: boolean, enum, integer, number, string, path, command, color.
|
|
Column {
|
|
id: root
|
|
|
|
signal fieldChanged(string key, var value)
|
|
|
|
property var schema: []
|
|
property var entry: ({})
|
|
property color foregroundColor: Color.foreground
|
|
property string fontFamilyName: Style.font.family
|
|
spacing: 10
|
|
width: parent ? parent.width : 0
|
|
|
|
function currentValue(field) {
|
|
if (entry && entry[field.key] !== undefined) return entry[field.key]
|
|
if (field.defaultValue !== undefined) return field.defaultValue
|
|
switch (field.type) {
|
|
case "boolean": return false
|
|
case "integer":
|
|
case "number": return field.min !== undefined ? field.min : 0
|
|
case "enum": return field.options && field.options.length > 0 ? field.options[0] : ""
|
|
default: return ""
|
|
}
|
|
}
|
|
|
|
Repeater {
|
|
model: root.schema
|
|
|
|
Column {
|
|
required property var modelData
|
|
width: root.width
|
|
spacing: 4
|
|
|
|
Text {
|
|
text: modelData && modelData.label ? modelData.label : (modelData && modelData.key ? modelData.key : "")
|
|
color: Qt.darker(root.foregroundColor, 1.3)
|
|
font.family: root.fontFamilyName
|
|
font.pixelSize: Style.font.bodySmall
|
|
font.bold: true
|
|
visible: text !== ""
|
|
}
|
|
|
|
Text {
|
|
visible: !!(modelData && modelData.description)
|
|
text: modelData ? (modelData.description || "") : ""
|
|
color: Qt.darker(root.foregroundColor, 1.6)
|
|
font.family: root.fontFamilyName
|
|
font.pixelSize: Style.font.caption
|
|
wrapMode: Text.WordWrap
|
|
width: parent.width
|
|
}
|
|
|
|
Loader {
|
|
sourceComponent: {
|
|
if (!modelData || !modelData.type) return stringField
|
|
switch (String(modelData.type)) {
|
|
case "boolean": return booleanField
|
|
case "enum": return enumField
|
|
case "integer": return integerField
|
|
case "number": return numberField
|
|
case "color":
|
|
case "string":
|
|
case "path":
|
|
case "command":
|
|
default: return stringField
|
|
}
|
|
}
|
|
onLoaded: if (item && "fieldKey" in item) {
|
|
item.fieldKey = modelData.key
|
|
item.field = modelData
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: stringField
|
|
TextField {
|
|
property string fieldKey: ""
|
|
property var field: ({})
|
|
width: parent.width
|
|
foreground: root.foregroundColor
|
|
font.family: root.fontFamilyName
|
|
font.pixelSize: Style.font.body
|
|
text: root.currentValue(field) === undefined ? "" : String(root.currentValue(field))
|
|
onEditingFinished: if (fieldKey) root.fieldChanged(fieldKey, text)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: booleanField
|
|
CheckBox {
|
|
property string fieldKey: ""
|
|
property var field: ({})
|
|
font.family: root.fontFamilyName
|
|
font.pixelSize: Style.font.body
|
|
text: field && field.label ? "" : ""
|
|
checked: !!root.currentValue(field)
|
|
onToggled: if (fieldKey) root.fieldChanged(fieldKey, checked)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: enumField
|
|
ComboBox {
|
|
property string fieldKey: ""
|
|
property var field: ({})
|
|
width: parent.width
|
|
font.family: root.fontFamilyName
|
|
font.pixelSize: Style.font.body
|
|
model: field && field.options ? field.options : []
|
|
currentIndex: {
|
|
var v = root.currentValue(field)
|
|
for (var i = 0; i < (field && field.options ? field.options.length : 0); i++)
|
|
if (field.options[i] === v) return i
|
|
return 0
|
|
}
|
|
onActivated: function(index) {
|
|
if (fieldKey && field && field.options) root.fieldChanged(fieldKey, field.options[index])
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: integerField
|
|
SpinBox {
|
|
property string fieldKey: ""
|
|
property var field: ({})
|
|
from: field && field.min !== undefined ? field.min : 0
|
|
to: field && field.max !== undefined ? field.max : 9999
|
|
stepSize: field && field.step !== undefined ? field.step : 1
|
|
value: {
|
|
var v = root.currentValue(field)
|
|
var n = typeof v === "number" ? v : parseInt(String(v || 0), 10)
|
|
return isFinite(n) ? n : 0
|
|
}
|
|
onValueModified: if (fieldKey) root.fieldChanged(fieldKey, value)
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: numberField
|
|
Row {
|
|
property string fieldKey: ""
|
|
property var field: ({})
|
|
width: parent.width
|
|
spacing: 8
|
|
property real currentNumber: {
|
|
var v = root.currentValue(field)
|
|
var n = typeof v === "number" ? v : parseFloat(String(v || 0))
|
|
return isFinite(n) ? n : 0
|
|
}
|
|
Slider {
|
|
id: slider
|
|
width: parent.width - readout.width - 8
|
|
from: field && field.min !== undefined ? field.min : 0
|
|
to: field && field.max !== undefined ? field.max : 1
|
|
stepSize: field && field.step !== undefined ? field.step : 0.01
|
|
value: parent.currentNumber
|
|
onMoved: if (parent.fieldKey) root.fieldChanged(parent.fieldKey, value)
|
|
}
|
|
Text {
|
|
id: readout
|
|
text: slider.value.toFixed(2)
|
|
color: root.foregroundColor
|
|
font.family: root.fontFamilyName
|
|
font.pixelSize: Style.font.bodySmall
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Text {
|
|
visible: !root.schema || root.schema.length === 0
|
|
text: "No settings."
|
|
color: Qt.darker(root.foregroundColor, 1.5)
|
|
font.family: root.fontFamilyName
|
|
font.pixelSize: Style.font.bodySmall
|
|
}
|
|
}
|