Pull closer to use
This commit is contained in:
+1
-4
@@ -23,12 +23,9 @@ shell/
|
|||||||
services/
|
services/
|
||||||
PluginRegistry.qml discovers, validates plugins, looks up enabled state in shell.json
|
PluginRegistry.qml discovers, validates plugins, looks up enabled state in shell.json
|
||||||
BarWidgetRegistry.qml unified registry for bar widgets (1p + 3p)
|
BarWidgetRegistry.qml unified registry for bar widgets (1p + 3p)
|
||||||
ui/
|
|
||||||
settings/
|
|
||||||
DynamicSettingsForm.qml renders plugin-declared schemas
|
|
||||||
plugins/
|
plugins/
|
||||||
bar/ first-party plugins (see plugins/README.md)
|
bar/ first-party plugins (see plugins/README.md)
|
||||||
settings/
|
settings/ settings panel + plugin-declared widget forms
|
||||||
launcher/
|
launcher/
|
||||||
image-picker/
|
image-picker/
|
||||||
menu/
|
menu/
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import Quickshell.Io
|
|||||||
import qs.Commons
|
import qs.Commons
|
||||||
import qs.Ui
|
import qs.Ui
|
||||||
|
|
||||||
import "../../ui/settings" as SettingsUi
|
|
||||||
import "./components" as Cmp
|
import "./components" as Cmp
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
@@ -1147,10 +1146,10 @@ Item {
|
|||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: dynamicSettingsComponent
|
id: dynamicSettingsComponent
|
||||||
SettingsUi.DynamicSettingsForm {
|
Cmp.DynamicSettingsForm {
|
||||||
schema: root.widgetSchema(entry.id || "")
|
schema: root.widgetSchema(entry.id || "")
|
||||||
foregroundColor: root.foreground
|
foreground: root.foreground
|
||||||
fontFamilyName: root.fontFamily
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user