widgets: share the lock editor with the desktop, adjust overlapping

face, share desktop clock widget with lock screen
This commit is contained in:
bbedward
2026-10-04 22:23:14 -04:00
parent e26d0294c8
commit 076288604e
26 changed files with 636 additions and 1073 deletions
Generated
+3 -3
View File
@@ -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": {
+1 -1
View File
@@ -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";
+2
View File
@@ -61,6 +61,8 @@ Item {
DesktopWidgetLayer {}
DesktopWidgetEditor {}
Lock {
id: lock
}
+5
View File
@@ -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"
}
@@ -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
}
}
}
@@ -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
}
}
}
}
}
+1 -1
View File
@@ -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);
@@ -1,6 +1,6 @@
import QtQuick
import QtQuick.Window
import "LockPlacement.js" as Placement
import "../../Common/WidgetPlacement.js" as Placement
Item {
id: root
+7 -134
View File
@@ -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()
}
}
@@ -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"
}
}
}
@@ -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
}
}
}
}
@@ -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 {}
}
@@ -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
}
@@ -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
@@ -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()
}
}
}
@@ -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);
}
}
}
@@ -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())
@@ -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
@@ -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"
+42 -17
View File
@@ -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()
}
}
}
}
@@ -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())
}
}
@@ -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");
+1 -1
View File
@@ -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;
@@ -0,0 +1,3 @@
import qs.DankCommon.Widgets as DankCommon
DankCommon.DankOverlapClockFace {}