1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-15 07:35:20 -04:00

feat(popouts): implement hover popout functionality

This commit is contained in:
purian23
2026-06-12 23:19:29 -04:00
parent 3701b3d7a3
commit fc72b6d779
12 changed files with 1251 additions and 315 deletions
+44
View File
@@ -24,6 +24,7 @@ Item {
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
property bool suspendShadowWhileResizing: false
property bool shouldBeVisible: false
property bool hoverDismissEnabled: false
property var customKeyboardFocus: null
property bool backgroundInteractive: true
property bool contentHandlesKeys: false
@@ -82,6 +83,8 @@ Item {
readonly property real alignedY: impl.item ? impl.item.alignedY : 0
readonly property real alignedWidth: impl.item ? impl.item.alignedWidth : 0
readonly property real alignedHeight: impl.item ? impl.item.alignedHeight : 0
readonly property real renderedAlignedY: impl.item ? (impl.item.renderedAlignedY ?? impl.item.alignedY) : 0
readonly property real renderedAlignedHeight: impl.item ? (impl.item.renderedAlignedHeight ?? impl.item.alignedHeight) : 0
readonly property real maskX: impl.item ? impl.item.maskX : 0
readonly property real maskY: impl.item ? impl.item.maskY : 0
readonly property real maskWidth: impl.item ? impl.item.maskWidth : 0
@@ -172,6 +175,32 @@ Item {
impl.item.close();
}
function cancelHoverDismiss() {
if (impl.item?.cancelHoverDismiss)
impl.item.cancelHoverDismiss();
}
function closeFromHoverDismiss() {
hoverDismissEnabled = false;
if (impl.item) {
impl.item.animationsEnabled = true;
impl.item.animationDuration = Math.round(Theme.expressiveDurations.expressiveDefaultSpatial);
impl.item.animationExitCurve = Theme.expressiveCurves.expressiveDefaultSpatial;
}
if (dashVisible !== undefined) {
dashVisible = false;
return;
}
if (notificationHistoryVisible !== undefined) {
notificationHistoryVisible = false;
return;
}
if (impl.item)
impl.item.close();
else
close();
}
function toggle() {
(shouldBeVisible || _pendingOpen) ? close() : open();
}
@@ -210,6 +239,20 @@ Item {
impl.item.updateSurfacePosition();
}
function containsGlobalPoint(gx, gy) {
if (!screen)
return false;
const presented = shouldBeVisible || (impl.item?.isClosing ?? false);
if (!presented)
return false;
const padding = 24;
const x = alignedX - padding;
const y = renderedAlignedY - padding;
const w = alignedWidth + padding * 2;
const h = renderedAlignedHeight + padding * 2;
return gx >= x && gx <= x + w && gy >= y && gy <= y + h;
}
Loader {
id: impl
active: root.screen !== null
@@ -261,6 +304,7 @@ Item {
it.screen = Qt.binding(() => root.screen);
it.effectiveBarPosition = Qt.binding(() => root.effectiveBarPosition);
it.effectiveBarBottomGap = Qt.binding(() => root.effectiveBarBottomGap);
it.hoverDismissEnabled = Qt.binding(() => root.hoverDismissEnabled);
it.shouldBeVisible = root.shouldBeVisible;
if (root._primeContent && typeof it.primeContent === "function")
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
@@ -407,6 +408,20 @@ Item {
onFrameOwnsConnectedChromeChanged: _syncPopoutChromeState()
property bool animationsEnabled: true
property bool hoverDismissEnabled: false
function cancelHoverDismiss() {
hoverDismissTracker.cancelPending();
}
function closeFromHoverDismiss() {
if (isClosing || !shouldBeVisible)
return;
if (popoutHandle?.closeFromHoverDismiss)
popoutHandle.closeFromHoverDismiss();
else
close();
}
function open() {
if (!screen)
@@ -761,6 +776,27 @@ Item {
visible: false
color: "transparent"
MouseArea {
anchors.fill: parent
z: -1
acceptedButtons: Qt.NoButton
hoverEnabled: true
onPositionChanged: mouse => {
const gp = mapToItem(null, mouse.x, mouse.y);
PopoutManager.updateHoverCursor(gp.x, gp.y);
}
}
HoverDismissTracker {
id: hoverDismissTracker
anchors.fill: parent
enabled: root.hoverDismissEnabled && root.shouldBeVisible
shouldDismiss: function () {
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
}
onDismissRequested: root.closeFromHoverDismiss()
}
WindowBlur {
id: popoutBlur
targetWindow: contentWindow
@@ -5,6 +5,7 @@ import Quickshell
import Quickshell.Wayland
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
@@ -35,6 +36,21 @@ Item {
property bool shouldBeVisible: false
property bool isClosing: false
property bool animationsEnabled: true
property bool hoverDismissEnabled: false
function cancelHoverDismiss() {
hoverDismissTracker.cancelPending();
}
function closeFromHoverDismiss() {
if (isClosing || !shouldBeVisible)
return;
if (popoutHandle?.closeFromHoverDismiss)
popoutHandle.closeFromHoverDismiss();
else
close();
}
property var customKeyboardFocus: null
property bool backgroundInteractive: true
property bool contentHandlesKeys: false
@@ -585,6 +601,27 @@ Item {
color: "transparent"
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
MouseArea {
anchors.fill: parent
z: -1
acceptedButtons: Qt.NoButton
hoverEnabled: true
onPositionChanged: mouse => {
const gp = mapToItem(null, mouse.x, mouse.y);
PopoutManager.updateHoverCursor(gp.x, gp.y);
}
}
HoverDismissTracker {
id: hoverDismissTracker
anchors.fill: parent
enabled: root.hoverDismissEnabled && root.shouldBeVisible
shouldDismiss: function () {
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
}
onDismissRequested: root.closeFromHoverDismiss()
}
WindowBlur {
id: popoutBlur
targetWindow: contentWindow
+44 -2
View File
@@ -13,6 +13,7 @@ PanelWindow {
WlrLayershell.namespace: layerNamespace
property bool isVisible: false
property bool hoverDismissEnabled: false
property var targetScreen: null
property var modelData: null
property bool triggerUsesOverlayLayer: false
@@ -39,6 +40,24 @@ PanelWindow {
isVisible = false;
}
function hideFromHoverDismiss() {
hoverDismissEnabled = false;
slideAnimation.duration = Math.round(Theme.expressiveDurations.expressiveDefaultSpatial);
hide();
}
function cancelHoverDismiss() {
hoverDismissTracker.cancelPending();
}
function containsGlobalPoint(gx, gy) {
if (!isVisible || !modelData)
return false;
const padding = 24;
const topLeft = slideContainer.mapToItem(null, 0, 0);
return gx >= topLeft.x - padding && gx < topLeft.x + slideContainer.width + padding && gy >= topLeft.y - padding && gy < topLeft.y + slideContainer.height + padding;
}
function toggle() {
if (isVisible) {
hide();
@@ -60,6 +79,27 @@ PanelWindow {
color: "transparent"
MouseArea {
anchors.fill: parent
z: -1
acceptedButtons: Qt.NoButton
hoverEnabled: true
onPositionChanged: mouse => {
const gp = mapToItem(null, mouse.x, mouse.y);
PopoutManager.updateHoverCursor(gp.x, gp.y);
}
}
HoverDismissTracker {
id: hoverDismissTracker
anchors.fill: parent
enabled: root.hoverDismissEnabled && root.isVisible
shouldDismiss: function () {
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
}
onDismissRequested: root.hideFromHoverDismiss()
}
readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
WlrLayershell.layer: (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData)) ? WlrLayershell.Overlay : WlrLayershell.Top
@@ -104,8 +144,10 @@ PanelWindow {
easing.type: Easing.OutCubic
onRunningChanged: {
if (!running && !root.isVisible) {
root.mappedVisible = false;
if (!running) {
if (!root.isVisible)
root.mappedVisible = false;
slideAnimation.duration = 450;
}
}
}
@@ -0,0 +1,28 @@
pragma ComponentBehavior: Bound
import QtQuick
Item {
id: root
property bool enabled: false
property var shouldDismiss: null
signal dismissRequested
anchors.fill: parent
HoverHandler {
id: hoverHandler
enabled: root.enabled
onHoveredChanged: {
if (hoverHandler.hovered || !root.enabled)
return;
if (typeof root.shouldDismiss === "function" && !root.shouldDismiss())
return;
root.dismissRequested();
}
}
function cancelPending() {}
}