From 6b0572975fc99f736d90c686e53d1b5149a4ec98 Mon Sep 17 00:00:00 2001 From: Pico Date: Sat, 8 Nov 2025 19:06:41 +0300 Subject: [PATCH] make dropdown options like a overlay --- .../modules/common/widgets/DropdownButton.qml | 264 ++++++++++++++++++ .../common/widgets/LanguageDropdownButton.qml | 179 ------------ .../ii/modules/settings/GeneralConfig.qml | 3 +- 3 files changed, 266 insertions(+), 180 deletions(-) create mode 100644 dots/.config/quickshell/ii/modules/common/widgets/DropdownButton.qml delete mode 100644 dots/.config/quickshell/ii/modules/common/widgets/LanguageDropdownButton.qml diff --git a/dots/.config/quickshell/ii/modules/common/widgets/DropdownButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/DropdownButton.qml new file mode 100644 index 000000000..8443a1fa0 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/DropdownButton.qml @@ -0,0 +1,264 @@ +import QtQuick +import QtQuick.Layouts +import Quickshell +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.common.functions + +Item { + id: root + implicitWidth: dropdownButton.implicitWidth + implicitHeight: dropdownButton.implicitHeight + + property list options: [] + property var currentValue: null + + property string buttonText: "" + property string buttonIcon: "" + property string placeholder: "Select..." + property Component buttonContent: null + + property Component itemDelegate: null + property real maxPopupHeight: 300 + property real popupWidth: dropdownButton.width + + property real buttonRadius: dropdownButton.height / 2 + property color buttonBackground: Appearance.colors.colSecondaryContainer + property color buttonBackgroundHover: Appearance.colors.colSecondaryContainerHover + property color buttonBackgroundActive: Appearance.colors.colSecondaryContainerActive + + property bool popupVisible: false + + signal selected(var newValue) + + function findWindowRoot() { + var p = root.parent; + while (p && p.parent) { + p = p.parent; + } + return p || root.parent; + } + + function getGlobalPosition() { + var windowRoot = findWindowRoot(); + if (windowRoot) { + return root.mapToItem(windowRoot, 0, 0); + } + return { + x: 0, + y: 0 + }; + } + + function getCurrentDisplayName() { + for (let i = 0; i < options.length; i++) { + if (options[i].value === currentValue) { + return options[i].displayName; + } + } + return placeholder; + } + + GroupButton { + id: dropdownButton + anchors.fill: parent + buttonRadius: root.buttonRadius + buttonRadiusPressed: root.buttonRadius + leftRadius: root.buttonRadius + rightRadius: root.buttonRadius + horizontalPadding: 16 + verticalPadding: 10 + colBackground: root.buttonBackground + colBackgroundHover: root.buttonBackgroundHover + colBackgroundActive: root.buttonBackgroundActive + + contentItem: Loader { + sourceComponent: root.buttonContent || defaultButtonContent + } + + onClicked: { + root.popupVisible = !root.popupVisible; + } + } + + Component { + id: defaultButtonContent + + RowLayout { + spacing: 8 + + Loader { + Layout.alignment: Qt.AlignVCenter + active: root.buttonIcon.length > 0 + visible: active + sourceComponent: MaterialSymbol { + text: root.buttonIcon + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnSecondaryContainer + } + } + + StyledText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + color: Appearance.colors.colOnSecondaryContainer + text: root.buttonText || root.getCurrentDisplayName() + } + + MaterialSymbol { + Layout.alignment: Qt.AlignVCenter + text: root.popupVisible ? "arrow_drop_up" : "arrow_drop_down" + iconSize: Appearance.font.pixelSize.larger + color: Appearance.colors.colOnSecondaryContainer + } + } + } + + Rectangle { + id: scrim + visible: root.popupVisible + parent: root.findWindowRoot() + anchors.fill: parent + color: "transparent" + z: 999 + + MouseArea { + anchors.fill: parent + onClicked: { + root.popupVisible = false; + } + } + } + + Rectangle { + id: popupContent + visible: root.popupVisible + z: 1000 + + parent: root.findWindowRoot() + + width: root.popupWidth + height: Math.min(optionColumn.implicitHeight + 16, root.maxPopupHeight) + + radius: Appearance.rounding.normal + color: Appearance.colors.colSurfaceContainerHigh + border.width: 1 + border.color: Appearance.colors.colOutline + + Flickable { + id: scrollView + anchors.fill: parent + anchors.margins: 8 + contentHeight: optionColumn.implicitHeight + clip: true + + ColumnLayout { + id: optionColumn + width: scrollView.width + spacing: 2 + + Repeater { + model: root.options + + delegate: Loader { + id: itemLoader + required property var modelData + required property int index + Layout.fillWidth: true + + sourceComponent: root.itemDelegate || defaultItemDelegate + + onLoaded: { + if (item) { + item.modelData = Qt.binding(() => modelData); + item.index = Qt.binding(() => index); + } + } + } + } + } + } + + opacity: visible ? 1 : 0 + scale: visible ? 1 : 0.95 + transformOrigin: Item.Top + + Behavior on opacity { + NumberAnimation { + duration: 150 + easing.type: Easing.OutCubic + } + } + + Behavior on scale { + NumberAnimation { + duration: 150 + easing.type: Easing.OutCubic + } + } + + Behavior on height { + NumberAnimation { + duration: 150 + easing.type: Easing.OutCubic + } + } + } + + Component { + id: defaultItemDelegate + + GroupButton { + id: optionButton + property var modelData + property int index + + buttonText: modelData?.displayName || "" + toggled: modelData?.value === root.currentValue + buttonRadius: Appearance.rounding.small + buttonRadiusPressed: Appearance.rounding.small + leftRadius: Appearance.rounding.small + rightRadius: Appearance.rounding.small + horizontalPadding: 12 + verticalPadding: 8 + colBackground: "transparent" + colBackgroundHover: Appearance.colors.colSecondaryContainerHover + colBackgroundActive: Appearance.colors.colSecondaryContainerActive + colBackgroundToggled: Appearance.colors.colPrimary + colBackgroundToggledHover: Appearance.colors.colPrimaryHover + colBackgroundToggledActive: Appearance.colors.colPrimaryActive + + contentItem: RowLayout { + spacing: 8 + + MaterialSymbol { + Layout.alignment: Qt.AlignVCenter + text: "check" + iconSize: Appearance.font.pixelSize.normal + color: optionButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer + opacity: optionButton.toggled ? 1 : 0 + + Behavior on opacity { + NumberAnimation { + duration: 150 + easing.type: Easing.OutCubic + } + } + } + + StyledText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + color: optionButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer + text: optionButton.buttonText + } + } + + onClicked: { + root.selected(modelData.value); + root.popupVisible = false; + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/LanguageDropdownButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/LanguageDropdownButton.qml deleted file mode 100644 index 98db62580..000000000 --- a/dots/.config/quickshell/ii/modules/common/widgets/LanguageDropdownButton.qml +++ /dev/null @@ -1,179 +0,0 @@ -import QtQuick -import QtQuick.Layouts -import qs.services -import qs.modules.common -import qs.modules.common.widgets -import qs.modules.common.functions - -ColumnLayout { - id: root - Layout.fillWidth: true - spacing: 4 - - property list options: [] - property var currentValue: null - - signal selected(var newValue) - - function getCurrentDisplayName() { - for (let i = 0; i < options.length; i++) { - if (options[i].value === currentValue) { - return options[i].displayName; - } - } - return "Select language"; - } - - GroupButton { - id: dropdownButton - Layout.fillWidth: true - buttonRadius: height / 2 - buttonRadiusPressed: height / 2 - leftRadius: height / 2 - rightRadius: height / 2 - horizontalPadding: 16 - verticalPadding: 10 - colBackground: Appearance.colors.colSecondaryContainer - colBackgroundHover: Appearance.colors.colSecondaryContainerHover - colBackgroundActive: Appearance.colors.colSecondaryContainerActive - - contentItem: RowLayout { - spacing: 8 - - MaterialSymbol { - Layout.alignment: Qt.AlignVCenter - text: "language" - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnSecondaryContainer - } - - StyledText { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - color: Appearance.colors.colOnSecondaryContainer - text: root.getCurrentDisplayName() - } - - MaterialSymbol { - Layout.alignment: Qt.AlignVCenter - text: dropdownPopup.visible ? "arrow_drop_up" : "arrow_drop_down" - iconSize: Appearance.font.pixelSize.larger - color: Appearance.colors.colOnSecondaryContainer - - Behavior on text { - enabled: Appearance.animation.elementMoveFast.numberAnimation !== undefined - } - } - } - - onClicked: { - dropdownPopup.visible = !dropdownPopup.visible; - } - } - - Item { - id: dropdownPopup - visible: false - Layout.fillWidth: true - implicitHeight: visible ? Math.min(optionColumn.implicitHeight + 16, 300) : 0 - clip: true - - Rectangle { - anchors.fill: parent - radius: Appearance.rounding.normal - color: Appearance.colors.colSurfaceContainerHigh - border.width: 1 - border.color: Appearance.colors.colOutline - - Flickable { - id: scrollView - anchors.fill: parent - anchors.margins: 8 - contentHeight: optionColumn.implicitHeight - clip: true - - ColumnLayout { - id: optionColumn - width: parent.width - spacing: 2 - - Repeater { - model: root.options - - delegate: GroupButton { - id: optionButton - required property var modelData - required property int index - Layout.fillWidth: true - - buttonText: modelData.displayName - toggled: modelData.value === root.currentValue - buttonRadius: Appearance.rounding.small - buttonRadiusPressed: Appearance.rounding.small - leftRadius: Appearance.rounding.small - rightRadius: Appearance.rounding.small - horizontalPadding: 12 - verticalPadding: 8 - colBackground: "transparent" - colBackgroundHover: Appearance.colors.colSecondaryContainerHover - colBackgroundActive: Appearance.colors.colSecondaryContainerActive - colBackgroundToggled: Appearance.colors.colPrimary - colBackgroundToggledHover: Appearance.colors.colPrimaryHover - colBackgroundToggledActive: Appearance.colors.colPrimaryActive - - contentItem: RowLayout { - spacing: 8 - - MaterialSymbol { - Layout.alignment: Qt.AlignVCenter - text: "check" - iconSize: Appearance.font.pixelSize.normal - color: optionButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer - opacity: optionButton.toggled ? 1 : 0 - - Behavior on opacity { - enabled: Appearance.animation.elementMoveFast.numberAnimation !== undefined - NumberAnimation { - duration: 200 - easing.type: Easing.OutCubic - } - } - } - - StyledText { - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - color: optionButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer - text: optionButton.buttonText - } - } - - onClicked: { - root.selected(modelData.value); - dropdownPopup.visible = false; - } - } - } - } - } - } - - Behavior on implicitHeight { - enabled: Appearance.animation.elementMoveFast.numberAnimation !== undefined - NumberAnimation { - duration: 200 - easing.type: Easing.OutCubic - } - } - - opacity: visible ? 1 : 0 - - Behavior on opacity { - enabled: Appearance.animation.elementMoveFast.numberAnimation !== undefined - NumberAnimation { - duration: 200 - easing.type: Easing.OutCubic - } - } - } -} diff --git a/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml b/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml index b4ea73a5a..e9430a14b 100644 --- a/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml @@ -137,8 +137,9 @@ ContentPage { title: Translation.tr("Interface Language") tooltip: Translation.tr("Select the language for the user interface.\n\"Auto\" will use your system's locale.") - LanguageDropdownButton { + DropdownButton { id: languageSelector + buttonIcon: "language" currentValue: Config.options.language.ui onSelected: newValue => { Config.options.language.ui = newValue;