diff options
Diffstat (limited to 'raveos-hyprland-theme/theme-data/dms/Modules/Plugins/DesktopPluginWrapper.qml')
| -rw-r--r-- | raveos-hyprland-theme/theme-data/dms/Modules/Plugins/DesktopPluginWrapper.qml | 758 |
1 files changed, 758 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/dms/Modules/Plugins/DesktopPluginWrapper.qml b/raveos-hyprland-theme/theme-data/dms/Modules/Plugins/DesktopPluginWrapper.qml new file mode 100644 index 0000000..c7a5c76 --- /dev/null +++ b/raveos-hyprland-theme/theme-data/dms/Modules/Plugins/DesktopPluginWrapper.qml @@ -0,0 +1,758 @@ +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 + + required property string pluginId + required property var screen + + property var builtinComponent: null + property var pluginService: null + property string instanceId: "" + property var instanceData: null + property bool widgetEnabled: true + + readonly property bool isBuiltin: pluginId === "desktopClock" || pluginId === "systemMonitor" + readonly property var activeComponent: isBuiltin ? builtinComponent : PluginService.pluginDesktopComponents[pluginId] ?? null + + readonly property bool showOnOverlay: instanceData?.config?.showOnOverlay ?? false + 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 + enabled: !root.isBuiltin + + function onPluginLoaded(loadedPluginId) { + if (loadedPluginId === root.pluginId) + contentLoader.reloadComponent(); + } + + function onPluginUnloaded(unloadedPluginId) { + if (unloadedPluginId === root.pluginId) + contentLoader.reloadComponent(); + } + } + + readonly property string settingsKey: instanceId ? instanceId : pluginId + readonly property bool isInstance: instanceId !== "" && instanceData !== null + readonly property bool usePluginService: pluginService !== null && !isInstance + + QtObject { + id: instanceScopedPluginService + + readonly property var availablePlugins: PluginService.availablePlugins + readonly property var loadedPlugins: PluginService.loadedPlugins + readonly property var pluginDesktopComponents: PluginService.pluginDesktopComponents + + signal pluginDataChanged(string pluginId) + signal pluginLoaded(string pluginId) + signal pluginUnloaded(string pluginId) + + function loadPluginData(pluginId, key, defaultValue) { + const cfg = root.instanceData?.config; + if (cfg && key in cfg) + return cfg[key]; + return SettingsData.getPluginSetting(pluginId, key, defaultValue); + } + + function savePluginData(pluginId, key, value) { + if (!root.instanceId) + return false; + var updates = {}; + updates[key] = value; + SettingsData.updateDesktopWidgetInstanceConfig(root.instanceId, updates); + Qt.callLater(() => pluginDataChanged(pluginId)); + return true; + } + + function getPluginVariants(pluginId) { + return PluginService.getPluginVariants(pluginId); + } + + function isPluginLoaded(pluginId) { + return PluginService.isPluginLoaded(pluginId); + } + } + 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 + + readonly property bool useGhostPreview: !CompositorService.isNiri + + property real previewX: widgetX + property real previewY: widgetY + property real previewWidth: widgetWidth + property real previewHeight: widgetHeight + + readonly property bool hasSavedPosition: { + if (isInstance) + return instanceData?.positions?.[positionKey]?.x !== undefined; + if (usePluginService) + 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?.[positionKey]?.width !== undefined; + if (usePluginService) + return pluginService.loadPluginData(pluginId, "desktopWidth_" + positionKey, null) !== null; + return SettingsData.getDesktopWidgetPosition(pluginId, positionKey, "width", null) !== null; + } + + property real savedX: { + 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) { + 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) { + 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) { + 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 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 + property bool forceSquare: contentLoader.item?.forceSquare ?? false + property bool isInteracting: dragArea.pressed || resizeArea.pressed + + property var _gridSettingsTrigger: SettingsData.desktopWidgetGridSettings + readonly property int gridSize: { + void _gridSettingsTrigger; + return SettingsData.getDesktopWidgetGridSetting(screenKey, "size", 40); + } + readonly property bool gridEnabled: { + void _gridSettingsTrigger; + return SettingsData.getDesktopWidgetGridSetting(screenKey, "enabled", false); + } + + function snapToGrid(value) { + return Math.round(value / gridSize) * gridSize; + } + + function savePosition(finalX, finalY) { + const xVal = syncPositionAcrossScreens ? finalX / screenWidth : finalX; + const yVal = syncPositionAcrossScreens ? finalY / screenHeight : finalY; + if (isInstance && instanceData) { + SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, { + x: xVal, + y: yVal + }); + return; + } + if (usePluginService) { + pluginService.savePluginData(pluginId, "desktopX_" + positionKey, xVal); + pluginService.savePluginData(pluginId, "desktopY_" + positionKey, yVal); + return; + } + SettingsData.updateDesktopWidgetPosition(pluginId, positionKey, { + x: xVal, + y: yVal + }); + } + + function saveSize(finalW, finalH) { + const sizeVal = forceSquare ? Math.max(finalW, finalH) : finalW; + const heightVal = forceSquare ? sizeVal : finalH; + if (isInstance && instanceData) { + SettingsData.updateDesktopWidgetInstancePosition(instanceId, positionKey, { + width: sizeVal, + height: heightVal + }); + return; + } + if (usePluginService) { + pluginService.savePluginData(pluginId, "desktopWidth_" + positionKey, sizeVal); + pluginService.savePluginData(pluginId, "desktopHeight_" + positionKey, heightVal); + return; + } + SettingsData.updateDesktopWidgetPosition(pluginId, positionKey, { + width: sizeVal, + height: heightVal + }); + } + + PanelWindow { + id: widgetWindow + screen: root.screen + visible: { + if (!root.widgetEnabled || root.activeComponent === null) + return false; + if (root.showOnOverviewOnly) + return root.overviewActive; + return true; + } + color: "transparent" + + Region { + id: emptyMask + } + + mask: root.clickThrough ? emptyMask : null + + 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)) + return WlrLayer.Overlay; + return WlrLayer.Bottom; + } + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.keyboardFocus: { + if (!root.isInteracting) + return WlrKeyboardFocus.None; + if (CompositorService.useHyprlandFocusGrab) + return WlrKeyboardFocus.OnDemand; + return WlrKeyboardFocus.Exclusive; + } + + 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: + SettingsData.setDesktopWidgetGridSetting(root.screenKey, "enabled", !root.gridEnabled); + event.accepted = true; + break; + case Qt.Key_Z: + SettingsData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.max(10, root.gridSize - 10)); + event.accepted = true; + break; + case Qt.Key_X: + SettingsData.setDesktopWidgetGridSetting(root.screenKey, "size", Math.min(200, root.gridSize + 10)); + event.accepted = true; + break; + } + } + } + + anchors { + left: true + top: true + } + + WlrLayershell.margins { + left: root.widgetX + top: root.widgetY + } + + implicitWidth: root.widgetWidth + implicitHeight: root.widgetHeight + + Loader { + id: contentLoader + anchors.fill: parent + active: root.widgetEnabled && root.activeComponent !== null + sourceComponent: root.activeComponent + + function reloadComponent() { + active = false; + active = true; + } + + function updateInstanceData() { + if (!item || item.instanceData === undefined) + return; + item.instanceData = root.instanceData; + } + + Connections { + target: root + enabled: contentLoader.item !== null + + function onInstanceDataChanged() { + contentLoader.updateInstanceData(); + } + } + + onLoaded: { + if (!item) + return; + + if (item.pluginService !== undefined) { + item.pluginService = root.isInstance ? instanceScopedPluginService : root.pluginService; + } + if (item.pluginId !== undefined) + item.pluginId = root.pluginId; + if (item.instanceId !== undefined) + item.instanceId = root.instanceId; + if (item.instanceData !== undefined) + item.instanceData = root.instanceData; + if (!root.hasSavedSize) { + const defW = item.defaultWidth ?? item.widgetWidth ?? 280; + const defH = item.defaultHeight ?? item.widgetHeight ?? 180; + 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) { + 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); + if (item.widgetHeight !== undefined) + item.widgetHeight = Qt.binding(() => contentLoader.height); + } + } + + 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 = root.widgetX; + startY = root.widgetY; + root.previewX = root.widgetX; + root.previewY = root.widgetY; + root.dragOverrideX = root.widgetX; + root.dragOverrideY = root.widgetY; + } + + onPositionChanged: mouse => { + if (!pressed) + return; + const currentPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y); + let newX = Math.max(0, Math.min(startX + currentPos.x - startPos.x, root.screenWidth - root.widgetWidth)); + let newY = Math.max(0, Math.min(startY + currentPos.y - startPos.y, root.screenHeight - root.widgetHeight)); + if (root.gridEnabled) { + newX = Math.max(0, Math.min(root.snapToGrid(newX), root.screenWidth - root.widgetWidth)); + newY = Math.max(0, Math.min(root.snapToGrid(newY), root.screenHeight - root.widgetHeight)); + } + if (root.useGhostPreview) { + root.previewX = newX; + root.previewY = newY; + return; + } + root.dragOverrideX = newX; + root.dragOverrideY = newY; + } + + onReleased: { + const finalX = root.useGhostPreview ? root.previewX : root.dragOverrideX; + const finalY = root.useGhostPreview ? root.previewY : root.dragOverrideY; + root.savePosition(finalX, finalY); + root.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 = root.widgetWidth; + startHeight = root.widgetHeight; + root.previewWidth = root.widgetWidth; + root.previewHeight = root.widgetHeight; + root.dragOverrideW = root.widgetWidth; + root.dragOverrideH = root.widgetHeight; + } + + onPositionChanged: mouse => { + if (!pressed) + return; + const currentPos = root.useGhostPreview ? Qt.point(mouse.x, mouse.y) : mapToGlobal(mouse.x, mouse.y); + let newW = Math.max(root.minWidth, Math.min(startWidth + currentPos.x - startPos.x, root.screenWidth - root.widgetX)); + let newH = Math.max(root.minHeight, Math.min(startHeight + currentPos.y - startPos.y, root.screenHeight - root.widgetY)); + if (root.gridEnabled) { + newW = Math.max(root.minWidth, root.snapToGrid(newW)); + newH = Math.max(root.minHeight, root.snapToGrid(newH)); + } + if (root.forceSquare) { + const size = Math.max(newW, newH); + newW = Math.min(size, root.screenWidth - root.widgetX); + newH = Math.min(size, root.screenHeight - root.widgetY); + } + if (root.useGhostPreview) { + root.previewWidth = newW; + root.previewHeight = newH; + return; + } + root.dragOverrideW = newW; + root.dragOverrideH = newH; + } + + onReleased: { + const finalW = root.useGhostPreview ? root.previewWidth : root.dragOverrideW; + const finalH = root.useGhostPreview ? root.previewHeight : root.dragOverrideH; + root.saveSize(finalW, finalH); + root.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 + + Item { + id: gridOverlay + anchors.fill: parent + visible: root.gridEnabled + opacity: 0.3 + + Repeater { + model: Math.ceil(root.screenWidth / root.gridSize) + + Rectangle { + required property int index + x: index * root.gridSize + y: 0 + width: 1 + height: root.screenHeight + color: Theme.primary + } + } + + Repeater { + model: Math.ceil(root.screenHeight / root.gridSize) + + Rectangle { + required property int index + x: 0 + y: index * root.gridSize + width: root.screenWidth + height: 1 + color: Theme.primary + } + } + } + + 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 && root.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 + + Item { + anchors.fill: parent + opacity: 0.3 + + Repeater { + model: Math.ceil(root.screenWidth / root.gridSize) + + Rectangle { + required property int index + x: index * root.gridSize + y: 0 + width: 1 + height: root.screenHeight + color: Theme.primary + } + } + + Repeater { + model: Math.ceil(root.screenHeight / root.gridSize) + + Rectangle { + required property int index + x: 0 + y: index * root.gridSize + width: root.screenWidth + height: 1 + color: Theme.primary + } + } + } + } + } + + 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 + + Rectangle { + id: helperContent + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: Theme.spacingL + width: helperRow.implicitWidth + Theme.spacingM * 2 + height: 32 + radius: Theme.cornerRadius + color: Theme.surface + + Row { + id: helperRow + anchors.centerIn: parent + spacing: Theme.spacingM + height: parent.height + + DankIcon { + name: "grid_on" + size: 16 + color: root.gridEnabled ? Theme.primary : Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: root.gridEnabled ? I18n.tr("Grid: ON", "Widget grid snap status") : I18n.tr("Grid: OFF", "Widget grid snap status") + font.pixelSize: Theme.fontSizeSmall + font.family: Theme.fontFamily + color: root.gridEnabled ? Theme.primary : Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: 1 + height: 16 + color: Theme.outline + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: root.gridSize + "px" + font.pixelSize: Theme.fontSizeSmall + font.family: Theme.fontFamily + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + width: 1 + height: 16 + color: Theme.outline + anchors.verticalCenter: parent.verticalCenter + } + + Text { + text: I18n.tr("G: grid • Z/X: size", "Widget grid keyboard hints") + font.pixelSize: Theme.fontSizeSmall + font.family: Theme.fontFamily + font.italic: true + color: Theme.surfaceText + opacity: 0.7 + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + } +} |