Merge branch 'bartab-overhaul'

This commit is contained in:
Ly-sec 2025-09-08 12:21:18 +02:00
commit c02d3e3d22
59 changed files with 1651 additions and 821 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View file

@ -102,7 +102,7 @@ Singleton {
// FileView to load custom colors data from colors.json // FileView to load custom colors data from colors.json
FileView { FileView {
id: customColorsFile id: customColorsFile
path: Settings.configDir + "colors.json" path: Settings.directoriesCreated ? (Settings.configDir + "colors.json") : ""
watchChanges: true watchChanges: true
onFileChanged: { onFileChanged: {
Logger.log("Color", "Reloading colors from disk") Logger.log("Color", "Reloading colors from disk")
@ -112,6 +112,13 @@ Singleton {
Logger.log("Color", "Writing colors to disk") Logger.log("Color", "Writing colors to disk")
writeAdapter() writeAdapter()
} }
// Trigger initial load when path changes from empty to actual path
onPathChanged: {
if (path === Settings.configDir + "colors.json") {
reload()
}
}
onLoadFailed: function (error) { onLoadFailed: function (error) {
if (error.toString().includes("No such file") || error === 2) { if (error.toString().includes("No such file") || error === 2) {
// File doesn't exist, create it with default values // File doesn't exist, create it with default values

View file

@ -26,11 +26,13 @@ Singleton {
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers" property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos" property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
property string defaultLocation: "Tokyo" property string defaultLocation: "Tokyo"
property string defaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
// Used to access via Settings.data.xxx.yyy // Used to access via Settings.data.xxx.yyy
readonly property alias data: adapter readonly property alias data: adapter
property bool isLoaded: false property bool isLoaded: false
property bool directoriesCreated: false
// Signal emitted when settings are loaded after startupcale changes // Signal emitted when settings are loaded after startupcale changes
signal settingsLoaded signal settingsLoaded
@ -71,34 +73,93 @@ Singleton {
// ----------------------------------------------------- // -----------------------------------------------------
// If the settings structure has changed, ensure // If the settings structure has changed, ensure
// backward compatibility // backward compatibility by upgrading the settings
function upgradeSettingsData() { function upgradeSettingsData() {
for (var i = 0; i < adapter.bar.widgets.left.length; i++) {
var obj = adapter.bar.widgets.left[i] const sections = ["left", "center", "right"]
if (typeof obj === "string") {
adapter.bar.widgets.left[i] = { // -----------------
"id": obj // 1st. check our settings are not super old, when we only had the widget type as a plain string
for (var s = 0; s < sections.length; s++) {
const sectionName = sections[s]
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i]
if (typeof widget === "string") {
adapter.bar.widgets[sectionName][i] = {
"id": widget
}
} }
} }
} }
for (var i = 0; i < adapter.bar.widgets.center.length; i++) {
var obj = adapter.bar.widgets.center[i] // -----------------
if (typeof obj === "string") { // 2nd. migrate global settings to user settings
adapter.bar.widgets.center[i] = { for (var s = 0; s < sections.length; s++) {
"id": obj const sectionName = sections[s]
for (var i = 0; i < adapter.bar.widgets[sectionName].length; i++) {
var widget = adapter.bar.widgets[sectionName][i]
// Check if widget registry supports user settings, if it does not, then there is nothing to do
const reg = BarWidgetRegistry.widgetMetadata[widget.id]
if ((reg === undefined) || (reg.allowUserSettings === undefined) || !reg.allowUserSettings) {
continue
} }
}
} // Check that the widget was not previously migrated and skip if necessary
for (var i = 0; i < adapter.bar.widgets.right.length; i++) { const keys = Object.keys(widget)
var obj = adapter.bar.widgets.right[i] if (keys.length > 1) {
if (typeof obj === "string") { continue
adapter.bar.widgets.right[i] = {
"id": obj
} }
migrateWidget(widget)
Logger.log("Settings", JSON.stringify(widget))
} }
} }
} }
// -----------------------------------------------------
function migrateWidget(widget) {
Logger.log("Settings", `Migrating '${widget.id}' widget`)
switch (widget.id) {
case "ActiveWindow":
widget.showIcon = adapter.bar.showActiveWindowIcon
break
case "Battery":
widget.alwaysShowPercentage = adapter.bar.alwaysShowBatteryPercentage
break
case "Brightness":
widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage
break
case "Clock":
widget.showDate = adapter.location.showDateWithClock
widget.use12HourClock = adapter.location.use12HourClock
widget.reverseDayMonth = adapter.location.reverseDayMonth
widget.showSeconds = BarWidgetRegistry.widgetMetadata[widget.id].showSeconds
break
case "MediaMini":
widget.showAlbumArt = adapter.audio.showMiniplayerAlbumArt
widget.showVisualizer = adapter.audio.showMiniplayerCava
widget.visualizerType = BarWidgetRegistry.widgetMetadata[widget.id].visualizerType
break
case "NotificationHistory":
widget.showUnreadBadge = BarWidgetRegistry.widgetMetadata[widget.id].showUnreadBadge
widget.hideWhenZero = BarWidgetRegistry.widgetMetadata[widget.id].hideWhenZero
break
case "SidePanelToggle":
widget.useDistroLogo = adapter.bar.useDistroLogo
break
case "SystemMonitor":
widget.showNetworkStats = adapter.bar.showNetworkStats
break
case "Volume":
widget.alwaysShowPercentage = BarWidgetRegistry.widgetMetadata[widget.id].alwaysShowPercentage
break
case "Workspace":
widget.labelMode = adapter.bar.showWorkspaceLabel
break
}
}
// ----------------------------------------------------- // -----------------------------------------------------
// Kickoff essential services // Kickoff essential services
function kickOffServices() { function kickOffServices() {
@ -117,14 +178,15 @@ Singleton {
} }
// ----------------------------------------------------- // -----------------------------------------------------
Item { // Ensure directories exist before FileView tries to read files
Component.onCompleted: { Component.onCompleted: {
// ensure settings dir exists
Quickshell.execDetached(["mkdir", "-p", configDir])
Quickshell.execDetached(["mkdir", "-p", cacheDir])
Quickshell.execDetached(["mkdir", "-p", cacheDirImages])
// ensure settings dir exists // Mark directories as created and trigger file loading
Quickshell.execDetached(["mkdir", "-p", configDir]) directoriesCreated = true
Quickshell.execDetached(["mkdir", "-p", cacheDir])
Quickshell.execDetached(["mkdir", "-p", cacheDirImages])
}
} }
// Don't write settings to disk immediately // Don't write settings to disk immediately
@ -138,12 +200,16 @@ Singleton {
FileView { FileView {
id: settingsFileView id: settingsFileView
path: settingsFile path: directoriesCreated ? settingsFile : ""
watchChanges: true watchChanges: true
onFileChanged: reload() onFileChanged: reload()
onAdapterUpdated: saveTimer.start() onAdapterUpdated: saveTimer.start()
Component.onCompleted: function () {
reload() // Trigger initial load when path changes from empty to actual path
onPathChanged: {
if (path === settingsFile) {
reload()
}
} }
onLoaded: function () { onLoaded: function () {
if (!isLoaded) { if (!isLoaded) {
@ -174,15 +240,16 @@ Singleton {
// bar // bar
property JsonObject bar: JsonObject { property JsonObject bar: JsonObject {
property string position: "top" // Possible values: "top", "bottom" property string position: "top" // "top" or "bottom"
property bool showActiveWindowIcon: true
property bool alwaysShowBatteryPercentage: false
property bool showNetworkStats: false
property real backgroundOpacity: 1.0 property real backgroundOpacity: 1.0
property bool useDistroLogo: false
property string showWorkspaceLabel: "none"
property list<string> monitors: [] property list<string> monitors: []
property bool showActiveWindowIcon: true // TODO: delete
property bool alwaysShowBatteryPercentage: false // TODO: delete
property bool showNetworkStats: false // TODO: delete
property bool useDistroLogo: false // TODO: delete
property string showWorkspaceLabel: "none" // TODO: delete
// Widget configuration for modular bar system // Widget configuration for modular bar system
property JsonObject widgets property JsonObject widgets
widgets: JsonObject { widgets: JsonObject {
@ -236,9 +303,10 @@ Singleton {
property JsonObject location: JsonObject { property JsonObject location: JsonObject {
property string name: defaultLocation property string name: defaultLocation
property bool useFahrenheit: false property bool useFahrenheit: false
property bool reverseDayMonth: false
property bool use12HourClock: false property bool reverseDayMonth: false // TODO: delete
property bool showDateWithClock: false property bool use12HourClock: false // TODO: delete
property bool showDateWithClock: false // TODO: delete
} }
// screen recorder // screen recorder
@ -267,6 +335,7 @@ Singleton {
property int transitionDuration: 1500 // 1500 ms property int transitionDuration: 1500 // 1500 ms
property string transitionType: "random" property string transitionType: "random"
property real transitionEdgeSmoothness: 0.05 property real transitionEdgeSmoothness: 0.05
property string defaultWallpaper: root.defaultWallpaper
property list<var> monitors: [] property list<var> monitors: []
} }
@ -299,25 +368,27 @@ Singleton {
property JsonObject notifications: JsonObject { property JsonObject notifications: JsonObject {
property bool doNotDisturb: false property bool doNotDisturb: false
property list<string> monitors: [] property list<string> monitors: []
// Last time the user opened the notification history (ms since epoch)
property real lastSeenTs: 0
} }
// audio // audio
property JsonObject audio: JsonObject { property JsonObject audio: JsonObject {
property bool showMiniplayerAlbumArt: false
property bool showMiniplayerCava: false
property string visualizerType: "linear"
property int volumeStep: 5 property int volumeStep: 5
property int cavaFrameRate: 60 property int cavaFrameRate: 60
// MPRIS controls property string visualizerType: "linear"
property list<string> mprisBlacklist: [] property list<string> mprisBlacklist: []
property string preferredPlayer: "" property string preferredPlayer: ""
property bool showMiniplayerAlbumArt: false // TODO: delete
property bool showMiniplayerCava: false // TODO: delete
} }
// ui // ui
property JsonObject ui: JsonObject { property JsonObject ui: JsonObject {
property string fontDefault: "Roboto" // Default font for all text property string fontDefault: "Roboto"
property string fontFixed: "DejaVu Sans Mono" // Fixed width font for terminal property string fontFixed: "DejaVu Sans Mono"
property string fontBillboard: "Inter" // Large bold font for clocks and prominent displays property string fontBillboard: "Inter"
property list<var> monitorsScaling: [] property list<var> monitorsScaling: []
property bool idleInhibitorEnabled: false property bool idleInhibitorEnabled: false
} }

View file

@ -9,52 +9,38 @@ Singleton {
id: root id: root
property var date: new Date() property var date: new Date()
property string time: {
let timeFormat = Settings.data.location.use12HourClock ? "h:mm AP" : "HH:mm"
let timeString = Qt.formatDateTime(date, timeFormat)
if (Settings.data.location.showDateWithClock) { // Returns a Unix Timestamp (in seconds)
let dayName = date.toLocaleDateString(Qt.locale(), "ddd") readonly property int timestamp: {
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) return Math.floor(date / 1000)
let day = date.getDate()
let month = date.toLocaleDateString(Qt.locale(), "MMM")
return timeString + " - " + (Settings.data.location.reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
}
return timeString
} }
readonly property string dateString: {
function formatDate(reverseDayMonth = true) {
let now = date let now = date
let dayName = now.toLocaleDateString(Qt.locale(), "ddd") let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1) dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
let day = now.getDate() let day = now.getDate()
let suffix let suffix
if (day > 3 && day < 21) if (day > 3 && day < 21)
suffix = 'th' suffix = 'th'
else else
switch (day % 10) { switch (day % 10) {
case 1: case 1:
suffix = "st" suffix = "st"
break break
case 2: case 2:
suffix = "nd" suffix = "nd"
break break
case 3: case 3:
suffix = "rd" suffix = "rd"
break break
default: default:
suffix = "th" suffix = "th"
} }
let month = now.toLocaleDateString(Qt.locale(), "MMMM") let month = now.toLocaleDateString(Qt.locale(), "MMMM")
let year = now.toLocaleDateString(Qt.locale(), "yyyy") let year = now.toLocaleDateString(Qt.locale(), "yyyy")
return `${dayName}, `
+ (Settings.data.location.reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
}
// Returns a Unix Timestamp (in seconds) return `${dayName}, ` + (reverseDayMonth ? `${month} ${day}${suffix} ${year}` : `${day}${suffix} ${month} ${year}`)
readonly property int timestamp: {
return Math.floor(date / 1000)
} }

View file

@ -3,6 +3,8 @@ import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Services import qs.Services
import qs.Modules.SettingsPanel
import qs.Widgets
Variants { Variants {
id: backgroundVariants id: backgroundVariants
@ -20,6 +22,8 @@ Variants {
// Internal state management // Internal state management
property string transitionType: "fade" property string transitionType: "fade"
property real transitionProgress: 0 property real transitionProgress: 0
// Scaling support for widgets that rely on it
property real scaling: ScalingService.getScreenScale(screen)
readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness readonly property real edgeSmoothness: Settings.data.wallpaper.transitionEdgeSmoothness
readonly property var allTransitions: WallpaperService.allTransitions readonly property var allTransitions: WallpaperService.allTransitions
@ -87,6 +91,15 @@ Variants {
left: true left: true
} }
Connections {
target: ScalingService
function onScaleChanged(screenName, scale) {
if ((screen !== null) && (screenName === screen.name)) {
scaling = scale
}
}
}
Timer { Timer {
id: debounceTimer id: debounceTimer
interval: 333 interval: 333

View file

@ -76,6 +76,7 @@ Variants {
widgetProps: { widgetProps: {
"screen": root.modelData || null, "screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen), "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"barSection": parent.objectName, "barSection": parent.objectName,
"sectionWidgetIndex": index, "sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.left.length "sectionWidgetsCount": Settings.data.bar.widgets.left.length
@ -103,6 +104,7 @@ Variants {
widgetProps: { widgetProps: {
"screen": root.modelData || null, "screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen), "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"barSection": parent.objectName, "barSection": parent.objectName,
"sectionWidgetIndex": index, "sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.center.length "sectionWidgetsCount": Settings.data.bar.widgets.center.length
@ -131,6 +133,7 @@ Variants {
widgetProps: { widgetProps: {
"screen": root.modelData || null, "screen": root.modelData || null,
"scaling": ScalingService.getScreenScale(screen), "scaling": ScalingService.getScreenScale(screen),
"widgetId": modelData.id,
"barSection": parent.objectName, "barSection": parent.objectName,
"sectionWidgetIndex": index, "sectionWidgetIndex": index,
"sectionWidgetsCount": Settings.data.bar.widgets.right.length "sectionWidgetsCount": Settings.data.bar.widgets.right.length

View file

@ -12,6 +12,27 @@ RowLayout {
id: root id: root
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool showIcon: (widgetSettings.showIcon !== undefined) ? widgetSettings.showIcon : widgetMetadata.showIcon
readonly property real minWidth: 160 readonly property real minWidth: 160
readonly property real maxWidth: 400 readonly property real maxWidth: 400
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@ -74,7 +95,7 @@ RowLayout {
Layout.preferredWidth: Style.fontSizeL * scaling * 1.2 Layout.preferredWidth: Style.fontSizeL * scaling * 1.2
Layout.preferredHeight: Style.fontSizeL * scaling * 1.2 Layout.preferredHeight: Style.fontSizeL * scaling * 1.2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: getTitle() !== "" && Settings.data.bar.showActiveWindowIcon visible: getTitle() !== "" && showIcon
IconImage { IconImage {
id: windowIcon id: windowIcon

View file

@ -11,11 +11,42 @@ Item {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
// Track if we've already notified to avoid spam property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
// Resolve settings: try user settings or defaults from BarWidgetRegistry
readonly property bool alwaysShowPercentage: widgetSettings.alwaysShowPercentage
!== undefined ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
readonly property real warningThreshold: widgetSettings.warningThreshold
!== undefined ? widgetSettings.warningThreshold : widgetMetadata.warningThreshold
// Test mode
readonly property bool testMode: false
readonly property int testPercent: 50
readonly property bool testCharging: true
// Main properties
readonly property var battery: UPower.displayDevice
readonly property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery
&& battery.isPresent)
readonly property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
readonly property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
property bool hasNotifiedLowBattery: false property bool hasNotifiedLowBattery: false
implicitWidth: pill.width implicitWidth: pill.width
@ -23,15 +54,14 @@ Item {
// Helper to evaluate and possibly notify // Helper to evaluate and possibly notify
function maybeNotify(percent, charging) { function maybeNotify(percent, charging) {
const p = Math.round(percent) // Only notify once we are a below threshold
// Only notify exactly at 15%, not at 0% or any other percentage if (!charging && !root.hasNotifiedLowBattery && percent <= warningThreshold) {
if (!charging && p === 15 && !root.hasNotifiedLowBattery) { root.hasNotifiedLowBattery = true
// Maybe go with toast ?
Quickshell.execDetached( Quickshell.execDetached(
["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`]) ["notify-send", "-u", "critical", "-i", "battery-caution", "Low Battery", `Battery is at ${p}%. Please connect charger.`])
root.hasNotifiedLowBattery = true } else if (root.hasNotifiedLowBattery && (charging || percent > warningThreshold + 5)) {
} // Reset when charging starts or when battery recovers 5% above threshold
// Reset when charging starts or when battery recovers above 20%
if (charging || p > 20) {
root.hasNotifiedLowBattery = false root.hasNotifiedLowBattery = false
} }
} }
@ -40,19 +70,10 @@ Item {
Connections { Connections {
target: UPower.displayDevice target: UPower.displayDevice
function onPercentageChanged() { function onPercentageChanged() {
let battery = UPower.displayDevice
let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent
let percent = isReady ? (battery.percentage * 100) : 0
let charging = isReady ? battery.state === UPowerDeviceState.Charging : false
root.maybeNotify(percent, charging) root.maybeNotify(percent, charging)
} }
function onStateChanged() { function onStateChanged() {
let battery = UPower.displayDevice
let isReady = battery && battery.ready && battery.isLaptopBattery && battery.isPresent
let charging = isReady ? battery.state === UPowerDeviceState.Charging : false
// Reset notification flag when charging starts // Reset notification flag when charging starts
if (charging) { if (charging) {
root.hasNotifiedLowBattery = false root.hasNotifiedLowBattery = false
@ -63,15 +84,6 @@ Item {
NPill { NPill {
id: pill id: pill
// Test mode
property bool testMode: false
property int testPercent: 50
property bool testCharging: true
property var battery: UPower.displayDevice
property bool isReady: testMode ? true : (battery && battery.ready && battery.isLaptopBattery && battery.isPresent)
property real percent: testMode ? testPercent : (isReady ? (battery.percentage * 100) : 0)
property bool charging: testMode ? testCharging : (isReady ? battery.state === UPowerDeviceState.Charging : false)
rightOpen: BarWidgetRegistry.getNPillDirection(root) rightOpen: BarWidgetRegistry.getNPillDirection(root)
icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent, icon: testMode ? BatteryService.getIcon(testPercent, testCharging, true) : BatteryService.getIcon(percent,
charging, isReady) charging, isReady)
@ -81,7 +93,7 @@ Item {
iconCircleColor: Color.mPrimary iconCircleColor: Color.mPrimary
collapsedIconColor: Color.mOnSurface collapsedIconColor: Color.mOnSurface
autoHide: false autoHide: false
forceOpen: isReady && (testMode || battery.isLaptopBattery) && Settings.data.bar.alwaysShowBatteryPercentage forceOpen: isReady && (testMode || battery.isLaptopBattery) && alwaysShowPercentage
disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery)) disableOpen: (!isReady || (!testMode && !battery.isLaptopBattery))
tooltipText: { tooltipText: {
let lines = [] let lines = []

View file

@ -10,10 +10,28 @@ Item {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool userAlwaysShowPercentage: (widgetSettings.alwaysShowPercentage
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
// Used to avoid opening the pill on Quickshell startup // Used to avoid opening the pill on Quickshell startup
property bool firstBrightnessReceived: false property bool firstBrightnessReceived: false
@ -69,6 +87,7 @@ Item {
var monitor = getMonitor() var monitor = getMonitor()
return monitor ? (Math.round(monitor.brightness * 100) + "%") : "" return monitor ? (Math.round(monitor.brightness * 100) + "%") : ""
} }
forceOpen: userAlwaysShowPercentage
tooltipText: { tooltipText: {
var monitor = getMonitor() var monitor = getMonitor()
if (!monitor) if (!monitor)

View file

@ -10,24 +10,70 @@ Rectangle {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
// Resolve settings: try user settings or defaults from BarWidgetRegistry
readonly property bool showDate: widgetSettings.showDate !== undefined ? widgetSettings.showDate : widgetMetadata.showDate
readonly property bool use12h: widgetSettings.use12HourClock !== undefined ? widgetSettings.use12HourClock : widgetMetadata.use12HourClock
readonly property bool showSeconds: widgetSettings.showSeconds !== undefined ? widgetSettings.showSeconds : widgetMetadata.showSeconds
readonly property bool reverseDayMonth: widgetSettings.reverseDayMonth
!== undefined ? widgetSettings.reverseDayMonth : widgetMetadata.reverseDayMonth
implicitWidth: clock.width + Style.marginM * 2 * scaling implicitWidth: clock.width + Style.marginM * 2 * scaling
implicitHeight: Math.round(Style.capsuleHeight * scaling) implicitHeight: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling) radius: Math.round(Style.radiusM * scaling)
color: Color.mSurfaceVariant color: Color.mSurfaceVariant
// Clock Icon with attached calendar // Clock Icon with attached calendar
NClock { NText {
id: clock id: clock
anchors.verticalCenter: parent.verticalCenter text: {
anchors.horizontalCenter: parent.horizontalCenter const now = Time.date
const timeFormat = use12h ? (showSeconds ? "h:mm:ss AP" : "h:mm AP") : (showSeconds ? "HH:mm:ss" : "HH:mm")
const timeString = Qt.formatDateTime(now, timeFormat)
NTooltip { if (showDate) {
id: tooltip let dayName = now.toLocaleDateString(Qt.locale(), "ddd")
text: `${Time.dateString}.` dayName = dayName.charAt(0).toUpperCase() + dayName.slice(1)
target: clock let day = now.getDate()
positionAbove: Settings.data.bar.position === "bottom" let month = now.toLocaleDateString(Qt.locale(), "MMM")
return timeString + " - " + (reverseDayMonth ? `${dayName}, ${month} ${day}` : `${dayName}, ${day} ${month}`)
}
return timeString
} }
anchors.centerIn: parent
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
}
NTooltip {
id: tooltip
text: `${Time.formatDate(reverseDayMonth)}.`
target: clock
positionAbove: Settings.data.bar.position === "bottom"
}
MouseArea {
id: clockMouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: { onEntered: {
if (!PanelService.getPanel("calendarPanel")?.active) { if (!PanelService.getPanel("calendarPanel")?.active) {
tooltip.show() tooltip.show()

View file

@ -13,11 +13,13 @@ NIconButton {
property var screen property var screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
// Get user settings from Settings data property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase() var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
@ -30,30 +32,27 @@ NIconButton {
} }
// Use settings or defaults from BarWidgetRegistry // Use settings or defaults from BarWidgetRegistry
readonly property string userIcon: widgetSettings.icon || BarWidgetRegistry.widgetMetadata["CustomButton"].icon readonly property string customIcon: widgetSettings.icon || widgetMetadata.icon
readonly property string userLeftClickExec: widgetSettings.leftClickExec readonly property string leftClickExec: widgetSettings.leftClickExec || widgetMetadata.leftClickExec
|| BarWidgetRegistry.widgetMetadata["CustomButton"].leftClickExec readonly property string rightClickExec: widgetSettings.rightClickExec || widgetMetadata.rightClickExec
readonly property string userRightClickExec: widgetSettings.rightClickExec readonly property string middleClickExec: widgetSettings.middleClickExec || widgetMetadata.middleClickExec
|| BarWidgetRegistry.widgetMetadata["CustomButton"].rightClickExec readonly property bool hasExec: (leftClickExec || rightClickExec || middleClickExec)
readonly property string userMiddleClickExec: widgetSettings.middleClickExec
|| BarWidgetRegistry.widgetMetadata["CustomButton"].middleClickExec
readonly property bool hasExec: (userLeftClickExec || userRightClickExec || userMiddleClickExec)
sizeRatio: 0.8 sizeRatio: 0.8
icon: userIcon icon: customIcon
tooltipText: { tooltipText: {
if (!hasExec) { if (!hasExec) {
return "Custom Button - Configure in settings" return "Custom Button - Configure in settings"
} else { } else {
var lines = [] var lines = []
if (userLeftClickExec !== "") { if (leftClickExec !== "") {
lines.push(`Left click: <i>${userLeftClickExec}</i>.`) lines.push(`Left click: <i>${leftClickExec}</i>.`)
} }
if (userRightClickExec !== "") { if (rightClickExec !== "") {
lines.push(`Right click: <i>${userRightClickExec}</i>.`) lines.push(`Right click: <i>${rightClickExec}</i>.`)
} }
if (userMiddleClickExec !== "") { if (middleClickExec !== "") {
lines.push(`Middle click: <i>${userMiddleClickExec}</i>.`) lines.push(`Middle click: <i>${middleClickExec}</i>.`)
} }
return lines.join("<br/>") return lines.join("<br/>")
} }
@ -61,9 +60,9 @@ NIconButton {
opacity: hasExec ? Style.opacityFull : Style.opacityMedium opacity: hasExec ? Style.opacityFull : Style.opacityMedium
onClicked: { onClicked: {
if (userLeftClickExec) { if (leftClickExec) {
Quickshell.execDetached(["sh", "-c", userLeftClickExec]) Quickshell.execDetached(["sh", "-c", leftClickExec])
Logger.log("CustomButton", `Executing command: ${userLeftClickExec}`) Logger.log("CustomButton", `Executing command: ${leftClickExec}`)
} else if (!hasExec) { } else if (!hasExec) {
// No script was defined, open settings // No script was defined, open settings
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
@ -73,16 +72,16 @@ NIconButton {
} }
onRightClicked: { onRightClicked: {
if (userRightClickExec) { if (rightClickExec) {
Quickshell.execDetached(["sh", "-c", userRightClickExec]) Quickshell.execDetached(["sh", "-c", rightClickExec])
Logger.log("CustomButton", `Executing command: ${userRightClickExec}`) Logger.log("CustomButton", `Executing command: ${rightClickExec}`)
} }
} }
onMiddleClicked: { onMiddleClicked: {
if (userMiddleClickExec) { if (middleClickExec) {
Quickshell.execDetached(["sh", "-c", userMiddleClickExec]) Quickshell.execDetached(["sh", "-c", middleClickExec])
Logger.log("CustomButton", `Executing command: ${userMiddleClickExec}`) Logger.log("CustomButton", `Executing command: ${middleClickExec}`)
} }
} }
} }

View file

@ -12,9 +12,6 @@ Item {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
property string barSection: ""
property int sectionWidgetIndex: 0
property int sectionWidgetsCount: 0
// Use the shared service for keyboard layout // Use the shared service for keyboard layout
property string currentLayout: KeyboardLayoutService.currentLayout property string currentLayout: KeyboardLayoutService.currentLayout

View file

@ -12,18 +12,44 @@ RowLayout {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool showAlbumArt: (widgetSettings.showAlbumArt
!== undefined) ? widgetSettings.showAlbumArt : widgetMetadata.showAlbumArt
readonly property bool showVisualizer: (widgetSettings.showVisualizer
!== undefined) ? widgetSettings.showVisualizer : widgetMetadata.showVisualizer
readonly property string visualizerType: (widgetSettings.visualizerType !== undefined && widgetSettings.visualizerType
!== "") ? widgetSettings.visualizerType : widgetMetadata.visualizerType
readonly property real minWidth: 160 readonly property real minWidth: 160
readonly property real maxWidth: 400 readonly property real maxWidth: 400
function getTitle() {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
}
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
visible: MediaService.currentPlayer !== null && MediaService.canPlay visible: MediaService.currentPlayer !== null && MediaService.canPlay
Layout.preferredWidth: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0 Layout.preferredWidth: MediaService.currentPlayer !== null && MediaService.canPlay ? implicitWidth : 0
function getTitle() {
return MediaService.trackTitle + (MediaService.trackArtist !== "" ? ` - ${MediaService.trackArtist}` : "")
}
// A hidden text element to safely measure the full title width // A hidden text element to safely measure the full title width
NText { NText {
id: fullTitleMetrics id: fullTitleMetrics
@ -58,8 +84,7 @@ RowLayout {
Loader { Loader {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "linear" active: showVisualizer && visualizerType == "linear" && MediaService.isPlaying
&& MediaService.isPlaying
z: 0 z: 0
sourceComponent: LinearSpectrum { sourceComponent: LinearSpectrum {
@ -74,8 +99,7 @@ RowLayout {
Loader { Loader {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "mirrored" active: showVisualizer && visualizerType == "mirrored" && MediaService.isPlaying
&& MediaService.isPlaying
z: 0 z: 0
sourceComponent: MirroredSpectrum { sourceComponent: MirroredSpectrum {
@ -90,8 +114,7 @@ RowLayout {
Loader { Loader {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "wave" active: showVisualizer && visualizerType == "wave" && MediaService.isPlaying
&& MediaService.isPlaying
z: 0 z: 0
sourceComponent: WaveSpectrum { sourceComponent: WaveSpectrum {
@ -115,12 +138,12 @@ RowLayout {
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: !Settings.data.audio.showMiniplayerAlbumArt && getTitle() !== "" && !trackArt.visible visible: !showAlbumArt && getTitle() !== "" && !trackArt.visible
} }
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: Settings.data.audio.showMiniplayerAlbumArt visible: showAlbumArt
spacing: 0 spacing: 0
Item { Item {

View file

@ -12,10 +12,28 @@ Item {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
// Used to avoid opening the pill on Quickshell startup // Used to avoid opening the pill on Quickshell startup
property bool firstInputVolumeReceived: false property bool firstInputVolumeReceived: false
property int wheelAccumulator: 0 property int wheelAccumulator: 0
@ -78,6 +96,7 @@ Item {
collapsedIconColor: Color.mOnSurface collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.inputVolume * 100) + "%" text: Math.floor(AudioService.inputVolume * 100) + "%"
forceOpen: alwaysShowPercentage
tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100) tooltipText: "Microphone: " + Math.round(AudioService.inputVolume * 100)
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."

View file

@ -4,6 +4,7 @@ import QtQuick.Controls
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import qs.Commons import qs.Commons
import qs.Modules.SettingsPanel
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@ -14,7 +15,6 @@ NIconButton {
property real scaling: 1.0 property real scaling: 1.0
sizeRatio: 0.8 sizeRatio: 0.8
colorBg: Color.mSurfaceVariant colorBg: Color.mSurfaceVariant
colorFg: Color.mOnSurface colorFg: Color.mOnSurface
colorBorder: Color.transparent colorBorder: Color.transparent
@ -26,7 +26,7 @@ NIconButton {
onRightClicked: { onRightClicked: {
var settingsPanel = PanelService.getPanel("settingsPanel") var settingsPanel = PanelService.getPanel("settingsPanel")
settingsPanel.requestedTab = SettingsPanel.Tab.Display settingsPanel.requestedTab = SettingsPanel.Tab.Brightness
settingsPanel.open(screen) settingsPanel.open(screen)
} }
} }

View file

@ -13,6 +13,45 @@ NIconButton {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool showUnreadBadge: (widgetSettings.showUnreadBadge
!== undefined) ? widgetSettings.showUnreadBadge : widgetMetadata.showUnreadBadge
readonly property bool hideWhenZero: (widgetSettings.hideWhenZero
!== undefined) ? widgetSettings.hideWhenZero : widgetMetadata.hideWhenZero
function lastSeenTs() {
return Settings.data.notifications?.lastSeenTs || 0
}
function computeUnreadCount() {
var since = lastSeenTs()
var count = 0
var model = NotificationService.historyModel
for (var i = 0; i < model.count; i++) {
var item = model.get(i)
var ts = item.timestamp instanceof Date ? item.timestamp.getTime() : item.timestamp
if (ts > since)
count++
}
return count
}
sizeRatio: 0.8 sizeRatio: 0.8
icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications" icon: Settings.data.notifications.doNotDisturb ? "notifications_off" : "notifications"
tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'." tooltipText: Settings.data.notifications.doNotDisturb ? "Notification history.\nRight-click to disable 'Do Not Disturb'." : "Notification history.\nRight-click to enable 'Do Not Disturb'."
@ -21,7 +60,40 @@ NIconButton {
colorBorder: Color.transparent colorBorder: Color.transparent
colorBorderHover: Color.transparent colorBorderHover: Color.transparent
onClicked: PanelService.getPanel("notificationHistoryPanel")?.toggle(screen, this) onClicked: {
var panel = PanelService.getPanel("notificationHistoryPanel")
panel?.toggle(screen, this)
Settings.data.notifications.lastSeenTs = Time.timestamp * 1000
}
onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb onRightClicked: Settings.data.notifications.doNotDisturb = !Settings.data.notifications.doNotDisturb
Loader {
anchors.right: parent.right
anchors.top: parent.top
anchors.rightMargin: -4 * scaling
anchors.topMargin: -4 * scaling
z: 2
active: showUnreadBadge && (!hideWhenZero || computeUnreadCount() > 0)
sourceComponent: Rectangle {
id: badge
readonly property int count: computeUnreadCount()
readonly property string label: count <= 99 ? String(count) : "99+"
readonly property real pad: 8 * scaling
height: 16 * scaling
width: Math.max(height, textNode.implicitWidth + pad)
radius: height / 2
color: Color.mError
border.color: Color.mSurface
border.width: 1
visible: count > 0 || !hideWhenZero
NText {
id: textNode
anchors.centerIn: parent
text: badge.label
font.pointSize: Style.fontSizeXXS * scaling
color: Color.mOnError
}
}
}
} }

View file

@ -1,3 +1,4 @@
import QtQuick
import Quickshell import Quickshell
import Quickshell.Widgets import Quickshell.Widgets
import QtQuick.Effects import QtQuick.Effects
@ -11,7 +12,28 @@ NIconButton {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
icon: Settings.data.bar.useDistroLogo ? "" : "widgets" // Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool useDistroLogo: (widgetSettings.useDistroLogo
!== undefined) ? widgetSettings.useDistroLogo : widgetMetadata.useDistroLogo
icon: useDistroLogo ? "" : "widgets"
tooltipText: "Open side panel." tooltipText: "Open side panel."
sizeRatio: 0.8 sizeRatio: 0.8
@ -24,14 +46,13 @@ NIconButton {
onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this) onClicked: PanelService.getPanel("sidePanel")?.toggle(screen, this)
onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen) onRightClicked: PanelService.getPanel("settingsPanel")?.toggle(screen)
// When enabled, draw the distro logo instead of the icon glyph
IconImage { IconImage {
id: logo id: logo
anchors.centerIn: parent anchors.centerIn: parent
width: root.width * 0.6 width: root.width * 0.6
height: width height: width
source: Settings.data.bar.useDistroLogo ? DistroLogoService.osLogo : "" source: useDistroLogo ? DistroLogoService.osLogo : ""
visible: false //Settings.data.bar.useDistroLogo && source !== "" visible: useDistroLogo && source !== ""
smooth: true smooth: true
} }

View file

@ -12,11 +12,13 @@ Item {
property var screen property var screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: -1 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
// Get user settings from Settings data - make it reactive property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: { property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase() var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) { if (section && sectionWidgetIndex >= 0) {
@ -29,19 +31,10 @@ Item {
} }
// Use settings or defaults from BarWidgetRegistry // Use settings or defaults from BarWidgetRegistry
readonly property int userWidth: { readonly property int spacerWidth: widgetSettings.width !== undefined ? widgetSettings.width : widgetMetadata.width
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex].width || BarWidgetRegistry.widgetMetadata["Spacer"].width
}
}
return BarWidgetRegistry.widgetMetadata["Spacer"].width
}
// Set the width based on user settings // Set the width based on user settings
implicitWidth: userWidth * scaling implicitWidth: spacerWidth * scaling
implicitHeight: Style.barHeight * scaling implicitHeight: Style.barHeight * scaling
width: implicitWidth width: implicitWidth
height: implicitHeight height: implicitHeight
@ -51,6 +44,6 @@ Item {
anchors.fill: parent anchors.fill: parent
color: Qt.rgba(1, 0, 0, 0.1) // Very subtle red tint color: Qt.rgba(1, 0, 0, 0.1) // Very subtle red tint
visible: Settings.data.general.debugMode || false visible: Settings.data.general.debugMode || false
radius: 2 * scaling radius: Style.radiusXXS * scaling
} }
} }

View file

@ -11,6 +11,34 @@ RowLayout {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool showCpuUsage: (widgetSettings.showCpuUsage
!== undefined) ? widgetSettings.showCpuUsage : widgetMetadata.showCpuUsage
readonly property bool showCpuTemp: (widgetSettings.showCpuTemp !== undefined) ? widgetSettings.showCpuTemp : widgetMetadata.showCpuTemp
readonly property bool showMemoryUsage: (widgetSettings.showMemoryUsage
!== undefined) ? widgetSettings.showMemoryUsage : widgetMetadata.showMemoryUsage
readonly property bool showMemoryAsPercent: (widgetSettings.showMemoryAsPercent
!== undefined) ? widgetSettings.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
readonly property bool showNetworkStats: (widgetSettings.showNetworkStats
!== undefined) ? widgetSettings.showNetworkStats : widgetMetadata.showNetworkStats
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
@ -34,6 +62,7 @@ RowLayout {
id: cpuUsageLayout id: cpuUsageLayout
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showCpuUsage
NIcon { NIcon {
id: cpuUsageIcon id: cpuUsageIcon
@ -59,6 +88,7 @@ RowLayout {
// spacing is thin here to compensate for the vertical thermometer icon // spacing is thin here to compensate for the vertical thermometer icon
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showCpuTemp
NIcon { NIcon {
text: "thermometer" text: "thermometer"
@ -81,6 +111,7 @@ RowLayout {
id: memoryUsageLayout id: memoryUsageLayout
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: showMemoryUsage
NIcon { NIcon {
text: "memory" text: "memory"
@ -88,7 +119,7 @@ RowLayout {
} }
NText { NText {
text: `${SystemStatService.memGb}G` text: showMemoryAsPercent ? `${SystemStatService.memPercent}%` : `${SystemStatService.memGb}G`
font.family: Settings.data.ui.fontFixed font.family: Settings.data.ui.fontFixed
font.pointSize: Style.fontSizeS * scaling font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightMedium font.weight: Style.fontWeightMedium
@ -103,7 +134,7 @@ RowLayout {
id: networkDownloadLayout id: networkDownloadLayout
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: Settings.data.bar.showNetworkStats visible: showNetworkStats
NIcon { NIcon {
text: "download" text: "download"
@ -126,7 +157,7 @@ RowLayout {
id: networkUploadLayout id: networkUploadLayout
spacing: Style.marginXS * scaling spacing: Style.marginXS * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
visible: Settings.data.bar.showNetworkStats visible: showNetworkStats
NIcon { NIcon {
text: "upload" text: "upload"

View file

@ -15,6 +15,7 @@ Rectangle {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
readonly property real itemSize: 24 * scaling readonly property real itemSize: 24 * scaling
function onLoaded() { function onLoaded() {

View file

@ -12,10 +12,28 @@ Item {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: "" property string barSection: ""
property int sectionWidgetIndex: 0 property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0 property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property bool alwaysShowPercentage: (widgetSettings.alwaysShowPercentage
!== undefined) ? widgetSettings.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
// Used to avoid opening the pill on Quickshell startup // Used to avoid opening the pill on Quickshell startup
property bool firstVolumeReceived: false property bool firstVolumeReceived: false
property int wheelAccumulator: 0 property int wheelAccumulator: 0
@ -63,6 +81,7 @@ Item {
collapsedIconColor: Color.mOnSurface collapsedIconColor: Color.mOnSurface
autoHide: false // Important to be false so we can hover as long as we want autoHide: false // Important to be false so we can hover as long as we want
text: Math.floor(AudioService.volume * 100) + "%" text: Math.floor(AudioService.volume * 100) + "%"
forceOpen: alwaysShowPercentage
tooltipText: "Volume: " + Math.round(AudioService.volume * 100) tooltipText: "Volume: " + Math.round(AudioService.volume * 100)
+ "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute." + "%\nLeft click for advanced settings.\nScroll up/down to change volume.\nRight click to toggle mute."

View file

@ -14,6 +14,26 @@ Item {
property ShellScreen screen property ShellScreen screen
property real scaling: 1.0 property real scaling: 1.0
// Widget properties passed from Bar.qml for per-instance settings
property string widgetId: ""
property string barSection: ""
property int sectionWidgetIndex: -1
property int sectionWidgetsCount: 0
property var widgetMetadata: BarWidgetRegistry.widgetMetadata[widgetId]
property var widgetSettings: {
var section = barSection.replace("Section", "").toLowerCase()
if (section && sectionWidgetIndex >= 0) {
var widgets = Settings.data.bar.widgets[section]
if (widgets && sectionWidgetIndex < widgets.length) {
return widgets[sectionWidgetIndex]
}
}
return {}
}
readonly property string labelMode: (widgetSettings.labelMode !== undefined) ? widgetSettings.labelMode : widgetMetadata.labelMode
property bool isDestroying: false property bool isDestroying: false
property bool hovered: false property bool hovered: false
@ -22,8 +42,8 @@ Item {
property bool effectsActive: false property bool effectsActive: false
property color effectColor: Color.mPrimary property color effectColor: Color.mPrimary
property int horizontalPadding: Math.round(16 * scaling) property int horizontalPadding: Math.round(Style.marginS * scaling)
property int spacingBetweenPills: Math.round(8 * scaling) property int spacingBetweenPills: Math.round(Style.marginXS * scaling)
signal workspaceChanged(int workspaceId, color accentColor) signal workspaceChanged(int workspaceId, color accentColor)
@ -124,7 +144,7 @@ Item {
Rectangle { Rectangle {
id: workspaceBackground id: workspaceBackground
width: parent.width - Style.marginS * scaling * 2 width: parent.width
height: Math.round(Style.capsuleHeight * scaling) height: Math.round(Style.capsuleHeight * scaling)
radius: Math.round(Style.radiusM * scaling) radius: Math.round(Style.radiusM * scaling)
@ -145,7 +165,7 @@ Item {
model: localWorkspaces model: localWorkspaces
Item { Item {
id: workspacePillContainer id: workspacePillContainer
height: (Settings.data.bar.showWorkspaceLabel !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling) height: (labelMode !== "none") ? Math.round(18 * scaling) : Math.round(14 * scaling)
width: root.calculatedWsWidth(model) width: root.calculatedWsWidth(model)
Rectangle { Rectangle {
@ -153,15 +173,13 @@ Item {
anchors.fill: parent anchors.fill: parent
Loader { Loader {
active: (Settings.data.bar.showWorkspaceLabel !== "none") active: (labelMode !== "none")
sourceComponent: Component { sourceComponent: Component {
Text { Text {
// Center horizontally
x: (pill.width - width) / 2 x: (pill.width - width) / 2
// Center vertically accounting for font metrics
y: (pill.height - height) / 2 + (height - contentHeight) / 2 y: (pill.height - height) / 2 + (height - contentHeight) / 2
text: { text: {
if (Settings.data.bar.showWorkspaceLabel === "name" && model.name && model.name.length > 0) { if (labelMode === "name" && model.name && model.name.length > 0) {
return model.name.substring(0, 2) return model.name.substring(0, 2)
} else { } else {
return model.idx.toString() return model.idx.toString()

View file

@ -39,7 +39,7 @@ NBox {
const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => { const totalSum = JSON.stringify(widget).split('').reduce((acc, character) => {
return acc + character.charCodeAt(0) return acc + character.charCodeAt(0)
}, 0) }, 0)
switch (totalSum % 10) { switch (totalSum % 5) {
case 0: case 0:
return Color.mPrimary return Color.mPrimary
case 1: case 1:
@ -50,16 +50,6 @@ NBox {
return Color.mError return Color.mError
case 4: case 4:
return Color.mOnSurface return Color.mOnSurface
case 5:
return Qt.darker(Color.mPrimary, 1.3)
case 6:
return Qt.darker(Color.mSecondary, 1.3)
case 7:
return Qt.darker(Color.mTertiary, 1.3)
case 8:
return Qt.darker(Color.mError, 1.3)
case 9:
return Qt.darker(Color.mOnSurface, 1.3)
} }
} }
@ -75,7 +65,7 @@ NBox {
text: sectionName + " Section" text: sectionName + " Section"
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mSecondary color: Color.mOnSurface
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@ -89,7 +79,7 @@ NBox {
description: "" description: ""
placeholder: "Select a widget to add..." placeholder: "Select a widget to add..."
onSelected: key => comboBox.currentKey = key onSelected: key => comboBox.currentKey = key
popupHeight: 240 * scaling popupHeight: 340 * scaling
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
} }
@ -188,13 +178,33 @@ NBox {
colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight) colorBgHover: Qt.alpha(Color.mOnPrimary, Style.opacityLight)
colorFgHover: Color.mOnPrimary colorFgHover: Color.mOnPrimary
onClicked: { onClicked: {
var dialog = Qt.createComponent("BarWidgetSettingsDialog.qml").createObject(root, { var component = Qt.createComponent(Qt.resolvedUrl("BarWidgetSettingsDialog.qml"))
"widgetIndex": index, function instantiateAndOpen() {
"widgetData": modelData, var dialog = component.createObject(root, {
"widgetId": modelData.id, "widgetIndex": index,
"parent": Overlay.overlay "widgetData": modelData,
}) "widgetId": modelData.id,
dialog.open() "parent": Overlay.overlay
})
if (dialog) {
dialog.open()
} else {
Logger.error("BarSectionEditor", "Failed to create settings dialog instance")
}
}
if (component.status === Component.Ready) {
instantiateAndOpen()
} else if (component.status === Component.Error) {
Logger.error("BarSectionEditor", component.errorString())
} else {
component.statusChanged.connect(function () {
if (component.status === Component.Ready) {
instantiateAndOpen()
} else if (component.status === Component.Error) {
Logger.error("BarSectionEditor", component.errorString())
}
})
}
} }
} }
} }
@ -221,7 +231,7 @@ NBox {
MouseArea { MouseArea {
id: flowDragArea id: flowDragArea
anchors.fill: parent anchors.fill: parent
z: 999 // Above all widgets to ensure it gets events first z: -1 // Ensure this mouse area is below the Settings and Close buttons
// Critical properties for proper event handling // Critical properties for proper event handling
acceptedButtons: Qt.LeftButton acceptedButtons: Qt.LeftButton

View file

@ -0,0 +1,134 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
import "./WidgetSettings" as WidgetSettings
// Widget Settings Dialog Component
Popup {
id: settingsPopup
property int widgetIndex: -1
property var widgetData: null
property string widgetId: ""
// Center popup in parent
x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5
width: 420 * scaling
height: content.implicitHeight + padding * 2
padding: Style.marginXL * scaling
modal: true
background: Rectangle {
id: bgRect
color: Color.mSurface
radius: Style.radiusL * scaling
border.color: Color.mPrimary
border.width: Style.borderM * scaling
}
// Load settings when popup opens with data
onOpened: {
if (widgetData && widgetId) {
loadWidgetSettings()
}
}
function loadWidgetSettings() {
const widgetSettingsMap = {
"ActiveWindow": "WidgetSettings/ActiveWindowSettings.qml",
"Battery": "WidgetSettings/BatterySettings.qml",
"Brightness": "WidgetSettings/BrightnessSettings.qml",
"Clock": "WidgetSettings/ClockSettings.qml",
"CustomButton": "WidgetSettings/CustomButtonSettings.qml",
"MediaMini": "WidgetSettings/MediaMiniSettings.qml",
"Microphone": "WidgetSettings/MicrophoneSettings.qml",
"NotificationHistory": "WidgetSettings/NotificationHistorySettings.qml",
"Workspace": "WidgetSettings/WorkspaceSettings.qml",
"SidePanelToggle": "WidgetSettings/SidePanelToggleSettings.qml",
"Spacer": "WidgetSettings/SpacerSettings.qml",
"SystemMonitor": "WidgetSettings/SystemMonitorSettings.qml",
"Volume": "WidgetSettings/VolumeSettings.qml"
}
const source = widgetSettingsMap[widgetId]
if (source) {
// Use setSource to pass properties at creation time
settingsLoader.setSource(source, {
"widgetData": widgetData,
"widgetMetadata": BarWidgetRegistry.widgetMetadata[widgetId]
})
}
}
ColumnLayout {
id: content
width: parent.width
spacing: Style.marginM * scaling
// Title
RowLayout {
Layout.fillWidth: true
NText {
text: `${settingsPopup.widgetId} Settings`
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.fillWidth: true
}
NIconButton {
icon: "close"
onClicked: settingsPopup.close()
}
}
// Separator
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
}
// Settings based on widget type
// Will be triggered via settingsLoader.setSource()
Loader {
id: settingsLoader
Layout.fillWidth: true
}
// Action buttons
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
Item {
Layout.fillWidth: true
}
NButton {
text: "Cancel"
outlined: true
onClicked: settingsPopup.close()
}
NButton {
text: "Apply"
icon: "check"
onClicked: {
if (settingsLoader.item && settingsLoader.item.saveSettings) {
var newSettings = settingsLoader.item.saveSettings()
root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings)
settingsPopup.close()
}
}
}
}
}
}

View file

@ -0,0 +1,32 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueShowIcon: widgetData.showIcon !== undefined ? widgetData.showIcon : widgetMetadata.showIcon
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.showIcon = valueShowIcon
return settings
}
NToggle {
id: showIcon
Layout.fillWidth: true
label: "Show app icon"
checked: root.valueShowIcon
onToggled: checked => root.valueShowIcon = checked
}
}

View file

@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage
return settings
}
NToggle {
label: "Always show percentage"
checked: root.valueAlwaysShowPercentage
onToggled: checked => root.valueAlwaysShowPercentage = checked
}
}

View file

@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage
return settings
}
NToggle {
label: "Always show percentage"
checked: valueAlwaysShowPercentage
onToggled: checked => valueAlwaysShowPercentage = checked
}
}

View file

@ -0,0 +1,54 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueShowDate: widgetData.showDate !== undefined ? widgetData.showDate : widgetMetadata.showDate
property bool valueUse12h: widgetData.use12HourClock !== undefined ? widgetData.use12HourClock : widgetMetadata.use12HourClock
property bool valueShowSeconds: widgetData.showSeconds !== undefined ? widgetData.showSeconds : widgetMetadata.showSeconds
property bool valueReverseDayMonth: widgetData.reverseDayMonth !== undefined ? widgetData.reverseDayMonth : widgetMetadata.reverseDayMonth
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.showDate = valueShowDate
settings.use12HourClock = valueUse12h
settings.showSeconds = valueShowSeconds
settings.reverseDayMonth = valueReverseDayMonth
return settings
}
NToggle {
label: "Show date"
checked: valueShowDate
onToggled: checked => valueShowDate = checked
}
NToggle {
label: "Use 12-hour clock"
checked: valueUse12h
onToggled: checked => valueUse12h = checked
}
NToggle {
label: "Show seconds"
checked: valueShowSeconds
onToggled: checked => valueShowSeconds = checked
}
NToggle {
label: "Reverse day and month"
checked: valueReverseDayMonth
onToggled: checked => valueReverseDayMonth = checked
}
}

View file

@ -0,0 +1,58 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.icon = iconInput.text
settings.leftClickExec = leftClickExecInput.text
settings.rightClickExec = rightClickExecInput.text
settings.middleClickExec = middleClickExecInput.text
return settings
}
// Icon setting
NTextInput {
id: iconInput
Layout.fillWidth: true
label: "Icon Name"
description: "Choose a name from the Material Icon set."
placeholderText: "Enter icon name (e.g., favorite, home, settings)"
text: widgetData?.icon || widgetMetadata.icon
}
NTextInput {
id: leftClickExecInput
Layout.fillWidth: true
label: "Left Click Command"
placeholderText: "Enter command to execute (app or custom script)"
text: widgetData?.leftClickExec || widgetMetadata.leftClickExec
}
NTextInput {
id: rightClickExecInput
Layout.fillWidth: true
label: "Right Click Command"
placeholderText: "Enter command to execute (app or custom script)"
text: widgetData?.rightClickExec || widgetMetadata.rightClickExec
}
NTextInput {
id: middleClickExecInput
Layout.fillWidth: true
label: "Middle Click Command"
placeholderText: "Enter command to execute (app or custom script)"
text: widgetData.middleClickExec || widgetMetadata.middleClickExec
}
}

View file

@ -0,0 +1,62 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueShowAlbumArt: widgetData.showAlbumArt !== undefined ? widgetData.showAlbumArt : widgetMetadata.showAlbumArt
property bool valueShowVisualizer: widgetData.showVisualizer !== undefined ? widgetData.showVisualizer : widgetMetadata.showVisualizer
property string valueVisualizerType: widgetData.visualizerType || widgetMetadata.visualizerType
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.showAlbumArt = valueShowAlbumArt
settings.showVisualizer = valueShowVisualizer
settings.visualizerType = valueVisualizerType
return settings
}
NToggle {
label: "Show album art"
checked: valueShowAlbumArt
onToggled: checked => valueShowAlbumArt = checked
}
NToggle {
label: "Show visualizer"
checked: valueShowVisualizer
onToggled: checked => valueShowVisualizer = checked
}
NComboBox {
visible: valueShowVisualizer
label: "Visualizer type"
model: ListModel {
ListElement {
key: "linear"
name: "Linear"
}
ListElement {
key: "mirrored"
name: "Mirrored"
}
ListElement {
key: "wave"
name: "Wave"
}
}
currentKey: valueVisualizerType
onSelected: key => valueVisualizerType = key
minimumWidth: 200 * scaling
}
}

View file

@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage
return settings
}
NToggle {
label: "Always show percentage"
checked: valueAlwaysShowPercentage
onToggled: checked => valueAlwaysShowPercentage = checked
}
}

View file

@ -0,0 +1,38 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueShowUnreadBadge: widgetData.showUnreadBadge !== undefined ? widgetData.showUnreadBadge : widgetMetadata.showUnreadBadge
property bool valueHideWhenZero: widgetData.hideWhenZero !== undefined ? widgetData.hideWhenZero : widgetMetadata.hideWhenZero
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.showUnreadBadge = valueShowUnreadBadge
settings.hideWhenZero = valueHideWhenZero
return settings
}
NToggle {
label: "Show unread badge"
checked: valueShowUnreadBadge
onToggled: checked => valueShowUnreadBadge = checked
}
NToggle {
label: "Hide badge when zero"
checked: valueHideWhenZero
onToggled: checked => valueHideWhenZero = checked
}
}

View file

@ -0,0 +1,30 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueUseDistroLogo: widgetData.useDistroLogo !== undefined ? widgetData.useDistroLogo : widgetMetadata.useDistroLogo
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.useDistroLogo = valueUseDistroLogo
return settings
}
NToggle {
label: "Use distro logo instead of icon"
checked: valueUseDistroLogo
onToggled: checked => valueUseDistroLogo = checked
}
}

View file

@ -0,0 +1,30 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.width = parseInt(widthInput.text) || widgetMetadata.width
return settings
}
NTextInput {
id: widthInput
Layout.fillWidth: true
label: "Width"
description: "Spacing width in pixels"
text: widgetData.width || widgetMetadata.width
placeholderText: "Enter width in pixels"
}
}

View file

@ -0,0 +1,74 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local, editable state for checkboxes
property bool valueShowCpuUsage: widgetData.showCpuUsage !== undefined ? widgetData.showCpuUsage : widgetMetadata.showCpuUsage
property bool valueShowCpuTemp: widgetData.showCpuTemp !== undefined ? widgetData.showCpuTemp : widgetMetadata.showCpuTemp
property bool valueShowMemoryUsage: widgetData.showMemoryUsage !== undefined ? widgetData.showMemoryUsage : widgetMetadata.showMemoryUsage
property bool valueShowMemoryAsPercent: widgetData.showMemoryAsPercent
!== undefined ? widgetData.showMemoryAsPercent : widgetMetadata.showMemoryAsPercent
property bool valueShowNetworkStats: widgetData.showNetworkStats
!== undefined ? widgetData.showNetworkStats : widgetMetadata.showNetworkStats
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.showCpuUsage = valueShowCpuUsage
settings.showCpuTemp = valueShowCpuTemp
settings.showMemoryUsage = valueShowMemoryUsage
settings.showMemoryAsPercent = valueShowMemoryAsPercent
settings.showNetworkStats = valueShowNetworkStats
return settings
}
NToggle {
id: showCpuUsage
Layout.fillWidth: true
label: "CPU usage"
checked: valueShowCpuUsage
onToggled: checked => valueShowCpuUsage = checked
}
NToggle {
id: showCpuTemp
Layout.fillWidth: true
label: "CPU temperature"
checked: valueShowCpuTemp
onToggled: checked => valueShowCpuTemp = checked
}
NToggle {
id: showMemoryUsage
Layout.fillWidth: true
label: "Memory usage"
checked: valueShowMemoryUsage
onToggled: checked => valueShowMemoryUsage = checked
}
NToggle {
id: showMemoryAsPercent
Layout.fillWidth: true
label: "Show memory as percentage"
checked: valueShowMemoryAsPercent
onToggled: checked => valueShowMemoryAsPercent = checked
}
NToggle {
id: showNetworkStats
Layout.fillWidth: true
label: "Network traffic"
checked: valueShowNetworkStats
onToggled: checked => valueShowNetworkStats = checked
}
}

View file

@ -0,0 +1,31 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
// Local state
property bool valueAlwaysShowPercentage: widgetData.alwaysShowPercentage
!== undefined ? widgetData.alwaysShowPercentage : widgetMetadata.alwaysShowPercentage
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.alwaysShowPercentage = valueAlwaysShowPercentage
return settings
}
NToggle {
label: "Always show percentage"
checked: valueAlwaysShowPercentage
onToggled: checked => valueAlwaysShowPercentage = checked
}
}

View file

@ -0,0 +1,44 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
ColumnLayout {
id: root
spacing: Style.marginM * scaling
// Properties to receive data from parent
property var widgetData: null
property var widgetMetadata: null
function saveSettings() {
var settings = Object.assign({}, widgetData || {})
settings.labelMode = labelModeCombo.currentKey
return settings
}
NComboBox {
id: labelModeCombo
label: "Label Mode"
model: ListModel {
ListElement {
key: "none"
name: "None"
}
ListElement {
key: "index"
name: "Index"
}
ListElement {
key: "name"
name: "Name"
}
}
currentKey: widgetData.labelMode || widgetMetadata.labelMode
onSelected: key => labelModeCombo.currentKey = key
minimumWidth: 200 * scaling
}
}

View file

@ -1,186 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import qs.Commons
import qs.Widgets
import qs.Services
// Widget Settings Dialog Component
Popup {
id: settingsPopup
property int widgetIndex: -1
property var widgetData: null
property string widgetId: ""
// Center popup in parent
x: (parent.width - width) * 0.5
y: (parent.height - height) * 0.5
width: 420 * scaling
height: content.implicitHeight + padding * 2
padding: Style.marginXL * scaling
modal: true
background: Rectangle {
id: bgRect
color: Color.mSurface
radius: Style.radiusL * scaling
border.color: Color.mPrimary
border.width: Style.borderM * scaling
}
ColumnLayout {
id: content
width: parent.width
spacing: Style.marginM * scaling
// Title
RowLayout {
Layout.fillWidth: true
NText {
text: "Widget Settings: " + settingsPopup.widgetId
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mPrimary
Layout.fillWidth: true
}
NIconButton {
icon: "close"
onClicked: settingsPopup.close()
}
}
// Separator
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Color.mOutline
}
// Settings based on widget type
Loader {
id: settingsLoader
Layout.fillWidth: true
sourceComponent: {
if (settingsPopup.widgetId === "CustomButton") {
return customButtonSettings
} else if (settingsPopup.widgetId === "Spacer") {
return spacerSettings
}
// Add more widget settings components here as needed
return null
}
}
// Action buttons
RowLayout {
Layout.fillWidth: true
Layout.topMargin: Style.marginM * scaling
Item {
Layout.fillWidth: true
}
NButton {
text: "Cancel"
outlined: true
onClicked: settingsPopup.close()
}
NButton {
text: "Save"
onClicked: {
if (settingsLoader.item && settingsLoader.item.saveSettings) {
var newSettings = settingsLoader.item.saveSettings()
root.updateWidgetSettings(sectionId, settingsPopup.widgetIndex, newSettings)
settingsPopup.close()
}
}
}
}
}
// CustomButton settings component
Component {
id: customButtonSettings
ColumnLayout {
spacing: Style.marginM * scaling
function saveSettings() {
var settings = Object.assign({}, settingsPopup.widgetData)
settings.icon = iconInput.text
settings.leftClickExec = leftClickExecInput.text
settings.rightClickExec = rightClickExecInput.text
settings.middleClickExec = middleClickExecInput.text
return settings
}
// Icon setting
NTextInput {
id: iconInput
Layout.fillWidth: true
Layout.bottomMargin: Style.marginXL * scaling
label: "Icon Name"
description: "Use Material Icon names from the icon set."
text: settingsPopup.widgetData.icon || ""
placeholderText: "Enter icon name (e.g., favorite, home, settings)"
}
NTextInput {
id: leftClickExecInput
Layout.fillWidth: true
label: "Left Click Command"
description: "Command or application to run when left clicked."
text: settingsPopup.widgetData.leftClickExec || ""
placeholderText: "Enter command to execute (app or custom script)"
}
NTextInput {
id: rightClickExecInput
Layout.fillWidth: true
label: "Right Click Command"
description: "Command or application to run when right clicked."
text: settingsPopup.widgetData.rightClickExec || ""
placeholderText: "Enter command to execute (app or custom script)"
}
NTextInput {
id: middleClickExecInput
Layout.fillWidth: true
label: "Middle Click Command"
description: "Command or application to run when middle clicked."
text: settingsPopup.widgetData.middleClickExec || ""
placeholderText: "Enter command to execute (app or custom script)"
}
}
}
// Spacer settings component
Component {
id: spacerSettings
ColumnLayout {
spacing: Style.marginM * scaling
function saveSettings() {
var settings = Object.assign({}, settingsPopup.widgetData)
settings.width = parseInt(widthInput.text) || 20
return settings
}
NTextInput {
id: widthInput
Layout.fillWidth: true
label: "Width (pixels)"
description: "Width of the spacer in pixels."
text: settingsPopup.widgetData.width || "20"
placeholderText: "Enter width in pixels"
}
}
}
}

View file

@ -39,7 +39,7 @@ NPanel {
General, General,
Network, Network,
ScreenRecorder, ScreenRecorder,
TimeWeather, Weather,
Wallpaper, Wallpaper,
WallpaperSelector WallpaperSelector
} }
@ -90,8 +90,8 @@ NPanel {
Tabs.NetworkTab {} Tabs.NetworkTab {}
} }
Component { Component {
id: timeWeatherTab id: weatherTab
Tabs.TimeWeatherTab {} Tabs.WeatherTab {}
} }
Component { Component {
id: colorSchemeTab id: colorSchemeTab
@ -156,10 +156,10 @@ NPanel {
"icon": "brightness_6", "icon": "brightness_6",
"source": brightnessTab "source": brightnessTab
}, { }, {
"id": SettingsPanel.Tab.TimeWeather, "id": SettingsPanel.Tab.Weather,
"label": "Time & Weather", "label": "Weather",
"icon": "schedule", "icon": "partly_cloudy_day",
"source": timeWeatherTab "source": weatherTab
}, { }, {
"id": SettingsPanel.Tab.ColorScheme, "id": SettingsPanel.Tab.ColorScheme,
"label": "Color Scheme", "label": "Color Scheme",

View file

@ -242,21 +242,7 @@ ColumnLayout {
Layout.bottomMargin: Style.marginS * scaling Layout.bottomMargin: Style.marginS * scaling
} }
// Miniplayer section // Preferred player
NToggle {
label: "Show Album Art In Bar Media Player"
description: "Show the album art of the currently playing song next to the title."
checked: Settings.data.audio.showMiniplayerAlbumArt
onToggled: checked => Settings.data.audio.showMiniplayerAlbumArt = checked
}
NToggle {
label: "Show Audio Visualizer In Bar Media Player"
description: "Shows an audio visualizer in the background of the miniplayer."
checked: Settings.data.audio.showMiniplayerCava
onToggled: checked => Settings.data.audio.showMiniplayerCava = checked
}
// Preferred player (persistent)
NTextInput { NTextInput {
label: "Preferred Player" label: "Preferred Player"
description: "Substring to match MPRIS player (identity/bus/desktop)." description: "Substring to match MPRIS player (identity/bus/desktop)."

View file

@ -4,7 +4,7 @@ import QtQuick.Layouts
import qs.Commons import qs.Commons
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.SettingsPanel.Extras import qs.Modules.SettingsPanel.Bar
ColumnLayout { ColumnLayout {
id: root id: root
@ -70,57 +70,6 @@ ColumnLayout {
} }
} }
} }
NToggle {
label: "Show Active Window's Icon"
description: "Display the app icon next to the title of the currently focused window."
checked: Settings.data.bar.showActiveWindowIcon
onToggled: checked => Settings.data.bar.showActiveWindowIcon = checked
}
NToggle {
label: "Show Battery Percentage"
description: "Display battery percentage at all times."
checked: Settings.data.bar.alwaysShowBatteryPercentage
onToggled: checked => Settings.data.bar.alwaysShowBatteryPercentage = checked
}
NToggle {
label: "Show Network Statistics"
description: "Display network upload and download speeds in the system monitor."
checked: Settings.data.bar.showNetworkStats
onToggled: checked => Settings.data.bar.showNetworkStats = checked
}
NToggle {
label: "Replace SidePanel toggle with distro logo"
description: "Show distro logo instead of the SidePanel toggle button in the bar."
checked: Settings.data.bar.useDistroLogo
onToggled: checked => {
Settings.data.bar.useDistroLogo = checked
}
}
NComboBox {
label: "Show Workspaces Labels"
description: "Show the workspace name or index within the workspace indicator."
model: ListModel {
ListElement {
key: "none"
name: "None"
}
ListElement {
key: "index"
name: "Index"
}
ListElement {
key: "name"
name: "Name"
}
}
currentKey: Settings.data.bar.showWorkspaceLabel
onSelected: key => Settings.data.bar.showWorkspaceLabel = key
}
} }
NDivider { NDivider {
@ -138,7 +87,7 @@ ColumnLayout {
text: "Widgets Positioning" text: "Widgets Positioning"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnSurface color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling Layout.bottomMargin: Style.marginS * scaling
} }

View file

@ -299,8 +299,7 @@ ColumnLayout {
currentKey: Settings.data.nightLight.manualSunrise currentKey: Settings.data.nightLight.manualSunrise
placeholder: "Select start time" placeholder: "Select start time"
onSelected: key => Settings.data.nightLight.manualSunrise = key onSelected: key => Settings.data.nightLight.manualSunrise = key
minimumWidth: 120 * scaling
preferredWidth: 120 * scaling
} }
Item {// add a little more spacing Item {// add a little more spacing
@ -316,8 +315,7 @@ ColumnLayout {
currentKey: Settings.data.nightLight.manualSunset currentKey: Settings.data.nightLight.manualSunset
placeholder: "Select stop time" placeholder: "Select stop time"
onSelected: key => Settings.data.nightLight.manualSunset = key onSelected: key => Settings.data.nightLight.manualSunset = key
minimumWidth: 120 * scaling
preferredWidth: 120 * scaling
} }
} }
} }

View file

@ -8,6 +8,7 @@ import qs.Widgets
ColumnLayout { ColumnLayout {
id: root id: root
spacing: 0
// Cache for scheme JSON (can be flat or {dark, light}) // Cache for scheme JSON (can be flat or {dark, light})
property var schemeColorsCache: ({}) property var schemeColorsCache: ({})
@ -103,225 +104,218 @@ ColumnLayout {
} }
} }
// Main Toggles - Dark Mode / Matugen
ColumnLayout { ColumnLayout {
spacing: 0 spacing: Style.marginL * scaling
Layout.fillWidth: true
Item { // Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants)
Layout.fillWidth: true NToggle {
Layout.preferredHeight: 0 label: "Dark Mode"
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
checked: Settings.data.colorSchemes.darkMode
enabled: true
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
} }
ColumnLayout { // Use Matugen
spacing: Style.marginL * scaling NToggle {
Layout.fillWidth: true label: "Enable Matugen"
description: "Automatically generate colors based on your active wallpaper."
checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => {
if (checked) {
// Check if matugen is installed
matugenCheck.running = true
} else {
Settings.data.colorSchemes.useWallpaperColors = false
ToastService.showNotice("Matugen", "Disabled")
// Dark Mode Toggle (affects both Matugen and predefined schemes that provide variants) if (Settings.data.colorSchemes.predefinedScheme) {
NToggle {
label: "Dark Mode"
description: Settings.data.colorSchemes.useWallpaperColors ? "Generate dark theme colors when using Matugen." : "Use a dark variant if available."
checked: Settings.data.colorSchemes.darkMode
enabled: true
onToggled: checked => Settings.data.colorSchemes.darkMode = checked
}
// Use Matugen ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
NToggle {
label: "Enable Matugen"
description: "Automatically generate colors based on your active wallpaper."
checked: Settings.data.colorSchemes.useWallpaperColors
onToggled: checked => {
if (checked) {
// Check if matugen is installed
matugenCheck.running = true
} else {
Settings.data.colorSchemes.useWallpaperColors = false
ToastService.showNotice("Matugen", "Disabled")
if (Settings.data.colorSchemes.predefinedScheme) {
ColorSchemeService.applyScheme(Settings.data.colorSchemes.predefinedScheme)
}
} }
} }
} }
}
}
NDivider { NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling
} }
ColumnLayout { // Predefined Color Schemes
spacing: Style.marginS * scaling ColumnLayout {
Layout.fillWidth: true spacing: Style.marginM * scaling
Layout.fillWidth: true
NText { NText {
text: "Predefined Color Schemes" text: "Predefined Color Schemes"
font.pointSize: Style.fontSizeXXL * scaling font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mSecondary color: Color.mSecondary
} }
NText {
text: "To use these color schemes, you must turn off Matugen. With Matugen enabled, colors are automatically generated from your wallpaper."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
// Color Schemes Grid
GridLayout {
columns: 3
rowSpacing: Style.marginM * scaling
columnSpacing: Style.marginM * scaling
Layout.fillWidth: true
Repeater {
model: ColorSchemeService.schemes
Rectangle {
id: schemeCard
property string schemePath: modelData
NText {
text: "These color schemes are only active when 'Use Matugen' is turned off. With Matugen enabled, colors will be automatically generated from your wallpaper. You can still switch between light and dark themes while using Matugen."
font.pointSize: Style.fontSizeM * scaling
color: Color.mOnSurfaceVariant
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap Layout.preferredHeight: 120 * scaling
} radius: Style.radiusM * scaling
} color: getSchemeColor(modelData, "mSurface")
border.width: Math.max(1, Style.borderL * scaling)
border.color: (!Settings.data.colorSchemes.useWallpaperColors
&& (Settings.data.colorSchemes.predefinedScheme === modelData)) ? Color.mPrimary : Color.mOutline
scale: root.cardScaleLow
// Color Schemes Grid // Mouse area for selection
GridLayout { MouseArea {
columns: 3 anchors.fill: parent
rowSpacing: Style.marginM * scaling onClicked: {
columnSpacing: Style.marginM * scaling // Disable useWallpaperColors when picking a predefined color scheme
Layout.fillWidth: true Settings.data.colorSchemes.useWallpaperColors = false
Logger.log("ColorSchemeTab", "Disabled matugen setting")
Repeater { Settings.data.colorSchemes.predefinedScheme = schemePath
model: ColorSchemeService.schemes ColorSchemeService.applyScheme(schemePath)
}
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
Rectangle { onEntered: {
id: schemeCard schemeCard.scale = root.cardScaleHigh
property string schemePath: modelData
Layout.fillWidth: true
Layout.preferredHeight: 120 * scaling
radius: Style.radiusM * scaling
color: getSchemeColor(modelData, "mSurface")
border.width: Math.max(1, Style.borderL * scaling)
border.color: (!Settings.data.colorSchemes.useWallpaperColors
&& (Settings.data.colorSchemes.predefinedScheme === modelData)) ? Color.mPrimary : Color.mOutline
scale: root.cardScaleLow
// Mouse area for selection
MouseArea {
anchors.fill: parent
onClicked: {
// Disable useWallpaperColors when picking a predefined color scheme
Settings.data.colorSchemes.useWallpaperColors = false
Logger.log("ColorSchemeTab", "Disabled matugen setting")
Settings.data.colorSchemes.predefinedScheme = schemePath
ColorSchemeService.applyScheme(schemePath)
}
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
schemeCard.scale = root.cardScaleHigh
}
onExited: {
schemeCard.scale = root.cardScaleLow
}
} }
// Card content onExited: {
ColumnLayout { schemeCard.scale = root.cardScaleLow
anchors.fill: parent }
anchors.margins: Style.marginXL * scaling }
// Card content
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.marginXL * scaling
spacing: Style.marginS * scaling
// Scheme name
NText {
text: {
// Remove json and the full path
var chunks = schemePath.replace(".json", "").split("/")
return chunks[chunks.length - 1]
}
font.pointSize: Style.fontSizeM * scaling
font.weight: Style.fontWeightBold
color: getSchemeColor(modelData, "mOnSurface")
Layout.fillWidth: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
// Color swatches
RowLayout {
id: swatches
spacing: Style.marginS * scaling spacing: Style.marginS * scaling
Layout.fillWidth: true
Layout.alignment: Qt.AlignHCenter
// Scheme name readonly property int swatchSize: 20 * scaling
NText {
text: { // Primary color swatch
// Remove json and the full path Rectangle {
var chunks = schemePath.replace(".json", "").split("/") width: swatches.swatchSize
return chunks[chunks.length - 1] height: swatches.swatchSize
} radius: width * 0.5
font.pointSize: Style.fontSizeM * scaling color: getSchemeColor(modelData, "mPrimary")
font.weight: Style.fontWeightBold
color: getSchemeColor(modelData, "mOnSurface")
Layout.fillWidth: true
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
} }
// Color swatches // Secondary color swatch
RowLayout { Rectangle {
id: swatches width: swatches.swatchSize
height: swatches.swatchSize
radius: width * 0.5
color: getSchemeColor(modelData, "mSecondary")
}
spacing: Style.marginS * scaling // Tertiary color swatch
Layout.fillWidth: true Rectangle {
Layout.alignment: Qt.AlignHCenter width: swatches.swatchSize
height: swatches.swatchSize
radius: width * 0.5
color: getSchemeColor(modelData, "mTertiary")
}
readonly property int swatchSize: 20 * scaling // Error color swatch
Rectangle {
// Primary color swatch width: swatches.swatchSize
Rectangle { height: swatches.swatchSize
width: swatches.swatchSize radius: width * 0.5
height: swatches.swatchSize color: getSchemeColor(modelData, "mError")
radius: width * 0.5
color: getSchemeColor(modelData, "mPrimary")
}
// Secondary color swatch
Rectangle {
width: swatches.swatchSize
height: swatches.swatchSize
radius: width * 0.5
color: getSchemeColor(modelData, "mSecondary")
}
// Tertiary color swatch
Rectangle {
width: swatches.swatchSize
height: swatches.swatchSize
radius: width * 0.5
color: getSchemeColor(modelData, "mTertiary")
}
// Error color swatch
Rectangle {
width: swatches.swatchSize
height: swatches.swatchSize
radius: width * 0.5
color: getSchemeColor(modelData, "mError")
}
} }
} }
}
// Selection indicator (Checkmark) // Selection indicator (Checkmark)
Rectangle { Rectangle {
visible: !Settings.data.colorSchemes.useWallpaperColors visible: !Settings.data.colorSchemes.useWallpaperColors
&& (Settings.data.colorSchemes.predefinedScheme === schemePath) && (Settings.data.colorSchemes.predefinedScheme === schemePath)
anchors.right: parent.right anchors.right: parent.right
anchors.top: parent.top anchors.top: parent.top
anchors.margins: Style.marginS * scaling anchors.margins: Style.marginS * scaling
width: 24 * scaling width: 24 * scaling
height: 24 * scaling height: 24 * scaling
radius: width * 0.5 radius: width * 0.5
color: Color.mPrimary color: Color.mPrimary
NText { NText {
anchors.centerIn: parent anchors.centerIn: parent
text: "✓" text: "✓"
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
color: Color.mOnPrimary color: Color.mOnPrimary
}
} }
}
// Smooth animations // Smooth animations
Behavior on scale { Behavior on scale {
NumberAnimation { NumberAnimation {
duration: Style.animationNormal duration: Style.animationNormal
easing.type: Easing.OutCubic easing.type: Easing.OutCubic
}
} }
}
Behavior on border.color { Behavior on border.color {
ColorAnimation { ColorAnimation {
duration: Style.animationNormal duration: Style.animationNormal
}
} }
}
Behavior on border.width { Behavior on border.width {
NumberAnimation { NumberAnimation {
duration: Style.animationFast duration: Style.animationFast
}
} }
} }
} }

View file

@ -70,52 +70,6 @@ ColumnLayout {
onToggled: checked => Settings.data.general.dimDesktop = checked onToggled: checked => Settings.data.general.dimDesktop = checked
} }
NToggle {
label: "Auto-hide Dock"
description: "Automatically hide the dock when not in use."
checked: Settings.data.dock.autoHide
onToggled: checked => Settings.data.dock.autoHide = checked
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Dock Background Opacity"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Adjust the background opacity of the dock."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.dock.backgroundOpacity
onMoved: Settings.data.dock.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
ColumnLayout { ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
@ -175,7 +129,70 @@ ColumnLayout {
} }
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Dock
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Dock"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
Layout.bottomMargin: Style.marginS * scaling
}
NToggle {
label: "Auto-hide Dock"
description: "Automatically hide the dock when not in use."
checked: Settings.data.dock.autoHide
onToggled: checked => Settings.data.dock.autoHide = checked
}
ColumnLayout {
spacing: Style.marginXXS * scaling
Layout.fillWidth: true
NText {
text: "Dock Background Opacity"
font.pointSize: Style.fontSizeL * scaling
font.weight: Style.fontWeightBold
color: Color.mOnSurface
}
NText {
text: "Adjust the background opacity of the dock."
font.pointSize: Style.fontSizeXS * scaling
color: Color.mOnSurfaceVariant
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
RowLayout {
NSlider {
Layout.fillWidth: true
from: 0
to: 1
stepSize: 0.01
value: Settings.data.dock.backgroundOpacity
onMoved: Settings.data.dock.backgroundOpacity = value
cutoutColor: Color.mSurface
}
NText {
text: Math.floor(Settings.data.dock.backgroundOpacity * 100) + "%"
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: Style.marginS * scaling
color: Color.mOnSurface
}
}
}
}
NDivider { NDivider {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling Layout.topMargin: Style.marginXL * scaling
@ -206,6 +223,7 @@ ColumnLayout {
currentKey: Settings.data.ui.fontDefault currentKey: Settings.data.ui.fontDefault
placeholder: "Select default font..." placeholder: "Select default font..."
popupHeight: 420 * scaling popupHeight: 420 * scaling
minimumWidth: 300 * scaling
onSelected: function (key) { onSelected: function (key) {
Settings.data.ui.fontDefault = key Settings.data.ui.fontDefault = key
} }
@ -218,6 +236,7 @@ ColumnLayout {
currentKey: Settings.data.ui.fontFixed currentKey: Settings.data.ui.fontFixed
placeholder: "Select monospace font..." placeholder: "Select monospace font..."
popupHeight: 320 * scaling popupHeight: 320 * scaling
minimumWidth: 300 * scaling
onSelected: function (key) { onSelected: function (key) {
Settings.data.ui.fontFixed = key Settings.data.ui.fontFixed = key
} }
@ -230,6 +249,7 @@ ColumnLayout {
currentKey: Settings.data.ui.fontBillboard currentKey: Settings.data.ui.fontBillboard
placeholder: "Select display font..." placeholder: "Select display font..."
popupHeight: 320 * scaling popupHeight: 320 * scaling
minimumWidth: 300 * scaling
onSelected: function (key) { onSelected: function (key) {
Settings.data.ui.fontBillboard = key Settings.data.ui.fontBillboard = key
} }

View file

@ -52,20 +52,6 @@ ColumnLayout {
} }
} }
NToggle {
label: "Enable Clipboard History"
description: "Show clipboard history in the launcher."
checked: Settings.data.appLauncher.enableClipboardHistory
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
}
NToggle {
label: "Use App2Unit for Launching"
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
checked: Settings.data.appLauncher.useApp2Unit
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
}
ColumnLayout { ColumnLayout {
spacing: Style.marginXXS * scaling spacing: Style.marginXXS * scaling
Layout.fillWidth: true Layout.fillWidth: true
@ -105,6 +91,20 @@ ColumnLayout {
} }
} }
} }
NToggle {
label: "Enable Clipboard History"
description: "Show clipboard history in the launcher."
checked: Settings.data.appLauncher.enableClipboardHistory
onToggled: checked => Settings.data.appLauncher.enableClipboardHistory = checked
}
NToggle {
label: "Use App2Unit for Launching"
description: "Use app2unit -- 'desktop-entry' when launching applications for better systemd integration."
checked: Settings.data.appLauncher.useApp2Unit
onToggled: checked => Settings.data.appLauncher.useApp2Unit = checked
}
} }
NDivider { NDivider {

View file

@ -52,46 +52,6 @@ ColumnLayout {
Layout.bottomMargin: Style.marginXL * scaling Layout.bottomMargin: Style.marginXL * scaling
} }
// Time section
ColumnLayout {
spacing: Style.marginL * scaling
Layout.fillWidth: true
NText {
text: "Time Format"
font.pointSize: Style.fontSizeXXL * scaling
font.weight: Style.fontWeightBold
color: Color.mSecondary
}
NToggle {
label: "Use 12-Hour Clock"
description: "Display time in 12-hour format (AM/PM) instead of 24-hour."
checked: Settings.data.location.use12HourClock
onToggled: checked => Settings.data.location.use12HourClock = checked
}
NToggle {
label: "Reverse Day/Month"
description: "Display date as dd/mm instead of mm/dd."
checked: Settings.data.location.reverseDayMonth
onToggled: checked => Settings.data.location.reverseDayMonth = checked
}
NToggle {
label: "Show Date with Clock"
description: "Display date alongside time (e.g., 18:12 - Sat, 23 Aug)."
checked: Settings.data.location.showDateWithClock
onToggled: checked => Settings.data.location.showDateWithClock = checked
}
}
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
// Weather section // Weather section
ColumnLayout { ColumnLayout {
spacing: Style.marginM * scaling spacing: Style.marginM * scaling
@ -111,10 +71,4 @@ ColumnLayout {
onToggled: checked => Settings.data.location.useFahrenheit = checked onToggled: checked => Settings.data.location.useFahrenheit = checked
} }
} }
NDivider {
Layout.fillWidth: true
Layout.topMargin: Style.marginXL * scaling
Layout.bottomMargin: Style.marginXL * scaling
}
} }

View file

@ -38,6 +38,26 @@ Singleton {
}) })
property var widgetMetadata: ({ property var widgetMetadata: ({
"ActiveWindow": {
"allowUserSettings": true,
"showIcon": true
},
"Battery": {
"allowUserSettings": true,
"alwaysShowPercentage": false,
"warningThreshold": 30
},
"Brightness": {
"allowUserSettings": true,
"alwaysShowPercentage": false
},
"Clock": {
"allowUserSettings": true,
"showDate": false,
"use12HourClock": false,
"showSeconds": false,
"reverseDayMonth": true
},
"CustomButton": { "CustomButton": {
"allowUserSettings": true, "allowUserSettings": true,
"icon": "favorite", "icon": "favorite",
@ -45,10 +65,44 @@ Singleton {
"rightClickExec": "", "rightClickExec": "",
"middleClickExec": "" "middleClickExec": ""
}, },
"Microphone": {
"allowUserSettings": true,
"alwaysShowPercentage": false
},
"NotificationHistory": {
"allowUserSettings": true,
"showUnreadBadge": true,
"hideWhenZero": true
},
"Spacer": { "Spacer": {
"allowUserSettings": true, "allowUserSettings": true,
"icon": "space_bar",
"width": 20 "width": 20
},
"SystemMonitor": {
"allowUserSettings": true,
"showCpuUsage": true,
"showCpuTemp": true,
"showMemoryUsage": true,
"showMemoryAsPercent": false,
"showNetworkStats": false
},
"Workspace": {
"allowUserSettings": true,
"labelMode": "index"
},
"MediaMini": {
"allowUserSettings": true,
"showAlbumArt": false,
"showVisualizer": false,
"visualizerType": "linear"
},
"SidePanelToggle": {
"allowUserSettings": true,
"useDistroLogo": false
},
"Volume": {
"allowUserSettings": true,
"alwaysShowPercentage": false
} }
}) })

View file

@ -37,9 +37,7 @@ Singleton {
Process { Process {
id: process id: process
stdinEnabled: true stdinEnabled: true
running: (Settings.data.audio.visualizerType !== "none") running: true
&& (PanelService.getPanel("sidePanel").active || Settings.data.audio.showMiniplayerCava
|| (PanelService.lockScreen && PanelService.lockScreen.active))
command: ["cava", "-p", "/dev/stdin"] command: ["cava", "-p", "/dev/stdin"]
onExited: { onExited: {
stdinEnabled = true stdinEnabled = true

View file

@ -45,7 +45,7 @@ Singleton {
property string version: "Unknown" property string version: "Unknown"
property var contributors: [] property var contributors: []
property double timestamp: 0 property real timestamp: 0
} }
} }

View file

@ -80,7 +80,7 @@ Singleton {
JsonAdapter { JsonAdapter {
id: historyAdapter id: historyAdapter
property var history: [] property var history: []
property double timestamp: 0 property real timestamp: 0
} }
} }
@ -201,12 +201,17 @@ Singleton {
const items = historyAdapter.history || [] const items = historyAdapter.history || []
for (var i = 0; i < items.length; i++) { for (var i = 0; i < items.length; i++) {
const it = items[i] const it = items[i]
// Coerce legacy second-based timestamps to milliseconds
var ts = it.timestamp
if (typeof ts === "number" && ts < 1e12) {
ts = ts * 1000
}
historyModel.append({ historyModel.append({
"summary": it.summary || "", "summary": it.summary || "",
"body": it.body || "", "body": it.body || "",
"appName": it.appName || "", "appName": it.appName || "",
"urgency": it.urgency, "urgency": it.urgency,
"timestamp": it.timestamp ? new Date(it.timestamp) : new Date() "timestamp": ts ? new Date(ts) : new Date()
}) })
} }
} catch (e) { } catch (e) {
@ -225,7 +230,10 @@ Singleton {
"body": n.body, "body": n.body,
"appName": n.appName, "appName": n.appName,
"urgency": n.urgency, "urgency": n.urgency,
"timestamp": (n.timestamp instanceof Date) ? n.timestamp.getTime() : n.timestamp "timestamp"// Always persist in milliseconds
: (n.timestamp instanceof Date) ? n.timestamp.getTime(
) : (typeof n.timestamp === "number"
&& n.timestamp < 1e12 ? n.timestamp * 1000 : n.timestamp)
}) })
} }
historyAdapter.history = arr historyAdapter.history = arr

View file

@ -216,7 +216,11 @@ Singleton {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
// Get specific monitor wallpaper - now from cache // Get specific monitor wallpaper - now from cache
function getWallpaper(screenName) { function getWallpaper(screenName) {
return currentWallpapers[screenName] || "" var path = currentWallpapers[screenName] || ""
if (path === "") {
return Settings.data.wallpaper.defaultWallpaper || ""
}
return path
} }
// ------------------------------------------------------------------- // -------------------------------------------------------------------

View file

@ -77,10 +77,12 @@ Rectangle {
RowLayout { RowLayout {
id: contentRow id: contentRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Style.marginS * scaling spacing: Style.marginXS * scaling
// Icon (optional) // Icon (optional)
NIcon { NIcon {
Layout.alignment: Qt.AlignVCenter
layoutTopMargin: 1 * scaling
visible: root.icon !== "" visible: root.icon !== ""
text: root.icon text: root.icon
font.pointSize: root.iconSize font.pointSize: root.iconSize
@ -105,6 +107,7 @@ Rectangle {
// Text // Text
NText { NText {
Layout.alignment: Qt.AlignVCenter
visible: root.text !== "" visible: root.text !== ""
text: root.text text: root.text
font.pointSize: root.fontSize font.pointSize: root.fontSize

View file

@ -27,6 +27,11 @@ RowLayout {
visible: root.label !== "" || root.description !== "" visible: root.label !== "" || root.description !== ""
} }
// Spacer to push the checkbox to the far right
Item {
Layout.fillWidth: true
}
Rectangle { Rectangle {
id: box id: box
@ -39,13 +44,13 @@ RowLayout {
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Style.animationNormal duration: Style.animationFast
} }
} }
Behavior on border.color { Behavior on border.color {
ColorAnimation { ColorAnimation {
duration: Style.animationNormal duration: Style.animationFast
} }
} }

View file

@ -1,34 +0,0 @@
import QtQuick
import qs.Commons
import qs.Services
import qs.Widgets
Rectangle {
id: root
signal entered
signal exited
signal clicked
width: textItem.paintedWidth
height: textItem.paintedHeight
color: Color.transparent
NText {
id: textItem
text: Time.time
anchors.centerIn: parent
font.pointSize: Style.fontSizeS * scaling
font.weight: Style.fontWeightBold
}
MouseArea {
id: clockMouseArea
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onEntered: root.entered()
onExited: root.exited()
onClicked: root.clicked()
}
}

View file

@ -8,8 +8,7 @@ import qs.Widgets
RowLayout { RowLayout {
id: root id: root
readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling property real minimumWidth: 280 * scaling
property real preferredWidth: 320 * scaling
property real popupHeight: 180 * scaling property real popupHeight: 180 * scaling
property string label: "" property string label: ""
@ -20,9 +19,11 @@ RowLayout {
property string currentKey: "" property string currentKey: ""
property string placeholder: "" property string placeholder: ""
readonly property real preferredHeight: Style.baseWidgetSize * 1.1 * scaling
signal selected(string key) signal selected(string key)
spacing: Style.marginS * scaling spacing: Style.marginL * scaling
Layout.fillWidth: true Layout.fillWidth: true
function findIndexByKey(key) { function findIndexByKey(key) {
@ -39,11 +40,15 @@ RowLayout {
description: root.description description: root.description
} }
Item {
Layout.fillWidth: true
}
ComboBox { ComboBox {
id: combo id: combo
Layout.preferredWidth: root.preferredWidth Layout.minimumWidth: root.minimumWidth
Layout.preferredHeight: height Layout.preferredHeight: root.preferredHeight
model: model model: model
currentIndex: findIndexByKey(currentKey) currentIndex: findIndexByKey(currentKey)
onActivated: { onActivated: {

View file

@ -1,8 +1,11 @@
import QtQuick import QtQuick
import qs.Commons import qs.Commons
import qs.Widgets import qs.Widgets
import QtQuick.Layouts
Text { Text {
// Optional layout nudge for optical alignment when used inside Layouts
property real layoutTopMargin: 0
text: "question_mark" text: "question_mark"
font.family: "Material Symbols Rounded" font.family: "Material Symbols Rounded"
font.pointSize: Style.fontSizeL * scaling font.pointSize: Style.fontSizeL * scaling
@ -12,4 +15,5 @@ Text {
} }
color: Color.mOnSurface color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.topMargin: layoutTopMargin
} }

View file

@ -20,6 +20,7 @@ ColumnLayout {
font.capitalization: Font.Capitalize font.capitalization: Font.Capitalize
color: labelColor color: labelColor
visible: label !== "" visible: label !== ""
Layout.fillWidth: true
} }
NText { NText {

View file

@ -39,13 +39,13 @@ Item {
property bool shouldAnimateHide: false property bool shouldAnimateHide: false
// Exposed width logic // Exposed width logic
readonly property int pillHeight: Style.baseWidgetSize * sizeRatio * scaling readonly property int iconSize: Math.round(Style.baseWidgetSize * sizeRatio * scaling)
readonly property int iconSize: Style.baseWidgetSize * sizeRatio * scaling readonly property int pillHeight: iconSize
readonly property int pillPaddingHorizontal: Style.marginM * scaling readonly property int pillPaddingHorizontal: Style.marginS * scaling
readonly property int pillOverlap: iconSize * 0.5 readonly property int pillOverlap: iconSize * 0.5
readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap) readonly property int maxPillWidth: Math.max(1, textItem.implicitWidth + pillPaddingHorizontal * 2 + pillOverlap)
width: iconSize + (effectiveShown ? maxPillWidth - pillOverlap : 0) width: iconSize + Math.max(0, pill.width - pillOverlap)
height: pillHeight height: pillHeight
Rectangle { Rectangle {
@ -67,7 +67,13 @@ Item {
NText { NText {
id: textItem id: textItem
anchors.centerIn: parent anchors.verticalCenter: parent.verticalCenter
x: {
// Little tweak to have a better text horizontal centering
var centerX = (parent.width - width) / 2
var offset = rightOpen ? Style.marginXS * scaling : -Style.marginXS * scaling
return centerX + offset
}
text: root.text text: root.text
font.pointSize: Style.fontSizeXS * scaling font.pointSize: Style.fontSizeXS * scaling
font.weight: Style.fontWeightBold font.weight: Style.fontWeightBold
@ -102,8 +108,7 @@ Item {
border.width: Math.max(1, Style.borderS * scaling) border.width: Math.max(1, Style.borderS * scaling)
border.color: forceOpen ? Qt.alpha(Color.mOutline, 0.5) : Color.transparent border.color: forceOpen ? Qt.alpha(Color.mOutline, 0.5) : Color.transparent
anchors.left: rightOpen ? parent.left : undefined x: rightOpen ? 0 : (parent.width - width)
anchors.right: rightOpen ? undefined : parent.right
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {

View file

@ -13,4 +13,5 @@ Text {
font.kerning: true font.kerning: true
color: Color.mOnSurface color: Color.mOnSurface
renderType: Text.QtRendering renderType: Text.QtRendering
verticalAlignment: Text.AlignVCenter
} }