From 5d94c0a7d44a2255b81815a52a7056a94a39842d Mon Sep 17 00:00:00 2001 From: nippy Date: Sat, 18 Apr 2026 13:49:56 +0200 Subject: update Raveos themes --- .../quickshell/Modules/Plugins/BasePill.qml | 195 ++++++ .../quickshell/Modules/Plugins/ColorSetting.qml | 104 +++ .../Modules/Plugins/DesktopPluginComponent.qml | 67 ++ .../Modules/Plugins/DesktopPluginWrapper.qml | 758 +++++++++++++++++++++ .../quickshell/Modules/Plugins/ListSetting.qml | 132 ++++ .../Modules/Plugins/ListSettingWithInput.qml | 256 +++++++ .../quickshell/Modules/Plugins/PluginComponent.qml | 333 +++++++++ .../Modules/Plugins/PluginControlCenterWrapper.qml | 51 ++ .../quickshell/Modules/Plugins/PluginPopout.qml | 83 +++ .../quickshell/Modules/Plugins/PluginSettings.qml | 311 +++++++++ .../quickshell/Modules/Plugins/PopoutComponent.qml | 87 +++ .../Modules/Plugins/SelectionSetting.qml | 91 +++ .../quickshell/Modules/Plugins/SliderSetting.qml | 81 +++ .../quickshell/Modules/Plugins/StringSetting.qml | 84 +++ .../quickshell/Modules/Plugins/ToggleSetting.qml | 81 +++ 15 files changed, 2714 insertions(+) create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/BasePill.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ColorSetting.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/DesktopPluginComponent.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/DesktopPluginWrapper.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSetting.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSettingWithInput.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginComponent.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginControlCenterWrapper.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginPopout.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginSettings.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PopoutComponent.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SelectionSetting.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SliderSetting.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/StringSetting.qml create mode 100644 raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ToggleSetting.qml (limited to 'raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins') diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/BasePill.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/BasePill.qml new file mode 100644 index 0000000..7dea95a --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/BasePill.qml @@ -0,0 +1,195 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + property var axis: null + property string section: "center" + property var popoutTarget: null + property var parentScreen: null + property real widgetThickness: 30 + property real barThickness: 48 + property real barSpacing: 4 + property var barConfig: null + property var blurBarWindow: null + property alias content: contentLoader.sourceComponent + property bool isVerticalOrientation: axis?.isVertical ?? false + property bool isFirst: false + property bool isLast: false + property real sectionSpacing: 0 + property bool enableBackgroundHover: true + property bool enableCursor: true + readonly property bool isMouseHovered: mouseArea.containsMouse + property bool isLeftBarEdge: false + property bool isRightBarEdge: false + property bool isTopBarEdge: false + property bool isBottomBarEdge: false + readonly property real dpr: parentScreen ? CompositorService.getScreenScale(parentScreen) : 1 + readonly property real horizontalPadding: (barConfig?.removeWidgetPadding ?? false) ? 0 : Theme.snap((barConfig?.widgetPadding ?? 12) * (widgetThickness / 30), dpr) + readonly property real visualWidth: Theme.snap(isVerticalOrientation ? widgetThickness : (contentLoader.item ? (contentLoader.item.implicitWidth + horizontalPadding * 2) : 0), dpr) + readonly property real visualHeight: Theme.snap(isVerticalOrientation ? (contentLoader.item ? (contentLoader.item.implicitHeight + horizontalPadding * 2) : 0) : widgetThickness, dpr) + readonly property alias visualContent: visualContent + readonly property real barEdgeExtension: 1000 + readonly property real gapExtension: sectionSpacing + readonly property real leftMargin: !isVerticalOrientation ? (isLeftBarEdge && isFirst ? barEdgeExtension : (isFirst ? gapExtension : gapExtension / 2)) : 0 + readonly property real rightMargin: !isVerticalOrientation ? (isRightBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0 + readonly property real topMargin: isVerticalOrientation ? (isTopBarEdge && isFirst ? barEdgeExtension : (isFirst ? gapExtension : gapExtension / 2)) : 0 + readonly property real bottomMargin: isVerticalOrientation ? (isBottomBarEdge && isLast ? barEdgeExtension : (isLast ? gapExtension : gapExtension / 2)) : 0 + + signal clicked + signal rightClicked(real rootX, real rootY) + signal wheel(var wheelEvent) + + function triggerRipple(sourceItem, mouseX, mouseY) { + const pos = sourceItem.mapToItem(visualContent, mouseX, mouseY); + rippleLayer.trigger(pos.x, pos.y); + } + + width: isVerticalOrientation ? barThickness : visualWidth + height: isVerticalOrientation ? visualHeight : barThickness + + Item { + id: visualContent + width: root.visualWidth + height: root.visualHeight + anchors.centerIn: parent + + Rectangle { + id: outline + anchors.centerIn: parent + width: { + const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0; + return parent.width + borderWidth * 2; + } + height: { + const borderWidth = (barConfig?.widgetOutlineEnabled ?? false) ? (barConfig?.widgetOutlineThickness ?? 1) : 0; + return parent.height + borderWidth * 2; + } + radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius + color: "transparent" + border.width: { + if (barConfig?.widgetOutlineEnabled ?? false) { + return barConfig?.widgetOutlineThickness ?? 1; + } + return 0; + } + border.color: { + if (!(barConfig?.widgetOutlineEnabled ?? false)) { + return "transparent"; + } + const colorOption = barConfig?.widgetOutlineColor || "primary"; + const opacity = barConfig?.widgetOutlineOpacity ?? 1.0; + switch (colorOption) { + case "surfaceText": + return Theme.withAlpha(Theme.surfaceText, opacity); + case "secondary": + return Theme.withAlpha(Theme.secondary, opacity); + case "primary": + return Theme.withAlpha(Theme.primary, opacity); + default: + return Theme.withAlpha(Theme.primary, opacity); + } + } + } + + Rectangle { + id: background + anchors.fill: parent + radius: (barConfig?.noBackground ?? false) ? 0 : Theme.cornerRadius + color: { + if (barConfig?.noBackground ?? false) { + return "transparent"; + } + + const rawTransparency = (root.barConfig && root.barConfig.widgetTransparency !== undefined) ? root.barConfig.widgetTransparency : 1.0; + const isHovered = root.enableBackgroundHover && (mouseArea.containsMouse || (root.isHovered || false)); + const transparency = isHovered ? Math.max(0.3, rawTransparency) : rawTransparency; + const baseColor = isHovered ? BlurService.hoverColor(Theme.widgetBaseHoverColor) : Theme.widgetBaseBackgroundColor; + + if (Theme.widgetBackgroundHasAlpha) { + return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, baseColor.a * transparency); + } + return Theme.withAlpha(baseColor, transparency); + } + } + + DankRipple { + id: rippleLayer + rippleColor: Theme.surfaceText + cornerRadius: background.radius + } + + Loader { + id: contentLoader + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + } + } + + MouseArea { + id: mouseArea + z: -1 + x: -root.leftMargin + y: -root.topMargin + width: root.width + root.leftMargin + root.rightMargin + height: root.height + root.topMargin + root.bottomMargin + hoverEnabled: true + cursorShape: root.enableCursor ? Qt.PointingHandCursor : Qt.ArrowCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + onPressed: function (mouse) { + if (mouse.button === Qt.RightButton) { + const rPos = mouseArea.mapToItem(root, mouse.x, mouse.y); + root.rightClicked(rPos.x, rPos.y); + return; + } + const ripplePos = mouseArea.mapToItem(visualContent, mouse.x, mouse.y); + rippleLayer.trigger(ripplePos.x, ripplePos.y); + if (popoutTarget) { + // Ensure bar context is set first if supported + if (popoutTarget.setBarContext) { + const pos = root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1)); + const bottomGap = root.barConfig ? (root.barConfig.bottomGap !== undefined ? root.barConfig.bottomGap : 0) : 0; + popoutTarget.setBarContext(pos, bottomGap); + } + + if (popoutTarget.setTriggerPosition) { + const globalPos = root.visualContent.mapToItem(null, 0, 0); + const currentScreen = parentScreen || Screen; + const barPosition = root.axis?.edge === "left" ? 2 : (root.axis?.edge === "right" ? 3 : (root.axis?.edge === "top" ? 0 : 1)); + const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, root.visualWidth, root.barSpacing, barPosition, root.barConfig); + popoutTarget.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen, barPosition, barThickness, root.barSpacing, root.barConfig); + } + } + root.clicked(); + } + onWheel: function (wheelEvent) { + wheelEvent.accepted = false; + root.wheel(wheelEvent); + } + } + + property bool _blurRegistered: false + readonly property bool _shouldBlur: BlurService.enabled && blurBarWindow && blurBarWindow.registerBlurWidget && !(barConfig?.noBackground ?? false) && root.visible && root.width > 0 + + on_ShouldBlurChanged: _updateBlurRegistration() + + function _updateBlurRegistration() { + if (_shouldBlur && !_blurRegistered) { + blurBarWindow.registerBlurWidget(visualContent); + _blurRegistered = true; + } else if (!_shouldBlur && _blurRegistered) { + if (blurBarWindow && blurBarWindow.unregisterBlurWidget) + blurBarWindow.unregisterBlurWidget(visualContent); + _blurRegistered = false; + } + } + + Component.onCompleted: _updateBlurRegistration() + Component.onDestruction: { + if (_blurRegistered && blurBarWindow && blurBarWindow.unregisterBlurWidget) + blurBarWindow.unregisterBlurWidget(visualContent); + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ColorSetting.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ColorSetting.qml new file mode 100644 index 0000000..5ef3190 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ColorSetting.qml @@ -0,0 +1,104 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +Column { + id: root + + required property string settingKey + required property string label + property string description: "" + property color defaultValue: Theme.primary + property color value: defaultValue + + width: parent.width + spacing: Theme.spacingS + + property bool isInitialized: false + + function loadValue() { + const settings = findSettings(); + if (settings && settings.pluginService) { + const loadedValue = settings.loadValue(settingKey, defaultValue); + value = loadedValue; + isInitialized = true; + } + } + + Component.onCompleted: { + Qt.callLater(loadValue); + } + + onValueChanged: { + if (!isInitialized) + return; + const settings = findSettings(); + if (settings) { + settings.saveValue(settingKey, value); + } + } + + function findSettings() { + let item = parent; + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) { + return item; + } + item = item.parent; + } + return null; + } + + StyledText { + text: root.label + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: root.description + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + visible: root.description !== "" + } + + Row { + width: parent.width + spacing: Theme.spacingS + + Rectangle { + width: 100 + height: 36 + radius: Theme.cornerRadius + color: root.value + border.color: Theme.outlineStrong + border.width: 2 + + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: { + if (PopoutService && PopoutService.colorPickerModal) { + PopoutService.colorPickerModal.selectedColor = root.value; + PopoutService.colorPickerModal.pickerTitle = root.label; + PopoutService.colorPickerModal.onColorSelectedCallback = function (selectedColor) { + root.value = selectedColor; + }; + PopoutService.colorPickerModal.show(); + } + } + } + } + + StyledText { + text: root.value.toString() + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceText + anchors.verticalCenter: parent.verticalCenter + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/DesktopPluginComponent.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/DesktopPluginComponent.qml new file mode 100644 index 0000000..1fd7ab8 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/DesktopPluginComponent.qml @@ -0,0 +1,67 @@ +import QtQuick +import qs.Common + +Item { + id: root + + property var pluginService: null + property string pluginId: "" + property string instanceId: "" + property var instanceData: null + + property real widgetWidth: 200 + property real widgetHeight: 200 + property real minWidth: 100 + property real minHeight: 100 + + readonly property bool isInstance: instanceId !== "" && instanceData !== null + readonly property var instanceConfig: instanceData?.config ?? {} + + property var pluginData: isInstance ? instanceConfig : _globalPluginData + property var _globalPluginData: ({}) + + Component.onCompleted: loadPluginData() + onPluginServiceChanged: loadPluginData() + onPluginIdChanged: loadPluginData() + onInstanceDataChanged: { + if (isInstance) + Qt.callLater(() => { + pluginData = instanceConfig; + }); + } + + Connections { + target: pluginService + enabled: pluginService !== null + + function onPluginDataChanged(changedPluginId) { + if (changedPluginId !== pluginId) + return; + loadPluginData(); + } + } + + function loadPluginData() { + if (!pluginService || !pluginId) { + _globalPluginData = {}; + return; + } + if (isInstance) { + pluginData = instanceConfig; + return; + } + _globalPluginData = SettingsData.getPluginSettingsForPlugin(pluginId); + } + + function getData(key, defaultValue) { + if (!pluginService || !pluginId) + return defaultValue; + return pluginService.loadPluginData(pluginId, key, defaultValue); + } + + function setData(key, value) { + if (!pluginService || !pluginId) + return; + pluginService.savePluginData(pluginId, key, value); + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/DesktopPluginWrapper.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/DesktopPluginWrapper.qml new file mode 100644 index 0000000..c7a5c76 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/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 + } + } + } + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSetting.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSetting.qml new file mode 100644 index 0000000..5f636f5 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSetting.qml @@ -0,0 +1,132 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Column { + id: root + + required property string settingKey + required property string label + property string description: "" + property var defaultValue: [] + property var items: defaultValue + property Component delegate: null + + width: parent.width + spacing: Theme.spacingM + + Component.onCompleted: { + const settings = findSettings() + if (settings) { + items = settings.loadValue(settingKey, defaultValue) + } + } + + onItemsChanged: { + const settings = findSettings() + if (settings) { + settings.saveValue(settingKey, items) + } + } + + function findSettings() { + let item = parent + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) { + return item + } + item = item.parent + } + return null + } + + function addItem(item) { + items = items.concat([item]) + } + + function removeItem(index) { + const newItems = items.slice() + newItems.splice(index, 1) + items = newItems + } + + StyledText { + text: root.label + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: root.description + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + visible: root.description !== "" + } + + Column { + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: root.items + delegate: root.delegate ? root.delegate : defaultDelegate + } + + StyledText { + text: I18n.tr("No items added yet") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + visible: root.items.length === 0 + } + } + + Component { + id: defaultDelegate + StyledRect { + width: parent.width + height: 40 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.width: 0 + + StyledText { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + text: modelData + color: Theme.surfaceText + } + + Rectangle { + anchors.right: parent.right + anchors.rightMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + width: 60 + height: 28 + color: removeArea.containsMouse ? Theme.errorHover : Theme.error + radius: Theme.cornerRadius + + StyledText { + anchors.centerIn: parent + text: I18n.tr("Remove") + color: Theme.errorText + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + } + + MouseArea { + id: removeArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + root.removeItem(index) + } + } + } + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSettingWithInput.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSettingWithInput.qml new file mode 100644 index 0000000..8da4fb6 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ListSettingWithInput.qml @@ -0,0 +1,256 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Column { + id: root + + required property string settingKey + required property string label + property string description: "" + property var fields: [] + property var defaultValue: [] + property var items: defaultValue + + width: parent.width + spacing: Theme.spacingM + + property bool isLoading: false + + Component.onCompleted: { + loadValue() + } + + function loadValue() { + const settings = findSettings() + if (settings) { + isLoading = true + items = settings.loadValue(settingKey, defaultValue) + isLoading = false + } + } + + onItemsChanged: { + if (isLoading) { + return + } + const settings = findSettings() + if (settings) { + settings.saveValue(settingKey, items) + } + } + + function findSettings() { + let item = parent + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) { + return item + } + item = item.parent + } + return null + } + + function addItem(item) { + items = items.concat([item]) + } + + function removeItem(index) { + const newItems = items.slice() + newItems.splice(index, 1) + items = newItems + } + + StyledText { + text: root.label + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: root.description + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + visible: root.description !== "" + } + + Flow { + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: root.fields + + StyledText { + text: modelData.label + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + color: Theme.surfaceText + width: modelData.width || 200 + } + } + } + + Flow { + id: inputRow + width: parent.width + spacing: Theme.spacingS + + property var inputFields: [] + + Repeater { + id: inputRepeater + model: root.fields + + DankTextField { + width: modelData.width || 200 + placeholderText: modelData.placeholder || "" + + Component.onCompleted: { + inputRow.inputFields.push(this) + } + + Keys.onReturnPressed: { + addButton.clicked() + } + } + } + + DankButton { + id: addButton + width: 50 + height: 36 + text: I18n.tr("Add") + + onClicked: { + let newItem = {} + let hasValue = false + + for (let i = 0; i < root.fields.length; i++) { + const field = root.fields[i] + const input = inputRow.inputFields[i] + const value = input.text.trim() + + if (value !== "") { + hasValue = true + } + + if (field.required && value === "") { + return + } + + newItem[field.id] = value || (field.default || "") + } + + if (hasValue) { + root.addItem(newItem) + for (let i = 0; i < inputRow.inputFields.length; i++) { + inputRow.inputFields[i].text = "" + } + if (inputRow.inputFields.length > 0) { + inputRow.inputFields[0].forceActiveFocus() + } + } + } + } + } + + StyledText { + text: I18n.tr("Current Items") + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + visible: root.items.length > 0 + } + + Column { + width: parent.width + spacing: Theme.spacingS + + Repeater { + model: root.items + + StyledRect { + width: parent.width + height: 40 + radius: Theme.cornerRadius + color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) + border.width: 0 + + required property int index + required property var modelData + + Row { + id: itemRow + anchors.left: parent.left + anchors.leftMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingM + + property var itemData: parent.modelData + + Repeater { + model: root.fields + + StyledText { + required property int index + required property var modelData + + text: { + const field = modelData + const item = itemRow.itemData + if (!field || !field.id || !item) { + return "" + } + const value = item[field.id] + return value || "" + } + color: Theme.surfaceText + font.pixelSize: Theme.fontSizeMedium + width: modelData ? (modelData.width || 200) : 200 + elide: Text.ElideRight + } + } + } + + Rectangle { + anchors.right: parent.right + anchors.rightMargin: Theme.spacingM + anchors.verticalCenter: parent.verticalCenter + width: 60 + height: 28 + color: removeArea.containsMouse ? Theme.errorHover : Theme.error + radius: Theme.cornerRadius + + StyledText { + anchors.centerIn: parent + text: I18n.tr("Remove") + color: Theme.onError + font.pixelSize: Theme.fontSizeSmall + font.weight: Font.Medium + } + + MouseArea { + id: removeArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + root.removeItem(index) + } + } + } + } + } + + StyledText { + text: I18n.tr("No items added yet") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + visible: root.items.length === 0 + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginComponent.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginComponent.qml new file mode 100644 index 0000000..85dd32d --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginComponent.qml @@ -0,0 +1,333 @@ +import QtQuick +import Quickshell.Io +import qs.Common + +Item { + id: root + + property string layerNamespacePlugin: "plugin" + + property var axis: null + property string section: "center" + property var parentScreen: null + property real widgetThickness: 30 + property real barThickness: 48 + property real barSpacing: 4 + property var barConfig: null + property var blurBarWindow: null + property string pluginId: "" + property var pluginService: null + + property string visibilityCommand: "" + property int visibilityInterval: 0 + property bool conditionVisible: true + property bool _visibilityOverride: false + property bool _visibilityOverrideValue: true + + readonly property bool effectiveVisible: { + if (_visibilityOverride) + return _visibilityOverrideValue; + if (!visibilityCommand) + return true; + return conditionVisible; + } + + property Component horizontalBarPill: null + property Component verticalBarPill: null + property Component popoutContent: null + property real popoutWidth: 400 + property real popoutHeight: 0 + property var pillClickAction: null + property var pillRightClickAction: null + + property Component controlCenterWidget: null + property string ccWidgetIcon: "" + property string ccWidgetPrimaryText: "" + property string ccWidgetSecondaryText: "" + property bool ccWidgetIsActive: false + property bool ccWidgetIsToggle: true + property Component ccDetailContent: null + property real ccDetailHeight: 250 + + signal ccWidgetToggled + signal ccWidgetExpanded + + property var pluginData: ({}) + property var variants: [] + + readonly property bool isVertical: axis?.isVertical ?? false + readonly property bool hasHorizontalPill: horizontalBarPill !== null + readonly property bool hasVerticalPill: verticalBarPill !== null + readonly property bool hasPopout: popoutContent !== null + + readonly property int iconSize: Theme.barIconSize(barThickness, -4, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) + readonly property int iconSizeLarge: Theme.barIconSize(barThickness, undefined, root.barConfig?.maximizeWidgetIcons, root.barConfig?.iconScale) + + Component.onCompleted: { + loadPluginData(); + if (visibilityCommand) + Qt.callLater(checkVisibility); + } + + onPluginServiceChanged: { + loadPluginData(); + } + + onPluginIdChanged: { + loadPluginData(); + } + + Connections { + target: pluginService + function onPluginDataChanged(changedPluginId) { + if (changedPluginId === pluginId) { + loadPluginData(); + } + } + } + + function loadPluginData() { + if (!pluginService || !pluginId) { + pluginData = {}; + variants = []; + return; + } + pluginData = SettingsData.getPluginSettingsForPlugin(pluginId); + variants = pluginService.getPluginVariants(pluginId); + } + + function checkVisibility() { + if (!visibilityCommand) { + conditionVisible = true; + return; + } + visibilityProcess.running = true; + } + + function setVisibilityOverride(visible) { + _visibilityOverride = true; + _visibilityOverrideValue = visible; + } + + function clearVisibilityOverride() { + _visibilityOverride = false; + if (visibilityCommand) + checkVisibility(); + } + + onVisibilityCommandChanged: { + if (visibilityCommand) + Qt.callLater(checkVisibility); + else + conditionVisible = true; + } + + onVisibilityIntervalChanged: { + if (visibilityInterval > 0 && visibilityCommand) { + visibilityTimer.restart(); + } else { + visibilityTimer.stop(); + } + } + + Timer { + id: visibilityTimer + interval: root.visibilityInterval * 1000 + repeat: true + running: root.visibilityInterval > 0 && root.visibilityCommand !== "" + onTriggered: root.checkVisibility() + } + + Process { + id: visibilityProcess + command: ["sh", "-c", root.visibilityCommand] + running: false + onExited: (exitCode, exitStatus) => { + root.conditionVisible = (exitCode === 0); + } + } + + function createVariant(variantName, variantConfig) { + if (!pluginService || !pluginId) { + return null; + } + return pluginService.createPluginVariant(pluginId, variantName, variantConfig); + } + + function removeVariant(variantId) { + if (!pluginService || !pluginId) { + return; + } + pluginService.removePluginVariant(pluginId, variantId); + } + + function updateVariant(variantId, variantConfig) { + if (!pluginService || !pluginId) { + return; + } + pluginService.updatePluginVariant(pluginId, variantId, variantConfig); + } + + width: isVertical ? (hasVerticalPill ? verticalPill.width : 0) : (hasHorizontalPill ? horizontalPill.width : 0) + height: isVertical ? (hasVerticalPill ? verticalPill.height : 0) : (hasHorizontalPill ? horizontalPill.height : 0) + + BasePill { + id: horizontalPill + visible: !isVertical && hasHorizontalPill + opacity: root.effectiveVisible ? 1 : 0 + axis: root.axis + section: root.section + popoutTarget: hasPopout ? pluginPopout : null + parentScreen: root.parentScreen + widgetThickness: root.widgetThickness + barThickness: root.barThickness + barSpacing: root.barSpacing + barConfig: root.barConfig + blurBarWindow: root.blurBarWindow + content: root.horizontalBarPill + + states: State { + name: "hidden" + when: !root.effectiveVisible + PropertyChanges { + target: horizontalPill + width: 0 + } + } + + transitions: Transition { + NumberAnimation { + properties: "width,opacity" + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + + onClicked: { + if (pillClickAction) { + if (pillClickAction.length === 0) { + pillClickAction(); + } else { + const globalPos = mapToItem(null, 0, 0); + const currentScreen = parentScreen || Screen; + const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width); + pillClickAction(pos.x, pos.y, pos.width, section, currentScreen); + } + } else if (hasPopout) { + pluginPopout.toggle(); + } + } + onRightClicked: { + if (pillRightClickAction) { + if (pillRightClickAction.length === 0) { + pillRightClickAction(); + } else { + const globalPos = mapToItem(null, 0, 0); + const currentScreen = parentScreen || Screen; + const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width); + pillRightClickAction(pos.x, pos.y, pos.width, section, currentScreen); + } + } + } + } + + BasePill { + id: verticalPill + visible: isVertical && hasVerticalPill + opacity: root.effectiveVisible ? 1 : 0 + axis: root.axis + section: root.section + popoutTarget: hasPopout ? pluginPopout : null + parentScreen: root.parentScreen + widgetThickness: root.widgetThickness + barThickness: root.barThickness + barSpacing: root.barSpacing + barConfig: root.barConfig + blurBarWindow: root.blurBarWindow + content: root.verticalBarPill + isVerticalOrientation: true + + states: State { + name: "hidden" + when: !root.effectiveVisible + PropertyChanges { + target: verticalPill + height: 0 + } + } + + transitions: Transition { + NumberAnimation { + properties: "height,opacity" + duration: Theme.shortDuration + easing.type: Theme.standardEasing + } + } + + onClicked: { + if (pillClickAction) { + if (pillClickAction.length === 0) { + pillClickAction(); + } else { + const globalPos = mapToItem(null, 0, 0); + const currentScreen = parentScreen || Screen; + const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width); + pillClickAction(pos.x, pos.y, pos.width, section, currentScreen); + } + } else if (hasPopout) { + pluginPopout.toggle(); + } + } + onRightClicked: { + if (pillRightClickAction) { + if (pillRightClickAction.length === 0) { + pillRightClickAction(); + } else { + const globalPos = mapToItem(null, 0, 0); + const currentScreen = parentScreen || Screen; + const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, width); + pillRightClickAction(pos.x, pos.y, pos.width, section, currentScreen); + } + } + } + } + + function closePopout() { + if (pluginPopout) { + pluginPopout.close(); + } + } + + function triggerPopout() { + if (pillClickAction) { + if (pillClickAction.length === 0) { + pillClickAction(); + return; + } + const pill = isVertical ? verticalPill : horizontalPill; + const globalPos = pill.mapToItem(null, 0, 0); + const currentScreen = parentScreen || Screen; + const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, pill.width); + pillClickAction(pos.x, pos.y, pos.width, section, currentScreen); + return; + } + if (!hasPopout) + return; + + const pill = isVertical ? verticalPill : horizontalPill; + const globalPos = pill.visualContent.mapToItem(null, 0, 0); + const currentScreen = parentScreen || Screen; + const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1)); + const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, pill.visualWidth, barSpacing, barPosition, barConfig); + + pluginPopout.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen, barPosition, barThickness, barSpacing, barConfig); + pluginPopout.toggle(); + } + + PluginPopout { + id: pluginPopout + contentWidth: root.popoutWidth + contentHeight: root.popoutHeight + pluginContent: root.popoutContent + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginControlCenterWrapper.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginControlCenterWrapper.qml new file mode 100644 index 0000000..5d40826 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginControlCenterWrapper.qml @@ -0,0 +1,51 @@ +import QtQuick +import qs.Common +import qs.Services +import qs.Widgets + +Item { + id: root + + property string pluginId: "" + property var pluginInstance: null + property bool isCompoundPill: false + property bool isSmallToggle: false + + readonly property bool hasDetail: pluginInstance?.ccDetailContent !== null + readonly property string iconName: pluginInstance?.ccWidgetIcon || "extension" + readonly property string primaryText: pluginInstance?.ccWidgetPrimaryText || "Plugin" + readonly property string secondaryText: pluginInstance?.ccWidgetSecondaryText || "" + readonly property bool isActive: pluginInstance?.ccWidgetIsActive || false + readonly property Component detailContent: pluginInstance?.ccDetailContent || null + readonly property real detailHeight: pluginInstance?.ccDetailHeight || 250 + + signal toggled() + signal expanded() + + Component.onCompleted: { + if (pluginInstance) { + pluginInstance.ccWidgetToggled.connect(handleToggled) + pluginInstance.ccWidgetExpanded.connect(handleExpanded) + } + } + + function handleToggled() { + toggled() + } + + function handleExpanded() { + expanded() + } + + function invokeToggle() { + if (pluginInstance) { + pluginInstance.ccWidgetToggled() + } + } + + function invokeExpand() { + if (pluginInstance) { + pluginInstance.ccWidgetExpanded() + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginPopout.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginPopout.qml new file mode 100644 index 0000000..acd24b9 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginPopout.qml @@ -0,0 +1,83 @@ +import QtQuick +import qs.Common +import qs.Widgets + +DankPopout { + id: root + + layerNamespace: "dms:plugins:" + layerNamespacePlugin + + property var triggerScreen: null + property Component pluginContent: null + property real contentWidth: 400 + property real contentHeight: 0 + + popupWidth: contentWidth + popupHeight: contentHeight + screen: triggerScreen + shouldBeVisible: false + + onBackgroundClicked: close() + + content: Component { + Rectangle { + id: popoutContainer + + implicitHeight: popoutColumn.implicitHeight + Theme.spacingL * 2 + color: "transparent" + focus: true + + Component.onCompleted: { + if (root.shouldBeVisible) { + forceActiveFocus(); + } + } + + Keys.onPressed: event => { + if (event.key === Qt.Key_Escape) { + root.close(); + event.accepted = true; + } + } + + Connections { + target: root + function onShouldBeVisibleChanged() { + if (root.shouldBeVisible) { + Qt.callLater(() => { + popoutContainer.forceActiveFocus(); + }); + } + } + } + + Column { + id: popoutColumn + width: parent.width - Theme.spacingS * 2 + x: Theme.spacingS + y: Theme.spacingS + spacing: Theme.spacingS + + Loader { + id: popoutContentLoader + width: parent.width + sourceComponent: root.pluginContent + + onLoaded: { + if (item && "closePopout" in item) { + item.closePopout = function () { + root.close(); + }; + } + if (item && "parentPopout" in item) { + item.parentPopout = root; + } + if (item) { + root.contentHeight = Qt.binding(() => item.implicitHeight + Theme.spacingS * 2); + } + } + } + } + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginSettings.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginSettings.qml new file mode 100644 index 0000000..2ba5d05 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PluginSettings.qml @@ -0,0 +1,311 @@ +import QtQuick +import Quickshell +import qs.Common +import qs.Widgets + +Item { + id: root + + required property string pluginId + property var pluginService: null + default property list content + + signal settingChanged + + property var variants: [] + property alias variantsModel: variantsListModel + + implicitHeight: hasPermission ? settingsColumn.implicitHeight : errorText.implicitHeight + height: implicitHeight + + readonly property bool isDesktopPlugin: { + if (!pluginService?.availablePlugins || !pluginId) + return false; + const plugin = pluginService.availablePlugins[pluginId]; + return plugin?.type === "desktop"; + } + + readonly property bool hasPermission: { + if (!pluginService?.availablePlugins || !pluginId) + return true; + const plugin = pluginService.availablePlugins[pluginId]; + if (!plugin) + return true; + const permissions = Array.isArray(plugin.permissions) ? plugin.permissions : []; + return permissions.indexOf("settings_write") !== -1; + } + + Component.onCompleted: { + loadVariants(); + } + + onPluginServiceChanged: { + if (pluginService) { + loadVariants(); + for (let i = 0; i < content.length; i++) { + const child = content[i]; + if (child.loadValue) { + child.loadValue(); + } + } + } + } + + onContentChanged: { + for (let i = 0; i < content.length; i++) { + const item = content[i]; + if (item instanceof Item) { + item.parent = settingsColumn; + } + } + } + + Connections { + target: pluginService + enabled: pluginService !== null + + function onPluginDataChanged(changedPluginId) { + if (changedPluginId === pluginId) { + loadVariants(); + reloadChildValues(); + } + } + } + + function reloadChildValues() { + for (let i = 0; i < content.length; i++) { + const child = content[i]; + if (child.loadValue) { + child.loadValue(); + } + } + } + + function loadVariants() { + if (!pluginService?.getPluginVariants || !pluginId) { + variants = []; + return; + } + variants = pluginService.getPluginVariants(pluginId); + syncVariantsToModel(); + } + + function syncVariantsToModel() { + variantsListModel.clear(); + for (let i = 0; i < variants.length; i++) { + variantsListModel.append(variants[i]); + } + } + + onVariantsChanged: { + syncVariantsToModel(); + } + + ListModel { + id: variantsListModel + } + + function createVariant(variantName, variantConfig) { + if (!pluginService?.createPluginVariant || !pluginId) { + return null; + } + return pluginService.createPluginVariant(pluginId, variantName, variantConfig); + } + + function removeVariant(variantId) { + if (!pluginService?.removePluginVariant || !pluginId) { + return; + } + pluginService.removePluginVariant(pluginId, variantId); + } + + function updateVariant(variantId, variantConfig) { + if (!pluginService?.updatePluginVariant || !pluginId) { + return; + } + pluginService.updatePluginVariant(pluginId, variantId, variantConfig); + } + + function saveValue(key, value) { + if (!pluginService) { + return; + } + if (!hasPermission) { + console.warn("PluginSettings: Plugin", pluginId, "does not have settings_write permission"); + return; + } + if (pluginService.savePluginData) { + pluginService.savePluginData(pluginId, key, value); + settingChanged(); + } + } + + function loadValue(key, defaultValue) { + if (pluginService && pluginService.loadPluginData) { + return pluginService.loadPluginData(pluginId, key, defaultValue); + } + return defaultValue; + } + + function saveState(key, value) { + if (!pluginService) + return; + if (pluginService.savePluginState) + pluginService.savePluginState(pluginId, key, value); + } + + function loadState(key, defaultValue) { + if (pluginService && pluginService.loadPluginState) + return pluginService.loadPluginState(pluginId, key, defaultValue); + return defaultValue; + } + + function clearState() { + if (pluginService && pluginService.clearPluginState) + pluginService.clearPluginState(pluginId); + } + + function findFlickable(item) { + var current = item?.parent; + while (current) { + if (current.contentY !== undefined && current.contentHeight !== undefined) { + return current; + } + current = current.parent; + } + return null; + } + + function ensureItemVisible(item) { + if (!item) + return; + var flickable = findFlickable(root); + if (!flickable) + return; + var itemGlobalY = item.mapToItem(null, 0, 0).y; + var itemHeight = item.height; + var flickableGlobalY = flickable.mapToItem(null, 0, 0).y; + var viewportHeight = flickable.height; + + var itemRelativeY = itemGlobalY - flickableGlobalY; + var viewportTop = 0; + var viewportBottom = viewportHeight; + + if (itemRelativeY < viewportTop) { + flickable.contentY = Math.max(0, flickable.contentY - (viewportTop - itemRelativeY) - Theme.spacingL); + } else if (itemRelativeY + itemHeight > viewportBottom) { + flickable.contentY = Math.min(flickable.contentHeight - viewportHeight, flickable.contentY + (itemRelativeY + itemHeight - viewportBottom) + Theme.spacingL); + } + } + + StyledText { + id: errorText + visible: pluginService && !root.hasPermission + anchors.fill: parent + text: I18n.tr("This plugin does not have 'settings_write' permission.\n\nAdd \"permissions\": [\"settings_read\", \"settings_write\"] to plugin.json") + color: Theme.error + font.pixelSize: Theme.fontSizeMedium + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + + Column { + id: settingsColumn + visible: root.hasPermission + width: parent.width + spacing: Theme.spacingM + + Item { + id: desktopDisplaySettings + visible: root.isDesktopPlugin + width: parent.width + height: visible ? displaySettingsColumn.implicitHeight : 0 + + Column { + id: displaySettingsColumn + width: parent.width + spacing: Theme.spacingS + + Rectangle { + width: parent.width + height: 1 + color: Theme.outline + opacity: 0.3 + visible: root.content.length > 0 + } + + StyledText { + text: I18n.tr("Display Settings") + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: I18n.tr("Choose which displays show this widget") + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + } + + DankToggle { + width: parent.width + text: I18n.tr("All displays") + checked: { + const prefs = root.loadValue("displayPreferences", ["all"]); + return Array.isArray(prefs) && (prefs.includes("all") || prefs.length === 0); + } + onToggled: isChecked => { + if (isChecked) { + root.saveValue("displayPreferences", ["all"]); + } else { + root.saveValue("displayPreferences", []); + } + } + } + + Column { + width: parent.width + spacing: Theme.spacingXS + visible: { + const prefs = root.loadValue("displayPreferences", ["all"]); + return !Array.isArray(prefs) || (!prefs.includes("all") && prefs.length >= 0); + } + + Repeater { + model: Quickshell.screens + + DankToggle { + required property var modelData + width: parent.width + text: SettingsData.getScreenDisplayName(modelData) + description: modelData.width + "×" + modelData.height + checked: { + const prefs = root.loadValue("displayPreferences", ["all"]); + if (!Array.isArray(prefs) || prefs.includes("all")) + return false; + return prefs.some(p => p.name === modelData.name); + } + onToggled: isChecked => { + var prefs = root.loadValue("displayPreferences", ["all"]); + if (!Array.isArray(prefs) || prefs.includes("all")) { + prefs = []; + } + prefs = prefs.filter(p => p.name !== modelData.name); + if (isChecked) { + prefs.push({ + name: modelData.name, + model: modelData.model || "" + }); + } + root.saveValue("displayPreferences", prefs); + } + } + } + } + } + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PopoutComponent.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PopoutComponent.qml new file mode 100644 index 0000000..7ccf637 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/PopoutComponent.qml @@ -0,0 +1,87 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Column { + id: root + + property string headerText: "" + property string detailsText: "" + property bool showCloseButton: false + property var closePopout: null + property var parentPopout: null + property alias headerActions: headerActionsLoader.sourceComponent + + readonly property int headerHeight: popoutHeader.visible ? popoutHeader.height : 0 + readonly property int detailsHeight: popoutDetails.visible ? popoutDetails.implicitHeight : 0 + + spacing: 0 + + Item { + id: popoutHeader + width: parent.width + height: 40 + visible: headerText.length > 0 + + StyledText { + anchors.left: parent.left + anchors.leftMargin: Theme.spacingS + anchors.verticalCenter: parent.verticalCenter + text: root.headerText + font.pixelSize: Theme.fontSizeLarge + 4 + font.weight: Font.Bold + color: Theme.surfaceText + } + + Row { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: Theme.spacingXS + + Loader { + id: headerActionsLoader + anchors.verticalCenter: parent.verticalCenter + } + + Rectangle { + id: closeButton + width: 32 + height: 32 + radius: 16 + color: closeArea.containsMouse ? Theme.errorHover : "transparent" + visible: root.showCloseButton + + DankIcon { + anchors.centerIn: parent + name: "close" + size: Theme.iconSize - 4 + color: closeArea.containsMouse ? Theme.error : Theme.surfaceText + } + + MouseArea { + id: closeArea + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onPressed: { + if (root.closePopout) { + root.closePopout(); + } + } + } + } + } + } + + StyledText { + id: popoutDetails + width: parent.width + leftPadding: Theme.spacingS + bottomPadding: Theme.spacingS + text: root.detailsText + font.pixelSize: Theme.fontSizeMedium + color: Theme.surfaceVariantText + visible: detailsText.length > 0 + wrapMode: Text.WordWrap + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SelectionSetting.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SelectionSetting.qml new file mode 100644 index 0000000..0f8fac0 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SelectionSetting.qml @@ -0,0 +1,91 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Column { + id: root + + required property string settingKey + required property string label + property string description: "" + required property var options + property string defaultValue: "" + property string value: defaultValue + + width: parent.width + spacing: Theme.spacingS + + function loadValue() { + const settings = findSettings() + if (settings && settings.pluginService) { + value = settings.loadValue(settingKey, defaultValue) + } + } + + Component.onCompleted: { + loadValue() + } + + readonly property var optionLabels: { + const labels = [] + for (let i = 0; i < options.length; i++) { + labels.push(options[i].label || options[i]) + } + return labels + } + + readonly property var valueToLabel: { + const map = {} + for (let i = 0; i < options.length; i++) { + const opt = options[i] + if (typeof opt === 'object') { + map[opt.value] = opt.label + } else { + map[opt] = opt + } + } + return map + } + + readonly property var labelToValue: { + const map = {} + for (let i = 0; i < options.length; i++) { + const opt = options[i] + if (typeof opt === 'object') { + map[opt.label] = opt.value + } else { + map[opt] = opt + } + } + return map + } + + onValueChanged: { + const settings = findSettings() + if (settings) { + settings.saveValue(settingKey, value) + } + } + + function findSettings() { + let item = parent + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) { + return item + } + item = item.parent + } + return null + } + + DankDropdown { + width: parent.width + text: root.label + description: root.description + currentValue: root.valueToLabel[root.value] || root.value + options: root.optionLabels + onValueChanged: newValue => { + root.value = root.labelToValue[newValue] || newValue + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SliderSetting.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SliderSetting.qml new file mode 100644 index 0000000..f540fd4 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/SliderSetting.qml @@ -0,0 +1,81 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Column { + id: root + + required property string settingKey + required property string label + property string description: "" + property int defaultValue: 0 + property int value: defaultValue + property int minimum: 0 + property int maximum: 100 + property string leftIcon: "" + property string rightIcon: "" + property string unit: "" + + width: parent.width + spacing: Theme.spacingS + + function loadValue() { + const settings = findSettings() + if (settings && settings.pluginService) { + value = settings.loadValue(settingKey, defaultValue) + } + } + + Component.onCompleted: { + loadValue() + } + + onValueChanged: { + const settings = findSettings() + if (settings) { + settings.saveValue(settingKey, value) + } + } + + function findSettings() { + let item = parent + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) { + return item + } + item = item.parent + } + return null + } + + StyledText { + text: root.label + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: root.description + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + visible: root.description !== "" + } + + DankSlider { + width: parent.width + value: root.value + minimum: root.minimum + maximum: root.maximum + leftIcon: root.leftIcon + rightIcon: root.rightIcon + unit: root.unit + wheelEnabled: false + thumbOutlineColor: Theme.withAlpha(Theme.surfaceContainerHighest, Theme.popupTransparency) + onSliderValueChanged: newValue => { + root.value = newValue + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/StringSetting.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/StringSetting.qml new file mode 100644 index 0000000..849807b --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/StringSetting.qml @@ -0,0 +1,84 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Column { + id: root + + required property string settingKey + required property string label + property string description: "" + property string placeholder: "" + property string defaultValue: "" + property string value: defaultValue + + width: parent.width + spacing: Theme.spacingS + + property bool isInitialized: false + + function loadValue() { + const settings = findSettings(); + if (settings && settings.pluginService) { + const loadedValue = settings.loadValue(settingKey, defaultValue); + if (textField.activeFocus && isInitialized) + return; + value = loadedValue; + textField.text = loadedValue; + isInitialized = true; + } + } + + Component.onCompleted: { + Qt.callLater(loadValue); + } + + function commit() { + if (!isInitialized) + return; + if (textField.text === value) + return; + value = textField.text; + const settings = findSettings(); + if (settings) + settings.saveValue(settingKey, value); + } + + function findSettings() { + let item = parent; + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) { + return item; + } + item = item.parent; + } + return null; + } + + StyledText { + text: root.label + font.pixelSize: Theme.fontSizeMedium + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: root.description + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + visible: root.description !== "" + } + + DankTextField { + id: textField + width: parent.width + placeholderText: root.placeholder + onEditingFinished: root.commit() + onActiveFocusChanged: { + if (!activeFocus) + root.commit(); + } + } +} diff --git a/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ToggleSetting.qml b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ToggleSetting.qml new file mode 100644 index 0000000..3a6eae5 --- /dev/null +++ b/raveos-theme/hyprland/theme-data/DankMaterialShell/quickshell/Modules/Plugins/ToggleSetting.qml @@ -0,0 +1,81 @@ +import QtQuick +import qs.Common +import qs.Widgets + +Row { + id: root + + required property string settingKey + required property string label + property string description: "" + property bool defaultValue: false + property bool value: defaultValue + + width: parent.width + spacing: Theme.spacingM + + property bool isInitialized: false + + function loadValue() { + const settings = findSettings() + if (settings && settings.pluginService) { + const loadedValue = settings.loadValue(settingKey, defaultValue) + value = loadedValue + isInitialized = true + } + } + + Component.onCompleted: { + Qt.callLater(loadValue) + } + + onValueChanged: { + if (!isInitialized) return + const settings = findSettings() + if (settings) { + settings.saveValue(settingKey, value) + } + } + + function findSettings() { + let item = parent + while (item) { + if (item.saveValue !== undefined && item.loadValue !== undefined) { + return item + } + item = item.parent + } + return null + } + + Column { + width: parent.width - toggle.width - Theme.spacingM + spacing: Theme.spacingXS + anchors.verticalCenter: parent.verticalCenter + + StyledText { + text: root.label + font.pixelSize: Theme.fontSizeLarge + font.weight: Font.Medium + color: Theme.surfaceText + } + + StyledText { + text: root.description + font.pixelSize: Theme.fontSizeSmall + color: Theme.surfaceVariantText + width: parent.width + wrapMode: Text.WordWrap + visible: root.description !== "" + } + } + + DankToggle { + id: toggle + anchors.verticalCenter: parent.verticalCenter + checked: root.value + onToggled: isChecked => { + root.value = isChecked + } + } +} -- cgit v1.3