Pull closer to use

This commit is contained in:
David Heinemeier Hansson
2026-05-20 14:46:08 +02:00
parent dbdcd862e7
commit b337652cd6
4 changed files with 213 additions and 204 deletions
+1 -4
View File
@@ -23,12 +23,9 @@ shell/
services/
PluginRegistry.qml discovers, validates plugins, looks up enabled state in shell.json
BarWidgetRegistry.qml unified registry for bar widgets (1p + 3p)
ui/
settings/
DynamicSettingsForm.qml renders plugin-declared schemas
plugins/
bar/ first-party plugins (see plugins/README.md)
settings/
settings/ settings panel + plugin-declared widget forms
launcher/
image-picker/
menu/
+3 -4
View File
@@ -6,7 +6,6 @@ import Quickshell.Io
import qs.Commons
import qs.Ui
import "../../ui/settings" as SettingsUi
import "./components" as Cmp
Item {
@@ -1147,10 +1146,10 @@ Item {
Component {
id: dynamicSettingsComponent
SettingsUi.DynamicSettingsForm {
Cmp.DynamicSettingsForm {
schema: root.widgetSchema(entry.id || "")
foregroundColor: root.foreground
fontFamilyName: root.fontFamily
foreground: root.foreground
fontFamily: root.fontFamily
}
}
@@ -0,0 +1,209 @@
import QtQuick
import QtQuick.Controls as QQC
import qs.Commons
import qs.Ui as Ui
Column {
id: root
signal fieldChanged(string key, var value)
property var schema: []
property var entry: ({})
property color foreground: Color.popups.text
property color accent: Color.accent
property string fontFamily: Style.font.family
spacing: Style.spacing.xl
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: Style.spacing.labelGap
Text {
visible: text !== ""
text: modelData && modelData.label ? modelData.label : (modelData && modelData.key ? modelData.key : "")
color: Qt.darker(root.foreground, 1.3)
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
font.bold: true
}
Text {
visible: !!(modelData && modelData.description)
text: modelData ? (modelData.description || "") : ""
color: Qt.darker(root.foreground, 1.6)
font.family: root.fontFamily
font.pixelSize: Style.font.caption
wrapMode: Text.WordWrap
width: parent.width
}
Loader {
width: parent.width
height: item ? item.implicitHeight : 0
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
default: return stringField
}
}
onLoaded: {
if (!item) return
item.fieldKey = modelData.key
item.field = modelData
}
}
}
}
Text {
visible: !root.schema || root.schema.length === 0
text: "No settings."
color: Qt.darker(root.foreground, 1.5)
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
}
Component {
id: stringField
Ui.TextField {
property string fieldKey: ""
property var field: ({})
width: parent.width
foreground: root.foreground
accent: root.accent
font.family: root.fontFamily
font.pixelSize: Style.font.body
text: root.currentValue(field) === undefined ? "" : String(root.currentValue(field))
onEditingFinished: if (fieldKey) root.fieldChanged(fieldKey, text)
}
}
Component {
id: booleanField
Ui.Toggle {
property string fieldKey: ""
property var field: ({})
width: parent.width
label: "Enabled"
foreground: root.foreground
accent: root.accent
fontFamily: root.fontFamily
checked: !!root.currentValue(field)
onClicked: if (fieldKey) root.fieldChanged(fieldKey, !checked)
}
}
Component {
id: enumField
Ui.Dropdown {
property string fieldKey: ""
property var field: ({})
width: parent.width
showLabel: false
foreground: root.foreground
accent: root.accent
fontFamily: root.fontFamily
options: field && field.options ? field.options : []
value: String(root.currentValue(field))
onChanged: function(value) {
if (fieldKey) root.fieldChanged(fieldKey, value)
}
}
}
Component {
id: integerField
Ui.NumberField {
property string fieldKey: ""
property var field: ({})
foreground: root.foreground
accent: root.accent
fontFamily: root.fontFamily
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
}
onModified: function(value) { if (fieldKey) root.fieldChanged(fieldKey, value) }
}
}
Component {
id: numberField
Row {
property string fieldKey: ""
property var field: ({})
property real currentNumber: {
var v = root.currentValue(field)
var n = typeof v === "number" ? v : parseFloat(String(v || 0))
return isFinite(n) ? n : 0
}
width: parent.width
spacing: Style.spacing.rowGap
QQC.Slider {
id: slider
width: parent.width - readout.width - Style.spacing.rowGap
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.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
-196
View File
@@ -1,196 +0,0 @@
import QtQuick
import QtQuick.Controls as QQC
import QtQuick.Layouts
import qs.Commons
import qs.Ui as 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: Style.spacing.xl
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: Style.spacing.labelGap
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 {
width: parent.width
height: item ? item.implicitHeight : 0
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
Ui.TextField {
property string fieldKey: ""
property var field: ({})
width: parent.width
foreground: root.foregroundColor
accent: Color.accent
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
Ui.Toggle {
property string fieldKey: ""
property var field: ({})
width: parent.width
label: "Enabled"
foreground: root.foregroundColor
accent: Color.accent
fontFamily: root.fontFamilyName
checked: !!root.currentValue(field)
onClicked: if (fieldKey) root.fieldChanged(fieldKey, !checked)
}
}
Component {
id: enumField
Ui.Dropdown {
property string fieldKey: ""
property var field: ({})
width: parent.width
showLabel: false
foreground: root.foregroundColor
accent: Color.accent
fontFamily: root.fontFamilyName
options: field && field.options ? field.options : []
value: String(root.currentValue(field))
onChanged: function(value) {
if (fieldKey) root.fieldChanged(fieldKey, value)
}
}
}
Component {
id: integerField
Ui.NumberField {
property string fieldKey: ""
property var field: ({})
foreground: root.foregroundColor
accent: Color.accent
fontFamily: root.fontFamilyName
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
}
onModified: function(value) { if (fieldKey) root.fieldChanged(fieldKey, value) }
}
}
Component {
id: numberField
Row {
property string fieldKey: ""
property var field: ({})
width: parent.width
spacing: Style.spacing.rowGap
property real currentNumber: {
var v = root.currentValue(field)
var n = typeof v === "number" ? v : parseFloat(String(v || 0))
return isFinite(n) ? n : 0
}
QQC.Slider {
id: slider
width: parent.width - readout.width - Style.spacing.rowGap
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
}
}