popout: keep hover popouts open after a transient menu closes

fixes #3492
This commit is contained in:
bbedward committed 2026-09-24 18:13:20 -04:00
1 parent 8508121f4a
commit 7975136c10
20 files changed
+86 -21

No files matched your search

@@ -78,7 +78,7 @@ Item {
width: CcMetrics.configMenuWidth
height: menu.implicitHeight + Theme.spacingS * 2
radius: Theme.windowRadius
color: Theme.nestedSurface
color: Theme.readableSurface
border.width: Theme.layerOutlineWidth
border.color: Theme.outlineMedium
x: Math.max(Theme.spacingS, Math.min(root.anchorX + root.anchorWidth - width, root.width - width - Theme.spacingS))
@@ -133,7 +133,7 @@ Item {
width: CcMetrics.menuMinWidth
height: root.visibleItems.length * Theme.menuItemHeight + Theme.spacingS * 2
radius: Theme.windowRadius
color: Theme.nestedSurface
color: Theme.foregroundColor(Theme.chipSurface, Theme.isFloatingWindow(root))
border.width: Theme.layerOutlineWidth
border.color: Theme.outlineMedium
opacity: root._open ? 1 : 0
@@ -49,7 +49,7 @@ Item {
topRightRadius: root.cornerRadii.y
bottomRightRadius: root.cornerRadii.z
bottomLeftRadius: root.cornerRadii.w
color: Qt.rgba(0, 0, 0, Theme.scrimAlpha)
color: Theme.withAlpha(Theme.scrimColor, Theme.scrimAlpha)
opacity: root.shown ? 1 : 0
Behavior on opacity {
@@ -81,7 +81,7 @@ Item {
width: Math.min(root.panelWidth, root.width - Theme.spacingL * 2)
height: column.implicitHeight + Theme.spacingL * 2
radius: Theme.windowRadius
color: Theme.nestedSurface
color: Theme.readableSurface
border.width: Theme.layerOutlineWidth
border.color: Theme.outlineMedium
opacity: root.shown ? 1 : 0
@@ -26,7 +26,7 @@ Rectangle {
implicitWidth: CcMetrics.libraryPanelWidth
implicitHeight: CcMetrics.libraryPanelHeight
radius: Theme.windowRadius
color: Theme.nestedSurface
color: Theme.readableSurface
border.width: Theme.layerOutlineWidth
border.color: Theme.outlineMedium
@@ -77,6 +77,8 @@ FocusScope {
target.parentPopout = root.dashHost;
if ("dashHost" in target)
target.dashHost = root.dashHost;
if ("transientSurfaceTracker" in target)
target.transientSurfaceTracker = root.dashHost.transientSurfaceTracker;
if ("targetScreen" in target)
target.targetScreen = Qt.binding(() => root.dashHost.screen);
if ("preferredFocusId" in target)
@@ -13,6 +13,8 @@ FocusScope {
property bool nested: false
property string entryId: "notifications"
property int currentTab: 0
property var transientSurfaceTracker: null
readonly property var surfaces: transientSurfaceTracker ?? localSurfaces
readonly property bool hasNotifications: list.count > 0
readonly property Item focusTarget: root
property bool blocksTabNavigation: false
@@ -105,7 +107,7 @@ FocusScope {
}
TransientSurfaceTracker {
id: surfaces
id: localSurfaces
}
NotificationKeyboardController {
@@ -131,7 +133,7 @@ FocusScope {
visible: root.currentTab === 0
keyboardController: keyboard
focusAllowed: root.activeFocus
transientSurfaceTracker: surfaces
transientSurfaceTracker: root.surfaces
trackStableContentHeight: false
}
@@ -198,7 +200,7 @@ FocusScope {
popupAnchorItem: dndButton
popupWidth: NotificationMetrics.menuWidth
openUpwards: true
transientSurfaceTracker: surfaces
transientSurfaceTracker: root.surfaces
options: [I18n.tr("Off")].concat(DndPresets.presetOptions.map(option => option.label))
onValueChanged: value => {
if (value === I18n.tr("Off")) {
@@ -12,6 +12,7 @@ Column {
property var eventData: null
property date initialDate: new Date()
property var transientSurfaceTracker: null
signal saved
signal closeRequested
@@ -240,6 +241,7 @@ Column {
DankDropdown {
width: parent.width
text: I18n.tr("Calendar")
transientSurfaceTracker: root.transientSurfaceTracker
options: root._cals.map(c => c.name)
currentValue: root._calendarName(root.fCalendarId)
onValueChanged: value => {
@@ -255,6 +257,7 @@ Column {
DankDropdown {
width: parent.width
text: I18n.tr("Reminder", "noun, calendar event reminder time dropdown label")
transientSurfaceTracker: root.transientSurfaceTracker
options: root._remLabels
currentValue: root._remLabels[Math.max(0, root._remMins.indexOf(root.fReminder))]
onValueChanged: value => {
@@ -16,6 +16,7 @@ DankEditableGrid {
property int columnCap: DashMetrics.maximumGridColumns
property bool panelPreviewing: false
property string preferredFocusId: "calendar"
property var transientSurfaceTracker: null
property string pendingFocusId: ""
property int pendingFocusReason: Qt.OtherFocusReason
property Item flashSlot: null
@@ -52,6 +52,8 @@ DankEditableGridSlot {
return;
if ("entryId" in item)
item.entryId = root.cardId;
if ("transientSurfaceTracker" in item)
item.transientSurfaceTracker = Qt.binding(() => root.grid.transientSurfaceTracker);
if (!root.entry?.isPlugin)
return;
if ("pluginId" in item)
@@ -5,6 +5,7 @@ import qs.Modules.DankDash
Card {
id: root
property bool live: Window.window?.visible ?? false
property var transientSurfaceTracker: null
readonly property bool opensTab: false
focusTarget: panel
activeFocusOnTab: interactive
@@ -23,5 +24,6 @@ Card {
live: root.live
interactive: root.interactive
nested: true
transientSurfaceTracker: root.transientSurfaceTracker
}
}
@@ -19,6 +19,7 @@ FocusScope {
property int rowBudget: DashMetrics.minimumTabRows
property int columnCap: DashMetrics.maximumGridColumns
property string preferredFocusId: "calendar"
property var transientSurfaceTracker: null
signal cardFocusChanged(string id)
readonly property Item focusTarget: grid
@@ -88,6 +89,7 @@ FocusScope {
columnCap: root.columnCap
live: root.live
preferredFocusId: root.preferredFocusId
transientSurfaceTracker: root.transientSurfaceTracker
onCardFocusChanged: id => root.cardFocusChanged(id)
onCardClicked: cardId => {
@@ -186,6 +188,7 @@ FocusScope {
sourceComponent: CalendarEventEditor {
eventData: editorSheet.eventData
initialDate: editorSheet.initialDate
transientSurfaceTracker: root.transientSurfaceTracker
onSaved: editorSheet.dismiss()
onCloseRequested: editorSheet.dismiss()
}
@@ -41,6 +41,7 @@ Item {
property int gridIndex: 0
property Item keyForwardTarget: null
property var parentPopout: null
property var transientSurfaceTracker: null
property bool enableAnimation: false
property string selectedFileName: ""
property var targetScreen: null
@@ -813,6 +814,7 @@ Item {
showTrigger: false
popupAnchorItem: sortButton
focusReturnTarget: sortButton
transientSurfaceTracker: root.transientSurfaceTracker
openUpwards: true
alignPopupRight: !I18n.isRtl
popupWidth: Math.min(root.width, Theme.smallBreakpoint / 2)
@@ -14,6 +14,7 @@ DashTabFace {
live: root.live
editMode: root.editMode
rowBudget: root.controller.dashboardRowBudget
transientSurfaceTracker: root.controller.transientSurfaces
onTabRequested: id => {
switch (id) {
case "media":
@@ -11,6 +11,7 @@ DashTabFace {
tabComponent: Component {
NotificationsTab {
live: root.live
transientSurfaceTracker: root.controller.transientSurfaces
}
}
@@ -15,6 +15,7 @@ DashTabFace {
pagerCachePages: 0
targetScreen: root.effectiveScreen
parentPopout: hostContract
transientSurfaceTracker: root.controller.transientSurfaces
}
}
@@ -97,7 +97,7 @@ Item {
property bool keyboardFocusArmed: true
readonly property var keyboardFocusPolicy: KeyboardFocus.keyboardFocus(controller.keyboardDismissRequested && root.keyboardFocusArmed && !controller.keyboardYielded, null)
readonly property bool wantsFocusGrab: KeyboardFocus.wantsGrab(controller.keyboardDismissRequested && !controller.keyboardYielded, null)
readonly property var transientFocusWindows: launcherTransientSurfaces.focusWindows
readonly property var transientFocusWindows: islandTransientSurfaces.focusWindows
readonly property string registryKey: IslandHostRegistry.key(root.screen?.name, root.barId)
property string registeredKey: ""
@@ -189,11 +189,12 @@ Item {
onKeyboardDismissRequestedChanged: root.requestKeyboardFocus()
onLauncherSessionActiveChanged: {
if (!launcherSessionActive)
launcherTransientSurfaces.closeAll();
islandTransientSurfaces.closeAll();
}
barConfig: root.barConfig
edge: root.edge
transientSurfaces: islandTransientSurfaces
freeMode: root.freeMode
dotMode: root.dotMode
dotSize: root.dotSize * root.freeScale
@@ -230,7 +231,7 @@ Item {
}
TransientSurfaceTracker {
id: launcherTransientSurfaces
id: islandTransientSurfaces
}
IslandMediaSource {
@@ -262,7 +263,7 @@ Item {
systemModel: systemSource
notificationModel: notificationSource
launcherController: launcherController
launcherTransientSurfaceTracker: launcherTransientSurfaces
launcherTransientSurfaceTracker: islandTransientSurfaces
effectiveScreen: root.screen
hostOriginX: root.hostOriginX
hostOriginY: root.hostOriginY
@@ -26,6 +26,14 @@ QtObject {
if (launcherInputFocused)
hoverExpanded = false;
}
property var transientSurfaces: null
readonly property bool transientSurfacesActive: transientSurfaces?.active ?? false
onTransientSurfacesActiveChanged: {
if (!transientSurfacesActive)
return;
hoverExpanded = false;
hoverCloseTimer.stop();
}
property string launcherPendingQuery: ""
property string launcherPendingMode: ""
property string controlCenterPendingSection: ""
@@ -200,7 +200,7 @@ Popup {
onClosed: focusRestore.restart()
background: Rectangle {
color: Theme.nestedSurface
color: Theme.isFloatingWindow(processContextMenu) ? Theme.floatingWindowSurface : Theme.readableSurface
border.width: Theme.layerOutlineWidth
border.color: Theme.outlineMedium
radius: Theme.windowRadius
@@ -274,10 +274,10 @@ Popup {
}
contentItem: Rectangle {
color: Theme.nestedSurface
color: Theme.foregroundColor(Theme.chipSurfaceNested, Theme.isFloatingWindow(processContextMenu))
radius: Theme.windowRadius
border.color: BlurService.borderColor
border.width: BlurService.borderWidth
border.color: Theme.outlineMedium
border.width: Theme.layerOutlineWidth
Item {
id: keyboardHandler
+14
View File
@@ -1422,6 +1422,20 @@ Item {
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: root.backgroundClicked()
}
HoverHandler {
enabled: root.hoverDismissEnabled && root.shouldBeVisible
function notePointer() {
if (!hovered)
return;
PopoutManager.updateHoverCursor(point.position.x, point.position.y);
hoverDismissController.notePointerMoved();
}
onHoveredChanged: notePointer()
onPointChanged: notePointer()
}
}
}
}
+27 -5
View File
@@ -12,6 +12,8 @@ Item {
property int graceInterval: 150
property bool bodyHovered: false
// A closed transient surface leaves bodyHovered stale; sway sends no enter until the pointer moves.
property bool pointerUnknown: false
property real globalOffsetX: 0
property real globalOffsetY: 0
@@ -19,12 +21,14 @@ Item {
function cancelPending() {
graceTimer.stop();
pointerUnknown = false;
hoverTracker.cancelPending();
}
function updateBodyHover(over) {
bodyHovered = over;
if (over) {
pointerUnknown = false;
graceTimer.stop();
} else if (dismissEnabled && !dismissSuspended && surfaceVisible) {
graceTimer.restart();
@@ -35,15 +39,26 @@ Item {
PopoutManager.updateHoverCursor(sceneX + globalOffsetX, sceneY + globalOffsetY);
}
function notePointerMoved() {
if (pointerUnknown)
Qt.callLater(root.resolveUnknownPointer);
}
function resolveUnknownPointer() {
if (!pointerUnknown || bodyHovered)
return;
pointerUnknown = false;
if (dismissEnabled && !dismissSuspended && surfaceVisible)
graceTimer.restart();
}
onDismissEnabledChanged: {
if (!dismissEnabled)
cancelPending();
}
onDismissSuspendedChanged: {
if (dismissSuspended)
graceTimer.stop();
else if (dismissEnabled && surfaceVisible && !bodyHovered)
graceTimer.restart();
graceTimer.stop();
pointerUnknown = !dismissSuspended && dismissEnabled && surfaceVisible && !bodyHovered;
}
onSurfaceVisibleChanged: {
if (!surfaceVisible)
@@ -70,6 +85,13 @@ Item {
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
}
onDismissRequested: root.dismissRequested()
onHoverMoved: (gx, gy) => root.updateCursor(gx, gy)
onHoveredChanged: {
if (hovered)
root.notePointerMoved();
}
onHoverMoved: (gx, gy) => {
root.updateCursor(gx, gy);
root.notePointerMoved();
}
}
}