dashboard: dynamic dashboard tabs + fix performance settings updating (#1253)

* [CI] chore: update flake

* Dashboard perf settings save, visibility on none enabled

* Dashboard heigh stutter fixed, persist current tab

* restore binding

* wrapper async=false

* ScriptModel, centralized tabs/panes, individual toggle

* fixes, missed mediaUpdateInterval, passing values

---------

Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Robin Seger 2026-03-10 15:22:23 +01:00 committed by GitHub
parent 0d56db3b6c
commit 3e0360401b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 220 additions and 104 deletions

View file

@ -258,7 +258,8 @@ Singleton {
return {
enabled: dashboard.enabled,
showOnHover: dashboard.showOnHover,
updateInterval: dashboard.updateInterval,
mediaUpdateInterval: dashboard.mediaUpdateInterval,
resourceUpdateInterval: dashboard.resourceUpdateInterval,
dragThreshold: dashboard.dragThreshold,
performance: {
showBattery: dashboard.performance.showBattery,

View file

@ -6,6 +6,10 @@ JsonObject {
property int mediaUpdateInterval: 500
property int resourceUpdateInterval: 1000
property int dragThreshold: 50
property bool showDashboard: true
property bool showMedia: true
property bool showPerformance: true
property bool showWeather: true
property Sizes sizes: Sizes {}
property Performance performance: Performance {}

View file

@ -22,9 +22,16 @@ Item {
// General Settings
property bool enabled: Config.dashboard.enabled ?? true
property bool showOnHover: Config.dashboard.showOnHover ?? true
property int updateInterval: Config.dashboard.updateInterval ?? 1000
property int mediaUpdateInterval: Config.dashboard.mediaUpdateInterval ?? 1000
property int resourceUpdateInterval: Config.dashboard.resourceUpdateInterval ?? 1000
property int dragThreshold: Config.dashboard.dragThreshold ?? 50
// Dashboard Tabs
property bool showDashboard: Config.dashboard.showDashboard ?? true
property bool showMedia: Config.dashboard.showMedia ?? true
property bool showPerformance: Config.dashboard.showPerformance ?? true
property bool showWeather: Config.dashboard.showWeather ?? true
// Performance Resources
property bool showBattery: Config.dashboard.performance.showBattery ?? false
property bool showGpu: Config.dashboard.performance.showGpu ?? true
@ -38,8 +45,13 @@ Item {
function saveConfig() {
Config.dashboard.enabled = root.enabled;
Config.dashboard.showOnHover = root.showOnHover;
Config.dashboard.updateInterval = root.updateInterval;
Config.dashboard.mediaUpdateInterval = root.mediaUpdateInterval;
Config.dashboard.resourceUpdateInterval = root.resourceUpdateInterval;
Config.dashboard.dragThreshold = root.dragThreshold;
Config.dashboard.showDashboard = root.showDashboard;
Config.dashboard.showMedia = root.showMedia;
Config.dashboard.showPerformance = root.showPerformance;
Config.dashboard.showWeather = root.showWeather;
Config.dashboard.performance.showBattery = root.showBattery;
Config.dashboard.performance.showGpu = root.showGpu;
Config.dashboard.performance.showCpu = root.showCpu;

View file

@ -38,24 +38,69 @@ SectionContainer {
}
}
SectionContainer {
contentSpacing: Appearance.spacing.normal
RowLayout {
Layout.fillWidth: true
spacing: Appearance.spacing.normal
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Dashboard tab")
checked: root.rootItem.showDashboard
onToggled: checked => {
root.rootItem.showDashboard = checked;
root.rootItem.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Media tab")
checked: root.rootItem.showMedia
onToggled: checked => {
root.rootItem.showMedia = checked;
root.rootItem.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Performance tab")
checked: root.rootItem.showPerformance
onToggled: checked => {
root.rootItem.showPerformance = checked;
root.rootItem.saveConfig();
}
}
SwitchRow {
Layout.fillWidth: true
label: qsTr("Show Weather tab")
checked: root.rootItem.showWeather
onToggled: checked => {
root.rootItem.showWeather = checked;
root.rootItem.saveConfig();
}
}
}
SliderInput {
Layout.fillWidth: true
label: qsTr("Update interval")
value: root.rootItem.updateInterval
label: qsTr("Media update interval")
value: root.rootItem.mediaUpdateInterval
from: 100
to: 10000
stepSize: 100
suffix: "ms"
validator: IntValidator { bottom: 100; top: 10000 }
formatValueFunction: (val) => Math.round(val).toString()
parseValueFunction: (text) => parseInt(text)
validator: IntValidator {
bottom: 100
top: 10000
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: (newValue) => {
root.rootItem.updateInterval = Math.round(newValue);
onValueModified: newValue => {
root.rootItem.mediaUpdateInterval = Math.round(newValue);
root.rootItem.saveConfig();
}
}
@ -68,14 +113,16 @@ SectionContainer {
from: 0
to: 100
suffix: "px"
validator: IntValidator { bottom: 0; top: 100 }
formatValueFunction: (val) => Math.round(val).toString()
parseValueFunction: (text) => parseInt(text)
validator: IntValidator {
bottom: 0
top: 100
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: (newValue) => {
onValueModified: newValue => {
root.rootItem.dragThreshold = Math.round(newValue);
root.rootItem.saveConfig();
}
}
}
}

View file

@ -33,7 +33,7 @@ SectionContainer {
opts.push({
"label": qsTr("Battery"),
"propertyName": "showBattery",
"onToggled": function(checked) {
"onToggled": function (checked) {
root.rootItem.showBattery = checked;
root.rootItem.saveConfig();
}
@ -43,7 +43,7 @@ SectionContainer {
opts.push({
"label": qsTr("GPU"),
"propertyName": "showGpu",
"onToggled": function(checked) {
"onToggled": function (checked) {
root.rootItem.showGpu = checked;
root.rootItem.saveConfig();
}
@ -52,28 +52,28 @@ SectionContainer {
opts.push({
"label": qsTr("CPU"),
"propertyName": "showCpu",
"onToggled": function(checked) {
"onToggled": function (checked) {
root.rootItem.showCpu = checked;
root.rootItem.saveConfig();
}
}, {
"label": qsTr("Memory"),
"propertyName": "showMemory",
"onToggled": function(checked) {
"onToggled": function (checked) {
root.rootItem.showMemory = checked;
root.rootItem.saveConfig();
}
}, {
"label": qsTr("Storage"),
"propertyName": "showStorage",
"onToggled": function(checked) {
"onToggled": function (checked) {
root.rootItem.showStorage = checked;
root.rootItem.saveConfig();
}
}, {
"label": qsTr("Network"),
"propertyName": "showNetwork",
"onToggled": function(checked) {
"onToggled": function (checked) {
root.rootItem.showNetwork = checked;
root.rootItem.saveConfig();
}
@ -82,4 +82,25 @@ SectionContainer {
}
}
SliderInput {
Layout.fillWidth: true
label: qsTr("Resource update interval")
value: root.rootItem.resourceUpdateInterval
from: 100
to: 10000
stepSize: 100
suffix: "ms"
validator: IntValidator {
bottom: 100
top: 10000
}
formatValueFunction: val => Math.round(val).toString()
parseValueFunction: text => parseInt(text)
onValueModified: newValue => {
root.rootItem.resourceUpdateInterval = Math.round(newValue);
root.rootItem.saveConfig();
}
}
}

View file

@ -14,6 +14,37 @@ Item {
required property PersistentProperties visibilities
required property PersistentProperties state
required property FileDialog facePicker
readonly property var dashboardTabs: {
const allTabs = [
{
component: dashComponent,
iconName: "dashboard",
text: qsTr("Dashboard"),
enabled: Config.dashboard.showDashboard
},
{
component: mediaComponent,
iconName: "queue_music",
text: qsTr("Media"),
enabled: Config.dashboard.showMedia
},
{
component: performanceComponent,
iconName: "speed",
text: qsTr("Performance"),
enabled: Config.dashboard.showPerformance && (Config.dashboard.performance.showCpu || Config.dashboard.performance.showGpu || Config.dashboard.performance.showMemory || Config.dashboard.performance.showStorage || Config.dashboard.performance.showNetwork || Config.dashboard.performance.showBattery)
},
{
component: weatherComponent,
iconName: "cloud",
text: qsTr("Weather"),
enabled: Config.dashboard.showWeather
}
];
return allTabs.filter(tab => tab.enabled);
}
readonly property real nonAnimWidth: view.implicitWidth + viewWrapper.anchors.margins * 2
readonly property real nonAnimHeight: tabs.implicitHeight + tabs.anchors.topMargin + view.implicitHeight + viewWrapper.anchors.margins * 2
@ -31,6 +62,7 @@ Item {
nonAnimWidth: root.nonAnimWidth - anchors.margins * 2
state: root.state
tabs: root.dashboardTabs
}
ClippingRectangle {
@ -86,31 +118,56 @@ Item {
RowLayout {
id: row
Pane {
index: 0
sourceComponent: Dash {
Repeater {
model: ScriptModel {
values: root.dashboardTabs
}
delegate: Loader {
id: paneLoader
required property int index
required property var modelData
Layout.alignment: Qt.AlignTop
sourceComponent: modelData.component
Component.onCompleted: active = Qt.binding(() => {
if (index === view.currentIndex)
return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
}
}
}
Component {
id: dashComponent
Dash {
visibilities: root.visibilities
state: root.state
facePicker: root.facePicker
}
}
Pane {
index: 1
sourceComponent: Media {
Component {
id: mediaComponent
Media {
visibilities: root.visibilities
}
}
Pane {
index: 2
sourceComponent: Performance {}
Component {
id: performanceComponent
Performance {}
}
Pane {
index: 3
sourceComponent: Weather {}
}
Component {
id: weatherComponent
Weather {}
}
Behavior on contentX {
@ -132,21 +189,4 @@ Item {
easing.bezierCurve: Appearance.anim.curves.emphasized
}
}
component Pane: Loader {
id: pane
required property int index
Layout.alignment: Qt.AlignTop
Component.onCompleted: active = Qt.binding(() => {
// Always keep current tab loaded
if (pane.index === view.currentIndex)
return true;
const vx = Math.floor(view.visibleArea.xPosition * view.contentWidth);
const vex = Math.floor(vx + view.visibleArea.widthRatio * view.contentWidth);
return (vx >= x && vx <= x + implicitWidth) || (vex >= x && vex <= x + implicitWidth);
})
}
}

View file

@ -14,6 +14,8 @@ Item {
required property real nonAnimWidth
required property PersistentProperties state
required property var tabs
readonly property alias count: bar.count
implicitHeight: bar.implicitHeight + indicator.implicitHeight + indicator.anchors.topMargin + separator.implicitHeight
@ -30,30 +32,18 @@ Item {
onCurrentIndexChanged: root.state.currentTab = currentIndex
Tab {
iconName: "dashboard"
text: qsTr("Dashboard")
Repeater {
model: ScriptModel {
values: root.tabs
}
Tab {
iconName: "queue_music"
text: qsTr("Media")
}
delegate: Tab {
required property var modelData
Tab {
iconName: "speed"
text: qsTr("Performance")
iconName: modelData.iconName
text: modelData.text
}
Tab {
iconName: "cloud"
text: qsTr("Weather")
}
// Tab {
// iconName: "workspaces"
// text: qsTr("Workspaces")
// }
}
Item {
@ -62,11 +52,13 @@ Item {
anchors.top: bar.bottom
anchors.topMargin: 5
implicitWidth: bar.currentItem.implicitWidth
implicitWidth: bar.currentItem?.implicitWidth ?? 0
implicitHeight: 3
x: {
const tab = bar.currentItem;
if (!tab)
return 0;
const width = (root.nonAnimWidth - bar.spacing * (bar.count - 1)) / bar.count;
return width * tab.TabBar.index + (width - tab.implicitWidth) / 2;
}

View file

@ -82,7 +82,6 @@ Item {
running: true
interval: Appearance.anim.durations.extraLarge
onTriggered: {
content.active = Qt.binding(() => (root.visibilities.dashboard && Config.dashboard.enabled) || root.visible);
content.visible = true;
}
}