From 076288604e11bcafec6237826fb54c7dcb423510 Mon Sep 17 00:00:00 2001 From: bbedward Date: Sun, 4 Oct 2026 22:22:30 -0400 Subject: [PATCH] widgets: share the lock editor with the desktop, adjust overlapping face, share desktop clock widget with lock screen --- dank-qml-common | 2 +- flake.lock | 6 +- .../WidgetPlacement.js} | 0 quickshell/Common/settings/SettingsStore.js | 2 +- quickshell/DMSShell.qml | 2 + quickshell/DMSShellIPC.qml | 5 + .../DesktopClockWidget.qml | 441 ++++++++++-------- quickshell/Modules/DesktopWidgetEditor.qml | 68 +++ quickshell/Modules/DesktopWidgetLayer.qml | 2 +- .../Modules/Lock/LockBackgroundSampler.qml | 2 +- quickshell/Modules/Lock/LockScreenContent.qml | 141 +----- .../Modules/Lock/Widgets/LockClockWidget.qml | 267 ----------- .../Modules/Plugins/DesktopPluginWrapper.qml | 277 ----------- .../Modules/Plugins/DesktopWidgetContent.qml | 6 - .../WidgetEditItem.qml} | 10 +- .../WidgetEditLayer.qml} | 53 ++- .../Modules/Plugins/WidgetEditorOverlay.qml | 143 ++++++ .../DesktopWidgetSettings/ClockSettings.qml | 82 +++- .../DesktopWidgetInstanceSettings.qml | 6 +- .../DesktopWidgetTypeSettings.qml | 11 - .../Modules/Settings/DesktopWidgetsTab.qml | 21 +- quickshell/Modules/Settings/LockScreenTab.qml | 59 ++- .../LockWidgetSettings/LockClockSettings.qml | 92 ---- quickshell/Services/DesktopWidgetRegistry.qml | 6 +- quickshell/Services/LockPlacementService.qml | 2 +- quickshell/Widgets/DankOverlapClockFace.qml | 3 + 26 files changed, 636 insertions(+), 1073 deletions(-) rename quickshell/{Modules/Lock/LockPlacement.js => Common/WidgetPlacement.js} (100%) create mode 100644 quickshell/Modules/DesktopWidgetEditor.qml delete mode 100644 quickshell/Modules/Lock/Widgets/LockClockWidget.qml rename quickshell/Modules/{Lock/LockWidgetItem.qml => Plugins/WidgetEditItem.qml} (95%) rename quickshell/Modules/{Lock/LockWidgetLayer.qml => Plugins/WidgetEditLayer.qml} (83%) create mode 100644 quickshell/Modules/Plugins/WidgetEditorOverlay.qml delete mode 100644 quickshell/Modules/Settings/LockWidgetSettings/LockClockSettings.qml create mode 100644 quickshell/Widgets/DankOverlapClockFace.qml diff --git a/dank-qml-common b/dank-qml-common index f5522736e..149337aa6 160000 --- a/dank-qml-common +++ b/dank-qml-common @@ -1 +1 @@ -Subproject commit f5522736ed2691484b5aa455bb1447c2b42415f4 +Subproject commit 149337aa6c88a1a2360343ba9aef2d6954265746 diff --git a/flake.lock b/flake.lock index d75294cc0..4e0b3863a 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "dank-qml-common": { "flake": false, "locked": { - "lastModified": 1791150538, - "narHash": "sha256-kqGf7c/98Fovm2ff7N9wffgBF0XMPLUHPLBRACJZ2wQ=", + "lastModified": 1791166878, + "narHash": "sha256-Ab3lkXkTOAiRpt3yuLb+5VpybO3ut8l0+/2Hq7VVBdk=", "owner": "AvengeMedia", "repo": "dank-qml-common", - "rev": "f5522736ed2691484b5aa455bb1447c2b42415f4", + "rev": "149337aa6c88a1a2360343ba9aef2d6954265746", "type": "github" }, "original": { diff --git a/quickshell/Modules/Lock/LockPlacement.js b/quickshell/Common/WidgetPlacement.js similarity index 100% rename from quickshell/Modules/Lock/LockPlacement.js rename to quickshell/Common/WidgetPlacement.js diff --git a/quickshell/Common/settings/SettingsStore.js b/quickshell/Common/settings/SettingsStore.js index bb6d81dbc..bffeb908f 100644 --- a/quickshell/Common/settings/SettingsStore.js +++ b/quickshell/Common/settings/SettingsStore.js @@ -735,7 +735,7 @@ function migrateLockScreenWidgets(settings) { if (present("lockScreenShowTime")) byId("lock_clock").enabled = !!settings.lockScreenShowTime; if (present("lockScreenClockStyle")) - byId("lock_clock").config.style = settings.lockScreenClockStyle; + byId("lock_clock").config.style = settings.lockScreenClockStyle === "vertical" ? "overlap" : "digital"; if (present("lockScreenShowDate")) byId("lock_date").enabled = !!settings.lockScreenShowDate; byId("lock_auth").config.profileVisibility = settings.lockScreenShowProfileImage === false ? "never" : "always"; diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index 863ecbf63..310de902c 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -61,6 +61,8 @@ Item { DesktopWidgetLayer {} + DesktopWidgetEditor {} + Lock { id: lock } diff --git a/quickshell/DMSShellIPC.qml b/quickshell/DMSShellIPC.qml index 0d815dda8..67291c447 100644 --- a/quickshell/DMSShellIPC.qml +++ b/quickshell/DMSShellIPC.qml @@ -1874,6 +1874,11 @@ Item { return enabledBool ? `DESKTOP_WIDGET_SYNC_POSITION_ENABLED: ${instanceId}` : `DESKTOP_WIDGET_SYNC_POSITION_DISABLED: ${instanceId}`; } + function edit(): string { + DesktopWidgetRegistry.editing = true; + return "DESKTOP_WIDGET_EDITOR_OPENED"; + } + target: "desktopWidget" } diff --git a/quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml b/quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml index 336ec3681..fb38bdb56 100644 --- a/quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml +++ b/quickshell/Modules/BuiltinDesktopPlugins/DesktopClockWidget.qml @@ -3,6 +3,7 @@ import Quickshell import qs.Common import qs.Services import qs.Widgets +import "../../DankCommon/Common/Hct.js" as Hct Item { id: root @@ -12,85 +13,140 @@ Item { property string instanceId: "" property var instanceData: null + property bool lockScreen: false + property var lockHost: null readonly property var cfg: instanceData?.config ?? ({}) - property string clockStyle: cfg.style ?? "analog" - property bool forceSquare: clockStyle === "analog" + readonly property string face: { + switch (cfg.style ?? "analog") { + case "analog": + case "stacked": + case "expressive": + case "overlap": + return cfg.style; + } + return "digital"; + } + readonly property bool analog: face === "analog" + readonly property bool resizable: true + property bool forceSquare: analog + readonly property real defaultScale: lockScreen ? 1.75 : 1 - property real defaultWidth: { - switch (clockStyle) { + function baseWidth() { + switch (face) { case "analog": return 200; case "stacked": return 100; - default: + case "overlap": return 160; + case "expressive": + return 240; } + return 160; } - property real defaultHeight: { - switch (clockStyle) { + + function baseHeight() { + switch (face) { case "analog": + case "overlap": return 200; case "stacked": return 160; - default: - return 70; + case "expressive": + return 120; } + return 70; } + + property real defaultWidth: baseWidth() * defaultScale + property real defaultHeight: baseHeight() * defaultScale property real minWidth: { - switch (clockStyle) { + switch (face) { case "analog": return 120; case "stacked": return 70; - default: - return 100; + case "overlap": + return 80; } + return 100; } property real minHeight: { - switch (clockStyle) { + switch (face) { case "analog": return 120; case "stacked": + case "overlap": return 100; - default: - return 45; } + return 45; } enabled: instanceData?.enabled ?? true - property real transparency: cfg.transparency ?? 0.8 - property string colorMode: cfg.colorMode ?? "primary" - property color customColor: cfg.customColor ?? "#ffffff" - property bool showDate: cfg.showDate ?? true - property bool showAnalogNumbers: cfg.showAnalogNumbers ?? false - - readonly property real scaleFactor: Math.min(width, height) / 200 - - readonly property color accentColor: { - if (colorMode === "primary") - return Theme.primary; - if (colorMode === "secondary") - return Theme.secondary; - if (colorMode === "custom") - return customColor; - return Theme.primary; - } - - readonly property color handColor: accentColor - readonly property color handColorDim: Theme.withAlpha(accentColor, 0.65) - readonly property color textColor: Theme.onSurface - readonly property color subtleTextColor: Theme.onSurfaceVariant - readonly property color backgroundColor: Theme.withAlpha(Theme.hostSurface, root.transparency) - + readonly property real transparency: cfg.transparency ?? (lockScreen ? 0 : 0.8) + readonly property string colorMode: cfg.colorMode ?? (lockScreen ? "default" : "primary") + readonly property color customColor: cfg.customColor ?? "#ffffff" + readonly property bool showDate: cfg.showDate ?? !lockScreen + readonly property bool twoTone: cfg.twoTone ?? lockScreen + readonly property bool showAnalogNumbers: cfg.showAnalogNumbers ?? false readonly property bool showAnalogSeconds: cfg.showAnalogSeconds ?? true readonly property bool showDigitalSeconds: cfg.showDigitalSeconds ?? false - readonly property bool needsSeconds: clockStyle === "analog" ? showAnalogSeconds : showDigitalSeconds - readonly property string formattedDate: { - if (SettingsData.clockDateFormat && SettingsData.clockDateFormat.length > 0) - return systemClock.date?.toLocaleDateString(I18n.locale(), SettingsData.clockDateFormat) ?? ""; - return systemClock.date?.toLocaleDateString(I18n.locale(), "ddd, MMM d") ?? ""; + readonly property bool italic: face === "digital" && (cfg.italic ?? false) + readonly property string fontFamily: cfg.fontFamily || (lockScreen ? SettingsData.lockScreenFontFamily : "") + readonly property int weight: cfg.weight ?? 0 + readonly property int digitWeight: weight > 0 ? weight : Theme.fontWeightMedium + + readonly property color accentColor: { + if (lockHost) + return lockHost.contentColor(colorMode, customColor); + switch (colorMode) { + case "secondary": + return Theme.secondary; + case "custom": + return customColor; + } + return Theme.primary; } + readonly property color minutesColor: twoTone ? Theme.primary : accentColor + readonly property color dimColor: Theme.withAlpha(accentColor, 0.65) + readonly property color backgroundColor: Theme.withAlpha(Theme.hostSurface, transparency) + readonly property bool themedOverlap: lockScreen && colorMode === "default" + readonly property var primaryHct: Hct.toHct(Theme.primary) + readonly property color overlapFaceColor: { + if (!themedOverlap) + return accentColor; + return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 65) : Theme.accentOnPrimaryContainer; + } + readonly property color overlapShadowColor: { + if (themedOverlap) + return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 30) : Theme.primaryContainer; + const source = Hct.toHct(accentColor); + return Hct.fromHct(source.hue, source.chroma, source.tone > 50 ? 30 : 80); + } + readonly property var contrastColors: { + switch (face) { + case "overlap": + return [overlapFaceColor, overlapShadowColor]; + case "digital": + case "stacked": + case "expressive": + return [accentColor, minutesColor]; + } + return [accentColor]; + } + + readonly property bool needsSeconds: analog ? showAnalogSeconds : showDigitalSeconds + readonly property string hoursText: { + const hours = systemClock.date?.getHours() ?? 0; + const display = SettingsData.use24HourClock ? hours : (hours % 12 || 12); + return SettingsData.use24HourClock || SettingsData.padHours12Hour ? String(display).padStart(2, "0") : String(display); + } + readonly property string minutesText: String(systemClock.date?.getMinutes() ?? 0).padStart(2, "0") + readonly property string secondsText: String(systemClock.date?.getSeconds() ?? 0).padStart(2, "0") + readonly property string meridiem: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM" + readonly property string dateFormat: lockScreen ? SettingsData.lockDateFormat : SettingsData.clockDateFormat + readonly property string formattedDate: systemClock.date?.toLocaleDateString(I18n.locale(), dateFormat || "ddd, MMM d") ?? "" SystemClock { id: systemClock @@ -106,21 +162,38 @@ Item { } } + component DigitText: StyledText { + font.weight: root.digitWeight + font.italic: root.italic + font.family: root.fontFamily !== "" ? root.fontFamily : resolvedFontFamily + font.features: ({ + "tnum": 1 + }) + color: root.accentColor + horizontalAlignment: Text.AlignHCenter + } + Rectangle { anchors.fill: parent radius: Theme.cornerRadius color: root.backgroundColor - visible: root.clockStyle !== "analog" + visible: !root.analog } Loader { anchors.fill: parent - anchors.margins: root.clockStyle === "analog" ? 0 : Theme.spacingM + anchors.margins: root.analog ? 0 : Theme.spacingM sourceComponent: { - if (root.clockStyle === "analog") + switch (root.face) { + case "analog": return analogClock; - if (root.clockStyle === "stacked") + case "stacked": return stackedClock; + case "expressive": + return expressiveClock; + case "overlap": + return overlapClock; + } return digitalClock; } } @@ -140,117 +213,125 @@ Item { } } + Component { + id: expressiveClock + + DankClockFace { + readonly property bool tallBox: height > width * 0.6 + + hours: root.hoursText + minutes: root.minutesText + seconds: root.showDigitalSeconds && !tallBox ? root.secondsText : "" + dateText: root.showDate ? root.formattedDate : "" + stacked: tallBox + color: root.accentColor + minutesColor: root.minutesColor + supportingColor: root.dimColor + } + } + + Component { + id: overlapClock + + Item { + DankOverlapClockFace { + anchors.centerIn: parent + width: implicitWidth + height: implicitHeight + hours: root.hoursText + minutes: root.minutesText + color: root.overlapFaceColor + shadowColor: root.overlapShadowColor + fontFamily: root.fontFamily + weight: root.weight > 0 ? root.weight : 1000 + scale: implicitWidth > 0 && implicitHeight > 0 ? Math.min(parent.width / implicitWidth, parent.height / implicitHeight) : 1 + renderScale: scale + } + } + } + Component { id: digitalClock Item { id: digitalRoot - property bool hasDate: root.showDate - property bool hasAmPm: !SettingsData.use24HourClock - property real verticalScale: hasDate && hasAmPm ? 0.55 : (hasDate || hasAmPm ? 0.65 : 0.8) - property real baseSize: Math.min(height * verticalScale, width * 0.22) - property real digitWidth: baseSize * 0.62 - property real smallSize: baseSize * 0.35 - - property string hoursStr: { - const hours = SettingsData.use24HourClock ? systemClock.date?.getHours() ?? 0 : ((systemClock.date?.getHours() ?? 0) % 12 || 12); - if (SettingsData.use24HourClock || SettingsData.padHours12Hour) - return String(hours).padStart(2, '0'); - return String(hours); - } - property string minutesStr: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0') - property string secondsStr: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0') + readonly property bool hasAmPm: !SettingsData.use24HourClock + readonly property real verticalScale: root.showDate && hasAmPm ? 0.55 : (root.showDate || hasAmPm ? 0.65 : 0.8) + readonly property real baseSize: Math.min(height * verticalScale, width * 0.22) + readonly property real digitWidth: baseSize * 0.62 + readonly property real smallSize: baseSize * 0.35 Column { anchors.centerIn: parent spacing: 0 - StyledText { + DigitText { visible: root.showDate anchors.horizontalCenter: parent.horizontalCenter text: root.formattedDate font.pixelSize: digitalRoot.smallSize - color: Theme.withAlpha(root.accentColor, 0.7) + color: root.dimColor } Row { anchors.horizontalCenter: parent.horizontalCenter spacing: 0 - StyledText { - visible: digitalRoot.hoursStr.length > 1 - width: digitalRoot.digitWidth - text: digitalRoot.hoursStr.charAt(0) - font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - horizontalAlignment: Text.AlignHCenter + Repeater { + model: root.hoursText.split("") + + DigitText { + required property string modelData + width: digitalRoot.digitWidth + text: modelData + font.pixelSize: digitalRoot.baseSize + } } - StyledText { - width: digitalRoot.digitWidth - text: digitalRoot.hoursStr.length > 1 ? digitalRoot.hoursStr.charAt(1) : digitalRoot.hoursStr.charAt(0) - font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - horizontalAlignment: Text.AlignHCenter - } - StyledText { + + DigitText { text: ":" font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor } - StyledText { - width: digitalRoot.digitWidth - text: digitalRoot.minutesStr.charAt(0) - font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - horizontalAlignment: Text.AlignHCenter + + Repeater { + model: root.minutesText.split("") + + DigitText { + required property string modelData + width: digitalRoot.digitWidth + text: modelData + font.pixelSize: digitalRoot.baseSize + color: root.minutesColor + } } - StyledText { - width: digitalRoot.digitWidth - text: digitalRoot.minutesStr.charAt(1) - font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - horizontalAlignment: Text.AlignHCenter - } - StyledText { + + DigitText { visible: root.showDigitalSeconds text: ":" font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: Theme.withAlpha(root.accentColor, 0.7) + color: root.dimColor } - StyledText { - visible: root.showDigitalSeconds - width: digitalRoot.digitWidth - text: digitalRoot.secondsStr.charAt(0) - font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: Theme.withAlpha(root.accentColor, 0.7) - horizontalAlignment: Text.AlignHCenter - } - StyledText { - visible: root.showDigitalSeconds - width: digitalRoot.digitWidth - text: digitalRoot.secondsStr.charAt(1) - font.pixelSize: digitalRoot.baseSize - font.weight: Theme.fontWeightMedium - color: Theme.withAlpha(root.accentColor, 0.7) - horizontalAlignment: Text.AlignHCenter + + Repeater { + model: root.showDigitalSeconds ? root.secondsText.split("") : [] + + DigitText { + required property string modelData + width: digitalRoot.digitWidth + text: modelData + font.pixelSize: digitalRoot.baseSize + color: root.dimColor + } } } - StyledText { - visible: !SettingsData.use24HourClock + DigitText { + visible: digitalRoot.hasAmPm anchors.horizontalCenter: parent.horizontalCenter - text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM" + text: root.meridiem font.pixelSize: digitalRoot.smallSize - font.weight: Theme.fontWeightMedium - color: Theme.withAlpha(root.accentColor, 0.7) + color: root.dimColor } } } @@ -262,14 +343,12 @@ Item { Item { id: stackedRoot - property bool hasSeconds: root.showDigitalSeconds - property bool hasDate: root.showDate - property bool hasAmPm: !SettingsData.use24HourClock - property real extraContent: (hasSeconds ? 0.12 : 0) + (hasDate ? 0.08 : 0) + (hasAmPm ? 0.08 : 0) - property real baseSize: height * (0.42 - extraContent * 0.5) - property real digitWidth: baseSize * 0.58 - property real smallSize: baseSize * 0.5 - property real rowSpacing: -baseSize * 0.17 + readonly property bool hasAmPm: !SettingsData.use24HourClock + readonly property real extraContent: (root.showDigitalSeconds ? 0.12 : 0) + (root.showDate ? 0.08 : 0) + (hasAmPm ? 0.08 : 0) + readonly property real baseSize: height * (0.42 - extraContent * 0.5) + readonly property real digitWidth: baseSize * 0.58 + readonly property real smallSize: baseSize * 0.5 + readonly property real rowSpacing: -baseSize * 0.17 Column { anchors.centerIn: parent @@ -283,34 +362,15 @@ Item { spacing: 0 anchors.horizontalCenter: parent.horizontalCenter - StyledText { - text: { - if (SettingsData.use24HourClock) - return String(systemClock.date?.getHours() ?? 0).padStart(2, '0').charAt(0); - const hours = systemClock.date?.getHours() ?? 0; - const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours; - return String(display).padStart(2, '0').charAt(0); - } - font.pixelSize: stackedRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - width: stackedRoot.digitWidth - horizontalAlignment: Text.AlignHCenter - } + Repeater { + model: root.hoursText.padStart(2, "0").split("") - StyledText { - text: { - if (SettingsData.use24HourClock) - return String(systemClock.date?.getHours() ?? 0).padStart(2, '0').charAt(1); - const hours = systemClock.date?.getHours() ?? 0; - const display = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours; - return String(display).padStart(2, '0').charAt(1); + DigitText { + required property string modelData + width: stackedRoot.digitWidth + text: modelData + font.pixelSize: stackedRoot.baseSize } - font.pixelSize: stackedRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - width: stackedRoot.digitWidth - horizontalAlignment: Text.AlignHCenter } } @@ -318,71 +378,58 @@ Item { spacing: 0 anchors.horizontalCenter: parent.horizontalCenter - StyledText { - text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0').charAt(0) - font.pixelSize: stackedRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - width: stackedRoot.digitWidth - horizontalAlignment: Text.AlignHCenter - } + Repeater { + model: root.minutesText.split("") - StyledText { - text: String(systemClock.date?.getMinutes() ?? 0).padStart(2, '0').charAt(1) - font.pixelSize: stackedRoot.baseSize - font.weight: Theme.fontWeightMedium - color: root.accentColor - width: stackedRoot.digitWidth - horizontalAlignment: Text.AlignHCenter + DigitText { + required property string modelData + width: stackedRoot.digitWidth + text: modelData + font.pixelSize: stackedRoot.baseSize + color: root.minutesColor + } } } } Row { - visible: stackedRoot.hasSeconds + visible: root.showDigitalSeconds spacing: 0 anchors.horizontalCenter: parent.horizontalCenter - StyledText { - text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0').charAt(0) - font.pixelSize: stackedRoot.smallSize - font.weight: Theme.fontWeightMedium - color: Theme.withAlpha(root.accentColor, 0.7) - width: stackedRoot.smallSize * 0.58 - horizontalAlignment: Text.AlignHCenter - } + Repeater { + model: root.showDigitalSeconds ? root.secondsText.split("") : [] - StyledText { - text: String(systemClock.date?.getSeconds() ?? 0).padStart(2, '0').charAt(1) - font.pixelSize: stackedRoot.smallSize - font.weight: Theme.fontWeightMedium - color: Theme.withAlpha(root.accentColor, 0.7) - width: stackedRoot.smallSize * 0.58 - horizontalAlignment: Text.AlignHCenter + DigitText { + required property string modelData + width: stackedRoot.smallSize * 0.58 + text: modelData + font.pixelSize: stackedRoot.smallSize + color: root.dimColor + } } } Item { width: 1 height: stackedRoot.baseSize * 0.1 - visible: stackedRoot.hasDate + visible: root.showDate } - StyledText { - visible: stackedRoot.hasDate + DigitText { + visible: root.showDate anchors.horizontalCenter: parent.horizontalCenter text: systemClock.date?.toLocaleDateString(I18n.locale(), "MMM dd") ?? "" font.pixelSize: stackedRoot.smallSize * 0.7 - color: Theme.withAlpha(root.accentColor, 0.7) + color: root.dimColor } - StyledText { + DigitText { visible: stackedRoot.hasAmPm anchors.horizontalCenter: parent.horizontalCenter - text: (systemClock.date?.getHours() ?? 0) >= 12 ? "PM" : "AM" + text: root.meridiem font.pixelSize: stackedRoot.smallSize * 0.7 - font.weight: Theme.fontWeightMedium - color: Theme.withAlpha(root.accentColor, 0.7) + color: root.dimColor } } } diff --git a/quickshell/Modules/DesktopWidgetEditor.qml b/quickshell/Modules/DesktopWidgetEditor.qml new file mode 100644 index 000000000..126f3a148 --- /dev/null +++ b/quickshell/Modules/DesktopWidgetEditor.qml @@ -0,0 +1,68 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Common +import qs.Services +import qs.Modules.Plugins + +Scope { + Variants { + model: DesktopWidgetRegistry.editing ? Quickshell.screens : [] + + PanelWindow { + id: editorWindow + + required property var modelData + + screen: modelData + visible: true + + anchors { + top: true + bottom: true + left: true + right: true + } + + WlrLayershell.namespace: "dms:desktop-widget-editor" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusiveZone: -1 + WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + + color: "transparent" + + FocusScope { + anchors.fill: parent + focus: true + + Keys.onPressed: event => { + if (widgetLayer.handleKey(event)) + event.accepted = true; + } + Keys.onEscapePressed: overlay.dismiss() + + Rectangle { + anchors.fill: parent + color: Theme.scrimColor + opacity: Theme.scrimAlpha + } + + WidgetEditLayer { + id: widgetLayer + anchors.fill: parent + screenName: editorWindow.screen?.name ?? "" + editMode: true + bottomInset: overlay.fabReserved + onOptionsRequested: instanceData => overlay.showOptions(instanceData) + } + + WidgetEditorOverlay { + id: overlay + anchors.fill: parent + editLayer: widgetLayer + onFinished: DesktopWidgetRegistry.editing = false + } + } + } + } +} diff --git a/quickshell/Modules/DesktopWidgetLayer.qml b/quickshell/Modules/DesktopWidgetLayer.qml index eb256b6eb..7ad3a1ddd 100644 --- a/quickshell/Modules/DesktopWidgetLayer.qml +++ b/quickshell/Modules/DesktopWidgetLayer.qml @@ -110,7 +110,7 @@ Variants { } readonly property bool shouldBeVisible: { - if (!liveInstanceData.enabled) + if (!liveInstanceData.enabled || DesktopWidgetRegistry.editing) return false; const prefs = liveInstanceData.config?.displayPreferences ?? ["all"]; return DesktopWidgetRegistry.showsOnScreen(prefs, screenDelegate.screen); diff --git a/quickshell/Modules/Lock/LockBackgroundSampler.qml b/quickshell/Modules/Lock/LockBackgroundSampler.qml index c0e59ce14..f680551ad 100644 --- a/quickshell/Modules/Lock/LockBackgroundSampler.qml +++ b/quickshell/Modules/Lock/LockBackgroundSampler.qml @@ -1,6 +1,6 @@ import QtQuick import QtQuick.Window -import "LockPlacement.js" as Placement +import "../../Common/WidgetPlacement.js" as Placement Item { id: root diff --git a/quickshell/Modules/Lock/LockScreenContent.qml b/quickshell/Modules/Lock/LockScreenContent.qml index 7abf325eb..d9853b2b4 100644 --- a/quickshell/Modules/Lock/LockScreenContent.qml +++ b/quickshell/Modules/Lock/LockScreenContent.qml @@ -4,11 +4,7 @@ import QtQuick import QtQuick.Window import qs.Common import qs.Modals -import qs.Modules.ControlCenter.Widgets -import qs.Modules.DankDash.Overview import qs.Modules.Plugins -import qs.Modules.Settings.DesktopWidgetSettings -import qs.Modules.Settings.Widgets import qs.Services import qs.Widgets import qs.DankCommon.Session @@ -75,12 +71,6 @@ FocusScope { return Theme.lockScreenContentColor; } - function addLockWidget(widgetType) { - const def = DesktopWidgetRegistry.getWidget(widgetType); - const instance = SettingsData.createDesktopWidgetInstance(widgetType, def?.name ?? widgetType, DesktopWidgetRegistry.getDefaultConfig(widgetType), "lockScreenWidgetInstances"); - widgetLayer.pendingIds = widgetLayer.pendingIds.concat([instance.id]); - } - Component.onCompleted: { WeatherService.addRef(); UserInfoService.getUserInfo(); @@ -154,31 +144,17 @@ FocusScope { screenName: root.screenName } - // Grid keys live here because a selected widget holds active focus inside the layer, not the editor. Keys.onPressed: event => { - if (!demoMode) - return; - switch (event.key) { - case Qt.Key_G: - widgetLayer.toggleGrid(); - break; - case Qt.Key_Z: - widgetLayer.stepGrid(-10); - break; - case Qt.Key_X: - widgetLayer.stepGrid(10); - break; - default: - return; - } - event.accepted = true; + if (widgetLayer.handleKey(event)) + event.accepted = true; } - LockWidgetLayer { + WidgetEditLayer { id: widgetLayer anchors.fill: parent focus: true screenName: root.screenName + lockScreen: true lockHost: root editMode: root.demoMode bottomInset: editorLoader.item?.fabReserved ?? 0 @@ -229,112 +205,9 @@ FocusScope { anchors.fill: parent active: root.demoMode - sourceComponent: FocusScope { - id: editor - - property bool libraryOpen: false - readonly property real fabReserved: fabBar.reservedHeight - - function closeLibrary() { - libraryOpen = false; - forceActiveFocus(); - } - - function showOptions(instanceData) { - optionsSheet.instanceId = instanceData?.id ?? ""; - optionsSheet.opened = true; - } - - focus: true - Component.onCompleted: forceActiveFocus() - - Keys.onEscapePressed: { - if (editControls.pendingAction !== "") { - editControls.cancelConfirmation(); - return; - } - if (libraryOpen) { - closeLibrary(); - return; - } - root.unlockRequested(); - } - - MouseArea { - anchors.fill: parent - visible: editor.libraryOpen - acceptedButtons: Qt.AllButtons - onClicked: editor.closeLibrary() - } - - Loader { - anchors.centerIn: parent - active: editor.libraryOpen - - sourceComponent: CcWidgetLibrary { - width: Math.min(implicitWidth, editor.width - Theme.spacingL * 2) - height: Math.min(implicitHeight, editor.height - Theme.spacingL * 2) - widgets: DesktopWidgetRegistry.registeredWidgetsList.filter(widget => widget.id !== "lockAuth" || !SettingsData.lockWidgetInstance("lockAuth")).map(widget => ({ - id: widget.id, - text: widget.name, - icon: widget.icon, - description: widget.description - })) - Component.onCompleted: reset() - onChosen: widgetId => { - root.addLockWidget(widgetId); - editor.closeLibrary(); - } - onDismissed: editor.closeLibrary() - } - } - - DankBottomSheet { - id: optionsSheet - - property string instanceId: "" - readonly property var instanceData: SettingsData.getDesktopWidgetInstance(instanceId) - readonly property var widgetDef: DesktopWidgetRegistry.getWidget(instanceData?.widgetType ?? "") - - maximumWidth: Math.min(Theme.mediumBreakpoint, editor.width - Theme.spacingXL * 2) - topMargin: editor.height * 0.3 - returnFocusItem: editor - title: instanceData?.name || widgetDef?.name || "" - onDismissRequested: opened = false - onActiveChanged: { - if (!active) - instanceId = ""; - } - - DesktopWidgetTypeSettings { - width: parent.width - instanceId: optionsSheet.instanceId - instanceData: optionsSheet.instanceData - widgetDef: optionsSheet.widgetDef - } - } - - DesktopWidgetGridHint { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: fabBar.reservedHeight + Theme.spacingL - visible: fabBar.shown && (widgetLayer.selectedInstanceId !== "" || widgetLayer.interactingItem !== null) - gridEnabled: widgetLayer.gridEnabled - gridSize: widgetLayer.gridSize - } - - SettingsFabBar { - id: fabBar - shown: !editor.libraryOpen && !optionsSheet.active - - DashEditControls { - id: editControls - canClear: false - onAddRequested: editor.libraryOpen = true - onResetRequested: SettingsData.resetLockScreenWidgets() - onFinished: root.unlockRequested() - } - } + sourceComponent: WidgetEditorOverlay { + editLayer: widgetLayer + onFinished: root.unlockRequested() } } diff --git a/quickshell/Modules/Lock/Widgets/LockClockWidget.qml b/quickshell/Modules/Lock/Widgets/LockClockWidget.qml deleted file mode 100644 index cc66982c8..000000000 --- a/quickshell/Modules/Lock/Widgets/LockClockWidget.qml +++ /dev/null @@ -1,267 +0,0 @@ -pragma ComponentBehavior: Bound - -import QtQuick -import Quickshell -import qs.Common -import qs.Widgets -import qs.DankCommon.Session -import "../../../DankCommon/Common/Hct.js" as Hct - -Item { - id: root - - property var instanceData: null - property var lockHost: null - readonly property var cfg: instanceData?.config ?? ({}) - readonly property string style: cfg.style ?? "expressive" - readonly property bool textStyle: style === "horizontal" || style === "vertical" - readonly property string colorMode: cfg.colorMode ?? "default" - readonly property var primaryHct: Hct.toHct(Theme.primary) - readonly property color shadowFaceColor: { - if (colorMode !== "default") - return contentColor; - return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 65) : Theme.accentOnPrimaryContainer; - } - readonly property color shadowColor: { - if (colorMode === "default") - return Theme.isLightMode ? Hct.fromHct(primaryHct.hue, primaryHct.chroma, 30) : Theme.primaryContainer; - const source = Hct.toHct(shadowFaceColor); - return Hct.fromHct(source.hue, source.chroma, source.tone > 50 ? 30 : 80); - } - readonly property int weightOverride: cfg.weight ?? 0 - readonly property int digitWeight: weightOverride > 0 ? weightOverride : Theme.fontWeight - readonly property color contentColor: lockHost?.contentColor(cfg.colorMode ?? "default", cfg.customColor ?? "#ffffff") ?? Theme.lockScreenContentColor - readonly property string fontFamily: cfg.fontFamily || SettingsData.lockScreenFontFamily - readonly property color minutesColor: (cfg.twoTone ?? true) ? Theme.primary : contentColor - readonly property var contrastColors: style === "vertical" ? [shadowFaceColor, shadowColor] : style === "horizontal" || style === "expressive" ? [contentColor, minutesColor] : [contentColor] - readonly property real clockSize: LockMetrics.clockSize * (style === "horizontal" ? 1.5 : 1) - readonly property real clockDigitWidth: clockSize * 0.625 - - readonly property bool resizable: true - readonly property bool forceSquare: style === "analog" - readonly property real boxWidth: style === "analog" ? LockMetrics.clockSize * 2 : LockMetrics.clockSize * 4 - readonly property real boxHeight: style === "analog" ? LockMetrics.clockSize * 2 : LockMetrics.clockSize * 1.4 - readonly property real naturalWidth: face.item?.implicitWidth ?? 0 - readonly property real naturalHeight: face.item?.implicitHeight ?? 0 - readonly property real defaultWidth: textStyle ? naturalWidth : boxWidth - readonly property real defaultHeight: textStyle ? naturalHeight : boxHeight - readonly property real minWidth: textStyle ? naturalWidth / 2 : LockMetrics.fieldHeight * 2 - readonly property real minHeight: textStyle ? naturalHeight / 2 : LockMetrics.fieldHeight - readonly property real fitScale: textStyle && naturalWidth > 0 && naturalHeight > 0 ? Math.min(width / naturalWidth, height / naturalHeight) : 1 - - implicitWidth: textStyle ? naturalWidth : boxWidth - implicitHeight: textStyle ? naturalHeight : boxHeight - - component ClockDigitText: StyledText { - font.pixelSize: root.clockSize - font.weight: root.digitWeight - font.italic: root.cfg.italic ?? false - font.features: ({ - "tnum": 1 - }) - color: root.contentColor - horizontalAlignment: Text.AlignHCenter - font.family: root.fontFamily !== "" ? root.fontFamily : resolvedFontFamily - } - - SystemClock { - id: systemClock - precision: (root.style === "analog" ? (root.cfg.showAnalogSeconds ?? true) : SettingsData.getEffectiveTimeFormat().includes("s")) ? SystemClock.Seconds : SystemClock.Minutes - } - - QtObject { - id: time - - readonly property string ampm: SettingsData.use24HourClock ? "" : systemClock.date.toLocaleTimeString(I18n.locale(), "AP") - readonly property string hours: { - const format = SettingsData.use24HourClock || SettingsData.padHours12Hour ? "hh" : "h"; - const text = systemClock.date.toLocaleTimeString(I18n.locale(), ampm ? format + " AP" : format); - return ampm ? text.replace(ampm, "").trim() : text; - } - readonly property string minutes: systemClock.date.toLocaleTimeString(I18n.locale(), "mm") - readonly property string seconds: systemClock.date.toLocaleTimeString(I18n.locale(), "ss") - readonly property bool hasSeconds: SettingsData.showSeconds - } - - Loader { - id: face - anchors.fill: root.textStyle ? undefined : parent - anchors.centerIn: root.textStyle ? parent : undefined - width: root.textStyle ? root.naturalWidth : undefined - height: root.textStyle ? root.naturalHeight : undefined - scale: root.fitScale - sourceComponent: { - switch (root.style) { - case "vertical": - return shadowFace; - case "expressive": - return expressiveFace; - case "analog": - return analogFace; - } - return horizontalFace; - } - } - - Component { - id: horizontalFace - - Item { - implicitWidth: row.implicitWidth - implicitHeight: row.implicitHeight - - Row { - id: row - anchors.centerIn: parent - layoutDirection: Qt.LeftToRight - spacing: 0 - - ClockDigitText { - id: hourText - text: time.hours + ":" - } - - ClockDigitText { - text: time.minutes - color: root.minutesColor - } - - ClockDigitText { - text: ":" + time.seconds - color: root.minutesColor - visible: time.hasSeconds - } - - ClockDigitText { - text: " " + time.ampm - font.pixelSize: root.clockSize / 3 - anchors.baseline: hourText.baseline - visible: time.ampm !== "" - } - } - } - } - - Component { - id: shadowFace - - Item { - id: shadowItem - - readonly property real digitSize: root.clockSize * 1.25 - readonly property real tracking: -digitSize * 0.1 - readonly property real rowAdvance: digitSize * 0.7 - readonly property int weight: root.weightOverride > 0 ? root.weightOverride : 1000 - readonly property bool variableFont: root.fontFamily === "" || root.fontFamily === Theme.defaultFontFamily - // One cell width for all four glyphs keeps the columns aligned in fonts without tabular figures. - readonly property real cellWidth: Math.max(hoursRow.firstAdvance, hoursRow.secondAdvance, minutesRow.firstAdvance, minutesRow.secondAdvance) - implicitWidth: Math.max(hoursRow.width, minutesRow.width) - implicitHeight: Math.max(hoursRow.height, minutesRow.y + minutesRow.height) - - ShadowRow { - id: hoursRow - x: (shadowItem.width - shadowItem.implicitWidth) / 2 - text: time.hours.padStart(2, "0") - } - - ShadowRow { - id: minutesRow - x: hoursRow.x - y: hoursRow.baselineOffset + shadowItem.rowAdvance - baselineOffset - text: time.minutes - lightFirst: true - } - } - } - - component ClockDigit: StyledText { - id: digit - - readonly property rect inkBounds: metrics.tightBoundingRect - readonly property real advanceWidth: metrics.advanceWidth - - font.pixelSize: shadowItem.digitSize - font.family: root.fontFamily !== "" ? root.fontFamily : Theme.defaultFontFamily - font.weight: shadowItem.variableFont ? Font.Normal : Math.min(Font.Black, shadowItem.weight) - font.variableAxes: shadowItem.variableFont ? { - "wght": shadowItem.weight, - "ROND": 0, - "opsz": 18 - } : ({}) - font.features: ({ - "tnum": 1 - }) - textFormat: Text.PlainText - wrapMode: Text.NoWrap - elide: Text.ElideNone - verticalAlignment: Text.AlignTop - - TextMetrics { - id: metrics - font: digit.font - text: digit.text - renderType: digit.renderType - } - } - - component ShadowRow: Item { - id: shadowRow - - property string text: "" - property bool lightFirst: false - readonly property real firstAdvance: firstDigit.advanceWidth - readonly property real secondAdvance: secondDigit.advanceWidth - readonly property real cell: shadowItem.cellWidth - readonly property real topBearing: Math.min(firstDigit.inkBounds.y, secondDigit.inkBounds.y) - readonly property real bottomBearing: Math.max(firstDigit.inkBounds.y + firstDigit.inkBounds.height, secondDigit.inkBounds.y + secondDigit.inkBounds.height) - - implicitWidth: cell * 2 + shadowItem.tracking - implicitHeight: bottomBearing - topBearing - baselineOffset: -topBearing - - ClockDigit { - id: firstDigit - x: (shadowRow.cell - advanceWidth) / 2 - y: shadowRow.baselineOffset - baselineOffset - text: shadowRow.text[0] ?? "" - color: shadowRow.lightFirst ? root.shadowFaceColor : root.shadowColor - } - - ClockDigit { - id: secondDigit - x: shadowRow.cell + shadowItem.tracking + (shadowRow.cell - advanceWidth) / 2 - y: shadowRow.baselineOffset - baselineOffset - text: shadowRow.text[1] ?? "" - color: shadowRow.lightFirst ? root.shadowColor : root.shadowFaceColor - } - } - - Component { - id: expressiveFace - - DankClockFace { - readonly property bool tallBox: height > width * 0.6 - - hours: time.hours - minutes: time.minutes - seconds: time.hasSeconds && !tallBox ? time.seconds : "" - stacked: tallBox - color: root.contentColor - minutesColor: root.minutesColor - } - } - - Component { - id: analogFace - - DankAnalogClock { - hours: systemClock.date?.getHours() ?? 0 - minutes: systemClock.date?.getMinutes() ?? 0 - seconds: systemClock.date?.getSeconds() ?? 0 - showSeconds: root.cfg.showAnalogSeconds ?? true - showNumbers: root.cfg.showAnalogNumbers ?? false - color: root.contentColor - backgroundColor: "transparent" - } - } -} diff --git a/quickshell/Modules/Plugins/DesktopPluginWrapper.qml b/quickshell/Modules/Plugins/DesktopPluginWrapper.qml index 40bc8c757..f2eae13cb 100644 --- a/quickshell/Modules/Plugins/DesktopPluginWrapper.qml +++ b/quickshell/Modules/Plugins/DesktopPluginWrapper.qml @@ -1,11 +1,8 @@ import QtQuick -import QtQuick.Controls import Quickshell import Quickshell.Wayland -import Quickshell.Hyprland import qs.Common import qs.Services -import qs.Widgets Item { id: root @@ -55,15 +52,6 @@ Item { forceSquare: content.contentForceSquare } - readonly property bool useGhostPreview: !CompositorService.isNiri - - property real previewX: geometry.widgetX - property real previewY: geometry.widgetY - property real previewWidth: geometry.widgetWidth - property real previewHeight: geometry.widgetHeight - - property bool isInteracting: dragArea.pressed || resizeArea.pressed - PanelWindow { id: widgetWindow screen: root.screen @@ -78,8 +66,6 @@ Item { WlrLayershell.namespace: "dms:desktop-widget:" + root.pluginId + (root.instanceId ? ":" + root.instanceId : "") WlrLayershell.layer: { - if (root.isInteracting && !CompositorService.useHyprlandFocusGrab) - return WlrLayer.Overlay; if (root.showOnOverlay) return WlrLayer.Overlay; if (root.overviewActive && (root.showOnOverview || root.showOnOverviewOnly)) @@ -90,45 +76,11 @@ Item { WlrLayershell.keyboardFocus: { if (PopoutManager.screenshotActive) return WlrKeyboardFocus.None; - if (root.isInteracting) { - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } if (content.acceptsKeyboardFocus) return WlrKeyboardFocus.OnDemand; return WlrKeyboardFocus.None; } - HyprlandFocusGrab { - active: CompositorService.isHyprland && root.isInteracting - windows: [widgetWindow] - } - - Item { - anchors.fill: parent - focus: root.isInteracting - - Keys.onPressed: event => { - if (!root.isInteracting) - return; - switch (event.key) { - case Qt.Key_G: - SessionData.setWidgetGridSetting(false, geometry.screenKey, "enabled", !geometry.gridEnabled); - event.accepted = true; - break; - case Qt.Key_Z: - SessionData.setWidgetGridSetting(false, geometry.screenKey, "size", Math.max(10, geometry.gridSize - 10)); - event.accepted = true; - break; - case Qt.Key_X: - SessionData.setWidgetGridSetting(false, geometry.screenKey, "size", Math.min(200, geometry.gridSize + 10)); - event.accepted = true; - break; - } - } - } - anchors { left: true top: true @@ -153,234 +105,5 @@ Item { screen: root.screen geometry: geometry } - - Rectangle { - id: interactionBorder - anchors.fill: parent - color: "transparent" - border.color: Theme.primary - border.width: 2 - radius: Theme.cornerRadius - visible: root.isInteracting && !root.useGhostPreview - opacity: 0.8 - - Rectangle { - anchors.right: parent.right - anchors.bottom: parent.bottom - width: 48 - height: 48 - topLeftRadius: Theme.cornerRadius - bottomRightRadius: Theme.cornerRadius - color: Theme.primary - opacity: resizeArea.pressed ? 1 : 0.6 - } - } - - MouseArea { - id: dragArea - anchors.fill: parent - acceptedButtons: Qt.RightButton - enabled: !root.clickThrough - cursorShape: pressed ? Qt.ClosedHandCursor : Qt.ArrowCursor - - property point startPos - property real startX - property real startY - - onPressed: mouse => { - startPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y); - startX = geometry.widgetX; - startY = geometry.widgetY; - root.previewX = geometry.widgetX; - root.previewY = geometry.widgetY; - geometry.dragOverrideX = geometry.widgetX; - geometry.dragOverrideY = geometry.widgetY; - } - - onPositionChanged: mouse => { - if (!pressed) - return; - const currentPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y); - const next = geometry.dragMoveTo(startX, startY, currentPos.x - startPos.x, currentPos.y - startPos.y); - if (root.useGhostPreview) { - root.previewX = next.x; - root.previewY = next.y; - return; - } - geometry.dragOverrideX = next.x; - geometry.dragOverrideY = next.y; - } - - onReleased: { - const finalX = root.useGhostPreview ? root.previewX : geometry.dragOverrideX; - const finalY = root.useGhostPreview ? root.previewY : geometry.dragOverrideY; - geometry.savePosition(finalX, finalY); - geometry.clearDragOverrides(); - } - } - - MouseArea { - id: resizeArea - width: 48 - height: 48 - anchors.right: parent.right - anchors.bottom: parent.bottom - acceptedButtons: Qt.RightButton - enabled: !root.clickThrough - cursorShape: pressed ? Qt.SizeFDiagCursor : Qt.ArrowCursor - - property point startPos - property real startWidth - property real startHeight - - onPressed: mouse => { - startPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y); - startWidth = geometry.widgetWidth; - startHeight = geometry.widgetHeight; - root.previewWidth = geometry.widgetWidth; - root.previewHeight = geometry.widgetHeight; - geometry.dragOverrideW = geometry.widgetWidth; - geometry.dragOverrideH = geometry.widgetHeight; - } - - onPositionChanged: mouse => { - if (!pressed) - return; - const currentPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y); - const next = geometry.dragResizeTo(startWidth, startHeight, currentPos.x - startPos.x, currentPos.y - startPos.y); - if (root.useGhostPreview) { - root.previewWidth = next.width; - root.previewHeight = next.height; - return; - } - geometry.dragOverrideW = next.width; - geometry.dragOverrideH = next.height; - } - - onReleased: { - const finalW = root.useGhostPreview ? root.previewWidth : geometry.dragOverrideW; - const finalH = root.useGhostPreview ? root.previewHeight : geometry.dragOverrideH; - geometry.saveSize(finalW, finalH); - geometry.clearDragOverrides(); - } - } - } - - Loader { - active: root.isInteracting && root.useGhostPreview - - sourceComponent: PanelWindow { - id: ghostPreviewWindow - screen: root.screen - color: "transparent" - - anchors { - left: true - right: true - top: true - bottom: true - } - - mask: Region {} - - WlrLayershell.namespace: "dms:desktop-widget-preview" - WlrLayershell.layer: WlrLayer.Bottom - WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - DesktopWidgetGridLines { - id: gridOverlay - anchors.fill: parent - visible: geometry.gridEnabled - gridSize: geometry.gridSize - extentWidth: geometry.screenWidth - extentHeight: geometry.screenHeight - } - - Rectangle { - x: root.previewX - y: root.previewY - width: root.previewWidth - height: root.previewHeight - color: "transparent" - border.color: Theme.primary - border.width: 2 - radius: Theme.cornerRadius - - Rectangle { - width: 48 - height: 48 - anchors { - right: parent.right - bottom: parent.bottom - } - topLeftRadius: Theme.cornerRadius - bottomRightRadius: Theme.cornerRadius - color: Theme.primary - opacity: resizeArea.pressed ? 1 : 0.6 - } - } - } - } - - Loader { - active: root.isInteracting && geometry.gridEnabled && !root.useGhostPreview - - sourceComponent: PanelWindow { - screen: root.screen - color: "transparent" - - anchors { - left: true - right: true - top: true - bottom: true - } - - mask: Region {} - - WlrLayershell.namespace: "dms:desktop-widget-grid" - WlrLayershell.layer: root.overviewActive && (root.showOnOverview || root.showOnOverviewOnly) ? WlrLayer.Overlay : WlrLayer.Background - WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - DesktopWidgetGridLines { - anchors.fill: parent - gridSize: geometry.gridSize - extentWidth: geometry.screenWidth - extentHeight: geometry.screenHeight - } - } - } - - Loader { - active: root.isInteracting - - sourceComponent: PanelWindow { - id: helperWindow - screen: root.screen - color: "transparent" - - WlrLayershell.namespace: "dms:desktop-widget-helper" - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - anchors { - bottom: true - left: true - right: true - } - - implicitHeight: 60 - - DesktopWidgetGridHint { - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: Theme.spacingL - gridEnabled: geometry.gridEnabled - gridSize: geometry.gridSize - } - } } } diff --git a/quickshell/Modules/Plugins/DesktopWidgetContent.qml b/quickshell/Modules/Plugins/DesktopWidgetContent.qml index 12a99bb72..408908f0d 100644 --- a/quickshell/Modules/Plugins/DesktopWidgetContent.qml +++ b/quickshell/Modules/Plugins/DesktopWidgetContent.qml @@ -23,8 +23,6 @@ Loader { return clockComponent; case "systemMonitor": return systemMonitorComponent; - case "lockClock": - return lockClockComponent; case "lockDate": return lockDateComponent; case "lockAuth": @@ -54,10 +52,6 @@ Loader { SystemMonitorWidget {} } - property Component lockClockComponent: Component { - LockClockWidget {} - } - property Component lockDateComponent: Component { LockDateWidget {} } diff --git a/quickshell/Modules/Lock/LockWidgetItem.qml b/quickshell/Modules/Plugins/WidgetEditItem.qml similarity index 95% rename from quickshell/Modules/Lock/LockWidgetItem.qml rename to quickshell/Modules/Plugins/WidgetEditItem.qml index 5b1963efb..cbcf57ffe 100644 --- a/quickshell/Modules/Lock/LockWidgetItem.qml +++ b/quickshell/Modules/Plugins/WidgetEditItem.qml @@ -1,7 +1,6 @@ import QtQuick import qs.Common import qs.Widgets -import qs.Modules.Plugins Item { id: root @@ -14,13 +13,14 @@ Item { readonly property string instanceId: instanceData?.id ?? "" readonly property string widgetType: instanceData?.widgetType ?? "" + readonly property bool lockScreen: hostLayer.lockScreen readonly property bool removable: widgetType !== "lockAuth" readonly property bool selected: hostLayer.selectedInstanceId === instanceId readonly property bool interacting: editChrome.item?.interacting ?? false readonly property var contrastColors: content.item?.contrastColors ?? [] readonly property real bottomOverflow: content.item?.bottomOverflow ?? 0 readonly property bool hasSavedPosition: geometry.hasSavedPosition - readonly property bool automaticPlacement: widgetType === "lockClock" && (instanceData?.config?.autoPosition ?? true) && !hasSavedPosition + readonly property bool automaticPlacement: lockScreen && widgetType === "desktopClock" && (instanceData?.config?.autoPosition ?? true) && !hasSavedPosition readonly property bool dragging: editChrome.item?.dragging ?? false readonly property var stock: hostLayer.stockRect(widgetType, root) property bool snappedCenterX: false @@ -101,7 +101,7 @@ Item { return; } geometry.savePosition(finalX, finalY); - if (widgetType === "lockClock" && (instanceData?.config?.autoPosition ?? true)) + if (lockScreen && widgetType === "desktopClock" && (instanceData?.config?.autoPosition ?? true)) SettingsData.updateDesktopWidgetInstanceConfig(instanceId, { autoPosition: false }); @@ -112,7 +112,7 @@ Item { instanceId: root.instanceId instanceData: root.instanceData screen: root.screen - lockScreen: true + lockScreen: root.lockScreen minWidth: content.contentMinWidth minHeight: content.contentMinHeight forceSquare: content.contentForceSquare @@ -132,7 +132,7 @@ Item { instanceData: root.instanceData screen: root.screen geometry: geometry - lockScreen: true + lockScreen: root.lockScreen lockHost: root.lockHost persistDefaults: false } diff --git a/quickshell/Modules/Lock/LockWidgetLayer.qml b/quickshell/Modules/Plugins/WidgetEditLayer.qml similarity index 83% rename from quickshell/Modules/Lock/LockWidgetLayer.qml rename to quickshell/Modules/Plugins/WidgetEditLayer.qml index 57364d9b4..1f5123026 100644 --- a/quickshell/Modules/Lock/LockWidgetLayer.qml +++ b/quickshell/Modules/Plugins/WidgetEditLayer.qml @@ -4,29 +4,30 @@ import QtQuick import Quickshell import qs.Common import qs.Services -import qs.Modules.Plugins -import "LockPlacement.js" as Placement +import "../../Common/WidgetPlacement.js" as Placement import qs.DankCommon.Session FocusScope { id: root required property string screenName + property bool lockScreen: false property var lockHost: null property bool editMode: false property string selectedInstanceId: "" readonly property var screen: Quickshell.screens.find(s => s.name === screenName) ?? null - readonly property var instances: SettingsData.lockScreenWidgetInstances || [] + readonly property string listKey: lockScreen ? "lockScreenWidgetInstances" : "desktopWidgetInstances" + readonly property var instances: (lockScreen ? SettingsData.lockScreenWidgetInstances : SettingsData.desktopWidgetInstances) || [] readonly property string screenKey: SettingsData.getScreenDisplayName(screen) - property var _gridSettingsTrigger: SessionData.lockScreenWidgetGridSettings + property var _gridSettingsTrigger: lockScreen ? SessionData.lockScreenWidgetGridSettings : SessionData.desktopWidgetGridSettings readonly property int gridSize: { void _gridSettingsTrigger; - return SessionData.getWidgetGridSetting(true, screenKey, "size", 40); + return SessionData.getWidgetGridSetting(lockScreen, screenKey, "size", 40); } readonly property bool gridEnabled: { void _gridSettingsTrigger; - return SessionData.getWidgetGridSetting(true, screenKey, "enabled", false); + return SessionData.getWidgetGridSetting(lockScreen, screenKey, "enabled", false); } signal focusStolen @@ -70,11 +71,29 @@ FocusScope { } function toggleGrid() { - SessionData.setWidgetGridSetting(true, screenKey, "enabled", !gridEnabled); + SessionData.setWidgetGridSetting(lockScreen, screenKey, "enabled", !gridEnabled); } function stepGrid(delta) { - SessionData.setWidgetGridSetting(true, screenKey, "size", Math.max(10, Math.min(200, gridSize + delta))); + SessionData.setWidgetGridSetting(lockScreen, screenKey, "size", Math.max(10, Math.min(200, gridSize + delta))); + } + + // Called by the common ancestor of this layer and the editor bar, since either side can hold focus. + function handleKey(event) { + if (!editMode) + return false; + switch (event.key) { + case Qt.Key_G: + toggleGrid(); + return true; + case Qt.Key_Z: + stepGrid(-10); + return true; + case Qt.Key_X: + stepGrid(10); + return true; + } + return false; } activeFocusOnTab: false @@ -97,16 +116,16 @@ FocusScope { property var lockedPositions: ({}) // Snapshot taken when a drag or resize starts so the auto-placed clock cannot jump under the pointer. property var heldPlacement: ({}) - readonly property string backgroundKey: LockPlacementService.backgroundKey(screenName, width, height) + readonly property string backgroundKey: lockScreen ? LockPlacementService.backgroundKey(screenName, width, height) : "" readonly property var autoPositions: { - if (!editMode || !completed) + if (!lockScreen || !editMode || !completed) return lockedPositions; if (interactingItem) return heldPlacement; return currentPlacement(); } onInteractingItemChanged: { - if (interactingItem) + if (lockScreen && interactingItem) heldPlacement = currentPlacement(); } @@ -115,7 +134,7 @@ FocusScope { } function refreshPlacement() { - if (!completed || editMode || width <= 0 || height <= 0) + if (!lockScreen || !completed || editMode || width <= 0 || height <= 0) return; lockedPositions = currentPlacement(); SessionData.setLockScreenAutoPositions(screenKey, { @@ -133,10 +152,10 @@ FocusScope { onHeightChanged: refreshPlacement() onEditModeChanged: refreshPlacement() - readonly property string placementKey: JSON.stringify(instances.map(instance => { + readonly property string placementKey: lockScreen ? JSON.stringify(instances.map(instance => { const item = itemById(instance.id); return [instance.id, instance.enabled, instance.config, item?.width, item?.height, item?.automaticPlacement ? null : item?.x, item?.automaticPlacement ? null : item?.y, item?.hasSavedPosition, item?.contrastColors]; - }).concat([width, height, Theme.primary.toString(), Theme.secondary.toString(), Theme.lockScreenContentColor.toString(), SessionData.desktopWidgetInstancePositions])) + }).concat([width, height, Theme.primary.toString(), Theme.secondary.toString(), Theme.lockScreenContentColor.toString(), SessionData.desktopWidgetInstancePositions])) : "" function itemById(id) { for (let i = 0; i < repeater.count; i++) { @@ -148,9 +167,11 @@ FocusScope { } function stockRect(widgetType, item) { + if (!lockScreen) + return null; const centerX = (width - item.width) / 2; switch (widgetType) { - case "lockClock": + case "desktopClock": return item.automaticPlacement && autoPositions[item.instanceId] ? autoPositions[item.instanceId] : { x: Math.max(0, centerX), y: edgeInset @@ -273,7 +294,7 @@ FocusScope { values: root.instances } - LockWidgetItem { + WidgetEditItem { required property var modelData required property int index diff --git a/quickshell/Modules/Plugins/WidgetEditorOverlay.qml b/quickshell/Modules/Plugins/WidgetEditorOverlay.qml new file mode 100644 index 000000000..820e2b149 --- /dev/null +++ b/quickshell/Modules/Plugins/WidgetEditorOverlay.qml @@ -0,0 +1,143 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets +import qs.Modules.ControlCenter.Widgets +import qs.Modules.DankDash.Overview +import qs.Modules.Settings.DesktopWidgetSettings +import qs.Modules.Settings.Widgets + +FocusScope { + id: root + + required property WidgetEditLayer editLayer + readonly property bool lockScreen: editLayer.lockScreen + property bool libraryOpen: false + readonly property real fabReserved: fabBar.reservedHeight + readonly property var libraryWidgets: DesktopWidgetRegistry.registeredWidgetsList.filter(widget => { + if (!lockScreen) + return !widget.lockOnly; + return widget.id !== "lockAuth" || !SettingsData.lockWidgetInstance("lockAuth"); + }).map(widget => ({ + id: widget.id, + text: widget.name, + icon: widget.icon, + description: widget.description + })) + + signal finished + + function closeLibrary() { + libraryOpen = false; + forceActiveFocus(); + } + + function showOptions(instanceData) { + optionsSheet.instanceId = instanceData?.id ?? ""; + optionsSheet.opened = true; + } + + function addWidget(widgetType) { + const def = DesktopWidgetRegistry.getWidget(widgetType); + const instance = SettingsData.createDesktopWidgetInstance(widgetType, def?.name ?? widgetType, DesktopWidgetRegistry.getDefaultConfig(widgetType), editLayer.listKey); + editLayer.pendingIds = editLayer.pendingIds.concat([instance.id]); + } + + function resetLayout() { + if (lockScreen) { + SettingsData.resetLockScreenWidgets(); + return; + } + for (const instance of SettingsData.desktopWidgetInstances || []) + SessionData.resetDesktopWidgetInstanceGeometry(instance.id, ["x", "y", "width", "height"]); + } + + function dismiss() { + if (editControls.pendingAction !== "") { + editControls.cancelConfirmation(); + return; + } + if (libraryOpen) { + closeLibrary(); + return; + } + finished(); + } + + focus: true + Component.onCompleted: forceActiveFocus() + Keys.onEscapePressed: dismiss() + + MouseArea { + anchors.fill: parent + visible: root.libraryOpen + acceptedButtons: Qt.AllButtons + onClicked: root.closeLibrary() + } + + Loader { + anchors.centerIn: parent + active: root.libraryOpen + + sourceComponent: CcWidgetLibrary { + width: Math.min(implicitWidth, root.width - Theme.spacingL * 2) + height: Math.min(implicitHeight, root.height - Theme.spacingL * 2) + widgets: root.libraryWidgets + Component.onCompleted: reset() + onChosen: widgetId => { + root.addWidget(widgetId); + root.closeLibrary(); + } + onDismissed: root.closeLibrary() + } + } + + DankBottomSheet { + id: optionsSheet + + property string instanceId: "" + readonly property var instanceData: SettingsData.getDesktopWidgetInstance(instanceId) + readonly property var widgetDef: DesktopWidgetRegistry.getWidget(instanceData?.widgetType ?? "") + + maximumWidth: Math.min(Theme.mediumBreakpoint, root.width - Theme.spacingXL * 2) + topMargin: root.height * 0.3 + returnFocusItem: root + title: instanceData?.name || widgetDef?.name || "" + onDismissRequested: opened = false + onActiveChanged: { + if (!active) + instanceId = ""; + } + + DesktopWidgetTypeSettings { + width: parent.width + instanceId: optionsSheet.instanceId + instanceData: optionsSheet.instanceData + widgetDef: optionsSheet.widgetDef + } + } + + DesktopWidgetGridHint { + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: fabBar.reservedHeight + Theme.spacingL + visible: fabBar.shown && (root.editLayer.selectedInstanceId !== "" || root.editLayer.interactingItem !== null) + gridEnabled: root.editLayer.gridEnabled + gridSize: root.editLayer.gridSize + } + + SettingsFabBar { + id: fabBar + shown: !root.libraryOpen && !optionsSheet.active + + DashEditControls { + id: editControls + canClear: false + onAddRequested: root.libraryOpen = true + onResetRequested: root.resetLayout() + onFinished: root.finished() + } + } +} diff --git a/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml b/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml index 8859b469f..184889f3e 100644 --- a/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml +++ b/quickshell/Modules/Settings/DesktopWidgetSettings/ClockSettings.qml @@ -5,35 +5,56 @@ import qs.Modules.Settings.Widgets DesktopWidgetInstanceSettings { id: root - readonly property bool analog: cfg.style === "analog" + readonly property var styles: ["digital", "analog", "stacked", "expressive", "overlap"] + readonly property var styleLabels: [I18n.tr("Digital"), I18n.tr("Analog"), I18n.tr("Stacked", "desktop clock style option"), I18n.tr("Expressive"), I18n.tr("Overlapping", "clock style with overlapping digit rows")] + readonly property string style: cfg.style ?? "analog" + readonly property bool analog: style === "analog" + readonly property bool overlap: style === "overlap" SettingsDropdownRow { text: I18n.tr("Clock style") - options: [I18n.tr("Digital"), I18n.tr("Analog"), I18n.tr("Stacked", "desktop clock style option")] - currentValue: { - switch (root.cfg.style) { - case "analog": - return I18n.tr("Analog"); - case "stacked": - return I18n.tr("Stacked"); - default: - return I18n.tr("Digital"); - } - } + options: root.styleLabels + currentValue: root.styleLabels[Math.max(0, root.styles.indexOf(root.style))] onValueChanged: value => { - switch (value) { - case I18n.tr("Analog"): - root.updateConfig("style", "analog"); - return; - case I18n.tr("Stacked"): - root.updateConfig("style", "stacked"); - return; - default: - root.updateConfig("style", "digital"); - } + const index = root.styleLabels.indexOf(value); + if (index >= 0) + root.updateConfig("style", root.styles[index]); } } + SettingsFontDropdownRow { + visible: !root.analog + text: I18n.tr("Font") + currentFont: root.cfg.fontFamily ?? "" + defaultFamily: (root.lockScreenInstance ? SettingsData.lockScreenFontFamily : SettingsData.fontFamily) || Theme.defaultFontFamily + onFontSelected: family => root.updateConfig("fontFamily", family) + } + + SettingsSliderRow { + visible: !root.analog + text: I18n.tr("Weight") + minimum: 0 + maximum: 900 + step: 100 + minimumLabel: I18n.tr("Default") + value: root.cfg.weight ?? 0 + onSliderValueChanged: newValue => root.updateConfig("weight", newValue) + } + + SettingsToggleRow { + visible: root.style === "digital" + text: I18n.tr("Italic") + checked: root.cfg.italic ?? false + onToggled: checked => root.updateConfig("italic", checked) + } + + SettingsToggleRow { + visible: !root.analog && !root.overlap + text: I18n.tr("Accent minutes", "lock screen clock colors") + checked: root.cfg.twoTone ?? root.lockScreenInstance + onToggled: checked => root.updateConfig("twoTone", checked) + } + SettingsToggleRow { visible: root.analog text: I18n.tr("Show hour numbers") @@ -49,15 +70,28 @@ DesktopWidgetInstanceSettings { } SettingsToggleRow { - visible: !root.analog + visible: !root.analog && !root.overlap text: I18n.tr("Show seconds") checked: root.cfg.showDigitalSeconds ?? false onToggled: checked => root.updateConfig("showDigitalSeconds", checked) } SettingsToggleRow { + visible: !root.overlap text: I18n.tr("Show date") - checked: root.cfg.showDate ?? true + checked: root.cfg.showDate ?? !root.lockScreenInstance onToggled: checked => root.updateConfig("showDate", checked) } + + SettingsToggleRow { + visible: root.lockScreenInstance + text: I18n.tr("Automatic placement", "lock screen clock placement") + description: I18n.tr("Choose a clear area of the background. Drag the clock to place it yourself.", "lock screen automatic clock placement") + checked: root.cfg.autoPosition ?? true + onToggled: checked => { + if (checked) + SessionData.resetDesktopWidgetInstanceGeometry(root.instanceId, ["x", "y"]); + root.updateConfig("autoPosition", checked); + } + } } diff --git a/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml b/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml index 0f0679dca..e54748b8f 100644 --- a/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml +++ b/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetInstanceSettings.qml @@ -10,6 +10,7 @@ Column { property bool showAppearance: true property bool showPlacement: true readonly property var cfg: instanceData?.config ?? {} + readonly property bool lockScreenInstance: SettingsData.widgetInstanceListKey(instanceId) === "lockScreenWidgetInstances" default property alias rows: optionsCard.content function updateConfig(key, value) { @@ -33,12 +34,13 @@ Column { text: I18n.tr("Opacity") minimum: 0 maximum: 100 - value: Math.round((root.cfg.transparency ?? 0.8) * 100) + value: Math.round((root.cfg.transparency ?? (root.lockScreenInstance ? 0 : 0.8)) * 100) onSliderValueChanged: newValue => root.updateConfig("transparency", newValue / 100) } SettingsColorPicker { - colorMode: root.cfg.colorMode ?? "primary" + showDefault: root.lockScreenInstance + colorMode: root.cfg.colorMode ?? (root.lockScreenInstance ? "default" : "primary") customColor: root.cfg.customColor ?? "#ffffff" onColorModeSelected: mode => root.updateConfig("colorMode", mode) onCustomColorSelected: selectedColor => root.updateConfig("customColor", selectedColor.toString()) diff --git a/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetTypeSettings.qml b/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetTypeSettings.qml index c0bfdfe42..12f358428 100644 --- a/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetTypeSettings.qml +++ b/quickshell/Modules/Settings/DesktopWidgetSettings/DesktopWidgetTypeSettings.qml @@ -17,8 +17,6 @@ Loader { return clockSettings; case "systemMonitor": return systemMonitorSettings; - case "lockClock": - return lockClockSettings; case "lockAuth": return lockAuthSettings; case "lockNotifications": @@ -52,15 +50,6 @@ Loader { } } - Component { - id: lockClockSettings - - LWS.LockClockSettings { - instanceId: root.instanceId - instanceData: root.instanceData - } - } - Component { id: lockAuthSettings diff --git a/quickshell/Modules/Settings/DesktopWidgetsTab.qml b/quickshell/Modules/Settings/DesktopWidgetsTab.qml index 3585ae549..364decc87 100644 --- a/quickshell/Modules/Settings/DesktopWidgetsTab.qml +++ b/quickshell/Modules/Settings/DesktopWidgetsTab.qml @@ -244,20 +244,6 @@ Item { iconName: "info" title: I18n.tr("Help", "noun, card title for desktop widget usage tips") - SettingsRow { - iconName: "drag_pan" - iconBox: true - title: I18n.tr("Move", "verb, help item title for moving a desktop widget") - subtitle: I18n.tr("Right-click and drag anywhere on the widget") - } - - SettingsRow { - iconName: "open_in_full" - iconBox: true - title: I18n.tr("Resize", "verb, help item title for resizing a desktop widget") - subtitle: I18n.tr("Right-click and drag the bottom-right corner") - } - SettingsRow { iconName: "drag_indicator" iconBox: true @@ -274,6 +260,13 @@ Item { onClicked: root.showDesktopPluginBrowser() } + DankFab { + text: I18n.tr("Edit widgets") + iconName: "edit" + colorRole: "secondaryContainer" + onClicked: DesktopWidgetRegistry.editing = true + } + DankFab { text: I18n.tr("Add widget") iconName: "add" diff --git a/quickshell/Modules/Settings/LockScreenTab.qml b/quickshell/Modules/Settings/LockScreenTab.qml index fd0328aec..5621967ee 100644 --- a/quickshell/Modules/Settings/LockScreenTab.qml +++ b/quickshell/Modules/Settings/LockScreenTab.qml @@ -268,6 +268,16 @@ Item { } } + function showWidgetBrowser() { + lockWidgetBrowserLoader.active = true; + lockWidgetBrowserLoader.item?.show(); + } + + function showPluginBrowser() { + lockPluginBrowserLoader.active = true; + lockPluginBrowserLoader.item?.show(); + } + LazyLoader { id: lockWidgetBrowserLoader active: false @@ -280,6 +290,16 @@ Item { } } + LazyLoader { + id: lockPluginBrowserLoader + active: false + + PluginBrowser { + parentModal: root.parentModal + typeFilter: "desktop-widget" + } + } + SettingsPage { id: mainColumn @@ -316,23 +336,6 @@ Item { } } - SettingsRow { - iconName: "add" - title: I18n.tr("Add widget") - clickable: true - onClicked: { - lockWidgetBrowserLoader.active = true; - lockWidgetBrowserLoader.item?.show(); - } - } - - SettingsRow { - iconName: "edit" - title: I18n.tr("Edit widgets") - clickable: true - onClicked: SessionService.lockEditorRequested() - } - SettingsRow { iconName: "restart_alt" title: I18n.tr("Reset to default") @@ -859,5 +862,27 @@ Item { } } } + + SettingsFabBar { + DankFab { + text: I18n.tr("Browse plugins") + iconName: "store" + colorRole: "secondaryContainer" + onClicked: root.showPluginBrowser() + } + + DankFab { + text: I18n.tr("Edit widgets") + iconName: "edit" + colorRole: "secondaryContainer" + onClicked: SessionService.lockEditorRequested() + } + + DankFab { + text: I18n.tr("Add widget") + iconName: "add" + onClicked: root.showWidgetBrowser() + } + } } } diff --git a/quickshell/Modules/Settings/LockWidgetSettings/LockClockSettings.qml b/quickshell/Modules/Settings/LockWidgetSettings/LockClockSettings.qml deleted file mode 100644 index d7d290b01..000000000 --- a/quickshell/Modules/Settings/LockWidgetSettings/LockClockSettings.qml +++ /dev/null @@ -1,92 +0,0 @@ -import QtQuick -import qs.Common -import qs.Modules.Settings.Widgets -import qs.Modules.Settings.DesktopWidgetSettings - -DesktopWidgetInstanceSettings { - id: root - - readonly property var styles: ["horizontal", "vertical", "expressive", "analog"] - readonly property var styleLabels: [I18n.tr("Horizontal", "lock screen clock style option"), I18n.tr("Vertical", "lock screen clock style option"), I18n.tr("Expressive"), I18n.tr("Analog")] - readonly property string style: cfg.style ?? "expressive" - readonly property bool textStyle: style === "horizontal" || style === "vertical" - - showAppearance: false - - SettingsDropdownRow { - text: I18n.tr("Clock style") - options: root.styleLabels - currentValue: root.styleLabels[Math.max(0, root.styles.indexOf(root.style))] - onValueChanged: value => { - const idx = root.styleLabels.indexOf(value); - if (idx >= 0) - root.updateConfig("style", root.styles[idx]); - } - } - - SettingsSliderRow { - visible: root.textStyle - text: I18n.tr("Weight") - minimum: 0 - maximum: 900 - step: 100 - minimumLabel: I18n.tr("Default") - value: root.cfg.weight ?? 0 - onSliderValueChanged: newValue => root.updateConfig("weight", newValue) - } - - SettingsFontDropdownRow { - visible: root.textStyle - text: I18n.tr("Font") - currentFont: root.cfg.fontFamily ?? "" - defaultFamily: SettingsData.lockScreenFontFamily || Theme.defaultFontFamily - onFontSelected: family => root.updateConfig("fontFamily", family) - } - - SettingsToggleRow { - visible: root.style === "horizontal" - text: I18n.tr("Italic") - checked: root.cfg.italic ?? false - onToggled: checked => root.updateConfig("italic", checked) - } - - SettingsToggleRow { - visible: root.style === "horizontal" || root.style === "expressive" - text: I18n.tr("Accent minutes", "lock screen clock colors") - checked: root.cfg.twoTone ?? true - onToggled: checked => root.updateConfig("twoTone", checked) - } - - SettingsToggleRow { - text: I18n.tr("Automatic placement", "lock screen clock placement") - description: I18n.tr("Choose a clear area of the background. Drag the clock to place it yourself.", "lock screen automatic clock placement") - checked: root.cfg.autoPosition ?? true - onToggled: checked => { - if (checked) - SessionData.resetDesktopWidgetInstanceGeometry(root.instanceId, ["x", "y"]); - root.updateConfig("autoPosition", checked); - } - } - - SettingsToggleRow { - visible: root.style === "analog" - text: I18n.tr("Show hour numbers") - checked: root.cfg.showAnalogNumbers ?? false - onToggled: checked => root.updateConfig("showAnalogNumbers", checked) - } - - SettingsToggleRow { - visible: root.style === "analog" - text: I18n.tr("Show seconds") - checked: root.cfg.showAnalogSeconds ?? true - onToggled: checked => root.updateConfig("showAnalogSeconds", checked) - } - - SettingsColorPicker { - showDefault: true - colorMode: root.cfg.colorMode ?? "default" - customColor: root.cfg.customColor ?? "#ffffff" - onColorModeSelected: mode => root.updateConfig("colorMode", mode) - onCustomColorSelected: selectedColor => root.updateConfig("customColor", selectedColor.toString()) - } -} diff --git a/quickshell/Services/DesktopWidgetRegistry.qml b/quickshell/Services/DesktopWidgetRegistry.qml index d59c9a978..cc8336bf6 100644 --- a/quickshell/Services/DesktopWidgetRegistry.qml +++ b/quickshell/Services/DesktopWidgetRegistry.qml @@ -11,6 +11,7 @@ Singleton { property var registeredWidgets: ({}) property var registeredWidgetsList: [] + property bool editing: false signal registryChanged @@ -37,9 +38,9 @@ Singleton { function registerBuiltins() { registerWidget({ id: "desktopClock", - name: I18n.tr("Desktop Clock", "Desktop clock widget name"), + name: I18n.tr("Clock"), icon: "schedule", - description: I18n.tr("Analog, digital, or stacked clock display", "Desktop clock widget description"), + description: I18n.tr("Analog, digital, stacked, expressive, or overlapping clock", "Desktop clock widget description"), type: "builtin", component: "qs.Modules.BuiltinDesktopPlugins.DesktopClockWidget", settingsComponent: "qs.Modules.Settings.DesktopWidgetSettings.ClockSettings", @@ -80,7 +81,6 @@ Singleton { height: 200 } }); - lockWidget("lockClock", I18n.tr("Clock"), "schedule"); lockWidget("lockDate", I18n.tr("Date"), "event"); lockWidget("lockAuth", I18n.tr("Password"), "lock"); lockWidget("lockNotifications", I18n.tr("Notifications"), "notifications"); diff --git a/quickshell/Services/LockPlacementService.qml b/quickshell/Services/LockPlacementService.qml index 1234027d2..54e810baa 100644 --- a/quickshell/Services/LockPlacementService.qml +++ b/quickshell/Services/LockPlacementService.qml @@ -12,7 +12,7 @@ Singleton { function neededFor(screen) { return SettingsData.lockScreenWidgetInstances.some(instance => { - if (instance.widgetType !== "lockClock" || instance.enabled === false || instance.config?.autoPosition === false || !DesktopWidgetRegistry.showsOnScreen(instance.config?.displayPreferences, screen)) + if (instance.widgetType !== "desktopClock" || instance.enabled === false || instance.config?.autoPosition === false || !DesktopWidgetRegistry.showsOnScreen(instance.config?.displayPreferences, screen)) return false; const positionKey = instance.config?.syncPositionAcrossScreens ? "_synced" : SettingsData.getScreenDisplayName(screen); return SessionData.desktopWidgetInstancePositions[instance.id]?.[positionKey]?.x === undefined; diff --git a/quickshell/Widgets/DankOverlapClockFace.qml b/quickshell/Widgets/DankOverlapClockFace.qml new file mode 100644 index 000000000..d95ba7bf1 --- /dev/null +++ b/quickshell/Widgets/DankOverlapClockFace.qml @@ -0,0 +1,3 @@ +import qs.DankCommon.Widgets as DankCommon + +DankCommon.DankOverlapClockFace {}