summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/ConfirmModal.qml291
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/DankModal.qml496
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/InputModal.qml312
3 files changed, 1099 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/ConfirmModal.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/ConfirmModal.qml
new file mode 100644
index 0000000..b9c9b90
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/ConfirmModal.qml
@@ -0,0 +1,291 @@
+import QtQuick
+import qs.Common
+import qs.Modals.Common
+import qs.Widgets
+
+DankModal {
+ id: root
+
+ layerNamespace: "dms:confirm-modal"
+ keepPopoutsOpen: true
+
+ property string confirmTitle: ""
+ property string confirmMessage: ""
+ property string confirmButtonText: "Confirm"
+ property string cancelButtonText: "Cancel"
+ property color confirmButtonColor: Theme.primary
+ property var onConfirm: function () {}
+ property var onCancel: function () {}
+ property int selectedButton: -1
+ property bool keyboardNavigation: false
+
+ function show(title, message, onConfirmCallback, onCancelCallback) {
+ confirmTitle = title || "";
+ confirmMessage = message || "";
+ confirmButtonText = "Confirm";
+ cancelButtonText = "Cancel";
+ confirmButtonColor = Theme.primary;
+ onConfirm = onConfirmCallback || (() => {});
+ onCancel = onCancelCallback || (() => {});
+ selectedButton = -1;
+ keyboardNavigation = false;
+ open();
+ }
+
+ function showWithOptions(options) {
+ confirmTitle = options.title || "";
+ confirmMessage = options.message || "";
+ confirmButtonText = options.confirmText || "Confirm";
+ cancelButtonText = options.cancelText || "Cancel";
+ confirmButtonColor = options.confirmColor || Theme.primary;
+ onConfirm = options.onConfirm || (() => {});
+ onCancel = options.onCancel || (() => {});
+ selectedButton = -1;
+ keyboardNavigation = false;
+ open();
+ }
+
+ function selectButton() {
+ close();
+ if (selectedButton === 0) {
+ if (onCancel) {
+ onCancel();
+ }
+ } else {
+ if (onConfirm) {
+ onConfirm();
+ }
+ }
+ }
+
+ shouldBeVisible: false
+ allowStacking: true
+ modalWidth: 350
+ modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 160
+ enableShadow: true
+ shouldHaveFocus: true
+ onBackgroundClicked: {
+ close();
+ if (onCancel) {
+ onCancel();
+ }
+ }
+ onOpened: {
+ Qt.callLater(function () {
+ modalFocusScope.forceActiveFocus();
+ modalFocusScope.focus = true;
+ shouldHaveFocus = true;
+ });
+ }
+ modalFocusScope.Keys.onPressed: function (event) {
+ switch (event.key) {
+ case Qt.Key_Escape:
+ close();
+ if (onCancel) {
+ onCancel();
+ }
+ event.accepted = true;
+ break;
+ case Qt.Key_Left:
+ case Qt.Key_Up:
+ keyboardNavigation = true;
+ selectedButton = 0;
+ event.accepted = true;
+ break;
+ case Qt.Key_Right:
+ case Qt.Key_Down:
+ keyboardNavigation = true;
+ selectedButton = 1;
+ event.accepted = true;
+ break;
+ case Qt.Key_N:
+ if (event.modifiers & Qt.ControlModifier) {
+ keyboardNavigation = true;
+ selectedButton = (selectedButton + 1) % 2;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_P:
+ if (event.modifiers & Qt.ControlModifier) {
+ keyboardNavigation = true;
+ selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_J:
+ if (event.modifiers & Qt.ControlModifier) {
+ keyboardNavigation = true;
+ selectedButton = 1;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_K:
+ if (event.modifiers & Qt.ControlModifier) {
+ keyboardNavigation = true;
+ selectedButton = 0;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_H:
+ if (event.modifiers & Qt.ControlModifier) {
+ keyboardNavigation = true;
+ selectedButton = 0;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_L:
+ if (event.modifiers & Qt.ControlModifier) {
+ keyboardNavigation = true;
+ selectedButton = 1;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_Tab:
+ keyboardNavigation = true;
+ selectedButton = selectedButton === -1 ? 0 : (selectedButton + 1) % 2;
+ event.accepted = true;
+ break;
+ case Qt.Key_Return:
+ case Qt.Key_Enter:
+ if (selectedButton !== -1) {
+ selectButton();
+ } else {
+ selectedButton = 1;
+ selectButton();
+ }
+ event.accepted = true;
+ break;
+ }
+ }
+
+ content: Component {
+ Item {
+ anchors.fill: parent
+ implicitHeight: mainColumn.implicitHeight
+
+ Column {
+ id: mainColumn
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.leftMargin: Theme.spacingL
+ anchors.rightMargin: Theme.spacingL
+ anchors.topMargin: Theme.spacingL
+ spacing: 0
+
+ StyledText {
+ text: confirmTitle
+ font.pixelSize: Theme.fontSizeLarge
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Item {
+ width: 1
+ height: Theme.spacingL
+ }
+
+ StyledText {
+ text: confirmMessage
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ }
+
+ Item {
+ width: 1
+ height: Theme.spacingL * 1.5
+ }
+
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingM
+
+ Rectangle {
+ width: 120
+ height: 40
+ radius: Theme.cornerRadius
+ color: {
+ if (keyboardNavigation && selectedButton === 0) {
+ return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
+ } else if (cancelButton.containsMouse) {
+ return Theme.surfacePressed;
+ } else {
+ return Theme.surfaceVariantAlpha;
+ }
+ }
+ border.color: (keyboardNavigation && selectedButton === 0) ? Theme.primary : "transparent"
+ border.width: (keyboardNavigation && selectedButton === 0) ? 1 : 0
+
+ StyledText {
+ text: cancelButtonText
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ anchors.centerIn: parent
+ }
+
+ MouseArea {
+ id: cancelButton
+
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ selectedButton = 0;
+ selectButton();
+ }
+ }
+ }
+
+ Rectangle {
+ width: 120
+ height: 40
+ radius: Theme.cornerRadius
+ color: {
+ const baseColor = confirmButtonColor;
+ if (keyboardNavigation && selectedButton === 1) {
+ return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1);
+ } else if (confirmButton.containsMouse) {
+ return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9);
+ } else {
+ return baseColor;
+ }
+ }
+ border.color: (keyboardNavigation && selectedButton === 1) ? "white" : "transparent"
+ border.width: (keyboardNavigation && selectedButton === 1) ? 1 : 0
+
+ StyledText {
+ text: confirmButtonText
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.primaryText
+ font.weight: Font.Medium
+ anchors.centerIn: parent
+ }
+
+ MouseArea {
+ id: confirmButton
+
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: {
+ selectedButton = 1;
+ selectButton();
+ }
+ }
+ }
+ }
+
+ Item {
+ width: 1
+ height: Theme.spacingL
+ }
+ }
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/DankModal.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/DankModal.qml
new file mode 100644
index 0000000..cafa133
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/DankModal.qml
@@ -0,0 +1,496 @@
+import QtQuick
+import Quickshell
+import Quickshell.Wayland
+import qs.Common
+import qs.Services
+import qs.Widgets
+
+Item {
+ id: root
+
+ property string layerNamespace: "dms:modal"
+ property alias content: contentLoader.sourceComponent
+ property alias contentLoader: contentLoader
+ property Item directContent: null
+ property real modalWidth: 400
+ property real modalHeight: 300
+ property var targetScreen
+ readonly property var effectiveScreen: contentWindow.screen ?? targetScreen
+ readonly property real screenWidth: effectiveScreen?.width ?? 1920
+ readonly property real screenHeight: effectiveScreen?.height ?? 1080
+ readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1
+ property bool showBackground: true
+ property real backgroundOpacity: 0.5
+ property string positioning: "center"
+ property point customPosition: Qt.point(0, 0)
+ property bool closeOnEscapeKey: true
+ property bool closeOnBackgroundClick: true
+ property string animationType: "scale"
+ property int animationDuration: Theme.modalAnimationDuration
+ property real animationScaleCollapsed: 0.96
+ property real animationOffset: Theme.spacingL
+ property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
+ property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
+ property color backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
+ property color borderColor: Theme.outlineMedium
+ property real borderWidth: 0
+ property real cornerRadius: Theme.cornerRadius
+ property bool enableShadow: true
+ property alias modalFocusScope: focusScope
+ property bool shouldBeVisible: false
+ property bool shouldHaveFocus: shouldBeVisible
+ property bool allowFocusOverride: false
+ property bool allowStacking: false
+ property bool keepContentLoaded: false
+ property bool keepPopoutsOpen: false
+ property var customKeyboardFocus: null
+ property bool useOverlayLayer: false
+ readonly property alias contentWindow: contentWindow
+ readonly property alias clickCatcher: clickCatcher
+ readonly property bool useHyprlandFocusGrab: CompositorService.useHyprlandFocusGrab
+ readonly property bool useBackground: showBackground && SettingsData.modalDarkenBackground
+ readonly property bool useSingleWindow: CompositorService.isHyprland || useBackground
+
+ signal opened
+ signal dialogClosed
+ signal backgroundClicked
+
+ property bool animationsEnabled: true
+
+ function open() {
+ closeTimer.stop();
+ const focusedScreen = CompositorService.getFocusedScreen();
+ const screenChanged = focusedScreen && contentWindow.screen !== focusedScreen;
+ if (focusedScreen) {
+ if (screenChanged)
+ contentWindow.visible = false;
+ contentWindow.screen = focusedScreen;
+ if (!useSingleWindow) {
+ if (screenChanged)
+ clickCatcher.visible = false;
+ clickCatcher.screen = focusedScreen;
+ }
+ }
+ if (screenChanged) {
+ Qt.callLater(() => root._finishOpen());
+ } else {
+ _finishOpen();
+ }
+ }
+
+ function _finishOpen() {
+ ModalManager.openModal(root);
+ shouldBeVisible = true;
+ if (!useSingleWindow)
+ clickCatcher.visible = true;
+ contentWindow.visible = true;
+ shouldHaveFocus = false;
+ Qt.callLater(() => shouldHaveFocus = Qt.binding(() => shouldBeVisible));
+ }
+
+ function close() {
+ shouldBeVisible = false;
+ shouldHaveFocus = false;
+ ModalManager.closeModal(root);
+ closeTimer.restart();
+ }
+
+ function instantClose() {
+ animationsEnabled = false;
+ shouldBeVisible = false;
+ shouldHaveFocus = false;
+ ModalManager.closeModal(root);
+ closeTimer.stop();
+ contentWindow.visible = false;
+ if (!useSingleWindow)
+ clickCatcher.visible = false;
+ dialogClosed();
+ Qt.callLater(() => animationsEnabled = true);
+ }
+
+ function toggle() {
+ shouldBeVisible ? close() : open();
+ }
+
+ Connections {
+ target: ModalManager
+ function onCloseAllModalsExcept(excludedModal) {
+ if (excludedModal !== root && !allowStacking && shouldBeVisible)
+ close();
+ }
+ }
+
+ Connections {
+ target: Quickshell
+ function onScreensChanged() {
+ if (!contentWindow.screen)
+ return;
+ const currentScreenName = contentWindow.screen.name;
+ let screenStillExists = false;
+ for (let i = 0; i < Quickshell.screens.length; i++) {
+ if (Quickshell.screens[i].name === currentScreenName) {
+ screenStillExists = true;
+ break;
+ }
+ }
+ if (screenStillExists)
+ return;
+ const newScreen = CompositorService.getFocusedScreen();
+ if (newScreen) {
+ contentWindow.screen = newScreen;
+ if (!useSingleWindow)
+ clickCatcher.screen = newScreen;
+ }
+ }
+ }
+
+ Timer {
+ id: closeTimer
+ interval: animationDuration + 50
+ onTriggered: {
+ if (shouldBeVisible)
+ return;
+ contentWindow.visible = false;
+ if (!useSingleWindow)
+ clickCatcher.visible = false;
+ dialogClosed();
+ }
+ }
+
+ readonly property var shadowLevel: Theme.elevationLevel3
+ readonly property real shadowFallbackOffset: 6
+ readonly property real shadowRenderPadding: (root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled) ? Theme.elevationRenderPadding(shadowLevel, Theme.elevationLightDirection, shadowFallbackOffset, 8, 16) : 0
+ readonly property real shadowMotionPadding: animationType === "slide" ? 30 : Math.max(0, animationOffset)
+ readonly property real shadowBuffer: Theme.snap(shadowRenderPadding + shadowMotionPadding, dpr)
+ readonly property real alignedWidth: Theme.px(modalWidth, dpr)
+ readonly property real alignedHeight: Theme.px(modalHeight, dpr)
+
+ readonly property real alignedX: Theme.snap((() => {
+ switch (positioning) {
+ case "center":
+ return (screenWidth - alignedWidth) / 2;
+ case "top-right":
+ return Math.max(Theme.spacingL, screenWidth - alignedWidth - Theme.spacingL);
+ case "custom":
+ return customPosition.x;
+ default:
+ return 0;
+ }
+ })(), dpr)
+
+ readonly property real alignedY: Theme.snap((() => {
+ switch (positioning) {
+ case "center":
+ return (screenHeight - alignedHeight) / 2;
+ case "top-right":
+ return Theme.barHeight + Theme.spacingXS;
+ case "custom":
+ return customPosition.y;
+ default:
+ return 0;
+ }
+ })(), dpr)
+
+ PanelWindow {
+ id: clickCatcher
+ visible: false
+ color: "transparent"
+
+ WlrLayershell.namespace: root.layerNamespace + ":clickcatcher"
+ WlrLayershell.layer: WlrLayershell.Top
+ WlrLayershell.exclusiveZone: -1
+ WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
+
+ anchors {
+ top: true
+ left: true
+ right: true
+ bottom: true
+ }
+
+ mask: Region {
+ item: Rectangle {
+ x: root.alignedX
+ y: root.alignedY
+ width: root.alignedWidth
+ height: root.alignedHeight
+ }
+ intersection: Intersection.Xor
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: root.closeOnBackgroundClick && root.shouldBeVisible
+ onClicked: root.backgroundClicked()
+ }
+ }
+
+ PanelWindow {
+ id: contentWindow
+ visible: false
+ color: "transparent"
+
+ WindowBlur {
+ targetWindow: contentWindow
+ readonly property real s: Math.min(1, modalContainer.scaleValue)
+ blurX: modalContainer.x + modalContainer.width * (1 - s) * 0.5 + Theme.snap(modalContainer.animX, root.dpr)
+ blurY: modalContainer.y + modalContainer.height * (1 - s) * 0.5 + Theme.snap(modalContainer.animY, root.dpr)
+ blurWidth: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.width * s : 0
+ blurHeight: (shouldBeVisible && animatedContent.opacity > 0) ? modalContainer.height * s : 0
+ blurRadius: root.cornerRadius
+ }
+
+ WlrLayershell.namespace: root.layerNamespace
+ WlrLayershell.layer: {
+ if (root.useOverlayLayer)
+ return WlrLayershell.Overlay;
+ switch (Quickshell.env("DMS_MODAL_LAYER")) {
+ case "bottom":
+ console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer.");
+ return WlrLayershell.Top;
+ case "background":
+ console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer.");
+ return WlrLayershell.Top;
+ case "overlay":
+ return WlrLayershell.Overlay;
+ default:
+ return WlrLayershell.Top;
+ }
+ }
+ WlrLayershell.exclusiveZone: -1
+ WlrLayershell.keyboardFocus: {
+ if (customKeyboardFocus !== null)
+ return customKeyboardFocus;
+ if (!shouldHaveFocus)
+ return WlrKeyboardFocus.None;
+ if (root.useHyprlandFocusGrab)
+ return WlrKeyboardFocus.OnDemand;
+ return WlrKeyboardFocus.Exclusive;
+ }
+
+ anchors {
+ left: true
+ top: true
+ right: root.useSingleWindow
+ bottom: root.useSingleWindow
+ }
+
+ WlrLayershell.margins {
+ left: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr))
+ top: root.useSingleWindow ? 0 : Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr))
+ right: 0
+ bottom: 0
+ }
+
+ implicitWidth: root.useSingleWindow ? 0 : root.alignedWidth + (shadowBuffer * 2)
+ implicitHeight: root.useSingleWindow ? 0 : root.alignedHeight + (shadowBuffer * 2)
+
+ onVisibleChanged: {
+ if (visible) {
+ opened();
+ } else {
+ if (Qt.inputMethod) {
+ Qt.inputMethod.hide();
+ Qt.inputMethod.reset();
+ }
+ }
+ }
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: root.useSingleWindow && root.closeOnBackgroundClick && root.shouldBeVisible
+ z: -2
+ onClicked: root.backgroundClicked()
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ z: -1
+ color: "black"
+ opacity: root.useBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0
+ visible: root.useBackground
+
+ Behavior on opacity {
+ enabled: root.animationsEnabled
+ DankAnim {
+ duration: root.animationDuration
+ easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
+ }
+ }
+ }
+
+ Item {
+ id: modalContainer
+ x: root.useSingleWindow ? root.alignedX : shadowBuffer
+ y: root.useSingleWindow ? root.alignedY : shadowBuffer
+
+ width: root.alignedWidth
+ height: root.alignedHeight
+
+ MouseArea {
+ anchors.fill: parent
+ enabled: root.useSingleWindow && root.shouldBeVisible
+ hoverEnabled: false
+ acceptedButtons: Qt.AllButtons
+ onPressed: mouse.accepted = true
+ onClicked: mouse.accepted = true
+ z: -1
+ }
+
+ readonly property bool slide: root.animationType === "slide"
+ readonly property real offsetX: slide ? 15 : 0
+ readonly property real offsetY: slide ? -30 : root.animationOffset
+
+ property real animX: 0
+ property real animY: 0
+ property real scaleValue: root.animationScaleCollapsed
+
+ onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
+ onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
+
+ Connections {
+ target: root
+ function onShouldBeVisibleChanged() {
+ modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr);
+ modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr);
+ modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
+ }
+ }
+
+ Behavior on animX {
+ enabled: root.animationsEnabled
+ DankAnim {
+ duration: root.animationDuration
+ easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
+ }
+ }
+
+ Behavior on animY {
+ enabled: root.animationsEnabled
+ DankAnim {
+ duration: root.animationDuration
+ easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
+ }
+ }
+
+ Behavior on scaleValue {
+ enabled: root.animationsEnabled
+ DankAnim {
+ duration: root.animationDuration
+ easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
+ }
+ }
+
+ Item {
+ id: contentContainer
+ anchors.centerIn: parent
+ width: parent.width
+ height: parent.height
+ clip: false
+
+ Item {
+ id: animatedContent
+ anchors.fill: parent
+ clip: false
+ opacity: root.shouldBeVisible ? 1 : 0
+ scale: modalContainer.scaleValue
+ x: Theme.snap(modalContainer.animX, root.dpr) + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5
+ y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5
+
+ Behavior on opacity {
+ enabled: root.animationsEnabled
+ NumberAnimation {
+ duration: animationDuration
+ easing.type: Easing.BezierSpline
+ easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
+ }
+ }
+
+ ElevationShadow {
+ id: modalShadowLayer
+ anchors.fill: parent
+ level: root.shadowLevel
+ fallbackOffset: root.shadowFallbackOffset
+ targetRadius: root.cornerRadius
+ targetColor: root.backgroundColor
+ borderColor: root.borderColor
+ borderWidth: root.borderWidth
+ shadowEnabled: root.enableShadow && Theme.elevationEnabled && SettingsData.modalElevationEnabled && Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
+ }
+
+ Rectangle {
+ anchors.fill: parent
+ radius: root.cornerRadius
+ color: "transparent"
+ border.color: BlurService.borderColor
+ border.width: BlurService.borderWidth
+ z: 100
+ }
+
+ FocusScope {
+ anchors.fill: parent
+ focus: root.shouldBeVisible
+ clip: false
+
+ Item {
+ id: directContentWrapper
+ anchors.fill: parent
+ visible: root.directContent !== null
+ focus: true
+ clip: false
+
+ Component.onCompleted: {
+ if (root.directContent) {
+ root.directContent.parent = directContentWrapper;
+ root.directContent.anchors.fill = directContentWrapper;
+ Qt.callLater(() => root.directContent.forceActiveFocus());
+ }
+ }
+
+ Connections {
+ target: root
+ function onDirectContentChanged() {
+ if (root.directContent) {
+ root.directContent.parent = directContentWrapper;
+ root.directContent.anchors.fill = directContentWrapper;
+ Qt.callLater(() => root.directContent.forceActiveFocus());
+ }
+ }
+ }
+ }
+
+ Loader {
+ id: contentLoader
+ anchors.fill: parent
+ active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible)
+ asynchronous: false
+ focus: true
+ clip: false
+ visible: root.directContent === null
+
+ onLoaded: {
+ if (item) {
+ Qt.callLater(() => item.forceActiveFocus());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ FocusScope {
+ id: focusScope
+ objectName: "modalFocusScope"
+ anchors.fill: parent
+ visible: root.shouldBeVisible || contentWindow.visible
+ focus: root.shouldBeVisible
+ Keys.onEscapePressed: event => {
+ if (root.closeOnEscapeKey && shouldHaveFocus) {
+ root.close();
+ event.accepted = true;
+ }
+ }
+ }
+ }
+}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/InputModal.qml b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/InputModal.qml
new file mode 100644
index 0000000..c65f3e0
--- /dev/null
+++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/quickshell/Modals/Common/InputModal.qml
@@ -0,0 +1,312 @@
+import QtQuick
+import qs.Common
+import qs.Modals.Common
+import qs.Widgets
+
+DankModal {
+ id: root
+
+ layerNamespace: "dms:input-modal"
+ keepPopoutsOpen: true
+
+ property string inputTitle: ""
+ property string inputMessage: ""
+ property string inputPlaceholder: ""
+ property string inputText: ""
+ property string confirmButtonText: "Confirm"
+ property string cancelButtonText: "Cancel"
+ property color confirmButtonColor: Theme.primary
+ property var onConfirm: function (text) {}
+ property var onCancel: function () {}
+ property int selectedButton: -1
+ property bool keyboardNavigation: false
+
+ function show(title, message, onConfirmCallback, onCancelCallback) {
+ inputTitle = title || "";
+ inputMessage = message || "";
+ inputPlaceholder = "";
+ inputText = "";
+ confirmButtonText = "Confirm";
+ cancelButtonText = "Cancel";
+ confirmButtonColor = Theme.primary;
+ onConfirm = onConfirmCallback || ((text) => {});
+ onCancel = onCancelCallback || (() => {});
+ selectedButton = -1;
+ keyboardNavigation = false;
+ open();
+ }
+
+ function showWithOptions(options) {
+ inputTitle = options.title || "";
+ inputMessage = options.message || "";
+ inputPlaceholder = options.placeholder || "";
+ inputText = options.initialText || "";
+ confirmButtonText = options.confirmText || "Confirm";
+ cancelButtonText = options.cancelText || "Cancel";
+ confirmButtonColor = options.confirmColor || Theme.primary;
+ onConfirm = options.onConfirm || ((text) => {});
+ onCancel = options.onCancel || (() => {});
+ selectedButton = -1;
+ keyboardNavigation = false;
+ open();
+ }
+
+ function confirmAndClose() {
+ const text = inputText;
+ close();
+ if (onConfirm) {
+ onConfirm(text);
+ }
+ }
+
+ function cancelAndClose() {
+ close();
+ if (onCancel) {
+ onCancel();
+ }
+ }
+
+ function selectButton() {
+ if (selectedButton === 0) {
+ cancelAndClose();
+ } else {
+ confirmAndClose();
+ }
+ }
+
+ shouldBeVisible: false
+ allowStacking: true
+ modalWidth: 350
+ modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 200
+ enableShadow: true
+ shouldHaveFocus: true
+ onBackgroundClicked: cancelAndClose()
+ onOpened: {
+ Qt.callLater(function () {
+ if (contentLoader.item && contentLoader.item.textInputRef) {
+ contentLoader.item.textInputRef.forceActiveFocus();
+ }
+ });
+ }
+
+ content: Component {
+ FocusScope {
+ anchors.fill: parent
+ implicitHeight: mainColumn.implicitHeight
+ focus: true
+
+ property alias textInputRef: textInput
+
+ Keys.onPressed: function (event) {
+ const textFieldFocused = textInput.activeFocus;
+
+ switch (event.key) {
+ case Qt.Key_Escape:
+ root.cancelAndClose();
+ event.accepted = true;
+ break;
+ case Qt.Key_Tab:
+ if (textFieldFocused) {
+ root.keyboardNavigation = true;
+ root.selectedButton = 0;
+ textInput.focus = false;
+ } else {
+ root.keyboardNavigation = true;
+ if (root.selectedButton === -1) {
+ root.selectedButton = 0;
+ } else if (root.selectedButton === 0) {
+ root.selectedButton = 1;
+ } else {
+ root.selectedButton = -1;
+ textInput.forceActiveFocus();
+ }
+ }
+ event.accepted = true;
+ break;
+ case Qt.Key_Left:
+ if (!textFieldFocused) {
+ root.keyboardNavigation = true;
+ root.selectedButton = 0;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_Right:
+ if (!textFieldFocused) {
+ root.keyboardNavigation = true;
+ root.selectedButton = 1;
+ event.accepted = true;
+ }
+ break;
+ case Qt.Key_Return:
+ case Qt.Key_Enter:
+ if (root.selectedButton !== -1) {
+ root.selectButton();
+ } else {
+ root.confirmAndClose();
+ }
+ event.accepted = true;
+ break;
+ }
+ }
+
+ Column {
+ id: mainColumn
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.leftMargin: Theme.spacingL
+ anchors.rightMargin: Theme.spacingL
+ anchors.topMargin: Theme.spacingL
+ spacing: 0
+
+ StyledText {
+ text: root.inputTitle
+ font.pixelSize: Theme.fontSizeLarge
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
+ }
+
+ Item {
+ width: 1
+ height: Theme.spacingL
+ }
+
+ StyledText {
+ text: root.inputMessage
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ width: parent.width
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ visible: root.inputMessage !== ""
+ }
+
+ Item {
+ width: 1
+ height: root.inputMessage !== "" ? Theme.spacingL : 0
+ visible: root.inputMessage !== ""
+ }
+
+ Rectangle {
+ width: parent.width
+ height: 40
+ radius: Theme.cornerRadius
+ color: Theme.surfaceVariantAlpha
+ border.color: textInput.activeFocus ? Theme.primary : "transparent"
+ border.width: textInput.activeFocus ? 1 : 0
+
+ TextInput {
+ id: textInput
+
+ anchors.fill: parent
+ anchors.leftMargin: Theme.spacingM
+ anchors.rightMargin: Theme.spacingM
+ verticalAlignment: TextInput.AlignVCenter
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ selectionColor: Theme.primary
+ selectedTextColor: Theme.primaryText
+ clip: true
+ text: root.inputText
+ onTextChanged: root.inputText = text
+
+ StyledText {
+ anchors.fill: parent
+ verticalAlignment: Text.AlignVCenter
+ font.pixelSize: Theme.fontSizeMedium
+ color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.4)
+ text: root.inputPlaceholder
+ visible: textInput.text === "" && !textInput.activeFocus
+ }
+ }
+ }
+
+ Item {
+ width: 1
+ height: Theme.spacingL * 1.5
+ }
+
+ Row {
+ anchors.horizontalCenter: parent.horizontalCenter
+ spacing: Theme.spacingM
+
+ Rectangle {
+ width: 120
+ height: 40
+ radius: Theme.cornerRadius
+ color: {
+ if (root.keyboardNavigation && root.selectedButton === 0) {
+ return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
+ } else if (cancelButton.containsMouse) {
+ return Theme.surfacePressed;
+ } else {
+ return Theme.surfaceVariantAlpha;
+ }
+ }
+ border.color: (root.keyboardNavigation && root.selectedButton === 0) ? Theme.primary : "transparent"
+ border.width: (root.keyboardNavigation && root.selectedButton === 0) ? 1 : 0
+
+ StyledText {
+ text: root.cancelButtonText
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.surfaceText
+ font.weight: Font.Medium
+ anchors.centerIn: parent
+ }
+
+ MouseArea {
+ id: cancelButton
+
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.cancelAndClose()
+ }
+ }
+
+ Rectangle {
+ width: 120
+ height: 40
+ radius: Theme.cornerRadius
+ color: {
+ const baseColor = root.confirmButtonColor;
+ if (root.keyboardNavigation && root.selectedButton === 1) {
+ return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 1);
+ } else if (confirmButton.containsMouse) {
+ return Qt.rgba(baseColor.r, baseColor.g, baseColor.b, 0.9);
+ } else {
+ return baseColor;
+ }
+ }
+ border.color: (root.keyboardNavigation && root.selectedButton === 1) ? "white" : "transparent"
+ border.width: (root.keyboardNavigation && root.selectedButton === 1) ? 1 : 0
+
+ StyledText {
+ text: root.confirmButtonText
+ font.pixelSize: Theme.fontSizeMedium
+ color: Theme.primaryText
+ font.weight: Font.Medium
+ anchors.centerIn: parent
+ }
+
+ MouseArea {
+ id: confirmButton
+
+ anchors.fill: parent
+ hoverEnabled: true
+ cursorShape: Qt.PointingHandCursor
+ onClicked: root.confirmAndClose()
+ }
+ }
+ }
+
+ Item {
+ width: 1
+ height: Theme.spacingL
+ }
+ }
+ }
+ }
+}