mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-10 07:42:09 -04:00
desktop widgets: sync position across screens option, clickthrough
option, grouping in settings, repositioning, new IPCs for control fixes #1300 fixes #1301
This commit is contained in:
@@ -26,6 +26,8 @@ Item {
|
||||
readonly property bool showOnOverview: instanceData?.config?.showOnOverview ?? false
|
||||
readonly property bool showOnOverviewOnly: instanceData?.config?.showOnOverviewOnly ?? false
|
||||
readonly property bool overviewActive: CompositorService.isNiri && NiriService.inOverview
|
||||
readonly property bool clickThrough: instanceData?.config?.clickThrough ?? false
|
||||
readonly property bool syncPositionAcrossScreens: instanceData?.config?.syncPositionAcrossScreens ?? false
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
@@ -83,6 +85,7 @@ Item {
|
||||
}
|
||||
}
|
||||
readonly property string screenKey: SettingsData.getScreenDisplayName(screen)
|
||||
readonly property string positionKey: syncPositionAcrossScreens ? "_synced" : screenKey
|
||||
|
||||
readonly property int screenWidth: screen?.width ?? 1920
|
||||
readonly property int screenHeight: screen?.height ?? 1080
|
||||
@@ -96,53 +99,114 @@ Item {
|
||||
|
||||
readonly property bool hasSavedPosition: {
|
||||
if (isInstance)
|
||||
return instanceData?.positions?.[screenKey]?.x !== undefined;
|
||||
return instanceData?.positions?.[positionKey]?.x !== undefined;
|
||||
if (usePluginService)
|
||||
return pluginService.loadPluginData(pluginId, "desktopX_" + screenKey, null) !== null;
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "x", null) !== null;
|
||||
return pluginService.loadPluginData(pluginId, "desktopX_" + positionKey, null) !== null;
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "x", null) !== null;
|
||||
}
|
||||
|
||||
readonly property bool hasSavedSize: {
|
||||
if (isInstance)
|
||||
return instanceData?.positions?.[screenKey]?.width !== undefined;
|
||||
return instanceData?.positions?.[positionKey]?.width !== undefined;
|
||||
if (usePluginService)
|
||||
return pluginService.loadPluginData(pluginId, "desktopWidth_" + screenKey, null) !== null;
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "width", null) !== null;
|
||||
return pluginService.loadPluginData(pluginId, "desktopWidth_" + positionKey, null) !== null;
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "width", null) !== null;
|
||||
}
|
||||
|
||||
property real savedX: {
|
||||
if (isInstance)
|
||||
return instanceData?.positions?.[screenKey]?.x ?? (screenWidth / 2 - savedWidth / 2);
|
||||
if (usePluginService)
|
||||
return pluginService.loadPluginData(pluginId, "desktopX_" + screenKey, screenWidth / 2 - savedWidth / 2);
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "x", screenWidth / 2 - savedWidth / 2);
|
||||
if (isInstance) {
|
||||
const val = instanceData?.positions?.[positionKey]?.x;
|
||||
if (val === undefined)
|
||||
return screenWidth / 2 - savedWidth / 2;
|
||||
return syncPositionAcrossScreens ? val * screenWidth : val;
|
||||
}
|
||||
if (usePluginService) {
|
||||
const val = pluginService.loadPluginData(pluginId, "desktopX_" + positionKey, null);
|
||||
if (val === null)
|
||||
return screenWidth / 2 - savedWidth / 2;
|
||||
return syncPositionAcrossScreens ? val * screenWidth : val;
|
||||
}
|
||||
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "x", null);
|
||||
if (val === null)
|
||||
return screenWidth / 2 - savedWidth / 2;
|
||||
return syncPositionAcrossScreens ? val * screenWidth : val;
|
||||
}
|
||||
property real savedY: {
|
||||
if (isInstance)
|
||||
return instanceData?.positions?.[screenKey]?.y ?? (screenHeight / 2 - savedHeight / 2);
|
||||
if (usePluginService)
|
||||
return pluginService.loadPluginData(pluginId, "desktopY_" + screenKey, screenHeight / 2 - savedHeight / 2);
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "y", screenHeight / 2 - savedHeight / 2);
|
||||
if (isInstance) {
|
||||
const val = instanceData?.positions?.[positionKey]?.y;
|
||||
if (val === undefined)
|
||||
return screenHeight / 2 - savedHeight / 2;
|
||||
return syncPositionAcrossScreens ? val * screenHeight : val;
|
||||
}
|
||||
if (usePluginService) {
|
||||
const val = pluginService.loadPluginData(pluginId, "desktopY_" + positionKey, null);
|
||||
if (val === null)
|
||||
return screenHeight / 2 - savedHeight / 2;
|
||||
return syncPositionAcrossScreens ? val * screenHeight : val;
|
||||
}
|
||||
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "y", null);
|
||||
if (val === null)
|
||||
return screenHeight / 2 - savedHeight / 2;
|
||||
return syncPositionAcrossScreens ? val * screenHeight : val;
|
||||
}
|
||||
property real savedWidth: {
|
||||
if (isInstance)
|
||||
return instanceData?.positions?.[screenKey]?.width ?? 280;
|
||||
if (usePluginService)
|
||||
return pluginService.loadPluginData(pluginId, "desktopWidth_" + screenKey, 200);
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "width", 280);
|
||||
if (isInstance) {
|
||||
const val = instanceData?.positions?.[positionKey]?.width;
|
||||
if (val === undefined)
|
||||
return 280;
|
||||
return val;
|
||||
}
|
||||
if (usePluginService) {
|
||||
const val = pluginService.loadPluginData(pluginId, "desktopWidth_" + positionKey, null);
|
||||
if (val === null)
|
||||
return 200;
|
||||
return val;
|
||||
}
|
||||
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "width", null);
|
||||
if (val === null)
|
||||
return 280;
|
||||
return val;
|
||||
}
|
||||
property real savedHeight: {
|
||||
if (isInstance)
|
||||
return instanceData?.positions?.[screenKey]?.height ?? 180;
|
||||
if (usePluginService)
|
||||
return pluginService.loadPluginData(pluginId, "desktopHeight_" + screenKey, 200);
|
||||
return SettingsData.getDesktopWidgetPosition(pluginId, screenKey, "height", 180);
|
||||
if (isInstance) {
|
||||
const val = instanceData?.positions?.[positionKey]?.height;
|
||||
if (val === undefined)
|
||||
return forceSquare ? savedWidth : 180;
|
||||
return forceSquare ? savedWidth : val;
|
||||
}
|
||||
if (usePluginService) {
|
||||
const val = pluginService.loadPluginData(pluginId, "desktopHeight_" + positionKey, null);
|
||||
if (val === null)
|
||||
return forceSquare ? savedWidth : 200;
|
||||
return forceSquare ? savedWidth : val;
|
||||
}
|
||||
const val = SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "height", null);
|
||||
if (val === null)
|
||||
return forceSquare ? savedWidth : 180;
|
||||
return forceSquare ? savedWidth : val;
|
||||
}
|
||||
|
||||
property real widgetX: Math.max(0, Math.min(savedX, screenWidth - widgetWidth))
|
||||
property real widgetY: Math.max(0, Math.min(savedY, screenHeight - widgetHeight))
|
||||
property real widgetWidth: Math.max(minWidth, Math.min(savedWidth, screenWidth))
|
||||
property real widgetHeight: Math.max(minHeight, Math.min(savedHeight, screenHeight))
|
||||
property real dragOverrideX: -1
|
||||
property real dragOverrideY: -1
|
||||
property real dragOverrideW: -1
|
||||
property real dragOverrideH: -1
|
||||
|
||||
readonly property real effectiveX: dragOverrideX >= 0 ? dragOverrideX : savedX
|
||||
readonly property real effectiveY: dragOverrideY >= 0 ? dragOverrideY : savedY
|
||||
readonly property real effectiveW: dragOverrideW >= 0 ? dragOverrideW : savedWidth
|
||||
readonly property real effectiveH: dragOverrideH >= 0 ? dragOverrideH : savedHeight
|
||||
|
||||
readonly property real widgetX: Math.max(0, Math.min(effectiveX, screenWidth - widgetWidth))
|
||||
readonly property real widgetY: Math.max(0, Math.min(effectiveY, screenHeight - widgetHeight))
|
||||
readonly property real widgetWidth: Math.max(minWidth, Math.min(effectiveW, screenWidth))
|
||||
readonly property real widgetHeight: Math.max(minHeight, Math.min(effectiveH, screenHeight))
|
||||
|
||||
function clearDragOverrides() {
|
||||
dragOverrideX = -1;
|
||||
dragOverrideY = -1;
|
||||
dragOverrideW = -1;
|
||||
dragOverrideH = -1;
|
||||
}
|
||||
|
||||
property real minWidth: contentLoader.item?.minWidth ?? 100
|
||||
property real minHeight: contentLoader.item?.minHeight ?? 100
|
||||
@@ -163,41 +227,45 @@ Item {
|
||||
return Math.round(value / gridSize) * gridSize;
|
||||
}
|
||||
|
||||
function savePosition() {
|
||||
function savePosition(finalX, finalY) {
|
||||
const xVal = syncPositionAcrossScreens ? finalX / screenWidth : finalX;
|
||||
const yVal = syncPositionAcrossScreens ? finalY / screenHeight : finalY;
|
||||
if (isInstance && instanceData) {
|
||||
SettingsData.updateDesktopWidgetInstancePosition(instanceId, screenKey, {
|
||||
x: root.widgetX,
|
||||
y: root.widgetY
|
||||
SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
|
||||
x: xVal,
|
||||
y: yVal
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (usePluginService) {
|
||||
pluginService.savePluginData(pluginId, "desktopX_" + screenKey, root.widgetX);
|
||||
pluginService.savePluginData(pluginId, "desktopY_" + screenKey, root.widgetY);
|
||||
pluginService.savePluginData(pluginId, "desktopX_" + positionKey, xVal);
|
||||
pluginService.savePluginData(pluginId, "desktopY_" + positionKey, yVal);
|
||||
return;
|
||||
}
|
||||
SettingsData.updateDesktopWidgetPosition(pluginId, screenKey, {
|
||||
x: root.widgetX,
|
||||
y: root.widgetY
|
||||
SettingsData.updateDesktopWidgetPosition(pluginId, positionKey, {
|
||||
x: xVal,
|
||||
y: yVal
|
||||
});
|
||||
}
|
||||
|
||||
function saveSize() {
|
||||
function saveSize(finalW, finalH) {
|
||||
const sizeVal = forceSquare ? Math.max(finalW, finalH) : finalW;
|
||||
const heightVal = forceSquare ? sizeVal : finalH;
|
||||
if (isInstance && instanceData) {
|
||||
SettingsData.updateDesktopWidgetInstancePosition(instanceId, screenKey, {
|
||||
width: root.widgetWidth,
|
||||
height: root.widgetHeight
|
||||
SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, {
|
||||
width: sizeVal,
|
||||
height: heightVal
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (usePluginService) {
|
||||
pluginService.savePluginData(pluginId, "desktopWidth_" + screenKey, root.widgetWidth);
|
||||
pluginService.savePluginData(pluginId, "desktopHeight_" + screenKey, root.widgetHeight);
|
||||
pluginService.savePluginData(pluginId, "desktopWidth_" + positionKey, sizeVal);
|
||||
pluginService.savePluginData(pluginId, "desktopHeight_" + positionKey, heightVal);
|
||||
return;
|
||||
}
|
||||
SettingsData.updateDesktopWidgetPosition(pluginId, screenKey, {
|
||||
width: root.widgetWidth,
|
||||
height: root.widgetHeight
|
||||
SettingsData.updateDesktopWidgetPosition(pluginId, positionKey, {
|
||||
width: sizeVal,
|
||||
height: heightVal
|
||||
});
|
||||
}
|
||||
|
||||
@@ -213,6 +281,12 @@ Item {
|
||||
}
|
||||
color: "transparent"
|
||||
|
||||
Region {
|
||||
id: emptyMask
|
||||
}
|
||||
|
||||
mask: root.clickThrough ? emptyMask : null
|
||||
|
||||
WlrLayershell.namespace: "quickshell:desktop-widget:" + root.pluginId + (root.instanceId ? ":" + root.instanceId : "")
|
||||
WlrLayershell.layer: {
|
||||
if (root.isInteracting && !CompositorService.useHyprlandFocusGrab)
|
||||
@@ -315,12 +389,14 @@ Item {
|
||||
if (!root.hasSavedSize) {
|
||||
const defW = item.defaultWidth ?? item.widgetWidth ?? 280;
|
||||
const defH = item.defaultHeight ?? item.widgetHeight ?? 180;
|
||||
root.widgetWidth = Math.max(root.minWidth, Math.min(defW, root.screenWidth));
|
||||
root.widgetHeight = Math.max(root.minHeight, Math.min(defH, root.screenHeight));
|
||||
const finalW = Math.max(root.minWidth, Math.min(defW, root.screenWidth));
|
||||
const finalH = Math.max(root.minHeight, Math.min(defH, root.screenHeight));
|
||||
root.saveSize(finalW, finalH);
|
||||
}
|
||||
if (!root.hasSavedPosition) {
|
||||
root.widgetX = Math.max(0, Math.min(root.screenWidth / 2 - root.widgetWidth / 2, root.screenWidth - root.widgetWidth));
|
||||
root.widgetY = Math.max(0, Math.min(root.screenHeight / 2 - root.widgetHeight / 2, root.screenHeight - root.widgetHeight));
|
||||
const finalX = Math.max(0, Math.min(root.screenWidth / 2 - root.widgetWidth / 2, root.screenWidth - root.widgetWidth));
|
||||
const finalY = Math.max(0, Math.min(root.screenHeight / 2 - root.widgetHeight / 2, root.screenHeight - root.widgetHeight));
|
||||
root.savePosition(finalX, finalY);
|
||||
}
|
||||
if (item.widgetWidth !== undefined)
|
||||
item.widgetWidth = Qt.binding(() => contentLoader.width);
|
||||
@@ -355,6 +431,7 @@ Item {
|
||||
id: dragArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
enabled: !root.clickThrough
|
||||
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.ArrowCursor
|
||||
|
||||
property point startPos
|
||||
@@ -367,6 +444,8 @@ Item {
|
||||
startY = root.widgetY;
|
||||
root.previewX = root.widgetX;
|
||||
root.previewY = root.widgetY;
|
||||
root.dragOverrideX = root.widgetX;
|
||||
root.dragOverrideY = root.widgetY;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
@@ -384,16 +463,15 @@ Item {
|
||||
root.previewY = newY;
|
||||
return;
|
||||
}
|
||||
root.widgetX = newX;
|
||||
root.widgetY = newY;
|
||||
root.dragOverrideX = newX;
|
||||
root.dragOverrideY = newY;
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (root.useGhostPreview) {
|
||||
root.widgetX = root.previewX;
|
||||
root.widgetY = root.previewY;
|
||||
}
|
||||
root.savePosition();
|
||||
const finalX = root.useGhostPreview ? root.previewX : root.dragOverrideX;
|
||||
const finalY = root.useGhostPreview ? root.previewY : root.dragOverrideY;
|
||||
root.savePosition(finalX, finalY);
|
||||
root.clearDragOverrides();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,6 +482,7 @@ Item {
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
acceptedButtons: Qt.RightButton
|
||||
enabled: !root.clickThrough
|
||||
cursorShape: pressed ? Qt.SizeFDiagCursor : Qt.ArrowCursor
|
||||
|
||||
property point startPos
|
||||
@@ -416,6 +495,8 @@ Item {
|
||||
startHeight = root.widgetHeight;
|
||||
root.previewWidth = root.widgetWidth;
|
||||
root.previewHeight = root.widgetHeight;
|
||||
root.dragOverrideW = root.widgetWidth;
|
||||
root.dragOverrideH = root.widgetHeight;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
@@ -438,16 +519,15 @@ Item {
|
||||
root.previewHeight = newH;
|
||||
return;
|
||||
}
|
||||
root.widgetWidth = newW;
|
||||
root.widgetHeight = newH;
|
||||
root.dragOverrideW = newW;
|
||||
root.dragOverrideH = newH;
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
if (root.useGhostPreview) {
|
||||
root.widgetWidth = root.previewWidth;
|
||||
root.widgetHeight = root.previewHeight;
|
||||
}
|
||||
root.saveSize();
|
||||
const finalW = root.useGhostPreview ? root.previewWidth : root.dragOverrideW;
|
||||
const finalH = root.useGhostPreview ? root.previewHeight : root.dragOverrideH;
|
||||
root.saveSize(finalW, finalH);
|
||||
root.clearDragOverrides();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,13 @@ SettingsCard {
|
||||
DankActionButton {
|
||||
id: menuButton
|
||||
iconName: "more_vert"
|
||||
onClicked: actionsMenu.open()
|
||||
onClicked: {
|
||||
if (actionsMenu.opened) {
|
||||
actionsMenu.close();
|
||||
return;
|
||||
}
|
||||
actionsMenu.open();
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: actionsMenu
|
||||
@@ -71,7 +77,7 @@ SettingsCard {
|
||||
y: parent.height + Theme.spacingXS
|
||||
width: 160
|
||||
padding: Theme.spacingXS
|
||||
modal: true
|
||||
modal: false
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
@@ -218,6 +224,73 @@ SettingsCard {
|
||||
|
||||
SettingsDivider {}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: groupRow.height + Theme.spacingM * 2
|
||||
visible: (SettingsData.desktopWidgetGroups || []).length > 0
|
||||
|
||||
Row {
|
||||
id: groupRow
|
||||
x: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Group")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 80
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
id: groupDropdown
|
||||
width: parent.width - 80 - Theme.spacingM
|
||||
compactMode: true
|
||||
|
||||
property var groupsData: {
|
||||
const groups = SettingsData.desktopWidgetGroups || [];
|
||||
const items = [
|
||||
{
|
||||
value: "",
|
||||
label: I18n.tr("None")
|
||||
}
|
||||
];
|
||||
for (const g of groups) {
|
||||
items.push({
|
||||
value: g.id,
|
||||
label: g.name
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
options: groupsData.map(g => g.label)
|
||||
currentValue: {
|
||||
const currentGroup = root.instanceData?.group ?? "";
|
||||
const item = groupsData.find(g => g.value === currentGroup);
|
||||
return item?.label ?? I18n.tr("None");
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
if (!root.instanceId)
|
||||
return;
|
||||
const item = groupsData.find(g => g.label === value);
|
||||
const groupId = item?.value ?? "";
|
||||
SettingsData.updateDesktopWidgetInstance(root.instanceId, {
|
||||
group: groupId || null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDivider {
|
||||
visible: (SettingsData.desktopWidgetGroups || []).length > 0
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Show on Overlay")
|
||||
checked: instanceData?.config?.showOnOverlay ?? false
|
||||
@@ -266,6 +339,38 @@ SettingsCard {
|
||||
|
||||
SettingsDivider {}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Click Through")
|
||||
description: I18n.tr("Allow clicks to pass through the widget")
|
||||
checked: instanceData?.config?.clickThrough ?? false
|
||||
onToggled: isChecked => {
|
||||
if (!root.instanceId)
|
||||
return;
|
||||
SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, {
|
||||
clickThrough: isChecked
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDivider {}
|
||||
|
||||
SettingsToggleRow {
|
||||
text: I18n.tr("Sync Position Across Screens")
|
||||
description: I18n.tr("Use the same position and size on all displays")
|
||||
checked: instanceData?.config?.syncPositionAcrossScreens ?? false
|
||||
onToggled: isChecked => {
|
||||
if (!root.instanceId)
|
||||
return;
|
||||
if (isChecked)
|
||||
SettingsData.syncDesktopWidgetPositionToAllScreens(root.instanceId);
|
||||
SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, {
|
||||
syncPositionAcrossScreens: isChecked
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDivider {}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: ipcColumn.height + Theme.spacingM * 2
|
||||
|
||||
@@ -14,7 +14,13 @@ Item {
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
property var expandedStates: ({})
|
||||
property var groupCollapsedStates: ({})
|
||||
property var parentModal: null
|
||||
property string editingGroupId: ""
|
||||
property string newGroupName: ""
|
||||
|
||||
readonly property var allInstances: SettingsData.desktopWidgetInstances || []
|
||||
readonly property var allGroups: SettingsData.desktopWidgetGroups || []
|
||||
|
||||
DesktopWidgetBrowser {
|
||||
id: widgetBrowser
|
||||
@@ -80,54 +86,500 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: instancesColumn
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: SettingsData.desktopWidgetInstances.length > 0
|
||||
iconName: "folder"
|
||||
title: I18n.tr("Groups")
|
||||
collapsible: true
|
||||
expanded: root.allGroups.length > 0
|
||||
|
||||
Repeater {
|
||||
id: instanceRepeater
|
||||
model: ScriptModel {
|
||||
id: instancesModel
|
||||
objectProp: "id"
|
||||
values: SettingsData.desktopWidgetInstances
|
||||
Column {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
x: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Organize widgets into collapsible groups")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
DesktopWidgetInstanceCard {
|
||||
required property var modelData
|
||||
required property int index
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
width: parent.width
|
||||
|
||||
readonly property string instanceIdRef: modelData.id
|
||||
readonly property var liveInstanceData: {
|
||||
const instances = SettingsData.desktopWidgetInstances || [];
|
||||
return instances.find(inst => inst.id === instanceIdRef) ?? modelData;
|
||||
DankTextField {
|
||||
id: newGroupField
|
||||
width: parent.width - addGroupBtn.width - Theme.spacingS
|
||||
placeholderText: I18n.tr("New group name...")
|
||||
text: root.newGroupName
|
||||
onTextChanged: root.newGroupName = text
|
||||
onAccepted: {
|
||||
if (!text.trim())
|
||||
return;
|
||||
SettingsData.createDesktopWidgetGroup(text.trim());
|
||||
root.newGroupName = "";
|
||||
text = "";
|
||||
}
|
||||
}
|
||||
|
||||
width: instancesColumn.width
|
||||
instanceData: liveInstanceData
|
||||
isExpanded: root.expandedStates[instanceIdRef] ?? false
|
||||
DankButton {
|
||||
id: addGroupBtn
|
||||
iconName: "add"
|
||||
text: I18n.tr("Add")
|
||||
enabled: root.newGroupName.trim().length > 0
|
||||
onClicked: {
|
||||
SettingsData.createDesktopWidgetGroup(root.newGroupName.trim());
|
||||
root.newGroupName = "";
|
||||
newGroupField.text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onExpandedChanged: {
|
||||
if (expanded === (root.expandedStates[instanceIdRef] ?? false))
|
||||
return;
|
||||
var states = Object.assign({}, root.expandedStates);
|
||||
states[instanceIdRef] = expanded;
|
||||
root.expandedStates = states;
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingXS
|
||||
visible: root.allGroups.length > 0
|
||||
|
||||
Repeater {
|
||||
model: root.allGroups
|
||||
|
||||
Rectangle {
|
||||
id: groupItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 40
|
||||
radius: Theme.cornerRadius
|
||||
color: groupMouseArea.containsMouse ? Theme.surfaceHover : Theme.surfaceContainer
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "folder"
|
||||
size: Theme.iconSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: root.editingGroupId === groupItem.modelData.id
|
||||
width: active ? parent.width - Theme.iconSizeSmall - deleteGroupBtn.width - Theme.spacingS * 3 : 0
|
||||
height: active ? 32 : 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
sourceComponent: DankTextField {
|
||||
text: groupItem.modelData.name
|
||||
onAccepted: {
|
||||
if (!text.trim())
|
||||
return;
|
||||
SettingsData.updateDesktopWidgetGroup(groupItem.modelData.id, {
|
||||
name: text.trim()
|
||||
});
|
||||
root.editingGroupId = "";
|
||||
}
|
||||
onEditingFinished: {
|
||||
if (!text.trim())
|
||||
return;
|
||||
SettingsData.updateDesktopWidgetGroup(groupItem.modelData.id, {
|
||||
name: text.trim()
|
||||
});
|
||||
root.editingGroupId = "";
|
||||
}
|
||||
Component.onCompleted: forceActiveFocus()
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: root.editingGroupId !== groupItem.modelData.id
|
||||
text: groupItem.modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
elide: Text.ElideRight
|
||||
width: parent.width - Theme.iconSizeSmall - deleteGroupBtn.width - Theme.spacingS * 3
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: deleteGroupBtn
|
||||
iconName: "delete"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
SettingsData.removeDesktopWidgetGroup(groupItem.modelData.id);
|
||||
ToastService.showInfo(I18n.tr("Group removed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: groupMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onDoubleClicked: root.editingGroupId = groupItem.modelData.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.allGroups
|
||||
|
||||
Column {
|
||||
id: groupSection
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
readonly property string groupId: modelData.id
|
||||
readonly property var groupInstances: root.allInstances.filter(inst => inst.group === groupId)
|
||||
|
||||
width: mainColumn.width
|
||||
spacing: Theme.spacingM
|
||||
visible: groupInstances.length > 0
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 44
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: (root.groupCollapsedStates[groupSection.groupId] ?? false) ? "expand_more" : "expand_less"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "folder"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: groupSection.modelData.name
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "(" + groupSection.groupInstances.length + ")"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(instanceIdRef)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
var states = Object.assign({}, root.groupCollapsedStates);
|
||||
states[groupSection.groupId] = !(states[groupSection.groupId] ?? false);
|
||||
root.groupCollapsedStates = states;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDeleteRequested: {
|
||||
SettingsData.removeDesktopWidgetInstance(instanceIdRef);
|
||||
ToastService.showInfo(I18n.tr("Widget removed"));
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: !(root.groupCollapsedStates[groupSection.groupId] ?? false)
|
||||
leftPadding: Theme.spacingM
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
objectProp: "id"
|
||||
values: groupSection.groupInstances
|
||||
}
|
||||
|
||||
Item {
|
||||
id: groupDelegateItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property bool held: groupDragArea.pressed
|
||||
property real originalY: y
|
||||
|
||||
readonly property string instanceIdRef: modelData.id
|
||||
readonly property var liveInstanceData: {
|
||||
const instances = root.allInstances;
|
||||
return instances.find(inst => inst.id === instanceIdRef) ?? modelData;
|
||||
}
|
||||
|
||||
width: groupSection.width - Theme.spacingM
|
||||
height: groupCard.height
|
||||
z: held ? 2 : 1
|
||||
|
||||
DesktopWidgetInstanceCard {
|
||||
id: groupCard
|
||||
width: parent.width
|
||||
headerLeftPadding: 20
|
||||
instanceData: groupDelegateItem.liveInstanceData
|
||||
isExpanded: root.expandedStates[groupDelegateItem.instanceIdRef] ?? false
|
||||
|
||||
onExpandedChanged: {
|
||||
if (expanded === (root.expandedStates[groupDelegateItem.instanceIdRef] ?? false))
|
||||
return;
|
||||
var states = Object.assign({}, root.expandedStates);
|
||||
states[groupDelegateItem.instanceIdRef] = expanded;
|
||||
root.expandedStates = states;
|
||||
}
|
||||
|
||||
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(groupDelegateItem.instanceIdRef)
|
||||
|
||||
onDeleteRequested: {
|
||||
SettingsData.removeDesktopWidgetInstance(groupDelegateItem.instanceIdRef);
|
||||
ToastService.showInfo(I18n.tr("Widget removed"));
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: groupDragArea
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 40
|
||||
height: 50
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
drag.target: groupDelegateItem.held ? groupDelegateItem : undefined
|
||||
drag.axis: Drag.YAxis
|
||||
preventStealing: true
|
||||
|
||||
onPressed: {
|
||||
groupDelegateItem.z = 2;
|
||||
groupDelegateItem.originalY = groupDelegateItem.y;
|
||||
}
|
||||
onReleased: {
|
||||
groupDelegateItem.z = 1;
|
||||
if (!drag.active) {
|
||||
groupDelegateItem.y = groupDelegateItem.originalY;
|
||||
return;
|
||||
}
|
||||
const spacing = Theme.spacingM;
|
||||
const itemH = groupDelegateItem.height + spacing;
|
||||
var newIndex = Math.round(groupDelegateItem.y / itemH);
|
||||
newIndex = Math.max(0, Math.min(newIndex, groupSection.groupInstances.length - 1));
|
||||
if (newIndex !== groupDelegateItem.index)
|
||||
SettingsData.reorderDesktopWidgetInstanceInGroup(groupDelegateItem.instanceIdRef, groupSection.groupId, newIndex);
|
||||
groupDelegateItem.y = groupDelegateItem.originalY;
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
x: Theme.spacingL - 2
|
||||
y: Theme.spacingL + (Theme.iconSize / 2) - (size / 2)
|
||||
name: "drag_indicator"
|
||||
size: 18
|
||||
color: Theme.outline
|
||||
opacity: groupDragArea.containsMouse || groupDragArea.pressed ? 1 : 0.5
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !groupDragArea.pressed && !groupDragArea.drag.active
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: ungroupedSection
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: ungroupedInstances.length > 0
|
||||
|
||||
readonly property var ungroupedInstances: root.allInstances.filter(inst => {
|
||||
if (!inst.group)
|
||||
return true;
|
||||
return !root.allGroups.some(g => g.id === inst.group);
|
||||
})
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 44
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainer
|
||||
visible: root.allGroups.length > 0
|
||||
|
||||
Row {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: (root.groupCollapsedStates["_ungrouped"] ?? false) ? "expand_more" : "expand_less"
|
||||
size: Theme.iconSize
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "widgets"
|
||||
size: Theme.iconSize
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Ungrouped")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: "(" + ungroupedSection.ungroupedInstances.length + ")"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
var states = Object.assign({}, root.groupCollapsedStates);
|
||||
states["_ungrouped"] = !(states["_ungrouped"] ?? false);
|
||||
root.groupCollapsedStates = states;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingM
|
||||
visible: !(root.groupCollapsedStates["_ungrouped"] ?? false)
|
||||
leftPadding: root.allGroups.length > 0 ? Theme.spacingM : 0
|
||||
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
objectProp: "id"
|
||||
values: ungroupedSection.ungroupedInstances
|
||||
}
|
||||
|
||||
Item {
|
||||
id: ungroupedDelegateItem
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property bool held: ungroupedDragArea.pressed
|
||||
property real originalY: y
|
||||
|
||||
readonly property string instanceIdRef: modelData.id
|
||||
readonly property var liveInstanceData: {
|
||||
const instances = root.allInstances;
|
||||
return instances.find(inst => inst.id === instanceIdRef) ?? modelData;
|
||||
}
|
||||
|
||||
width: ungroupedSection.width - (root.allGroups.length > 0 ? Theme.spacingM : 0)
|
||||
height: ungroupedCard.height
|
||||
z: held ? 2 : 1
|
||||
|
||||
DesktopWidgetInstanceCard {
|
||||
id: ungroupedCard
|
||||
width: parent.width
|
||||
headerLeftPadding: 20
|
||||
instanceData: ungroupedDelegateItem.liveInstanceData
|
||||
isExpanded: root.expandedStates[ungroupedDelegateItem.instanceIdRef] ?? false
|
||||
|
||||
onExpandedChanged: {
|
||||
if (expanded === (root.expandedStates[ungroupedDelegateItem.instanceIdRef] ?? false))
|
||||
return;
|
||||
var states = Object.assign({}, root.expandedStates);
|
||||
states[ungroupedDelegateItem.instanceIdRef] = expanded;
|
||||
root.expandedStates = states;
|
||||
}
|
||||
|
||||
onDuplicateRequested: SettingsData.duplicateDesktopWidgetInstance(ungroupedDelegateItem.instanceIdRef)
|
||||
|
||||
onDeleteRequested: {
|
||||
SettingsData.removeDesktopWidgetInstance(ungroupedDelegateItem.instanceIdRef);
|
||||
ToastService.showInfo(I18n.tr("Widget removed"));
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: ungroupedDragArea
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 40
|
||||
height: 50
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
drag.target: ungroupedDelegateItem.held ? ungroupedDelegateItem : undefined
|
||||
drag.axis: Drag.YAxis
|
||||
preventStealing: true
|
||||
|
||||
onPressed: {
|
||||
ungroupedDelegateItem.z = 2;
|
||||
ungroupedDelegateItem.originalY = ungroupedDelegateItem.y;
|
||||
}
|
||||
onReleased: {
|
||||
ungroupedDelegateItem.z = 1;
|
||||
if (!drag.active) {
|
||||
ungroupedDelegateItem.y = ungroupedDelegateItem.originalY;
|
||||
return;
|
||||
}
|
||||
const spacing = Theme.spacingM;
|
||||
const itemH = ungroupedDelegateItem.height + spacing;
|
||||
var newIndex = Math.round(ungroupedDelegateItem.y / itemH);
|
||||
newIndex = Math.max(0, Math.min(newIndex, ungroupedSection.ungroupedInstances.length - 1));
|
||||
if (newIndex !== ungroupedDelegateItem.index)
|
||||
SettingsData.reorderDesktopWidgetInstanceInGroup(ungroupedDelegateItem.instanceIdRef, null, newIndex);
|
||||
ungroupedDelegateItem.y = ungroupedDelegateItem.originalY;
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
x: Theme.spacingL - 2
|
||||
y: Theme.spacingL + (Theme.iconSize / 2) - (size / 2)
|
||||
name: "drag_indicator"
|
||||
size: 18
|
||||
color: Theme.outline
|
||||
opacity: ungroupedDragArea.containsMouse || ungroupedDragArea.pressed ? 1 : 0.5
|
||||
}
|
||||
|
||||
Behavior on y {
|
||||
enabled: !ungroupedDragArea.pressed && !ungroupedDragArea.drag.active
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: SettingsData.desktopWidgetInstances.length === 0
|
||||
visible: root.allInstances.length === 0
|
||||
text: I18n.tr("No widgets added. Click \"Add Widget\" to get started.")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
|
||||
@@ -19,6 +19,7 @@ StyledRect {
|
||||
property string iconName: ""
|
||||
property bool collapsible: false
|
||||
property bool expanded: true
|
||||
property real headerLeftPadding: 0
|
||||
|
||||
default property alias content: contentColumn.children
|
||||
property alias headerActions: headerActionsRow.children
|
||||
@@ -115,6 +116,7 @@ StyledRect {
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.headerLeftPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
|
||||
Reference in New Issue
Block a user