mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-28 14:05:21 -04:00
feat(HoverMode): implement hover popout & launcher functionality in all modes
- New Hover toggle found in DankBar Settings - New Hover to Reveal Launcher in FrameTab Settings
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
rev: v2.10.1
|
rev: v2.12.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: golangci-lint-fmt
|
- id: golangci-lint-fmt
|
||||||
require_serial: true
|
require_serial: true
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,6 @@ require (
|
|||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
go.etcd.io/bbolt v1.4.3
|
go.etcd.io/bbolt v1.4.3
|
||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f
|
|
||||||
golang.org/x/image v0.39.0
|
golang.org/x/image v0.39.0
|
||||||
tailscale.com v1.96.5
|
tailscale.com v1.96.5
|
||||||
)
|
)
|
||||||
@@ -64,6 +63,7 @@ require (
|
|||||||
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
github.com/yeqown/reedsolomon v1.0.0 // indirect
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
|
||||||
golang.org/x/crypto v0.50.0 // indirect
|
golang.org/x/crypto v0.50.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||||
golang.org/x/net v0.53.0 // indirect
|
golang.org/x/net v0.53.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v1.0.1 // indirect
|
golang.zx2c4.com/wireguard/windows v1.0.1 // indirect
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "golang.org/x/exp/constraints"
|
import "cmp"
|
||||||
|
|
||||||
func Clamp[T constraints.Ordered](val, min, max T) T {
|
func Clamp[T cmp.Ordered](val, min, max T) T {
|
||||||
if val < min {
|
if val < min {
|
||||||
return min
|
return min
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -16,8 +17,76 @@ Singleton {
|
|||||||
signal popoutOpening
|
signal popoutOpening
|
||||||
signal popoutChanged
|
signal popoutChanged
|
||||||
|
|
||||||
|
property real hoverCursorGlobalX: 0
|
||||||
|
property real hoverCursorGlobalY: 0
|
||||||
|
|
||||||
|
function updateHoverCursor(gx, gy) {
|
||||||
|
hoverCursorGlobalX = gx;
|
||||||
|
hoverCursorGlobalY = gy;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cursorOverBar(gx, gy, padding, excludedWindow) {
|
||||||
|
const pad = padding !== undefined ? padding : 16;
|
||||||
|
const bars = KeyboardFocus.barWindows || [];
|
||||||
|
for (let i = 0; i < bars.length; i++) {
|
||||||
|
const w = bars[i];
|
||||||
|
if (!w?.visible || w === excludedWindow)
|
||||||
|
continue;
|
||||||
|
if (typeof w.containsGlobalPoint === "function") {
|
||||||
|
if (w.containsGlobalPoint(gx, gy, pad))
|
||||||
|
return true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const item = w.contentItem;
|
||||||
|
if (!item || typeof item.mapToItem !== "function")
|
||||||
|
continue;
|
||||||
|
const topLeft = item.mapToItem(null, 0, 0);
|
||||||
|
if (!topLeft)
|
||||||
|
continue;
|
||||||
|
if (gx >= topLeft.x - pad && gx < topLeft.x + item.width + pad && gy >= topLeft.y - pad && gy < topLeft.y + item.height + pad)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isPopoutPresented(popout) {
|
||||||
|
if (!popout)
|
||||||
|
return false;
|
||||||
|
try {
|
||||||
|
if (popout.dashVisible !== undefined)
|
||||||
|
return !!popout.dashVisible;
|
||||||
|
if (popout.notificationHistoryVisible !== undefined)
|
||||||
|
return !!popout.notificationHistoryVisible;
|
||||||
|
return !!(popout.shouldBeVisible || popout.isClosing);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _openPopout(popout) {
|
||||||
|
if (popout.dashVisible !== undefined) {
|
||||||
|
if (popout.dashVisible && !popout.shouldBeVisible && !popout.isClosing)
|
||||||
|
popout.dashVisible = false;
|
||||||
|
popout.dashVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (popout.notificationHistoryVisible !== undefined) {
|
||||||
|
popout.notificationHistoryVisible = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
popout.open();
|
||||||
|
}
|
||||||
|
|
||||||
function _closePopout(popout) {
|
function _closePopout(popout) {
|
||||||
try {
|
try {
|
||||||
|
if (popout?.hoverDismissEnabled) {
|
||||||
|
if (typeof popout.closeFromHoverDismiss === "function") {
|
||||||
|
popout.closeFromHoverDismiss();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (popout.hoverDismissEnabled !== undefined)
|
||||||
|
popout.hoverDismissEnabled = false;
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case popout.dashVisible !== undefined:
|
case popout.dashVisible !== undefined:
|
||||||
popout.dashVisible = false;
|
popout.dashVisible = false;
|
||||||
@@ -89,7 +158,26 @@ Singleton {
|
|||||||
continue;
|
continue;
|
||||||
_closePopout(popout);
|
_closePopout(popout);
|
||||||
}
|
}
|
||||||
currentPopoutsByScreen = {};
|
// Keep map entries until each popout's close animation finishes (hidePopout).
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePopoutForScreen(screen) {
|
||||||
|
if (!screen)
|
||||||
|
return;
|
||||||
|
const screenName = screen.name;
|
||||||
|
const popout = currentPopoutsByScreen[screenName];
|
||||||
|
if (!popout || _isStale(popout)) {
|
||||||
|
currentPopoutsByScreen[screenName] = null;
|
||||||
|
currentPopoutTriggers[screenName] = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_closePopout(popout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelHoverDismiss(screen) {
|
||||||
|
const popout = getActivePopout(screen);
|
||||||
|
if (popout?.cancelHoverDismiss)
|
||||||
|
popout.cancelHoverDismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActivePopout(screen) {
|
function getActivePopout(screen) {
|
||||||
@@ -98,23 +186,37 @@ Singleton {
|
|||||||
return currentPopoutsByScreen[screen.name] || null;
|
return currentPopoutsByScreen[screen.name] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if the active popout is pinned for auto-dismissal
|
||||||
|
function isActivePopoutPinned(screen) {
|
||||||
|
const p = getActivePopout(screen);
|
||||||
|
if (!p || !_isPopoutPresented(p))
|
||||||
|
return false;
|
||||||
|
return p.hoverDismissEnabled === false || p.hoverDismissSuspended === true;
|
||||||
|
}
|
||||||
|
|
||||||
function isCurrentPopout(popout, screenName) {
|
function isCurrentPopout(popout, screenName) {
|
||||||
const name = screenName || popout?.screen?.name || "";
|
const name = screenName || popout?.screen?.name || "";
|
||||||
return !!name && currentPopoutsByScreen[name] === popout;
|
return !!name && currentPopoutsByScreen[name] === popout;
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestPopout(popout, tabIndex, triggerSource) {
|
function _requestPopout(popout, tabIndex, triggerSource, hoverRequest) {
|
||||||
if (!popout || !popout.screen)
|
if (!popout || !popout.screen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Clicking a transient popout pins it instead of toggling it closed.
|
||||||
|
const wasTransient = popout.hoverDismissEnabled === true;
|
||||||
|
if (!hoverRequest && popout.hoverDismissEnabled !== undefined)
|
||||||
|
popout.hoverDismissEnabled = false;
|
||||||
|
|
||||||
screenshotActive = false;
|
screenshotActive = false;
|
||||||
const screenName = popout.screen.name;
|
const screenName = popout.screen.name;
|
||||||
const currentPopout = currentPopoutsByScreen[screenName];
|
const currentPopout = currentPopoutsByScreen[screenName];
|
||||||
const triggerId = triggerSource !== undefined ? triggerSource : tabIndex;
|
const triggerId = triggerSource !== undefined ? triggerSource : tabIndex;
|
||||||
|
const alreadyPresented = currentPopout === popout && (hoverRequest ? _isPopoutPresented(popout) : popout.shouldBeVisible);
|
||||||
|
|
||||||
const willOpen = !(currentPopout === popout && popout.shouldBeVisible && triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId);
|
const willOpen = !(alreadyPresented && triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId);
|
||||||
if (willOpen) {
|
if (willOpen)
|
||||||
popoutOpening();
|
popoutOpening();
|
||||||
}
|
|
||||||
|
|
||||||
let movedFromOtherScreen = false;
|
let movedFromOtherScreen = false;
|
||||||
for (const otherScreenName in currentPopoutsByScreen) {
|
for (const otherScreenName in currentPopoutsByScreen) {
|
||||||
@@ -145,18 +247,26 @@ Singleton {
|
|||||||
currentPopoutsByScreen[screenName] = null;
|
currentPopoutsByScreen[screenName] = null;
|
||||||
currentPopoutTriggers[screenName] = null;
|
currentPopoutTriggers[screenName] = null;
|
||||||
} else {
|
} else {
|
||||||
|
if (hoverRequest && typeof currentPopout.beginSupersededClose === "function")
|
||||||
|
currentPopout.beginSupersededClose();
|
||||||
_closePopout(currentPopout);
|
_closePopout(currentPopout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPopout === popout && popout.shouldBeVisible && !movedFromOtherScreen) {
|
if (alreadyPresented && !movedFromOtherScreen) {
|
||||||
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) {
|
const sameDefinedTrigger = triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId;
|
||||||
_closePopout(popout);
|
if (hoverRequest && sameDefinedTrigger)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (triggerId === undefined) {
|
if (!hoverRequest && (triggerId === undefined || sameDefinedTrigger)) {
|
||||||
_closePopout(popout);
|
if (!wasTransient) {
|
||||||
|
_closePopout(popout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (popout.updateSurfacePosition)
|
||||||
|
popout.updateSurfacePosition();
|
||||||
|
if (triggerId !== undefined)
|
||||||
|
currentPopoutTriggers[screenName] = triggerId;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +276,8 @@ Singleton {
|
|||||||
if (popout.updateSurfacePosition)
|
if (popout.updateSurfacePosition)
|
||||||
popout.updateSurfacePosition();
|
popout.updateSurfacePosition();
|
||||||
currentPopoutTriggers[screenName] = triggerId;
|
currentPopoutTriggers[screenName] = triggerId;
|
||||||
|
if (hoverRequest && popout.hoverDismissEnabled !== undefined)
|
||||||
|
popout.hoverDismissEnabled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,16 +293,17 @@ Singleton {
|
|||||||
ModalManager.closeAllModalsExcept(null);
|
ModalManager.closeAllModalsExcept(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (movedFromOtherScreen) {
|
if (hoverRequest && popout.hoverDismissEnabled !== undefined)
|
||||||
popout.open();
|
popout.hoverDismissEnabled = true;
|
||||||
} else {
|
|
||||||
if (popout.dashVisible !== undefined) {
|
_openPopout(popout);
|
||||||
popout.dashVisible = true;
|
}
|
||||||
} else if (popout.notificationHistoryVisible !== undefined) {
|
|
||||||
popout.notificationHistoryVisible = true;
|
function requestPopout(popout, tabIndex, triggerSource) {
|
||||||
} else {
|
_requestPopout(popout, tabIndex, triggerSource, false);
|
||||||
popout.open();
|
}
|
||||||
}
|
|
||||||
}
|
function requestHoverPopout(popout, tabIndex, triggerSource) {
|
||||||
|
_requestPopout(popout, tabIndex, triggerSource, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,6 +291,8 @@ Singleton {
|
|||||||
onFrameLauncherEmergeSideChanged: saveSettings()
|
onFrameLauncherEmergeSideChanged: saveSettings()
|
||||||
property bool frameLauncherArcExtender: false
|
property bool frameLauncherArcExtender: false
|
||||||
onFrameLauncherArcExtenderChanged: saveSettings()
|
onFrameLauncherArcExtenderChanged: saveSettings()
|
||||||
|
property bool frameLauncherEdgeHover: false
|
||||||
|
onFrameLauncherEdgeHoverChanged: saveSettings()
|
||||||
readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top"
|
readonly property string frameModalEmergeSide: frameLauncherEmergeSide === "top" ? "bottom" : "top"
|
||||||
property string frameMode: "connected"
|
property string frameMode: "connected"
|
||||||
onFrameModeChanged: saveSettings()
|
onFrameModeChanged: saveSettings()
|
||||||
@@ -603,9 +605,9 @@ Singleton {
|
|||||||
if (!on && id !== "settings" && current.filter(t => t.enabled && t.id !== "settings").length <= 1)
|
if (!on && id !== "settings" && current.filter(t => t.enabled && t.id !== "settings").length <= 1)
|
||||||
return;
|
return;
|
||||||
dashTabs = current.map(t => t.id === id ? {
|
dashTabs = current.map(t => t.id === id ? {
|
||||||
"id": t.id,
|
"id": t.id,
|
||||||
"enabled": on
|
"enabled": on
|
||||||
} : t);
|
} : t);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetDashTabs() {
|
function resetDashTabs() {
|
||||||
@@ -999,7 +1001,9 @@ Singleton {
|
|||||||
"shadowOpacity": 60,
|
"shadowOpacity": 60,
|
||||||
"shadowColorMode": "default",
|
"shadowColorMode": "default",
|
||||||
"shadowCustomColor": "#000000",
|
"shadowCustomColor": "#000000",
|
||||||
"clickThrough": false
|
"clickThrough": false,
|
||||||
|
"hoverPopouts": false,
|
||||||
|
"hoverPopoutDelay": 150
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2436,6 +2440,46 @@ Singleton {
|
|||||||
return barConfigs.filter(cfg => cfg.enabled);
|
return barConfigs.filter(cfg => cfg.enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _sideToPosition(side) {
|
||||||
|
switch (side) {
|
||||||
|
case "top":
|
||||||
|
return SettingsData.Position.Top;
|
||||||
|
case "bottom":
|
||||||
|
return SettingsData.Position.Bottom;
|
||||||
|
case "left":
|
||||||
|
return SettingsData.Position.Left;
|
||||||
|
case "right":
|
||||||
|
return SettingsData.Position.Right;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a bar occupies the specified screen edge
|
||||||
|
function barOccupiesSide(screen, side) {
|
||||||
|
if (!screen)
|
||||||
|
return false;
|
||||||
|
const sidePos = _sideToPosition(side);
|
||||||
|
if (sidePos < 0)
|
||||||
|
return false;
|
||||||
|
const bars = getEnabledBarConfigs();
|
||||||
|
for (var i = 0; i < bars.length; i++) {
|
||||||
|
const bc = bars[i];
|
||||||
|
if (bc.position !== sidePos)
|
||||||
|
continue;
|
||||||
|
const prefs = bc.screenPreferences || ["all"];
|
||||||
|
if (prefs.includes("all") || isScreenInPreferences(screen, prefs))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the dock occupies the specified screen edge.
|
||||||
|
function dockOccupiesSide(side) {
|
||||||
|
if (!showDock)
|
||||||
|
return false;
|
||||||
|
return dockPosition === _sideToPosition(side);
|
||||||
|
}
|
||||||
|
|
||||||
function getScreensSortedByPosition() {
|
function getScreensSortedByPosition() {
|
||||||
const screens = [];
|
const screens = [];
|
||||||
for (var i = 0; i < Quickshell.screens.length; i++) {
|
for (var i = 0; i < Quickshell.screens.length; i++) {
|
||||||
|
|||||||
@@ -569,7 +569,9 @@ var SPEC = {
|
|||||||
shadowOpacity: 60,
|
shadowOpacity: 60,
|
||||||
shadowColorMode: "default",
|
shadowColorMode: "default",
|
||||||
shadowCustomColor: "#000000",
|
shadowCustomColor: "#000000",
|
||||||
clickThrough: false
|
clickThrough: false,
|
||||||
|
hoverPopouts: false,
|
||||||
|
hoverPopoutDelay: 150
|
||||||
}], onChange: "updateBarConfigs"
|
}], onChange: "updateBarConfigs"
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -642,6 +644,7 @@ var SPEC = {
|
|||||||
frameCloseGaps: { def: true },
|
frameCloseGaps: { def: true },
|
||||||
frameLauncherEmergeSide: { def: "bottom" },
|
frameLauncherEmergeSide: { def: "bottom" },
|
||||||
frameLauncherArcExtender: { def: false },
|
frameLauncherArcExtender: { def: false },
|
||||||
|
frameLauncherEdgeHover: { def: false },
|
||||||
frameMode: { def: "connected" },
|
frameMode: { def: "connected" },
|
||||||
barInsetPaddingShared: { def: -1 },
|
barInsetPaddingShared: { def: -1 },
|
||||||
barInsetPaddingSyncAll: { def: false },
|
barInsetPaddingSyncAll: { def: false },
|
||||||
|
|||||||
@@ -233,6 +233,12 @@ Item {
|
|||||||
sourceComponent: Frame {}
|
sourceComponent: Frame {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: SettingsData.frameEnabled && SettingsData.frameLauncherEdgeHover
|
||||||
|
asynchronous: false
|
||||||
|
sourceComponent: FrameLauncherHoverZone {}
|
||||||
|
}
|
||||||
|
|
||||||
DeferredAction {
|
DeferredAction {
|
||||||
id: frameSurfaceReloadAction
|
id: frameSurfaceReloadAction
|
||||||
onTriggered: root.frameSurfacesLoaded = true
|
onTriggered: root.frameSurfacesLoaded = true
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Item {
|
|||||||
readonly property string resolvedConnectedBarSide: impl.item ? (impl.item.resolvedConnectedBarSide ?? "") : ""
|
readonly property string resolvedConnectedBarSide: impl.item ? (impl.item.resolvedConnectedBarSide ?? "") : ""
|
||||||
readonly property bool launcherArcExtenderActive: impl.item ? (impl.item.launcherArcExtenderActive ?? false) : false
|
readonly property bool launcherArcExtenderActive: impl.item ? (impl.item.launcherArcExtenderActive ?? false) : false
|
||||||
property bool triggerUsesOverlayLayer: false
|
property bool triggerUsesOverlayLayer: false
|
||||||
|
property bool edgeHoverManaged: false
|
||||||
|
|
||||||
signal dialogClosed
|
signal dialogClosed
|
||||||
|
|
||||||
|
|||||||
@@ -394,6 +394,8 @@ Item {
|
|||||||
closeCleanupTimer.stop();
|
closeCleanupTimer.stop();
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
|
_edgeArmed = false;
|
||||||
|
_edgeBodyHover = false;
|
||||||
|
|
||||||
animationsEnabled = false;
|
animationsEnabled = false;
|
||||||
|
|
||||||
@@ -447,6 +449,9 @@ Item {
|
|||||||
|
|
||||||
keyboardActive = false;
|
keyboardActive = false;
|
||||||
spotlightOpen = false;
|
spotlightOpen = false;
|
||||||
|
_edgeRetractGrace.stop();
|
||||||
|
_edgeArmed = false;
|
||||||
|
_edgeBodyHover = false;
|
||||||
ModalManager.closeModal(modalHandle);
|
ModalManager.closeModal(modalHandle);
|
||||||
closeCleanupTimer.start();
|
closeCleanupTimer.start();
|
||||||
}
|
}
|
||||||
@@ -489,6 +494,31 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles hover dismissal grace periods for edge-hover sessions w/cursor
|
||||||
|
readonly property bool _edgeRetractEnabled: (modalHandle && modalHandle.edgeHoverManaged === true) && spotlightOpen && !isClosing
|
||||||
|
property bool _edgeBodyHover: false
|
||||||
|
property bool _edgeArmed: false
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: _edgeRetractGrace
|
||||||
|
interval: 150
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (root._edgeRetractEnabled && root._edgeArmed && !root._edgeBodyHover)
|
||||||
|
root.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _onEdgeBodyHoverChanged(over) {
|
||||||
|
root._edgeBodyHover = over;
|
||||||
|
if (over) {
|
||||||
|
root._edgeArmed = true;
|
||||||
|
_edgeRetractGrace.stop();
|
||||||
|
} else if (root._edgeRetractEnabled) {
|
||||||
|
_edgeRetractGrace.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: spotlightContent?.controller ?? null
|
target: spotlightContent?.controller ?? null
|
||||||
function onModeChanged(mode, userInitiated) {
|
function onModeChanged(mode, userInitiated) {
|
||||||
@@ -628,6 +658,13 @@ Item {
|
|||||||
width: root.alignedWidth
|
width: root.alignedWidth
|
||||||
height: root.contentSurfaceHeight
|
height: root.contentSurfaceHeight
|
||||||
|
|
||||||
|
// Passive tracker for edge-hover dismissal that preserves input events.
|
||||||
|
HoverHandler {
|
||||||
|
id: edgeBodyHoverHandler
|
||||||
|
enabled: root._edgeRetractEnabled
|
||||||
|
onHoveredChanged: root._onEdgeBodyHoverChanged(hovered)
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.spotlightOpen
|
enabled: root.spotlightOpen
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ DankPopout {
|
|||||||
property bool anyModalOpen: credentialsPromptOpen || wifiPasswordModalOpen || polkitModalOpen || powerMenuOpen
|
property bool anyModalOpen: credentialsPromptOpen || wifiPasswordModalOpen || polkitModalOpen || powerMenuOpen
|
||||||
|
|
||||||
backgroundInteractive: !anyModalOpen
|
backgroundInteractive: !anyModalOpen
|
||||||
|
hoverDismissSuspended: editMode || anyModalOpen
|
||||||
|
|
||||||
onCredentialsPromptOpenChanged: {
|
onCredentialsPromptOpenChanged: {
|
||||||
if (credentialsPromptOpen && shouldBeVisible)
|
if (credentialsPromptOpen && shouldBeVisible)
|
||||||
|
|||||||
@@ -95,6 +95,14 @@ Item {
|
|||||||
enableFrameInsetAnimation.schedule();
|
enableFrameInsetAnimation.schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: topBarContent._hasBarWindow ? topBarContent.barWindow.axis : null
|
||||||
|
|
||||||
|
function onEdgeChanged() {
|
||||||
|
topBarContent.resetHoverForBarGeometryChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on anchors.leftMargin {
|
Behavior on anchors.leftMargin {
|
||||||
enabled: _animateFrameInsets && _usesFrameBarChrome
|
enabled: _animateFrameInsets && _usesFrameBarChrome
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
@@ -380,6 +388,173 @@ Item {
|
|||||||
return "left";
|
return "left";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DankBarHoverController {
|
||||||
|
id: hoverController
|
||||||
|
barContent: topBarContent
|
||||||
|
barWindow: topBarContent.barWindow
|
||||||
|
barConfig: topBarContent.barConfig
|
||||||
|
hLeftSection: topBarContent.hLeftSection
|
||||||
|
hCenterSection: topBarContent.hCenterSection
|
||||||
|
hRightSection: topBarContent.hRightSection
|
||||||
|
vLeftSection: topBarContent.vLeftSection
|
||||||
|
vCenterSection: topBarContent.vCenterSection
|
||||||
|
vRightSection: topBarContent.vRightSection
|
||||||
|
leftWidgetsModel: topBarContent.leftWidgetsModel
|
||||||
|
centerWidgetsModel: topBarContent.centerWidgetsModel
|
||||||
|
rightWidgetsModel: topBarContent.rightWidgetsModel
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property string activeHoverTrigger: hoverController.activeHoverTrigger
|
||||||
|
readonly property bool hoverPopoutsEnabled: hoverController.hoverPopoutsEnabled
|
||||||
|
|
||||||
|
function queueHoverPopout(gx, gy) {
|
||||||
|
hoverController.queueHoverPoint(gx, gy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHoverPopout(gx, gy) {
|
||||||
|
hoverController.checkHoverPopout(gx, gy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findWidgetAtGlobalPoint(gx, gy) {
|
||||||
|
return hoverController.findWidgetAtGlobalPoint(gx, gy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleHoverClose(gx, gy) {
|
||||||
|
hoverController.scheduleHoverClose(gx, gy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHoverBarHovered(hovered) {
|
||||||
|
hoverController.updateBarHovered(hovered);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetHoverForBarGeometryChange() {
|
||||||
|
hoverController.resetForBarGeometryChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _dashTriggerSource(section, tabIndex) {
|
||||||
|
return hoverController.dashTriggerSource(section, tabIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBarPosition() {
|
||||||
|
return barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveWidgetTriggerGeometry(widgetItem, section, opts) {
|
||||||
|
opts = opts || {};
|
||||||
|
if (opts.useCenterSection && section === "center") {
|
||||||
|
const centerSection = barWindow.isVertical ? vCenterSection : hCenterSection;
|
||||||
|
if (centerSection) {
|
||||||
|
if (barWindow.isVertical) {
|
||||||
|
const centerY = centerSection.height / 2;
|
||||||
|
return {
|
||||||
|
triggerPos: centerSection.mapToItem(null, 0, centerY),
|
||||||
|
triggerWidth: centerSection.height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
triggerPos: centerSection.mapToItem(null, 0, 0),
|
||||||
|
triggerWidth: centerSection.width
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ref = opts.visualItem || widgetItem.visualContent || widgetItem;
|
||||||
|
const w = opts.triggerWidth !== undefined ? opts.triggerWidth : (widgetItem.visualWidth !== undefined ? widgetItem.visualWidth : widgetItem.width);
|
||||||
|
return {
|
||||||
|
triggerPos: ref.mapToItem(null, 0, 0),
|
||||||
|
triggerWidth: w
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function openWidgetPopout(spec) {
|
||||||
|
if (!spec?.loader)
|
||||||
|
return false;
|
||||||
|
spec.loader.active = true;
|
||||||
|
|
||||||
|
let popout = _resolvePopoutFromLoader(spec.loader);
|
||||||
|
if (!popout) {
|
||||||
|
_queuePopoutLoaderOpen(spec);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return _finishWidgetPopoutOpen(spec, popout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _resolvePopoutFromLoader(loader) {
|
||||||
|
if (!loader)
|
||||||
|
return null;
|
||||||
|
if (loader.item)
|
||||||
|
return loader.item;
|
||||||
|
|
||||||
|
const pairs = [[PopoutService.appDrawerLoader, PopoutService.appDrawerPopout], [PopoutService.batteryPopoutLoader, PopoutService.batteryPopout], [PopoutService.clipboardHistoryPopoutLoader, PopoutService.clipboardHistoryPopout], [PopoutService.controlCenterLoader, PopoutService.controlCenterPopout], [PopoutService.dankDashPopoutLoader, PopoutService.dankDashPopout], [PopoutService.layoutPopoutLoader, PopoutService.layoutPopout], [PopoutService.notificationCenterLoader, PopoutService.notificationCenterPopout], [PopoutService.processListPopoutLoader, PopoutService.processListPopout], [PopoutService.systemUpdateLoader, PopoutService.systemUpdatePopout], [PopoutService.vpnPopoutLoader, PopoutService.vpnPopout]];
|
||||||
|
for (let i = 0; i < pairs.length; i++) {
|
||||||
|
if (loader === pairs[i][0] && pairs[i][1])
|
||||||
|
return pairs[i][1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
property var _pendingPopoutOpenSpec: null
|
||||||
|
|
||||||
|
function _queuePopoutLoaderOpen(spec) {
|
||||||
|
if (_pendingPopoutOpenSpec && _pendingPopoutOpenSpec.loader === spec.loader)
|
||||||
|
return;
|
||||||
|
_pendingPopoutOpenSpec = spec;
|
||||||
|
const loader = spec.loader;
|
||||||
|
const onLoaded = function () {
|
||||||
|
if (!loader.item)
|
||||||
|
return;
|
||||||
|
if (loader.loaded)
|
||||||
|
loader.loaded.disconnect(onLoaded);
|
||||||
|
const pending = topBarContent._pendingPopoutOpenSpec;
|
||||||
|
if (!pending || pending.loader !== loader)
|
||||||
|
return;
|
||||||
|
topBarContent._pendingPopoutOpenSpec = null;
|
||||||
|
topBarContent._finishWidgetPopoutOpen(pending, loader.item);
|
||||||
|
if (pending.mode === "hover")
|
||||||
|
hoverController.recheckLatestPoint();
|
||||||
|
};
|
||||||
|
if (loader.item) {
|
||||||
|
onLoaded();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (loader.loaded)
|
||||||
|
loader.loaded.connect(onLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _finishWidgetPopoutOpen(spec, popout) {
|
||||||
|
const effectiveBarConfig = barConfig;
|
||||||
|
const barPosition = getBarPosition();
|
||||||
|
const widgetSection = spec.section || "right";
|
||||||
|
const mode = spec.mode || "click";
|
||||||
|
|
||||||
|
if (popout.setBarContext)
|
||||||
|
popout.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
||||||
|
|
||||||
|
if (spec.setTriggerScreen)
|
||||||
|
popout.triggerScreen = barWindow.screen;
|
||||||
|
|
||||||
|
if (popout.setTriggerPosition && spec.widgetItem) {
|
||||||
|
const geom = resolveWidgetTriggerGeometry(spec.widgetItem, widgetSection, {
|
||||||
|
useCenterSection: spec.useCenterSection,
|
||||||
|
visualItem: spec.visualItem,
|
||||||
|
triggerWidth: spec.triggerWidth
|
||||||
|
});
|
||||||
|
if (geom.triggerPos) {
|
||||||
|
const pos = SettingsData.getPopupTriggerPosition(geom.triggerPos, barWindow.screen, barWindow.effectiveBarThickness, geom.triggerWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
||||||
|
popout.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof popout.prepareForTrigger === "function")
|
||||||
|
popout.prepareForTrigger(spec.triggerSource, mode);
|
||||||
|
|
||||||
|
if (spec.prepare)
|
||||||
|
spec.prepare(popout);
|
||||||
|
|
||||||
|
const request = mode === "hover" ? PopoutManager.requestHoverPopout : PopoutManager.requestPopout;
|
||||||
|
request(popout, spec.tabIndex, spec.triggerSource);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var widgetVisibility: ({
|
readonly property var widgetVisibility: ({
|
||||||
"cpuUsage": DgopService.dgopAvailable,
|
"cpuUsage": DgopService.dgopAvailable,
|
||||||
"memUsage": DgopService.dgopAvailable,
|
"memUsage": DgopService.dgopAvailable,
|
||||||
@@ -702,27 +877,18 @@ Item {
|
|||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
popoutTarget: clipboardHistoryPopoutLoader.item ?? null
|
popoutTarget: clipboardHistoryPopoutLoader.item ?? null
|
||||||
|
|
||||||
function openClipboardPopout(initialTab) {
|
function openClipboardPopout(initialTab, mode) {
|
||||||
clipboardHistoryPopoutLoader.active = true;
|
openWidgetPopout({
|
||||||
if (!clipboardHistoryPopoutLoader.item) {
|
loader: clipboardHistoryPopoutLoader,
|
||||||
return;
|
widgetItem: clipboardWidget,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const popout = clipboardHistoryPopoutLoader.item;
|
triggerSource: "clipboard",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
mode: mode || "click",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
prepare: popout => {
|
||||||
if (popout.setBarContext) {
|
if (initialTab)
|
||||||
popout.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
popout.activeTab = initialTab;
|
||||||
}
|
}
|
||||||
if (popout.setTriggerPosition) {
|
});
|
||||||
const globalPos = clipboardWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, clipboardWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
popout.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
if (initialTab) {
|
|
||||||
popout.activeTab = initialTab;
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(popout, undefined, "clipboard");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClipboardClicked: openClipboardPopout("recents")
|
onClipboardClicked: openClipboardPopout("recents")
|
||||||
@@ -821,9 +987,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (!_preparePopout())
|
topBarContent.openWidgetPopout({
|
||||||
return;
|
loader: appDrawerLoader,
|
||||||
PopoutManager.requestPopout(appDrawerLoader.item, undefined, "appDrawer");
|
widgetItem: launcherButton,
|
||||||
|
section: launcherButton.section,
|
||||||
|
triggerSource: "appDrawer",
|
||||||
|
mode: "click",
|
||||||
|
visualItem: launcherButton
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -890,6 +1061,7 @@ Item {
|
|||||||
id: clockComponent
|
id: clockComponent
|
||||||
|
|
||||||
Clock {
|
Clock {
|
||||||
|
id: clockWidget
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
compactMode: topBarContent.overlapping
|
compactMode: topBarContent.overlapping
|
||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
@@ -909,43 +1081,17 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClockClicked: {
|
onClockClicked: {
|
||||||
dankDashPopoutLoader.active = true;
|
const section = topBarContent.getWidgetSection(parent) || "center";
|
||||||
if (dankDashPopoutLoader.item) {
|
topBarContent.openWidgetPopout({
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
loader: dankDashPopoutLoader,
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
widgetItem: clockWidget,
|
||||||
if (dankDashPopoutLoader.item.setBarContext) {
|
section,
|
||||||
dankDashPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
tabIndex: 0,
|
||||||
}
|
triggerSource: topBarContent._dashTriggerSource(section, 0),
|
||||||
if (dankDashPopoutLoader.item.setTriggerPosition) {
|
mode: "click",
|
||||||
let triggerPos, triggerWidth;
|
useCenterSection: true,
|
||||||
if (section === "center") {
|
setTriggerScreen: true
|
||||||
const centerSection = barWindow.isVertical ? (barWindow.axis?.edge === "left" ? vCenterSection : vCenterSection) : hCenterSection;
|
});
|
||||||
if (centerSection) {
|
|
||||||
if (barWindow.isVertical) {
|
|
||||||
const centerY = centerSection.height / 2;
|
|
||||||
const centerGlobalPos = centerSection.mapToItem(null, 0, centerY);
|
|
||||||
triggerPos = centerGlobalPos;
|
|
||||||
triggerWidth = centerSection.height;
|
|
||||||
} else {
|
|
||||||
const centerGlobalPos = centerSection.mapToItem(null, 0, 0);
|
|
||||||
triggerPos = centerGlobalPos;
|
|
||||||
triggerWidth = centerSection.width;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = visualWidth;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = visualWidth;
|
|
||||||
}
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(triggerPos, barWindow.screen, barWindow.effectiveBarThickness, triggerWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
} else {
|
|
||||||
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 0, (effectiveBarConfig?.id ?? "default") + "-" + section + "-0");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -954,6 +1100,7 @@ Item {
|
|||||||
id: mediaComponent
|
id: mediaComponent
|
||||||
|
|
||||||
Media {
|
Media {
|
||||||
|
id: mediaWidget
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
compactMode: topBarContent.spacingTight || topBarContent.overlapping
|
compactMode: topBarContent.spacingTight || topBarContent.overlapping
|
||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
@@ -962,43 +1109,17 @@ Item {
|
|||||||
popoutTarget: dankDashPopoutLoader.item ?? null
|
popoutTarget: dankDashPopoutLoader.item ?? null
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dankDashPopoutLoader.active = true;
|
const section = topBarContent.getWidgetSection(parent) || "center";
|
||||||
if (dankDashPopoutLoader.item) {
|
topBarContent.openWidgetPopout({
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
loader: dankDashPopoutLoader,
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
widgetItem: mediaWidget,
|
||||||
if (dankDashPopoutLoader.item.setBarContext) {
|
section,
|
||||||
dankDashPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
tabIndex: 1,
|
||||||
}
|
triggerSource: topBarContent._dashTriggerSource(section, 1),
|
||||||
if (dankDashPopoutLoader.item.setTriggerPosition) {
|
mode: "click",
|
||||||
let triggerPos, triggerWidth;
|
useCenterSection: true,
|
||||||
if (section === "center") {
|
setTriggerScreen: true
|
||||||
const centerSection = barWindow.isVertical ? (barWindow.axis?.edge === "left" ? vCenterSection : vCenterSection) : hCenterSection;
|
});
|
||||||
if (centerSection) {
|
|
||||||
if (barWindow.isVertical) {
|
|
||||||
const centerY = centerSection.height / 2;
|
|
||||||
const centerGlobalPos = centerSection.mapToItem(null, 0, centerY);
|
|
||||||
triggerPos = centerGlobalPos;
|
|
||||||
triggerWidth = centerSection.height;
|
|
||||||
} else {
|
|
||||||
const centerGlobalPos = centerSection.mapToItem(null, 0, 0);
|
|
||||||
triggerPos = centerGlobalPos;
|
|
||||||
triggerWidth = centerSection.width;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = visualWidth;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = visualWidth;
|
|
||||||
}
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(triggerPos, barWindow.screen, barWindow.effectiveBarThickness, triggerWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
} else {
|
|
||||||
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 1, (effectiveBarConfig?.id ?? "default") + "-" + section + "-1");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,6 +1128,7 @@ Item {
|
|||||||
id: weatherComponent
|
id: weatherComponent
|
||||||
|
|
||||||
Weather {
|
Weather {
|
||||||
|
id: weatherWidget
|
||||||
axis: barWindow.axis
|
axis: barWindow.axis
|
||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
@@ -1014,47 +1136,17 @@ Item {
|
|||||||
popoutTarget: dankDashPopoutLoader.item ?? null
|
popoutTarget: dankDashPopoutLoader.item ?? null
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dankDashPopoutLoader.active = true;
|
const section = topBarContent.getWidgetSection(parent) || "center";
|
||||||
if (dankDashPopoutLoader.item) {
|
topBarContent.openWidgetPopout({
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
loader: dankDashPopoutLoader,
|
||||||
// Calculate barPosition from axis.edge
|
widgetItem: weatherWidget,
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
section,
|
||||||
if (dankDashPopoutLoader.item.setBarContext) {
|
tabIndex: 3,
|
||||||
dankDashPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
triggerSource: topBarContent._dashTriggerSource(section, 3),
|
||||||
}
|
mode: "click",
|
||||||
if (dankDashPopoutLoader.item.setTriggerPosition) {
|
useCenterSection: true,
|
||||||
// For center section widgets, use center section bounds for DankDash centering
|
setTriggerScreen: true
|
||||||
let triggerPos, triggerWidth;
|
});
|
||||||
if (section === "center") {
|
|
||||||
const centerSection = barWindow.isVertical ? (barWindow.axis?.edge === "left" ? vCenterSection : vCenterSection) : hCenterSection;
|
|
||||||
if (centerSection) {
|
|
||||||
// For vertical bars, use center Y of section; for horizontal, use left edge
|
|
||||||
if (barWindow.isVertical) {
|
|
||||||
const centerY = centerSection.height / 2;
|
|
||||||
const centerGlobalPos = centerSection.mapToItem(null, 0, centerY);
|
|
||||||
triggerPos = centerGlobalPos;
|
|
||||||
triggerWidth = centerSection.height;
|
|
||||||
} else {
|
|
||||||
// For horizontal bars, use left edge (DankPopout will center it)
|
|
||||||
const centerGlobalPos = centerSection.mapToItem(null, 0, 0);
|
|
||||||
triggerPos = centerGlobalPos;
|
|
||||||
triggerWidth = centerSection.width;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = visualWidth;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
triggerPos = visualContent.mapToItem(null, 0, 0);
|
|
||||||
triggerWidth = visualWidth;
|
|
||||||
}
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(triggerPos, barWindow.screen, barWindow.effectiveBarThickness, triggerWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
dankDashPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
} else {
|
|
||||||
dankDashPopoutLoader.item.triggerScreen = barWindow.screen;
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(dankDashPopoutLoader.item, 3, (effectiveBarConfig?.id ?? "default") + "-" + section + "-3");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1100,22 +1192,13 @@ Item {
|
|||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onCpuClicked: {
|
onCpuClicked: {
|
||||||
processListPopoutLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!processListPopoutLoader.item) {
|
loader: processListPopoutLoader,
|
||||||
return;
|
widgetItem: cpuWidget,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
triggerSource: "cpu",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
mode: "click"
|
||||||
if (processListPopoutLoader.item.setBarContext) {
|
});
|
||||||
processListPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
|
||||||
}
|
|
||||||
if (processListPopoutLoader.item.setTriggerPosition) {
|
|
||||||
const globalPos = cpuWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, cpuWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
processListPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(processListPopoutLoader.item, undefined, "cpu");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1133,22 +1216,13 @@ Item {
|
|||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onRamClicked: {
|
onRamClicked: {
|
||||||
processListPopoutLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!processListPopoutLoader.item) {
|
loader: processListPopoutLoader,
|
||||||
return;
|
widgetItem: ramWidget,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
triggerSource: "memory",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
mode: "click"
|
||||||
if (processListPopoutLoader.item.setBarContext) {
|
});
|
||||||
processListPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
|
||||||
}
|
|
||||||
if (processListPopoutLoader.item.setTriggerPosition) {
|
|
||||||
const globalPos = ramWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, ramWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
processListPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(processListPopoutLoader.item, undefined, "memory");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1180,22 +1254,13 @@ Item {
|
|||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onCpuTempClicked: {
|
onCpuTempClicked: {
|
||||||
processListPopoutLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!processListPopoutLoader.item) {
|
loader: processListPopoutLoader,
|
||||||
return;
|
widgetItem: cpuTempWidget,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
triggerSource: "cpu_temp",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
mode: "click"
|
||||||
if (processListPopoutLoader.item.setBarContext) {
|
});
|
||||||
processListPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
|
||||||
}
|
|
||||||
if (processListPopoutLoader.item.setTriggerPosition) {
|
|
||||||
const globalPos = cpuTempWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, cpuTempWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
processListPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(processListPopoutLoader.item, undefined, "cpu_temp");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1213,22 +1278,13 @@ Item {
|
|||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
onGpuTempClicked: {
|
onGpuTempClicked: {
|
||||||
processListPopoutLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!processListPopoutLoader.item) {
|
loader: processListPopoutLoader,
|
||||||
return;
|
widgetItem: gpuTempWidget,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
triggerSource: "gpu_temp",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
mode: "click"
|
||||||
if (processListPopoutLoader.item.setBarContext) {
|
});
|
||||||
processListPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
|
||||||
}
|
|
||||||
if (processListPopoutLoader.item.setTriggerPosition) {
|
|
||||||
const globalPos = gpuTempWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, gpuTempWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
processListPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(processListPopoutLoader.item, undefined, "gpu_temp");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1253,23 +1309,14 @@ Item {
|
|||||||
popoutTarget: notificationCenterLoader.item ?? null
|
popoutTarget: notificationCenterLoader.item ?? null
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onClicked: {
|
onClicked: {
|
||||||
notificationCenterLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!notificationCenterLoader.item) {
|
loader: notificationCenterLoader,
|
||||||
return;
|
widgetItem: notificationButton,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
notificationCenterLoader.item.triggerScreen = barWindow.screen;
|
triggerSource: "notifications",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
mode: "click",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
setTriggerScreen: true
|
||||||
if (notificationCenterLoader.item.setBarContext) {
|
});
|
||||||
notificationCenterLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
|
||||||
}
|
|
||||||
if (notificationCenterLoader.item.setTriggerPosition) {
|
|
||||||
const globalPos = notificationButton.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, notificationButton.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
notificationCenterLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(notificationCenterLoader.item, undefined, "notifications");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1289,22 +1336,13 @@ Item {
|
|||||||
popoutTarget: batteryPopoutLoader.item ?? null
|
popoutTarget: batteryPopoutLoader.item ?? null
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onToggleBatteryPopup: {
|
onToggleBatteryPopup: {
|
||||||
batteryPopoutLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!batteryPopoutLoader.item) {
|
loader: batteryPopoutLoader,
|
||||||
return;
|
widgetItem: batteryWidget,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
triggerSource: "battery",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
mode: "click"
|
||||||
if (batteryPopoutLoader.item.setBarContext) {
|
});
|
||||||
batteryPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
|
||||||
}
|
|
||||||
if (batteryPopoutLoader.item.setTriggerPosition) {
|
|
||||||
const globalPos = batteryWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, batteryWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
batteryPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(batteryPopoutLoader.item, undefined, "battery");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1322,20 +1360,13 @@ Item {
|
|||||||
popoutTarget: layoutPopoutLoader.item ?? null
|
popoutTarget: layoutPopoutLoader.item ?? null
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onToggleLayoutPopup: {
|
onToggleLayoutPopup: {
|
||||||
layoutPopoutLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!layoutPopoutLoader.item)
|
loader: layoutPopoutLoader,
|
||||||
return;
|
widgetItem: layoutWidget,
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
section: topBarContent.getWidgetSection(parent) || "center",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
triggerSource: "layout",
|
||||||
|
mode: "click"
|
||||||
if (layoutPopoutLoader.item.setTriggerPosition) {
|
});
|
||||||
const globalPos = layoutWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, layoutWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "center";
|
|
||||||
layoutPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
PopoutManager.requestPopout(layoutPopoutLoader.item, undefined, "layout");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1355,24 +1386,13 @@ Item {
|
|||||||
popoutTarget: vpnPopoutLoader.item ?? null
|
popoutTarget: vpnPopoutLoader.item ?? null
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
onToggleVpnPopup: {
|
onToggleVpnPopup: {
|
||||||
vpnPopoutLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!vpnPopoutLoader.item)
|
loader: vpnPopoutLoader,
|
||||||
return;
|
widgetItem: vpnWidget,
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
triggerSource: "vpn",
|
||||||
|
mode: "click"
|
||||||
if (vpnPopoutLoader.item.setBarContext) {
|
});
|
||||||
vpnPopoutLoader.item.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vpnPopoutLoader.item.setTriggerPosition) {
|
|
||||||
const globalPos = vpnWidget.mapToItem(null, 0, 0);
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, vpnWidget.width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const widgetSection = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
vpnPopoutLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, widgetSection, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
PopoutManager.requestPopout(vpnPopoutLoader.item, undefined, "vpn");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1381,6 +1401,7 @@ Item {
|
|||||||
id: controlCenterButtonComponent
|
id: controlCenterButtonComponent
|
||||||
|
|
||||||
ControlCenterButton {
|
ControlCenterButton {
|
||||||
|
id: controlCenterButton
|
||||||
isActive: controlCenterLoader.item ? controlCenterLoader.item.shouldBeVisible : false
|
isActive: controlCenterLoader.item ? controlCenterLoader.item.shouldBeVisible : false
|
||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
@@ -1403,25 +1424,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
controlCenterLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!controlCenterLoader.item) {
|
loader: controlCenterLoader,
|
||||||
return;
|
widgetItem: controlCenterButton,
|
||||||
}
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
controlCenterLoader.item.triggerScreen = barWindow.screen;
|
triggerSource: "controlCenter",
|
||||||
if (controlCenterLoader.item.setTriggerPosition) {
|
mode: "click",
|
||||||
const globalPos = mapToItem(null, 0, 0);
|
setTriggerScreen: true
|
||||||
// Use topBarContent.barConfig directly
|
});
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
if (controlCenterLoader.item?.shouldBeVisible && NetworkService.wifiEnabled)
|
||||||
// Calculate barPosition from axis.edge like Battery widget does
|
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, barWindow.screen, barWindow.effectiveBarThickness, width, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
const section = topBarContent.getWidgetSection(parent) || "right";
|
|
||||||
controlCenterLoader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(controlCenterLoader.item, undefined, "controlCenter");
|
|
||||||
if (controlCenterLoader.item.shouldBeVisible && NetworkService.wifiEnabled) {
|
|
||||||
NetworkService.scanWifi();
|
NetworkService.scanWifi();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1531,6 +1543,7 @@ Item {
|
|||||||
id: systemUpdateComponent
|
id: systemUpdateComponent
|
||||||
|
|
||||||
SystemUpdate {
|
SystemUpdate {
|
||||||
|
id: systemUpdateWidget
|
||||||
isActive: systemUpdateLoader.item ? systemUpdateLoader.item.shouldBeVisible : false
|
isActive: systemUpdateLoader.item ? systemUpdateLoader.item.shouldBeVisible : false
|
||||||
widgetThickness: barWindow.widgetThickness
|
widgetThickness: barWindow.widgetThickness
|
||||||
barThickness: barWindow.effectiveBarThickness
|
barThickness: barWindow.effectiveBarThickness
|
||||||
@@ -1549,22 +1562,14 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
systemUpdateLoader.active = true;
|
topBarContent.openWidgetPopout({
|
||||||
if (!systemUpdateLoader.item)
|
loader: systemUpdateLoader,
|
||||||
return;
|
widgetItem: systemUpdateWidget,
|
||||||
const popout = systemUpdateLoader.item;
|
section: topBarContent.getWidgetSection(parent) || "right",
|
||||||
const effectiveBarConfig = topBarContent.barConfig;
|
triggerSource: "systemUpdate",
|
||||||
const barPosition = barWindow.axis?.edge === "left" ? 2 : (barWindow.axis?.edge === "right" ? 3 : (barWindow.axis?.edge === "top" ? 0 : 1));
|
mode: "click",
|
||||||
if (popout.setBarContext) {
|
visualItem: systemUpdateWidget
|
||||||
popout.setBarContext(barPosition, effectiveBarConfig?.bottomGap ?? 0);
|
});
|
||||||
}
|
|
||||||
if (popout.setTriggerPosition) {
|
|
||||||
const globalPos = visualContent.mapToItem(null, 0, 0);
|
|
||||||
const currentScreen = parentScreen || Screen;
|
|
||||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barWindow.effectiveBarThickness, visualWidth, effectiveBarConfig?.spacing ?? 4, barPosition, effectiveBarConfig);
|
|
||||||
popout.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen, barPosition, barWindow.effectiveBarThickness, effectiveBarConfig?.spacing ?? 4, effectiveBarConfig);
|
|
||||||
}
|
|
||||||
PopoutManager.requestPopout(popout, undefined, "systemUpdate");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,938 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var barContent
|
||||||
|
required property var barWindow
|
||||||
|
required property var barConfig
|
||||||
|
required property var hLeftSection
|
||||||
|
required property var hCenterSection
|
||||||
|
required property var hRightSection
|
||||||
|
required property var vLeftSection
|
||||||
|
required property var vCenterSection
|
||||||
|
required property var vRightSection
|
||||||
|
|
||||||
|
property var leftWidgetsModel
|
||||||
|
property var centerWidgetsModel
|
||||||
|
property var rightWidgetsModel
|
||||||
|
|
||||||
|
property string activeHoverTrigger: ""
|
||||||
|
readonly property bool hoverPopoutsEnabled: barConfig?.hoverPopouts ?? false
|
||||||
|
readonly property int hoverPopoutDelay: Math.max(0, barConfig?.hoverPopoutDelay ?? 150)
|
||||||
|
|
||||||
|
property real _lastHoverGlobalX: 0
|
||||||
|
property real _lastHoverGlobalY: 0
|
||||||
|
property bool _hitTestPending: false
|
||||||
|
property bool _barHovered: false
|
||||||
|
property bool _barExitPending: false
|
||||||
|
property var _pendingHoverHit: null
|
||||||
|
property string _pendingHoverTrigger: ""
|
||||||
|
|
||||||
|
property bool _candidateCacheValid: false
|
||||||
|
property var _candidateCache: []
|
||||||
|
property var _candidateWatchers: []
|
||||||
|
property bool _lastLookupWasMiss: false
|
||||||
|
|
||||||
|
width: 0
|
||||||
|
height: 0
|
||||||
|
|
||||||
|
onLeftWidgetsModelChanged: invalidateCandidateCache()
|
||||||
|
onCenterWidgetsModelChanged: invalidateCandidateCache()
|
||||||
|
onRightWidgetsModelChanged: invalidateCandidateCache()
|
||||||
|
|
||||||
|
onHoverPopoutsEnabledChanged: {
|
||||||
|
if (hoverPopoutsEnabled)
|
||||||
|
return;
|
||||||
|
cancelQueuedHitTest();
|
||||||
|
_cancelPendingHover();
|
||||||
|
_hoverCloseTimer.stop();
|
||||||
|
if (hasOpenHoverSurface() && !isActiveHoverSurfacePinned())
|
||||||
|
closeHoverSurfaces();
|
||||||
|
activeHoverTrigger = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: _disconnectCandidateWatchers()
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.barContent
|
||||||
|
|
||||||
|
function onWidthChanged() {
|
||||||
|
root.invalidateCandidateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHeightChanged() {
|
||||||
|
root.invalidateCandidateCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.barWindow
|
||||||
|
|
||||||
|
function onScreenChanged() {
|
||||||
|
root.invalidateCandidateCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: BarWidgetService
|
||||||
|
|
||||||
|
function onWidgetRegistered(_widgetId, screenName) {
|
||||||
|
if (screenName === root.barWindow?.screen?.name)
|
||||||
|
root.invalidateCandidateCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onWidgetUnregistered(_widgetId, screenName) {
|
||||||
|
if (screenName === root.barWindow?.screen?.name)
|
||||||
|
root.invalidateCandidateCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameAnimation {
|
||||||
|
running: root._hitTestPending
|
||||||
|
onTriggered: {
|
||||||
|
root._hitTestPending = false;
|
||||||
|
root.checkHoverPopout(root._lastHoverGlobalX, root._lastHoverGlobalY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: _hoverIntentTimer
|
||||||
|
interval: root.hoverPopoutDelay
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root._commitPendingHover()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grace timer to prevent flicker when crossing gaps.
|
||||||
|
Timer {
|
||||||
|
id: _hoverCloseTimer
|
||||||
|
interval: 120
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root._commitHoverClose()
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueHoverPoint(gx, gy) {
|
||||||
|
_lastHoverGlobalX = gx;
|
||||||
|
_lastHoverGlobalY = gy;
|
||||||
|
_barHovered = true;
|
||||||
|
_barExitPending = false;
|
||||||
|
PopoutManager.updateHoverCursor(gx, gy);
|
||||||
|
if (hoverPopoutsEnabled)
|
||||||
|
_hitTestPending = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBarHovered(hovered) {
|
||||||
|
_barHovered = hovered;
|
||||||
|
if (hovered) {
|
||||||
|
_barExitPending = false;
|
||||||
|
_hoverCloseTimer.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelQueuedHitTest();
|
||||||
|
_cancelPendingHover();
|
||||||
|
if (!hoverPopoutsEnabled || isActiveHoverSurfacePinned())
|
||||||
|
return;
|
||||||
|
_barExitPending = true;
|
||||||
|
_hoverCloseTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelQueuedHitTest() {
|
||||||
|
_hitTestPending = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function recheckLatestPoint() {
|
||||||
|
checkHoverPopout(_lastHoverGlobalX, _lastHoverGlobalY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForBarGeometryChange() {
|
||||||
|
invalidateCandidateCache();
|
||||||
|
cancelQueuedHitTest();
|
||||||
|
_cancelPendingHover();
|
||||||
|
_hoverCloseTimer.stop();
|
||||||
|
barContent._pendingPopoutOpenSpec = null;
|
||||||
|
|
||||||
|
const activePopout = PopoutManager.getActivePopout(barWindow?.screen);
|
||||||
|
const hasTransientSurface = activeHoverTrigger !== "" || activePopout?.hoverDismissEnabled === true;
|
||||||
|
if (hasTransientSurface && !isActiveHoverSurfacePinned())
|
||||||
|
closeHoverSurfaces();
|
||||||
|
else
|
||||||
|
activeHoverTrigger = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidateCandidateCache() {
|
||||||
|
_candidateCacheValid = false;
|
||||||
|
_candidateCache = [];
|
||||||
|
_lastLookupWasMiss = false;
|
||||||
|
_disconnectCandidateWatchers();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _disconnectCandidateWatchers() {
|
||||||
|
const watchers = _candidateWatchers;
|
||||||
|
_candidateWatchers = [];
|
||||||
|
for (let i = 0; i < watchers.length; i++) {
|
||||||
|
const watcher = watchers[i];
|
||||||
|
try {
|
||||||
|
const signal = watcher.object?.[watcher.signalName];
|
||||||
|
if (signal && typeof signal.disconnect === "function")
|
||||||
|
signal.disconnect(watcher.callback);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _watchCandidateObject(object) {
|
||||||
|
if (!object)
|
||||||
|
return;
|
||||||
|
for (let i = 0; i < _candidateWatchers.length; i++) {
|
||||||
|
if (_candidateWatchers[i].object === object)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const signalNames = ["xChanged", "yChanged", "widthChanged", "heightChanged", "visibleChanged", "parentChanged", "childrenChanged", "itemChanged", "activeChanged", "destroyed"];
|
||||||
|
for (let i = 0; i < signalNames.length; i++) {
|
||||||
|
const signalName = signalNames[i];
|
||||||
|
try {
|
||||||
|
const signal = object[signalName];
|
||||||
|
if (!signal || typeof signal.connect !== "function")
|
||||||
|
continue;
|
||||||
|
const callback = function () {
|
||||||
|
root.invalidateCandidateCache();
|
||||||
|
};
|
||||||
|
signal.connect(callback);
|
||||||
|
_candidateWatchers.push({
|
||||||
|
object,
|
||||||
|
signalName,
|
||||||
|
callback
|
||||||
|
});
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getBarSections() {
|
||||||
|
if (barWindow.isVertical) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
section: vLeftSection,
|
||||||
|
name: "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: vCenterSection,
|
||||||
|
name: "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: vRightSection,
|
||||||
|
name: "right"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
section: hLeftSection,
|
||||||
|
name: "left"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: hCenterSection,
|
||||||
|
name: "center"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
section: hRightSection,
|
||||||
|
name: "right"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// The widget registry is keyed by (widgetId, screenName)
|
||||||
|
function _itemBelongsToThisBar(item) {
|
||||||
|
const owner = barContent;
|
||||||
|
if (!owner || !item)
|
||||||
|
return true;
|
||||||
|
let node = item;
|
||||||
|
let guard = 0;
|
||||||
|
while (node && guard < 100) {
|
||||||
|
if (node === owner)
|
||||||
|
return true;
|
||||||
|
node = node.parent;
|
||||||
|
guard++;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _findWidgetHostInWrapper(wrapper) {
|
||||||
|
if (wrapper.widgetId !== undefined)
|
||||||
|
return wrapper;
|
||||||
|
const children = wrapper.children || [];
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
if (children[i].widgetId !== undefined)
|
||||||
|
return children[i];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _collectSectionWrappers(section) {
|
||||||
|
_watchCandidateObject(section);
|
||||||
|
const layoutLoader = section.widgetLayoutLoader;
|
||||||
|
_watchCandidateObject(layoutLoader);
|
||||||
|
const layout = layoutLoader?.item;
|
||||||
|
if (layout) {
|
||||||
|
_watchCandidateObject(layout);
|
||||||
|
return layout.children || [];
|
||||||
|
}
|
||||||
|
const children = section.children || [];
|
||||||
|
const wrappers = [];
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
if (!child || child === layoutLoader)
|
||||||
|
continue;
|
||||||
|
if (child.itemData !== undefined || child.widgetId !== undefined || _findWidgetHostInWrapper(child))
|
||||||
|
wrappers.push(child);
|
||||||
|
}
|
||||||
|
return wrappers;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _widgetSupportsHoverPopout(widgetId, widgetItem) {
|
||||||
|
if (!widgetId || !widgetItem)
|
||||||
|
return false;
|
||||||
|
if (typeof widgetItem.triggerHoverPopout === "function")
|
||||||
|
return true;
|
||||||
|
if (widgetId === "systemTray" && typeof widgetItem.openHoverAtGlobalPoint === "function")
|
||||||
|
return true;
|
||||||
|
switch (widgetId) {
|
||||||
|
case "launcherButton":
|
||||||
|
case "clipboard":
|
||||||
|
case "clock":
|
||||||
|
case "music":
|
||||||
|
case "weather":
|
||||||
|
case "cpuUsage":
|
||||||
|
case "memUsage":
|
||||||
|
case "cpuTemp":
|
||||||
|
case "gpuTemp":
|
||||||
|
case "notificationButton":
|
||||||
|
case "battery":
|
||||||
|
case "layout":
|
||||||
|
case "vpn":
|
||||||
|
case "controlCenterButton":
|
||||||
|
case "systemUpdate":
|
||||||
|
case "notepadButton":
|
||||||
|
case "systemTray":
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _enumerateWidgetHosts() {
|
||||||
|
const hosts = [];
|
||||||
|
const sections = _getBarSections();
|
||||||
|
for (let s = 0; s < sections.length; s++) {
|
||||||
|
const sectionEntry = sections[s];
|
||||||
|
const section = sectionEntry.section;
|
||||||
|
if (!section)
|
||||||
|
continue;
|
||||||
|
const wrappers = _collectSectionWrappers(section);
|
||||||
|
for (let i = 0; i < wrappers.length; i++) {
|
||||||
|
const wrapper = wrappers[i];
|
||||||
|
const host = _findWidgetHostInWrapper(wrapper);
|
||||||
|
if (!host?.widgetId)
|
||||||
|
continue;
|
||||||
|
_watchCandidateObject(wrapper);
|
||||||
|
_watchCandidateObject(host);
|
||||||
|
hosts.push({
|
||||||
|
host,
|
||||||
|
wrapper,
|
||||||
|
section: sectionEntry.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _collectHoverCandidates() {
|
||||||
|
const screenName = barWindow.screen?.name;
|
||||||
|
const candidates = [];
|
||||||
|
const seen = new Set();
|
||||||
|
|
||||||
|
function addCandidate(widgetId, widgetItem, sectionHint) {
|
||||||
|
if (!widgetId || !widgetItem || seen.has(widgetItem))
|
||||||
|
return;
|
||||||
|
if (!root._itemBelongsToThisBar(widgetItem))
|
||||||
|
return;
|
||||||
|
if (!root._widgetSupportsHoverPopout(widgetId, widgetItem))
|
||||||
|
return;
|
||||||
|
if (!root.barContent.getWidgetVisible(widgetId))
|
||||||
|
return;
|
||||||
|
seen.add(widgetItem);
|
||||||
|
candidates.push({
|
||||||
|
widgetId,
|
||||||
|
widgetItem,
|
||||||
|
section: widgetItem.section || sectionHint || "right",
|
||||||
|
wrapper: null,
|
||||||
|
host: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenName) {
|
||||||
|
const registry = BarWidgetService.widgetRegistry;
|
||||||
|
if (registry && typeof registry === "object") {
|
||||||
|
for (const widgetId in registry) {
|
||||||
|
const screenMap = registry[widgetId];
|
||||||
|
if (!screenMap || typeof screenMap !== "object")
|
||||||
|
continue;
|
||||||
|
const widgetItem = screenMap[screenName];
|
||||||
|
if (widgetItem)
|
||||||
|
addCandidate(widgetId, widgetItem, widgetItem.section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hosts = _enumerateWidgetHosts();
|
||||||
|
for (let i = 0; i < hosts.length; i++) {
|
||||||
|
const entry = hosts[i];
|
||||||
|
if (!entry.host?.item)
|
||||||
|
continue;
|
||||||
|
const existing = candidates.find(candidate => candidate.widgetItem === entry.host.item);
|
||||||
|
if (existing) {
|
||||||
|
existing.wrapper = entry.wrapper;
|
||||||
|
existing.host = entry.host;
|
||||||
|
if (!existing.section)
|
||||||
|
existing.section = entry.section;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!_widgetSupportsHoverPopout(entry.host.widgetId, entry.host.item))
|
||||||
|
continue;
|
||||||
|
candidates.push({
|
||||||
|
widgetId: entry.host.widgetId,
|
||||||
|
widgetItem: entry.host.item,
|
||||||
|
section: entry.host.item.section || entry.section,
|
||||||
|
wrapper: entry.wrapper,
|
||||||
|
host: entry.host
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _globalItemBounds(item) {
|
||||||
|
try {
|
||||||
|
const topLeft = item.mapToItem(null, 0, 0);
|
||||||
|
return {
|
||||||
|
x: topLeft.x,
|
||||||
|
y: topLeft.y,
|
||||||
|
width: item.width,
|
||||||
|
height: item.height
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _hitBoundsForWidget(widgetItem, wrapper) {
|
||||||
|
try {
|
||||||
|
if (!widgetItem?.visible)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (widgetItem.visualContent !== undefined) {
|
||||||
|
const visual = widgetItem.visualContent;
|
||||||
|
if (visual && visual.width > 0 && visual.height > 0)
|
||||||
|
return _globalItemBounds(visual);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widgetItem.width > 0 && widgetItem.height > 0)
|
||||||
|
return _globalItemBounds(widgetItem);
|
||||||
|
|
||||||
|
if (wrapper && wrapper.width > 0 && wrapper.height > 0)
|
||||||
|
return _globalItemBounds(wrapper);
|
||||||
|
} catch (e) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pointInBounds(gx, gy, bounds) {
|
||||||
|
return gx >= bounds.x && gx < bounds.x + bounds.width && gy >= bounds.y && gy < bounds.y + bounds.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sameBounds(a, b) {
|
||||||
|
return !!a && !!b && a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _buildCandidateCache() {
|
||||||
|
_disconnectCandidateWatchers();
|
||||||
|
const candidates = _collectHoverCandidates();
|
||||||
|
const cache = [];
|
||||||
|
for (let i = 0; i < candidates.length; i++) {
|
||||||
|
const entry = candidates[i];
|
||||||
|
const bounds = _hitBoundsForWidget(entry.widgetItem, entry.wrapper);
|
||||||
|
_watchCandidateObject(entry.widgetItem);
|
||||||
|
_watchCandidateObject(entry.wrapper);
|
||||||
|
_watchCandidateObject(entry.host);
|
||||||
|
try {
|
||||||
|
_watchCandidateObject(entry.widgetItem?.visualContent);
|
||||||
|
} catch (e) {}
|
||||||
|
if (!bounds || bounds.width <= 0 || bounds.height <= 0)
|
||||||
|
continue;
|
||||||
|
cache.push({
|
||||||
|
widgetId: entry.widgetId,
|
||||||
|
widgetItem: entry.widgetItem,
|
||||||
|
section: entry.section,
|
||||||
|
wrapper: entry.wrapper,
|
||||||
|
bounds
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_candidateCache = cache;
|
||||||
|
_candidateCacheValid = true;
|
||||||
|
_lastLookupWasMiss = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _scanCandidateCache(gx, gy) {
|
||||||
|
let best = null;
|
||||||
|
let bestArea = Infinity;
|
||||||
|
for (let i = 0; i < _candidateCache.length; i++) {
|
||||||
|
const entry = _candidateCache[i];
|
||||||
|
const bounds = entry.bounds;
|
||||||
|
if (!_pointInBounds(gx, gy, bounds))
|
||||||
|
continue;
|
||||||
|
const area = bounds.width * bounds.height;
|
||||||
|
if (area < bestArea) {
|
||||||
|
bestArea = area;
|
||||||
|
best = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _validatedHit(entry, gx, gy) {
|
||||||
|
if (!entry)
|
||||||
|
return null;
|
||||||
|
const liveBounds = _hitBoundsForWidget(entry.widgetItem, entry.wrapper);
|
||||||
|
if (!liveBounds || !_pointInBounds(gx, gy, liveBounds))
|
||||||
|
return null;
|
||||||
|
if (!_sameBounds(entry.bounds, liveBounds))
|
||||||
|
return null;
|
||||||
|
return {
|
||||||
|
widgetId: entry.widgetId,
|
||||||
|
widgetItem: entry.widgetItem,
|
||||||
|
section: entry.section
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function findWidgetAtGlobalPoint(gx, gy) {
|
||||||
|
if (!_candidateCacheValid)
|
||||||
|
_buildCandidateCache();
|
||||||
|
|
||||||
|
let entry = _scanCandidateCache(gx, gy);
|
||||||
|
let hit = _validatedHit(entry, gx, gy);
|
||||||
|
if (entry && !hit) {
|
||||||
|
invalidateCandidateCache();
|
||||||
|
_buildCandidateCache();
|
||||||
|
entry = _scanCandidateCache(gx, gy);
|
||||||
|
hit = _validatedHit(entry, gx, gy);
|
||||||
|
} else if (!entry && !_lastLookupWasMiss) {
|
||||||
|
// One live rebuild on entry into an empty gap covers layout changes whose
|
||||||
|
// source did not expose a QML geometry signal without rescanning every frame.
|
||||||
|
invalidateCandidateCache();
|
||||||
|
_buildCandidateCache();
|
||||||
|
entry = _scanCandidateCache(gx, gy);
|
||||||
|
hit = _validatedHit(entry, gx, gy);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastLookupWasMiss = !hit;
|
||||||
|
return hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dashTriggerSource(section, tabIndex) {
|
||||||
|
return (barConfig?.id ?? "default") + "-" + section + "-" + tabIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _notepadWidgetForScreen() {
|
||||||
|
// Prefer this bar's own enumerated candidates; the registry is screen-keyed and a
|
||||||
|
// sibling bar on the same screen can shadow it.
|
||||||
|
if (!_candidateCacheValid)
|
||||||
|
_buildCandidateCache();
|
||||||
|
for (let i = 0; i < _candidateCache.length; i++) {
|
||||||
|
if (_candidateCache[i].widgetId === "notepadButton")
|
||||||
|
return _candidateCache[i].widgetItem;
|
||||||
|
}
|
||||||
|
const screenName = barWindow?.screen?.name;
|
||||||
|
const fromRegistry = screenName ? BarWidgetService.getWidget("notepadButton", screenName) : null;
|
||||||
|
if (fromRegistry && _itemBelongsToThisBar(fromRegistry))
|
||||||
|
return fromRegistry;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function notepadContainsGlobalPoint(gx, gy) {
|
||||||
|
const instance = _notepadWidgetForScreen()?.notepadInstance;
|
||||||
|
if (!instance?.isVisible || typeof instance.containsGlobalPoint !== "function")
|
||||||
|
return false;
|
||||||
|
return instance.containsGlobalPoint(gx, gy);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActiveHoverSurfacePinned() {
|
||||||
|
if (activeHoverTrigger === "notepadButton") {
|
||||||
|
const instance = _notepadWidgetForScreen()?.notepadInstance;
|
||||||
|
if (instance?.hoverDismissSuspended === true)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return PopoutManager.isActivePopoutPinned(barWindow?.screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cursorOverHoverChain(gx, gy, excludedBarWindow) {
|
||||||
|
if (PopoutManager.cursorOverBar(gx, gy, undefined, excludedBarWindow))
|
||||||
|
return true;
|
||||||
|
const popout = PopoutManager.getActivePopout(barWindow?.screen);
|
||||||
|
if (popout?.containsGlobalPoint?.(gx, gy))
|
||||||
|
return true;
|
||||||
|
if (notepadContainsGlobalPoint(gx, gy))
|
||||||
|
return true;
|
||||||
|
const screenName = barWindow.screen?.name;
|
||||||
|
if (screenName && TrayMenuManager.activeTrayMenus[screenName])
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _closeHoverNotepad() {
|
||||||
|
if (activeHoverTrigger !== "notepadButton")
|
||||||
|
return;
|
||||||
|
const instance = _notepadWidgetForScreen()?.notepadInstance;
|
||||||
|
if (!instance)
|
||||||
|
return;
|
||||||
|
if (instance.hoverDismissEnabled !== undefined)
|
||||||
|
instance.hoverDismissEnabled = false;
|
||||||
|
if (typeof instance.hideFromHoverDismiss === "function")
|
||||||
|
instance.hideFromHoverDismiss();
|
||||||
|
else if (typeof instance.hide === "function")
|
||||||
|
instance.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeHoverSurfaces() {
|
||||||
|
_closeHoverNotepad();
|
||||||
|
activeHoverTrigger = "";
|
||||||
|
PopoutManager.closePopoutForScreen(barWindow?.screen);
|
||||||
|
TrayMenuManager.closeAllMenus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _beginSupersededCloseForActive() {
|
||||||
|
const popout = PopoutManager.getActivePopout(barWindow?.screen);
|
||||||
|
if (popout && typeof popout.beginSupersededClose === "function")
|
||||||
|
popout.beginSupersededClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
function openNotepadHover(widgetItem) {
|
||||||
|
const instance = widgetItem.prepareNotepadInstance?.(widgetItem.notepadInstance) ?? widgetItem.notepadInstance;
|
||||||
|
if (!instance || typeof instance.show !== "function")
|
||||||
|
return false;
|
||||||
|
if (instance.hoverDismissEnabled !== undefined)
|
||||||
|
instance.hoverDismissEnabled = true;
|
||||||
|
instance.show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _syncHoverTriggerState() {
|
||||||
|
if (activeHoverTrigger === "notepadButton") {
|
||||||
|
const instance = _notepadWidgetForScreen()?.notepadInstance;
|
||||||
|
if (!instance?.isVisible)
|
||||||
|
activeHoverTrigger = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (activeHoverTrigger !== "" && !hasOpenHoverSurface())
|
||||||
|
activeHoverTrigger = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasOpenHoverSurface() {
|
||||||
|
if (activeHoverTrigger === "")
|
||||||
|
return false;
|
||||||
|
if (activeHoverTrigger === "notepadButton") {
|
||||||
|
const instance = _notepadWidgetForScreen()?.notepadInstance;
|
||||||
|
return instance?.isVisible ?? false;
|
||||||
|
}
|
||||||
|
if (activeHoverTrigger.startsWith("tray-")) {
|
||||||
|
const screenName = barWindow.screen?.name;
|
||||||
|
return !!(screenName && TrayMenuManager.activeTrayMenus[screenName]);
|
||||||
|
}
|
||||||
|
const popout = PopoutManager.getActivePopout(barWindow?.screen);
|
||||||
|
if (!popout)
|
||||||
|
return false;
|
||||||
|
if (popout.dashVisible !== undefined)
|
||||||
|
return !!popout.dashVisible || !!popout.isClosing;
|
||||||
|
if (popout.notificationHistoryVisible !== undefined)
|
||||||
|
return !!popout.notificationHistoryVisible || !!popout.isClosing;
|
||||||
|
return !!(popout.shouldBeVisible || popout.isClosing);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _loaderForWidgetId(widgetId) {
|
||||||
|
switch (widgetId) {
|
||||||
|
case "launcherButton":
|
||||||
|
return PopoutService.appDrawerLoader;
|
||||||
|
case "clipboard":
|
||||||
|
return PopoutService.clipboardHistoryPopoutLoader;
|
||||||
|
case "clock":
|
||||||
|
case "music":
|
||||||
|
case "weather":
|
||||||
|
return PopoutService.dankDashPopoutLoader;
|
||||||
|
case "cpuUsage":
|
||||||
|
case "memUsage":
|
||||||
|
case "cpuTemp":
|
||||||
|
case "gpuTemp":
|
||||||
|
return PopoutService.processListPopoutLoader;
|
||||||
|
case "notificationButton":
|
||||||
|
return PopoutService.notificationCenterLoader;
|
||||||
|
case "battery":
|
||||||
|
return PopoutService.batteryPopoutLoader;
|
||||||
|
case "layout":
|
||||||
|
return PopoutService.layoutPopoutLoader;
|
||||||
|
case "vpn":
|
||||||
|
return PopoutService.vpnPopoutLoader;
|
||||||
|
case "controlCenterButton":
|
||||||
|
return PopoutService.controlCenterLoader;
|
||||||
|
case "systemUpdate":
|
||||||
|
return PopoutService.systemUpdateLoader;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openHoverPopoutForHit(hit) {
|
||||||
|
if (!hit?.widgetItem)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const widgetId = hit.widgetId;
|
||||||
|
const widgetItem = hit.widgetItem;
|
||||||
|
const section = hit.section;
|
||||||
|
const base = {
|
||||||
|
widgetItem,
|
||||||
|
section,
|
||||||
|
mode: "hover"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (widgetId === "systemTray") {
|
||||||
|
if (typeof widgetItem.openHoverAtGlobalPoint !== "function")
|
||||||
|
return false;
|
||||||
|
return !!widgetItem.openHoverAtGlobalPoint(hit.globalX, hit.globalY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof widgetItem.triggerHoverPopout === "function") {
|
||||||
|
widgetItem.triggerHoverPopout(hit.widgetId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loader = _loaderForWidgetId(widgetId);
|
||||||
|
switch (widgetId) {
|
||||||
|
case "launcherButton":
|
||||||
|
return barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
triggerSource: "appDrawer",
|
||||||
|
visualItem: widgetItem
|
||||||
|
}));
|
||||||
|
case "clipboard":
|
||||||
|
return barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
triggerSource: "clipboard",
|
||||||
|
prepare: popout => {
|
||||||
|
popout.activeTab = "recents";
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
case "clock":
|
||||||
|
case "music":
|
||||||
|
case "weather":
|
||||||
|
{
|
||||||
|
const tabIndex = widgetId === "clock" ? 0 : (widgetId === "music" ? 1 : 3);
|
||||||
|
return barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
tabIndex,
|
||||||
|
triggerSource: dashTriggerSource(section, tabIndex),
|
||||||
|
useCenterSection: true,
|
||||||
|
setTriggerScreen: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
case "cpuUsage":
|
||||||
|
case "memUsage":
|
||||||
|
case "cpuTemp":
|
||||||
|
case "gpuTemp":
|
||||||
|
{
|
||||||
|
const triggerSources = {
|
||||||
|
cpuUsage: "cpu",
|
||||||
|
memUsage: "memory",
|
||||||
|
cpuTemp: "cpu_temp",
|
||||||
|
gpuTemp: "gpu_temp"
|
||||||
|
};
|
||||||
|
return barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
triggerSource: triggerSources[widgetId]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
case "notificationButton":
|
||||||
|
return barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
triggerSource: "notifications",
|
||||||
|
setTriggerScreen: true
|
||||||
|
}));
|
||||||
|
case "battery":
|
||||||
|
case "layout":
|
||||||
|
case "vpn":
|
||||||
|
{
|
||||||
|
const triggerSources = {
|
||||||
|
battery: "battery",
|
||||||
|
layout: "layout",
|
||||||
|
vpn: "vpn"
|
||||||
|
};
|
||||||
|
return barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
triggerSource: triggerSources[widgetId]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
case "controlCenterButton":
|
||||||
|
if (barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
triggerSource: "controlCenter",
|
||||||
|
setTriggerScreen: true
|
||||||
|
}))) {
|
||||||
|
if (loader.item?.shouldBeVisible && NetworkService.wifiEnabled)
|
||||||
|
NetworkService.scanWifi();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case "systemUpdate":
|
||||||
|
return barContent.openWidgetPopout(Object.assign({}, base, {
|
||||||
|
loader,
|
||||||
|
triggerSource: "systemUpdate",
|
||||||
|
visualItem: widgetItem
|
||||||
|
}));
|
||||||
|
case "notepadButton":
|
||||||
|
return openNotepadHover(widgetItem);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkHoverPopout(gx, gy) {
|
||||||
|
if (!hoverPopoutsEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastHoverGlobalX = gx;
|
||||||
|
_lastHoverGlobalY = gy;
|
||||||
|
PopoutManager.updateHoverCursor(gx, gy);
|
||||||
|
_syncHoverTriggerState();
|
||||||
|
|
||||||
|
if (isActiveHoverSurfacePinned())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const hit = findWidgetAtGlobalPoint(gx, gy);
|
||||||
|
if (!hit) {
|
||||||
|
_cancelPendingHover();
|
||||||
|
scheduleHoverClose(gx, gy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hit.globalX = gx;
|
||||||
|
hit.globalY = gy;
|
||||||
|
|
||||||
|
let triggerKey = hit.widgetId;
|
||||||
|
if (hit.widgetId === "systemTray")
|
||||||
|
triggerKey = hit.widgetItem.hoverTriggerAtGlobalPoint?.(gx, gy) || "";
|
||||||
|
else if (hit.widgetId === "clock")
|
||||||
|
triggerKey = dashTriggerSource(hit.section, 0);
|
||||||
|
else if (hit.widgetId === "music")
|
||||||
|
triggerKey = dashTriggerSource(hit.section, 1);
|
||||||
|
else if (hit.widgetId === "weather")
|
||||||
|
triggerKey = dashTriggerSource(hit.section, 3);
|
||||||
|
|
||||||
|
if (!triggerKey) {
|
||||||
|
_cancelPendingHover();
|
||||||
|
scheduleHoverClose(gx, gy);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hoverCloseTimer.stop();
|
||||||
|
|
||||||
|
if (triggerKey === activeHoverTrigger && hasOpenHoverSurface()) {
|
||||||
|
_cancelPendingHover();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pendingHoverHit = hit;
|
||||||
|
if (_pendingHoverTrigger !== triggerKey) {
|
||||||
|
_pendingHoverTrigger = triggerKey;
|
||||||
|
if (hoverPopoutDelay <= 0)
|
||||||
|
_commitPendingHover();
|
||||||
|
else
|
||||||
|
_hoverIntentTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cancelPendingHover() {
|
||||||
|
_hoverIntentTimer.stop();
|
||||||
|
_pendingHoverHit = null;
|
||||||
|
_pendingHoverTrigger = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _hitTargetsActivePopout(hit) {
|
||||||
|
const active = PopoutManager.getActivePopout(barWindow?.screen);
|
||||||
|
if (!active || !hit)
|
||||||
|
return false;
|
||||||
|
const loader = _loaderForWidgetId(hit.widgetId);
|
||||||
|
if (!loader)
|
||||||
|
return false;
|
||||||
|
return barContent._resolvePopoutFromLoader(loader) === active;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _commitPendingHover() {
|
||||||
|
const hit = _pendingHoverHit;
|
||||||
|
const triggerKey = _pendingHoverTrigger;
|
||||||
|
_pendingHoverHit = null;
|
||||||
|
_pendingHoverTrigger = "";
|
||||||
|
if (!hit || !hoverPopoutsEnabled)
|
||||||
|
return;
|
||||||
|
if (isActiveHoverSurfacePinned())
|
||||||
|
return;
|
||||||
|
if (!PopoutManager.cursorOverBar(_lastHoverGlobalX, _lastHoverGlobalY))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const activePopout = PopoutManager.getActivePopout(barWindow?.screen);
|
||||||
|
const targetLoader = _loaderForWidgetId(hit.widgetId);
|
||||||
|
const targetPopout = barContent._resolvePopoutFromLoader(targetLoader);
|
||||||
|
const managerOwnsTransition = !!(activePopout && targetPopout);
|
||||||
|
|
||||||
|
if (triggerKey !== activeHoverTrigger && activeHoverTrigger !== "" && !_hitTargetsActivePopout(hit)) {
|
||||||
|
if (!managerOwnsTransition) {
|
||||||
|
_beginSupersededCloseForActive();
|
||||||
|
closeHoverSurfaces();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!openHoverPopoutForHit(hit)) {
|
||||||
|
if (activeHoverTrigger !== "")
|
||||||
|
closeHoverSurfaces();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeHoverTrigger = triggerKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleHoverClose(gx, gy) {
|
||||||
|
cancelQueuedHitTest();
|
||||||
|
_cancelPendingHover();
|
||||||
|
_barExitPending = false;
|
||||||
|
if (!hoverPopoutsEnabled)
|
||||||
|
return;
|
||||||
|
if (isActiveHoverSurfacePinned())
|
||||||
|
return;
|
||||||
|
if (cursorOverHoverChain(gx, gy))
|
||||||
|
return;
|
||||||
|
_hoverCloseTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _commitHoverClose() {
|
||||||
|
const gx = PopoutManager.hoverCursorGlobalX;
|
||||||
|
const gy = PopoutManager.hoverCursorGlobalY;
|
||||||
|
if (isActiveHoverSurfacePinned())
|
||||||
|
return;
|
||||||
|
if (_barHovered)
|
||||||
|
return;
|
||||||
|
const excludedBar = _barExitPending ? barWindow : null;
|
||||||
|
if (cursorOverHoverChain(gx, gy, excludedBar))
|
||||||
|
return;
|
||||||
|
_barExitPending = false;
|
||||||
|
closeHoverSurfaces();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -719,6 +719,14 @@ PanelWindow {
|
|||||||
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
|
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
|
||||||
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
|
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
|
||||||
|
|
||||||
|
function containsGlobalPoint(gx, gy, padding) {
|
||||||
|
const pad = padding !== undefined ? padding : 16;
|
||||||
|
if (!inputMask.showing)
|
||||||
|
return false;
|
||||||
|
const topLeft = inputMask.mapToItem(null, 0, 0);
|
||||||
|
return gx >= topLeft.x - pad && gx < topLeft.x + inputMask.width + pad && gy >= topLeft.y - pad && gy < topLeft.y + inputMask.height + pad;
|
||||||
|
}
|
||||||
|
|
||||||
function sectionRect(section, isCenter, _dep) {
|
function sectionRect(section, isCenter, _dep) {
|
||||||
if (!section)
|
if (!section)
|
||||||
return {
|
return {
|
||||||
@@ -1020,7 +1028,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onWheel: wheel => {
|
function processWheel(wheel) {
|
||||||
if (!(barConfig?.scrollEnabled ?? true) || actionInProgress) {
|
if (!(barConfig?.scrollEnabled ?? true) || actionInProgress) {
|
||||||
wheel.accepted = false;
|
wheel.accepted = false;
|
||||||
return;
|
return;
|
||||||
@@ -1089,6 +1097,8 @@ PanelWindow {
|
|||||||
|
|
||||||
wheel.accepted = false;
|
wheel.accepted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onWheel: wheel => processWheel(wheel)
|
||||||
}
|
}
|
||||||
|
|
||||||
DankBarContent {
|
DankBarContent {
|
||||||
@@ -1100,6 +1110,26 @@ PanelWindow {
|
|||||||
centerWidgetsModel: barWindow.centerWidgetsModel
|
centerWidgetsModel: barWindow.centerWidgetsModel
|
||||||
rightWidgetsModel: barWindow.rightWidgetsModel
|
rightWidgetsModel: barWindow.rightWidgetsModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Passive HoverHandler to track cursor without intercepting clicks or scroll events.
|
||||||
|
HoverHandler {
|
||||||
|
id: hoverPopoutHandler
|
||||||
|
enabled: (barConfig?.hoverPopouts ?? false) && !barWindow.clickThroughEnabled
|
||||||
|
|
||||||
|
property real lastGlobalX: 0
|
||||||
|
property real lastGlobalY: 0
|
||||||
|
|
||||||
|
onPointChanged: {
|
||||||
|
const gp = barUnitInset.mapToItem(null, point.position.x, point.position.y);
|
||||||
|
lastGlobalX = gp.x;
|
||||||
|
lastGlobalY = gp.y;
|
||||||
|
topBarContent.queueHoverPopout(gp.x, gp.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
onHoveredChanged: {
|
||||||
|
topBarContent.updateHoverBarHovered(hovered);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Item {
|
|||||||
property bool forceVerticalLayout: false
|
property bool forceVerticalLayout: false
|
||||||
|
|
||||||
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
||||||
|
property alias widgetLayoutLoader: layoutLoader
|
||||||
|
|
||||||
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
|
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
|
||||||
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
|
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Item {
|
|||||||
property bool forceVerticalLayout: false
|
property bool forceVerticalLayout: false
|
||||||
|
|
||||||
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
readonly property bool isVertical: overrideAxisLayout ? forceVerticalLayout : (axis?.isVertical ?? false)
|
||||||
|
property alias widgetLayoutLoader: layoutLoader
|
||||||
|
|
||||||
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
|
implicitHeight: layoutLoader.item ? layoutLoader.item.implicitHeight : 0
|
||||||
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
|
implicitWidth: layoutLoader.item ? layoutLoader.item.implicitWidth : 0
|
||||||
|
|||||||
@@ -2103,4 +2103,53 @@ BasePill {
|
|||||||
return;
|
return;
|
||||||
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
|
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _trayLayoutRoot() {
|
||||||
|
const contentChildren = root.visualContent?.children;
|
||||||
|
if (!contentChildren || contentChildren.length === 0)
|
||||||
|
return null;
|
||||||
|
const contentRoot = contentChildren[0];
|
||||||
|
return contentRoot?.layoutLoader?.item || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _trayHitAtGlobalPoint(gx, gy) {
|
||||||
|
if (!root.visible || root.width <= 0 || root.height <= 0)
|
||||||
|
return null;
|
||||||
|
const local = root.mapFromItem(null, gx, gy);
|
||||||
|
if (local.x < 0 || local.y < 0 || local.x > root.width || local.y > root.height)
|
||||||
|
return null;
|
||||||
|
const layout = _trayLayoutRoot();
|
||||||
|
if (!layout)
|
||||||
|
return null;
|
||||||
|
const layoutLocal = layout.mapFromItem(null, gx, gy);
|
||||||
|
const children = layout.children || [];
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const child = children[i];
|
||||||
|
if (!child.visible || child.width <= 0 || child.height <= 0)
|
||||||
|
continue;
|
||||||
|
if (layoutLocal.x < child.x || layoutLocal.x >= child.x + child.width)
|
||||||
|
continue;
|
||||||
|
if (layoutLocal.y < child.y || layoutLocal.y >= child.y + child.height)
|
||||||
|
continue;
|
||||||
|
if (child.trayItem)
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hoverTriggerAtGlobalPoint(gx, gy) {
|
||||||
|
const hit = _trayHitAtGlobalPoint(gx, gy);
|
||||||
|
if (!hit?.trayItem?.hasMenu)
|
||||||
|
return "";
|
||||||
|
return "tray-" + (hit.trayItem.id || hit.itemKey || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function openHoverAtGlobalPoint(gx, gy) {
|
||||||
|
const hit = _trayHitAtGlobalPoint(gx, gy);
|
||||||
|
if (!hit?.trayItem?.hasMenu)
|
||||||
|
return false;
|
||||||
|
const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
|
||||||
|
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
// Edge strip to trigger launcher hover-reveal when free of panel bars and dock.
|
||||||
|
Variants {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
delegate: Loader {
|
||||||
|
id: zoneLoader
|
||||||
|
|
||||||
|
required property var modelData
|
||||||
|
|
||||||
|
readonly property string emergeSide: SettingsData.frameLauncherEmergeSide || "bottom"
|
||||||
|
readonly property bool eligible: SettingsData.frameEnabled && SettingsData.frameLauncherEdgeHover && Theme.isConnectedEffect && SettingsData.isScreenInPreferences(zoneLoader.modelData, SettingsData.frameScreenPreferences) && CompositorService.usesConnectedFrameChromeForScreen(zoneLoader.modelData) && !SettingsData.barOccupiesSide(zoneLoader.modelData, zoneLoader.emergeSide) && !SettingsData.dockOccupiesSide(zoneLoader.emergeSide)
|
||||||
|
|
||||||
|
active: eligible
|
||||||
|
asynchronous: false
|
||||||
|
|
||||||
|
sourceComponent: PanelWindow {
|
||||||
|
id: zoneWindow
|
||||||
|
|
||||||
|
readonly property bool vertical: zoneLoader.emergeSide === "left" || zoneLoader.emergeSide === "right"
|
||||||
|
readonly property real triggerThickness: Math.max(6, SettingsData.frameThickness)
|
||||||
|
readonly property bool launcherOpen: PopoutService.dankLauncherV2Modal?.spotlightOpen ?? false
|
||||||
|
property bool _openedForCurrentHover: false
|
||||||
|
|
||||||
|
// Hot zone dimensions centered on the emerge edge to cover the launcher footprint.
|
||||||
|
readonly property real _launcherBaseW: SettingsData.dankLauncherV2Size === "micro" ? 500 : (SettingsData.dankLauncherV2Size === "medium" ? 720 : (SettingsData.dankLauncherV2Size === "large" ? 860 : 620))
|
||||||
|
readonly property real _launcherBaseH: SettingsData.dankLauncherV2Size === "micro" ? 480 : (SettingsData.dankLauncherV2Size === "medium" ? 720 : (SettingsData.dankLauncherV2Size === "large" ? 860 : 600))
|
||||||
|
readonly property real screenW: zoneLoader.modelData?.width ?? 0
|
||||||
|
readonly property real screenH: zoneLoader.modelData?.height ?? 0
|
||||||
|
readonly property real spanW: Math.round(Math.min(_launcherBaseW, screenW - 100) * 1.1)
|
||||||
|
readonly property real spanH: Math.round(Math.min(_launcherBaseH, screenH - 100) * 1.1)
|
||||||
|
|
||||||
|
function requestLauncherOpen() {
|
||||||
|
if (launcherOpen || _openedForCurrentHover)
|
||||||
|
return;
|
||||||
|
_openedForCurrentHover = true;
|
||||||
|
PopoutService.openDankLauncherV2(CompositorService.framePeerSurfacesUseOverlayForScreen(zoneLoader.modelData), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
screen: zoneLoader.modelData
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
WlrLayershell.namespace: "dms:frame-launcher-hover"
|
||||||
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
|
||||||
|
|
||||||
|
// Anchor and center the hover zone alignment with the launcher.
|
||||||
|
anchors {
|
||||||
|
top: zoneLoader.emergeSide === "top" || zoneWindow.vertical
|
||||||
|
bottom: zoneLoader.emergeSide === "bottom"
|
||||||
|
left: zoneLoader.emergeSide === "left" || !zoneWindow.vertical
|
||||||
|
right: zoneLoader.emergeSide === "right"
|
||||||
|
}
|
||||||
|
|
||||||
|
margins {
|
||||||
|
left: zoneWindow.vertical ? 0 : Math.max(0, (zoneWindow.screenW - zoneWindow.spanW) / 2)
|
||||||
|
top: zoneWindow.vertical ? Math.max(0, (zoneWindow.screenH - zoneWindow.spanH) / 2) : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
implicitWidth: zoneWindow.vertical ? zoneWindow.triggerThickness : zoneWindow.spanW
|
||||||
|
implicitHeight: zoneWindow.vertical ? zoneWindow.spanH : zoneWindow.triggerThickness
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: edgeHoverArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
|
||||||
|
onContainsMouseChanged: {
|
||||||
|
if (containsMouse)
|
||||||
|
zoneWindow.requestLauncherOpen();
|
||||||
|
else
|
||||||
|
zoneWindow._openedForCurrentHover = false;
|
||||||
|
}
|
||||||
|
onPositionChanged: {
|
||||||
|
if (containsMouse)
|
||||||
|
zoneWindow.requestLauncherOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ Item {
|
|||||||
property bool showSettingsMenu: false
|
property bool showSettingsMenu: false
|
||||||
property string pendingSaveContent: ""
|
property string pendingSaveContent: ""
|
||||||
readonly property bool conflictBannerVisible: currentTab !== null && NotepadStorageService.conflictTabId === currentTab.id
|
readonly property bool conflictBannerVisible: currentTab !== null && NotepadStorageService.conflictTabId === currentTab.id
|
||||||
|
readonly property bool anyModalOpen: fileDialogOpen || confirmationDialogOpen
|
||||||
property var slideout: null
|
property var slideout: null
|
||||||
property bool inPopout: false
|
property bool inPopout: false
|
||||||
property bool surfaceVisible: slideout ? slideout.isVisible : true
|
property bool surfaceVisible: slideout ? slideout.isVisible : true
|
||||||
@@ -50,6 +51,14 @@ Item {
|
|||||||
slideout.suppressOverlayLayer = fileDialogOpen;
|
slideout.suppressOverlayLayer = fileDialogOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: root.slideout
|
||||||
|
property: "hoverDismissSuspended"
|
||||||
|
value: root.anyModalOpen
|
||||||
|
when: root.slideout !== null
|
||||||
|
restoreMode: Binding.RestoreBindingOrValue
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: slideout
|
target: slideout
|
||||||
enabled: slideout !== null
|
enabled: slideout !== null
|
||||||
|
|||||||
@@ -330,6 +330,24 @@ Item {
|
|||||||
pluginPopout.toggle();
|
pluginPopout.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function triggerHoverPopout(widgetHostId) {
|
||||||
|
if (pillClickAction) {
|
||||||
|
triggerPopout();
|
||||||
|
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);
|
||||||
|
PopoutManager.requestHoverPopout(pluginPopout, undefined, widgetHostId || pluginId);
|
||||||
|
}
|
||||||
|
|
||||||
PluginPopout {
|
PluginPopout {
|
||||||
id: pluginPopout
|
id: pluginPopout
|
||||||
contentWidth: root.popoutWidth
|
contentWidth: root.popoutWidth
|
||||||
|
|||||||
@@ -26,6 +26,19 @@ DankPopout {
|
|||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function prepareForTrigger(triggerSource) {
|
||||||
|
switch (triggerSource) {
|
||||||
|
case "memory":
|
||||||
|
DgopService.setSortBy("memory");
|
||||||
|
break;
|
||||||
|
case "cpu":
|
||||||
|
case "cpu_temp":
|
||||||
|
case "gpu_temp":
|
||||||
|
DgopService.setSortBy("cpu");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
popupWidth: Math.round(Theme.fontSizeMedium * 46)
|
popupWidth: Math.round(Theme.fontSizeMedium * 46)
|
||||||
popupHeight: Math.round(Theme.fontSizeMedium * 39)
|
popupHeight: Math.round(Theme.fontSizeMedium * 39)
|
||||||
triggerWidth: 55
|
triggerWidth: 55
|
||||||
|
|||||||
@@ -171,6 +171,8 @@ Item {
|
|||||||
scrollEnabled: defaultBar.scrollEnabled ?? true,
|
scrollEnabled: defaultBar.scrollEnabled ?? true,
|
||||||
scrollXBehavior: defaultBar.scrollXBehavior ?? "column",
|
scrollXBehavior: defaultBar.scrollXBehavior ?? "column",
|
||||||
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace",
|
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace",
|
||||||
|
hoverPopouts: defaultBar.hoverPopouts ?? false,
|
||||||
|
hoverPopoutDelay: defaultBar.hoverPopoutDelay ?? 150,
|
||||||
shadowIntensity: defaultBar.shadowIntensity ?? 0,
|
shadowIntensity: defaultBar.shadowIntensity ?? 0,
|
||||||
shadowOpacity: defaultBar.shadowOpacity ?? 60,
|
shadowOpacity: defaultBar.shadowOpacity ?? 60,
|
||||||
shadowDirectionMode: defaultBar.shadowDirectionMode ?? "inherit",
|
shadowDirectionMode: defaultBar.shadowDirectionMode ?? "inherit",
|
||||||
@@ -1255,6 +1257,50 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleCard {
|
||||||
|
iconName: "touch_app"
|
||||||
|
title: I18n.tr("Hover Popouts")
|
||||||
|
description: I18n.tr("Open widget popouts by hovering over the bar. Moving to another widget switches the popout.")
|
||||||
|
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||||
|
enabled: !(selectedBarConfig?.clickThrough ?? false)
|
||||||
|
opacity: (selectedBarConfig?.clickThrough ?? false) ? 0.5 : 1.0
|
||||||
|
checked: selectedBarConfig?.hoverPopouts ?? false
|
||||||
|
onToggled: checked => SettingsData.updateBarConfig(selectedBarId, {
|
||||||
|
hoverPopouts: checked
|
||||||
|
})
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: selectedBarConfig?.hoverPopouts ?? false
|
||||||
|
leftPadding: Theme.spacingM
|
||||||
|
|
||||||
|
SettingsSliderRow {
|
||||||
|
id: hoverDelaySlider
|
||||||
|
width: parent.width - parent.leftPadding
|
||||||
|
text: I18n.tr("Open Delay")
|
||||||
|
description: I18n.tr("Time to rest on a widget before its popout opens")
|
||||||
|
value: selectedBarConfig?.hoverPopoutDelay ?? 150
|
||||||
|
minimum: 0
|
||||||
|
maximum: 1000
|
||||||
|
unit: "ms"
|
||||||
|
defaultValue: 150
|
||||||
|
onSliderValueChanged: newValue => {
|
||||||
|
SettingsData.updateBarConfig(selectedBarId, {
|
||||||
|
hoverPopoutDelay: newValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: hoverDelaySlider
|
||||||
|
property: "value"
|
||||||
|
value: selectedBarConfig?.hoverPopoutDelay ?? 150
|
||||||
|
restoreMode: Binding.RestoreBinding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsToggleCard {
|
SettingsToggleCard {
|
||||||
iconName: "fit_screen"
|
iconName: "fit_screen"
|
||||||
title: I18n.tr("Maximize Detection")
|
title: I18n.tr("Maximize Detection")
|
||||||
|
|||||||
@@ -357,6 +357,15 @@ Item {
|
|||||||
checked: SettingsData.frameLauncherArcExtender
|
checked: SettingsData.frameLauncherArcExtender
|
||||||
onToggled: checked => SettingsData.set("frameLauncherArcExtender", checked)
|
onToggled: checked => SettingsData.set("frameLauncherArcExtender", checked)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
settingKey: "frameLauncherEdgeHover"
|
||||||
|
tags: ["frame", "connected", "launcher", "hover", "edge", "reveal"]
|
||||||
|
text: I18n.tr("Edge Hover Reveal")
|
||||||
|
description: I18n.tr("Open the launcher by hovering the emerge edge (when free of bar and dock)")
|
||||||
|
checked: SettingsData.frameLauncherEdgeHover
|
||||||
|
onToggled: checked => SettingsData.set("frameLauncherEdgeHover", checked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ Item {
|
|||||||
tags: ["background", "color", "fill", "fit", "custom"]
|
tags: ["background", "color", "fill", "fit", "custom"]
|
||||||
settingKey: "wallpaperBackgroundColorMode"
|
settingKey: "wallpaperBackgroundColorMode"
|
||||||
text: I18n.tr("Background Color")
|
text: I18n.tr("Background Color")
|
||||||
description: I18n.tr("Color shown for areas not covered by wallpaper (e.g. Fit or Pad modes)")
|
description: I18n.tr("Color shown for areas not covered by wallpaper")
|
||||||
visible: root.currentWallpaper !== "" && !root.currentWallpaper.startsWith("#")
|
visible: root.currentWallpaper !== "" && !root.currentWallpaper.startsWith("#")
|
||||||
dropdownWidth: 220
|
dropdownWidth: 220
|
||||||
options: [
|
options: [
|
||||||
|
|||||||
@@ -502,15 +502,26 @@ Singleton {
|
|||||||
property string _dankLauncherV2PendingQuery: ""
|
property string _dankLauncherV2PendingQuery: ""
|
||||||
property string _dankLauncherV2PendingMode: ""
|
property string _dankLauncherV2PendingMode: ""
|
||||||
property bool _dankLauncherV2TriggerUsesOverlayLayer: false
|
property bool _dankLauncherV2TriggerUsesOverlayLayer: false
|
||||||
|
property bool _dankLauncherV2EdgeHoverManaged: false
|
||||||
|
|
||||||
function _setDankLauncherV2TriggerUsesOverlayLayer(value) {
|
function _setDankLauncherV2TriggerUsesOverlayLayer(value) {
|
||||||
_dankLauncherV2TriggerUsesOverlayLayer = value === true;
|
_dankLauncherV2TriggerUsesOverlayLayer = value === true;
|
||||||
|
// Disable edge-hover by default on every open/toggle path unless explicitly enabled.
|
||||||
|
_setDankLauncherV2EdgeHoverManaged(false);
|
||||||
if (dankLauncherV2Modal)
|
if (dankLauncherV2Modal)
|
||||||
dankLauncherV2Modal.triggerUsesOverlayLayer = _dankLauncherV2TriggerUsesOverlayLayer;
|
dankLauncherV2Modal.triggerUsesOverlayLayer = _dankLauncherV2TriggerUsesOverlayLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDankLauncherV2(triggerUsesOverlayLayer) {
|
// Set edgeHoverManaged to enable hover retraction for edge-hover triggered launcher sessions.
|
||||||
|
function _setDankLauncherV2EdgeHoverManaged(value) {
|
||||||
|
_dankLauncherV2EdgeHoverManaged = value === true;
|
||||||
|
if (dankLauncherV2Modal)
|
||||||
|
dankLauncherV2Modal.edgeHoverManaged = _dankLauncherV2EdgeHoverManaged;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDankLauncherV2(triggerUsesOverlayLayer, edgeHoverManaged) {
|
||||||
_setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer);
|
_setDankLauncherV2TriggerUsesOverlayLayer(triggerUsesOverlayLayer);
|
||||||
|
_setDankLauncherV2EdgeHoverManaged(edgeHoverManaged);
|
||||||
if (dankLauncherV2Modal) {
|
if (dankLauncherV2Modal) {
|
||||||
dankLauncherV2Modal.show();
|
dankLauncherV2Modal.show();
|
||||||
} else if (dankLauncherV2ModalLoader) {
|
} else if (dankLauncherV2ModalLoader) {
|
||||||
@@ -591,8 +602,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function _onDankLauncherV2ModalLoaded() {
|
function _onDankLauncherV2ModalLoaded() {
|
||||||
if (dankLauncherV2Modal)
|
if (dankLauncherV2Modal) {
|
||||||
dankLauncherV2Modal.triggerUsesOverlayLayer = _dankLauncherV2TriggerUsesOverlayLayer;
|
dankLauncherV2Modal.triggerUsesOverlayLayer = _dankLauncherV2TriggerUsesOverlayLayer;
|
||||||
|
dankLauncherV2Modal.edgeHoverManaged = _dankLauncherV2EdgeHoverManaged;
|
||||||
|
}
|
||||||
if (_dankLauncherV2WantsOpen) {
|
if (_dankLauncherV2WantsOpen) {
|
||||||
_dankLauncherV2WantsOpen = false;
|
_dankLauncherV2WantsOpen = false;
|
||||||
if (_dankLauncherV2PendingQuery) {
|
if (_dankLauncherV2PendingQuery) {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ Item {
|
|||||||
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
|
property list<real> animationExitCurve: Theme.variantPopoutExitCurve
|
||||||
property bool suspendShadowWhileResizing: false
|
property bool suspendShadowWhileResizing: false
|
||||||
property bool shouldBeVisible: false
|
property bool shouldBeVisible: false
|
||||||
|
property bool hoverDismissEnabled: false
|
||||||
|
property bool hoverDismissSuspended: false
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
property bool backgroundInteractive: true
|
property bool backgroundInteractive: true
|
||||||
property bool contentHandlesKeys: false
|
property bool contentHandlesKeys: false
|
||||||
@@ -82,6 +84,8 @@ Item {
|
|||||||
readonly property real alignedY: impl.item ? impl.item.alignedY : 0
|
readonly property real alignedY: impl.item ? impl.item.alignedY : 0
|
||||||
readonly property real alignedWidth: impl.item ? impl.item.alignedWidth : 0
|
readonly property real alignedWidth: impl.item ? impl.item.alignedWidth : 0
|
||||||
readonly property real alignedHeight: impl.item ? impl.item.alignedHeight : 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 maskX: impl.item ? impl.item.maskX : 0
|
||||||
readonly property real maskY: impl.item ? impl.item.maskY : 0
|
readonly property real maskY: impl.item ? impl.item.maskY : 0
|
||||||
readonly property real maskWidth: impl.item ? impl.item.maskWidth : 0
|
readonly property real maskWidth: impl.item ? impl.item.maskWidth : 0
|
||||||
@@ -172,6 +176,36 @@ Item {
|
|||||||
impl.item.close();
|
impl.item.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancelHoverDismiss() {
|
||||||
|
if (impl.item?.cancelHoverDismiss)
|
||||||
|
impl.item.cancelHoverDismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fade out in place during morph switch transitions.
|
||||||
|
function beginSupersededClose() {
|
||||||
|
if (impl.item?.beginSupersededClose)
|
||||||
|
impl.item.beginSupersededClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeFromHoverDismiss() {
|
||||||
|
if (hoverDismissSuspended)
|
||||||
|
return;
|
||||||
|
hoverDismissEnabled = false;
|
||||||
|
// Enable animations using standard Theme-bound popout motion to preserve bindings.
|
||||||
|
if (impl.item)
|
||||||
|
impl.item.animationsEnabled = true;
|
||||||
|
for (const prop of ["dashVisible", "notificationHistoryVisible"]) {
|
||||||
|
if (root[prop] !== undefined) {
|
||||||
|
root[prop] = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (impl.item)
|
||||||
|
impl.item.close();
|
||||||
|
else
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
(shouldBeVisible || _pendingOpen) ? close() : open();
|
(shouldBeVisible || _pendingOpen) ? close() : open();
|
||||||
}
|
}
|
||||||
@@ -210,6 +244,20 @@ Item {
|
|||||||
impl.item.updateSurfacePosition();
|
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 {
|
Loader {
|
||||||
id: impl
|
id: impl
|
||||||
active: root.screen !== null
|
active: root.screen !== null
|
||||||
@@ -261,6 +309,8 @@ Item {
|
|||||||
it.screen = Qt.binding(() => root.screen);
|
it.screen = Qt.binding(() => root.screen);
|
||||||
it.effectiveBarPosition = Qt.binding(() => root.effectiveBarPosition);
|
it.effectiveBarPosition = Qt.binding(() => root.effectiveBarPosition);
|
||||||
it.effectiveBarBottomGap = Qt.binding(() => root.effectiveBarBottomGap);
|
it.effectiveBarBottomGap = Qt.binding(() => root.effectiveBarBottomGap);
|
||||||
|
it.hoverDismissEnabled = Qt.binding(() => root.hoverDismissEnabled);
|
||||||
|
it.hoverDismissSuspended = Qt.binding(() => root.hoverDismissSuspended);
|
||||||
|
|
||||||
it.shouldBeVisible = root.shouldBeVisible;
|
it.shouldBeVisible = root.shouldBeVisible;
|
||||||
if (root._primeContent && typeof it.primeContent === "function")
|
if (root._primeContent && typeof it.primeContent === "function")
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Quickshell
|
|||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -41,6 +42,11 @@ Item {
|
|||||||
property real _chromeAnimTravelX: 1
|
property real _chromeAnimTravelX: 1
|
||||||
property real _chromeAnimTravelY: 1
|
property real _chromeAnimTravelY: 1
|
||||||
property bool _fullSyncQueued: false
|
property bool _fullSyncQueued: false
|
||||||
|
property bool _publishedBodyValid: false
|
||||||
|
property real _publishedBodyX: 0
|
||||||
|
property real _publishedBodyY: 0
|
||||||
|
property real _publishedBodyW: 0
|
||||||
|
property real _publishedBodyH: 0
|
||||||
|
|
||||||
property real storedBarThickness: Theme.barHeight - 4
|
property real storedBarThickness: Theme.barHeight - 4
|
||||||
property real storedBarSpacing: 4
|
property real storedBarSpacing: 4
|
||||||
@@ -130,7 +136,11 @@ Item {
|
|||||||
updateBodyState: function(_name, ownerId, bodyX, bodyY, bodyW, bodyH) {
|
updateBodyState: function(_name, ownerId, bodyX, bodyY, bodyW, bodyH) {
|
||||||
return ConnectedModeState.setPopoutBody(ownerId, bodyX, bodyY, bodyW, bodyH);
|
return ConnectedModeState.setPopoutBody(ownerId, bodyX, bodyY, bodyW, bodyH);
|
||||||
}
|
}
|
||||||
onRecoveryRequested: root._queueFullSync()
|
onClaimIdChanged: root._resetPublishedBody()
|
||||||
|
onRecoveryRequested: {
|
||||||
|
root._resetPublishedBody();
|
||||||
|
root._queueFullSync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
property var _lastOpenedScreen: null
|
property var _lastOpenedScreen: null
|
||||||
@@ -233,11 +243,15 @@ Item {
|
|||||||
const visible = visibleOverride !== undefined ? !!visibleOverride : contentWindow.visible;
|
const visible = visibleOverride !== undefined ? !!visibleOverride : contentWindow.visible;
|
||||||
const presented = contentWindow.visible || root.shouldBeVisible;
|
const presented = contentWindow.visible || root.shouldBeVisible;
|
||||||
const phase = root.isClosing ? "closing" : (!presented ? "hidden" : (!contentWindow.visible && root.shouldBeVisible ? "opening" : "open"));
|
const phase = root.isClosing ? "closing" : (!presented ? "hidden" : (!contentWindow.visible && root.shouldBeVisible ? "opening" : "open"));
|
||||||
|
const bodyX = Theme.snap(root.pubBodyX, root.dpr);
|
||||||
|
const bodyY = Theme.snap(root.pubBodyY, root.dpr);
|
||||||
|
const bodyW = Theme.snap(root.pubBodyW, root.dpr);
|
||||||
|
const bodyH = Theme.snap(root.pubBodyH, root.dpr);
|
||||||
const bodyRect = {
|
const bodyRect = {
|
||||||
"x": root.alignedX,
|
"x": bodyX,
|
||||||
"y": root.renderedAlignedY,
|
"y": bodyY,
|
||||||
"width": root.alignedWidth,
|
"width": bodyW,
|
||||||
"height": root.renderedAlignedHeight
|
"height": bodyH
|
||||||
};
|
};
|
||||||
const animationOffset = {
|
const animationOffset = {
|
||||||
"x": _connectedChromeAnimX(),
|
"x": _connectedChromeAnimX(),
|
||||||
@@ -254,10 +268,10 @@ Item {
|
|||||||
"animationOffset": animationOffset,
|
"animationOffset": animationOffset,
|
||||||
"scale": 1,
|
"scale": 1,
|
||||||
"opacity": Theme.connectedSurfaceColor.a,
|
"opacity": Theme.connectedSurfaceColor.a,
|
||||||
"bodyX": root.alignedX,
|
"bodyX": bodyX,
|
||||||
"bodyY": root.renderedAlignedY,
|
"bodyY": bodyY,
|
||||||
"bodyW": root.alignedWidth,
|
"bodyW": bodyW,
|
||||||
"bodyH": root.renderedAlignedHeight,
|
"bodyH": bodyH,
|
||||||
"animX": animationOffset.x,
|
"animX": animationOffset.x,
|
||||||
"animY": animationOffset.y,
|
"animY": animationOffset.y,
|
||||||
"screen": root.screen ? root.screen.name : "",
|
"screen": root.screen ? root.screen.name : "",
|
||||||
@@ -269,10 +283,15 @@ Item {
|
|||||||
function _publishConnectedChromeState(forceClaim, visibleOverride) {
|
function _publishConnectedChromeState(forceClaim, visibleOverride) {
|
||||||
if (!root.frameOwnsConnectedChrome || !root.screen)
|
if (!root.frameOwnsConnectedChrome || !root.screen)
|
||||||
return false;
|
return false;
|
||||||
return chromeLease.publish(_connectedChromeState(visibleOverride), !!forceClaim);
|
const state = _connectedChromeState(visibleOverride);
|
||||||
|
const published = chromeLease.publish(state, !!forceClaim);
|
||||||
|
if (published)
|
||||||
|
_rememberPublishedBody(state.bodyX, state.bodyY, state.bodyW, state.bodyH);
|
||||||
|
return published;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _releaseConnectedChromeState() {
|
function _releaseConnectedChromeState() {
|
||||||
|
_resetPublishedBody();
|
||||||
chromeLease.release();
|
chromeLease.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +330,26 @@ Item {
|
|||||||
return;
|
return;
|
||||||
if (!contentWindow.visible && !shouldBeVisible)
|
if (!contentWindow.visible && !shouldBeVisible)
|
||||||
return;
|
return;
|
||||||
chromeLease.updateBody(root.alignedX, root.renderedAlignedY, root.alignedWidth, root.renderedAlignedHeight);
|
const bodyX = Theme.snap(root.pubBodyX, root.dpr);
|
||||||
|
const bodyY = Theme.snap(root.pubBodyY, root.dpr);
|
||||||
|
const bodyW = Theme.snap(root.pubBodyW, root.dpr);
|
||||||
|
const bodyH = Theme.snap(root.pubBodyH, root.dpr);
|
||||||
|
if (_publishedBodyValid && _publishedBodyX === bodyX && _publishedBodyY === bodyY && _publishedBodyW === bodyW && _publishedBodyH === bodyH)
|
||||||
|
return;
|
||||||
|
if (chromeLease.updateBody(bodyX, bodyY, bodyW, bodyH))
|
||||||
|
_rememberPublishedBody(bodyX, bodyY, bodyW, bodyH);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _rememberPublishedBody(bodyX, bodyY, bodyW, bodyH) {
|
||||||
|
_publishedBodyX = bodyX;
|
||||||
|
_publishedBodyY = bodyY;
|
||||||
|
_publishedBodyW = bodyW;
|
||||||
|
_publishedBodyH = bodyH;
|
||||||
|
_publishedBodyValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _resetPublishedBody() {
|
||||||
|
_publishedBodyValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool _animSyncQueued: false
|
property bool _animSyncQueued: false
|
||||||
@@ -356,7 +394,10 @@ Item {
|
|||||||
onContentAnimYChanged: _queueAnimSync()
|
onContentAnimYChanged: _queueAnimSync()
|
||||||
onRenderedAlignedYChanged: _queueBodySync()
|
onRenderedAlignedYChanged: _queueBodySync()
|
||||||
onRenderedAlignedHeightChanged: _queueBodySync()
|
onRenderedAlignedHeightChanged: _queueBodySync()
|
||||||
onScreenChanged: _queueFullSync()
|
onScreenChanged: {
|
||||||
|
_resetPublishedBody();
|
||||||
|
_queueFullSync();
|
||||||
|
}
|
||||||
onEffectiveBarPositionChanged: _queueFullSync()
|
onEffectiveBarPositionChanged: _queueFullSync()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -407,14 +448,31 @@ Item {
|
|||||||
onFrameOwnsConnectedChromeChanged: _syncPopoutChromeState()
|
onFrameOwnsConnectedChromeChanged: _syncPopoutChromeState()
|
||||||
|
|
||||||
property bool animationsEnabled: true
|
property bool animationsEnabled: true
|
||||||
|
property bool hoverDismissEnabled: false
|
||||||
|
property bool hoverDismissSuspended: false
|
||||||
|
|
||||||
|
function cancelHoverDismiss() {
|
||||||
|
hoverDismissController.cancelPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeFromHoverDismiss() {
|
||||||
|
if (hoverDismissSuspended || isClosing || !shouldBeVisible)
|
||||||
|
return;
|
||||||
|
if (popoutHandle?.closeFromHoverDismiss)
|
||||||
|
popoutHandle.closeFromHoverDismiss();
|
||||||
|
else
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
function open() {
|
function open() {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
|
_resetPublishedBody();
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
animationsEnabled = false;
|
animationsEnabled = false;
|
||||||
_primeContent = true;
|
_primeContent = true;
|
||||||
|
_supersededClose = false;
|
||||||
|
|
||||||
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
|
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
|
||||||
if (screenChanged) {
|
if (screenChanged) {
|
||||||
@@ -429,6 +487,13 @@ Item {
|
|||||||
_captureChromeAnimTravel();
|
_captureChromeAnimTravel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Seed travel coordinates from the outgoing popout to morph continuously.
|
||||||
|
_beginMorphTravel();
|
||||||
|
|
||||||
|
// Skip emerge animation on morph switch.
|
||||||
|
if (morphTravelEnabled)
|
||||||
|
morph.openProgress = 1;
|
||||||
|
|
||||||
if (root.frameOwnsConnectedChrome) {
|
if (root.frameOwnsConnectedChrome) {
|
||||||
chromeLease.beginClaim();
|
chromeLease.beginClaim();
|
||||||
_publishConnectedChromeState(true, true);
|
_publishConnectedChromeState(true, true);
|
||||||
@@ -456,6 +521,11 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
|
if (_supersededClose && morphTravelEnabled)
|
||||||
|
_freezeMorphTravel();
|
||||||
|
else
|
||||||
|
_endMorphTravel();
|
||||||
|
_resetPublishedBody();
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
shouldBeVisible = false;
|
shouldBeVisible = false;
|
||||||
_primeContent = false;
|
_primeContent = false;
|
||||||
@@ -494,6 +564,7 @@ Item {
|
|||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!shouldBeVisible) {
|
if (!shouldBeVisible) {
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
|
root._endMorphTravel();
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
PopoutManager.hidePopout(popoutHandle);
|
PopoutManager.hidePopout(popoutHandle);
|
||||||
popoutClosed();
|
popoutClosed();
|
||||||
@@ -642,6 +713,108 @@ Item {
|
|||||||
easing.bezierCurve: root.renderedGeometryGrowing ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.renderedGeometryGrowing ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Morph transition coordinates to animate travel between popouts during switch.
|
||||||
|
property bool morphTravelEnabled: false
|
||||||
|
property real morphSeedX: 0
|
||||||
|
property real morphSeedY: 0
|
||||||
|
property real morphSeedW: 0
|
||||||
|
property real morphSeedH: 0
|
||||||
|
property real morphProgress: 1
|
||||||
|
// Distance-scaled duration for morph travel.
|
||||||
|
property int _morphTravelDuration: animationDuration
|
||||||
|
|
||||||
|
Behavior on morphProgress {
|
||||||
|
enabled: root.morphTravelEnabled && root.animationsEnabled
|
||||||
|
NumberAnimation {
|
||||||
|
duration: root._morphTravelDuration
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
// M3 Expressive spatial motion starts with momentum and settles gently,
|
||||||
|
// which keeps rapid hover retargets from pausing between surfaces.
|
||||||
|
easing.bezierCurve: Theme.variantEnterCurve
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property real pubBodyX: morphSeedX + (alignedX - morphSeedX) * morphProgress
|
||||||
|
readonly property real pubBodyY: morphSeedY + (renderedAlignedY - morphSeedY) * morphProgress
|
||||||
|
readonly property real pubBodyW: morphSeedW + (alignedWidth - morphSeedW) * morphProgress
|
||||||
|
readonly property real pubBodyH: morphSeedH + (renderedAlignedHeight - morphSeedH) * morphProgress
|
||||||
|
|
||||||
|
// One animation drives all four coordinates, so queue one coalesced state update
|
||||||
|
// per progress tick instead of reacting independently to each derived property.
|
||||||
|
onMorphProgressChanged: _queueBodySync()
|
||||||
|
|
||||||
|
function _beginMorphTravel() {
|
||||||
|
morphTravelEnabled = false;
|
||||||
|
morphProgress = 1;
|
||||||
|
if (!root.frameOwnsConnectedChrome || !root.screen)
|
||||||
|
return;
|
||||||
|
if (!root.hoverDismissEnabled)
|
||||||
|
return;
|
||||||
|
if (ConnectedModeState.popoutScreen !== root.screen.name)
|
||||||
|
return;
|
||||||
|
if (!ConnectedModeState.popoutOwnerId || ConnectedModeState.popoutOwnerId === chromeLease.claimId)
|
||||||
|
return;
|
||||||
|
const w = ConnectedModeState.popoutBodyW;
|
||||||
|
const h = ConnectedModeState.popoutBodyH;
|
||||||
|
if (!(w > 0 && h > 0))
|
||||||
|
return;
|
||||||
|
morphSeedX = ConnectedModeState.popoutBodyX;
|
||||||
|
morphSeedY = ConnectedModeState.popoutBodyY;
|
||||||
|
morphSeedW = w;
|
||||||
|
morphSeedH = h;
|
||||||
|
// Scale spatial motion with both travel and shape change. Never shorten the
|
||||||
|
// configured enter duration; cap long sweeps so hover switching stays responsive.
|
||||||
|
const base = Math.max(0, Theme.variantDuration(root.animationDuration, true));
|
||||||
|
const travel = Math.hypot(root.alignedX - morphSeedX, root.renderedAlignedY - morphSeedY);
|
||||||
|
const resize = Math.hypot(root.alignedWidth - morphSeedW, root.renderedAlignedHeight - morphSeedH);
|
||||||
|
const spatialDistance = travel + resize * 0.35;
|
||||||
|
_morphTravelDuration = Math.round(Math.min(base * 1.6, base + spatialDistance * 0.15));
|
||||||
|
morphProgress = 0;
|
||||||
|
morphTravelEnabled = true;
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (root.shouldBeVisible)
|
||||||
|
root.morphProgress = 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _freezeMorphTravel() {
|
||||||
|
const x = pubBodyX;
|
||||||
|
const y = pubBodyY;
|
||||||
|
const w = pubBodyW;
|
||||||
|
const h = pubBodyH;
|
||||||
|
|
||||||
|
// A third hover can supersede a morph before it settles. Freeze the outgoing
|
||||||
|
// content at the live rectangle so it fades in place while the next surface
|
||||||
|
// inherits exactly the same geometry.
|
||||||
|
morphTravelEnabled = false;
|
||||||
|
morphSeedX = x;
|
||||||
|
morphSeedY = y;
|
||||||
|
morphSeedW = w;
|
||||||
|
morphSeedH = h;
|
||||||
|
morphProgress = 0;
|
||||||
|
morphTravelEnabled = true;
|
||||||
|
_syncPopoutBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
function _endMorphTravel() {
|
||||||
|
morphTravelEnabled = false;
|
||||||
|
morphProgress = 1;
|
||||||
|
morphSeedX = 0;
|
||||||
|
morphSeedY = 0;
|
||||||
|
morphSeedW = 0;
|
||||||
|
morphSeedH = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flag to trigger in-place fade-out during a morph switch.
|
||||||
|
property bool _supersededClose: false
|
||||||
|
|
||||||
|
function beginSupersededClose() {
|
||||||
|
// Only set superseded flag for transient hover switches.
|
||||||
|
if (frameOwnsConnectedChrome && hoverDismissEnabled)
|
||||||
|
_supersededClose = true;
|
||||||
|
}
|
||||||
|
|
||||||
readonly property real connectedAnchorX: {
|
readonly property real connectedAnchorX: {
|
||||||
if (!root.usesConnectedSurfaceChrome)
|
if (!root.usesConnectedSurfaceChrome)
|
||||||
return triggerX;
|
return triggerX;
|
||||||
@@ -761,6 +934,15 @@ Item {
|
|||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
|
PopoutHoverDismiss {
|
||||||
|
id: hoverDismissController
|
||||||
|
anchors.fill: parent
|
||||||
|
dismissEnabled: root.hoverDismissEnabled
|
||||||
|
dismissSuspended: root.hoverDismissSuspended
|
||||||
|
surfaceVisible: root.shouldBeVisible
|
||||||
|
onDismissRequested: root.closeFromHoverDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
WindowBlur {
|
WindowBlur {
|
||||||
id: popoutBlur
|
id: popoutBlur
|
||||||
targetWindow: contentWindow
|
targetWindow: contentWindow
|
||||||
@@ -842,10 +1024,11 @@ Item {
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: contentContainer
|
id: contentContainer
|
||||||
x: root.alignedX
|
// Follow the morphing body bounds during transition.
|
||||||
y: root.renderedAlignedY
|
x: root.morphTravelEnabled ? root.pubBodyX : root.alignedX
|
||||||
width: root.alignedWidth
|
y: root.morphTravelEnabled ? root.pubBodyY : root.renderedAlignedY
|
||||||
height: root.renderedAlignedHeight
|
width: root.morphTravelEnabled ? root.pubBodyW : root.alignedWidth
|
||||||
|
height: root.morphTravelEnabled ? root.pubBodyH : root.renderedAlignedHeight
|
||||||
|
|
||||||
readonly property bool barTop: effectiveBarPosition === SettingsData.Position.Top
|
readonly property bool barTop: effectiveBarPosition === SettingsData.Position.Top
|
||||||
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
|
||||||
@@ -914,6 +1097,11 @@ Item {
|
|||||||
|
|
||||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||||
|
|
||||||
|
PopoutHoverBodyTracker {
|
||||||
|
controller: hoverDismissController
|
||||||
|
trackingEnabled: root.hoverDismissEnabled && root.shouldBeVisible
|
||||||
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: morph
|
id: morph
|
||||||
property real openProgress: 0
|
property real openProgress: 0
|
||||||
@@ -941,7 +1129,8 @@ Item {
|
|||||||
target: root
|
target: root
|
||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
root._captureChromeAnimTravel();
|
root._captureChromeAnimTravel();
|
||||||
morph.openProgress = root.shouldBeVisible ? 1 : 0;
|
// Skip reverse emerge animation during a superseded close.
|
||||||
|
morph.openProgress = (root.shouldBeVisible || root._supersededClose) ? 1 : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1067,23 +1256,27 @@ Item {
|
|||||||
|
|
||||||
property bool _renderActive: Theme.isDirectionalEffect || shouldBeVisible
|
property bool _renderActive: Theme.isDirectionalEffect || shouldBeVisible
|
||||||
property bool _animating: false
|
property bool _animating: false
|
||||||
property real publishedOpacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
|
readonly property bool _fadeWithOpacity: !Theme.isDirectionalEffect || root._supersededClose
|
||||||
|
// Fast fade duration for superseded close.
|
||||||
|
readonly property bool _supersededFade: root._supersededClose && !root.shouldBeVisible
|
||||||
|
readonly property real _targetOpacity: root._supersededClose ? (root.shouldBeVisible ? 1 : 0) : (Theme.isDirectionalEffect ? 1 : (root.shouldBeVisible ? 1 : 0))
|
||||||
|
property real publishedOpacity: _targetOpacity
|
||||||
|
|
||||||
opacity: Theme.isDirectionalEffect ? 1 : (shouldBeVisible ? 1 : 0)
|
opacity: _targetOpacity
|
||||||
visible: _renderActive
|
visible: _renderActive
|
||||||
|
|
||||||
scale: contentContainer.scaleValue
|
scale: contentContainer.scaleValue
|
||||||
x: Theme.snap(contentContainer.animX + (rollOutAdjuster.baseWidth - width) * (1 - scale) * 0.5, root.dpr)
|
x: Theme.snap(contentContainer.animX + (rollOutAdjuster.baseWidth - width) * (1 - scale) * 0.5, root.dpr)
|
||||||
y: Theme.snap(contentContainer.animY + (rollOutAdjuster.baseHeight - height) * (1 - scale) * 0.5, root.dpr)
|
y: Theme.snap(contentContainer.animY + (rollOutAdjuster.baseHeight - height) * (1 - scale) * 0.5, root.dpr)
|
||||||
|
|
||||||
layer.enabled: _animating || (!Theme.isDirectionalEffect && publishedOpacity < 1)
|
layer.enabled: _animating || (_fadeWithOpacity && publishedOpacity < 1)
|
||||||
layer.smooth: false
|
layer.smooth: false
|
||||||
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
layer.textureSize: root.dpr > 1 ? Qt.size(Math.ceil(width * root.dpr), Math.ceil(height * root.dpr)) : Qt.size(0, 0)
|
||||||
|
|
||||||
Behavior on opacity {
|
Behavior on opacity {
|
||||||
enabled: !Theme.isDirectionalEffect
|
enabled: contentWrapper._fadeWithOpacity
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
|
duration: contentWrapper._supersededFade ? Theme.shorterDuration : Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
onRunningChanged: {
|
onRunningChanged: {
|
||||||
@@ -1095,9 +1288,9 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Behavior on publishedOpacity {
|
Behavior on publishedOpacity {
|
||||||
enabled: !Theme.isDirectionalEffect
|
enabled: contentWrapper._fadeWithOpacity
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
|
duration: contentWrapper._supersededFade ? Theme.shorterDuration : Math.round(Theme.variantDuration(animationDuration, shouldBeVisible) * Theme.variantOpacityDurationScale)
|
||||||
easing.type: Easing.BezierSpline
|
easing.type: Easing.BezierSpline
|
||||||
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import Quickshell
|
|||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -35,6 +36,22 @@ Item {
|
|||||||
property bool shouldBeVisible: false
|
property bool shouldBeVisible: false
|
||||||
property bool isClosing: false
|
property bool isClosing: false
|
||||||
property bool animationsEnabled: true
|
property bool animationsEnabled: true
|
||||||
|
property bool hoverDismissEnabled: false
|
||||||
|
property bool hoverDismissSuspended: false
|
||||||
|
|
||||||
|
function cancelHoverDismiss() {
|
||||||
|
hoverDismissController.cancelPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeFromHoverDismiss() {
|
||||||
|
if (hoverDismissSuspended || isClosing || !shouldBeVisible)
|
||||||
|
return;
|
||||||
|
if (popoutHandle?.closeFromHoverDismiss)
|
||||||
|
popoutHandle.closeFromHoverDismiss();
|
||||||
|
else
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
property bool backgroundInteractive: true
|
property bool backgroundInteractive: true
|
||||||
property bool contentHandlesKeys: false
|
property bool contentHandlesKeys: false
|
||||||
@@ -585,6 +602,17 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
|
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
|
||||||
|
|
||||||
|
PopoutHoverDismiss {
|
||||||
|
id: hoverDismissController
|
||||||
|
anchors.fill: parent
|
||||||
|
dismissEnabled: root.hoverDismissEnabled
|
||||||
|
dismissSuspended: root.hoverDismissSuspended
|
||||||
|
surfaceVisible: root.shouldBeVisible
|
||||||
|
globalOffsetX: root._surfaceMarginLeft
|
||||||
|
globalOffsetY: root._fullHeight ? 0 : root._surfaceMarginTop
|
||||||
|
onDismissRequested: root.closeFromHoverDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
WindowBlur {
|
WindowBlur {
|
||||||
id: popoutBlur
|
id: popoutBlur
|
||||||
targetWindow: contentWindow
|
targetWindow: contentWindow
|
||||||
@@ -702,6 +730,11 @@ Item {
|
|||||||
|
|
||||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||||
|
|
||||||
|
PopoutHoverBodyTracker {
|
||||||
|
controller: hoverDismissController
|
||||||
|
trackingEnabled: root.hoverDismissEnabled && root.shouldBeVisible
|
||||||
|
}
|
||||||
|
|
||||||
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
|
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||||
QtObject {
|
QtObject {
|
||||||
id: morph
|
id: morph
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ PanelWindow {
|
|||||||
WlrLayershell.namespace: layerNamespace
|
WlrLayershell.namespace: layerNamespace
|
||||||
|
|
||||||
property bool isVisible: false
|
property bool isVisible: false
|
||||||
|
property bool hoverDismissEnabled: false
|
||||||
|
property bool hoverDismissSuspended: false
|
||||||
property var targetScreen: null
|
property var targetScreen: null
|
||||||
property var modelData: null
|
property var modelData: null
|
||||||
property bool triggerUsesOverlayLayer: false
|
property bool triggerUsesOverlayLayer: false
|
||||||
@@ -25,6 +27,7 @@ PanelWindow {
|
|||||||
property real edgeGap: 0
|
property real edgeGap: 0
|
||||||
property string slideEdge: "right"
|
property string slideEdge: "right"
|
||||||
readonly property bool slideFromLeft: slideEdge === "left"
|
readonly property bool slideFromLeft: slideEdge === "left"
|
||||||
|
readonly property real surfaceOriginX: slideFromLeft ? 0 : Math.max(0, (modelData?.width ?? width) - width)
|
||||||
property Component content: null
|
property Component content: null
|
||||||
property string title: ""
|
property string title: ""
|
||||||
property alias container: contentContainer
|
property alias container: contentContainer
|
||||||
@@ -46,6 +49,27 @@ PanelWindow {
|
|||||||
isVisible = false;
|
isVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideFromHoverDismiss() {
|
||||||
|
if (hoverDismissSuspended)
|
||||||
|
return;
|
||||||
|
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);
|
||||||
|
const globalX = surfaceOriginX + topLeft.x;
|
||||||
|
return gx >= globalX - padding && gx < globalX + slideContainer.width + padding && gy >= topLeft.y - padding && gy < topLeft.y + slideContainer.height + padding;
|
||||||
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
hide();
|
hide();
|
||||||
@@ -67,6 +91,17 @@ PanelWindow {
|
|||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
|
HoverDismissTracker {
|
||||||
|
id: hoverDismissTracker
|
||||||
|
parent: root.contentItem
|
||||||
|
enabled: root.hoverDismissEnabled && !root.hoverDismissSuspended && root.isVisible
|
||||||
|
shouldDismiss: function () {
|
||||||
|
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
|
||||||
|
}
|
||||||
|
onDismissRequested: root.hideFromHoverDismiss()
|
||||||
|
onHoverMoved: (sceneX, sceneY) => PopoutManager.updateHoverCursor(root.surfaceOriginX + sceneX, sceneY)
|
||||||
|
}
|
||||||
|
|
||||||
readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
|
readonly property bool slideoutBlurActive: root.visible && BlurService.enabled && Theme.connectedSurfaceBlurEnabled
|
||||||
|
|
||||||
WlrLayershell.layer: (!suppressOverlayLayer && (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData))) ? WlrLayershell.Overlay : WlrLayershell.Top
|
WlrLayershell.layer: (!suppressOverlayLayer && (triggerUsesOverlayLayer || CompositorService.framePeerSurfacesUseOverlayForScreen(modelData))) ? WlrLayershell.Overlay : WlrLayershell.Top
|
||||||
@@ -117,8 +152,10 @@ PanelWindow {
|
|||||||
easing.type: Easing.OutCubic
|
easing.type: Easing.OutCubic
|
||||||
|
|
||||||
onRunningChanged: {
|
onRunningChanged: {
|
||||||
if (!running && !root.isVisible) {
|
if (!running) {
|
||||||
root.mappedVisible = false;
|
if (!root.isVisible)
|
||||||
|
root.mappedVisible = false;
|
||||||
|
slideAnimation.duration = 450;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var shouldDismiss: null
|
||||||
|
|
||||||
|
signal dismissRequested
|
||||||
|
// Emitted on every hover move; passive to avoid blocking overlapping MouseAreas
|
||||||
|
signal hoverMoved(real gx, real gy)
|
||||||
|
|
||||||
|
onPointChanged: {
|
||||||
|
if (!enabled || !hovered)
|
||||||
|
return;
|
||||||
|
const gp = parent.mapToItem(null, point.position.x, point.position.y);
|
||||||
|
hoverMoved(gp.x, gp.y);
|
||||||
|
}
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered || !enabled)
|
||||||
|
return;
|
||||||
|
if (typeof shouldDismiss === "function" && !shouldDismiss())
|
||||||
|
return;
|
||||||
|
dismissRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelPending() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property var controller
|
||||||
|
property bool trackingEnabled: false
|
||||||
|
|
||||||
|
enabled: trackingEnabled
|
||||||
|
|
||||||
|
onTrackingEnabledChanged: {
|
||||||
|
if (!trackingEnabled)
|
||||||
|
controller.updateBodyHover(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
onHoveredChanged: controller.updateBodyHover(hovered)
|
||||||
|
onPointChanged: {
|
||||||
|
if (!hovered)
|
||||||
|
return;
|
||||||
|
const gp = parent.mapToItem(null, point.position.x, point.position.y);
|
||||||
|
controller.updateCursor(gp.x, gp.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property bool dismissEnabled
|
||||||
|
required property bool dismissSuspended
|
||||||
|
required property bool surfaceVisible
|
||||||
|
|
||||||
|
property int graceInterval: 150
|
||||||
|
property bool bodyHovered: false
|
||||||
|
property real globalOffsetX: 0
|
||||||
|
property real globalOffsetY: 0
|
||||||
|
|
||||||
|
signal dismissRequested
|
||||||
|
|
||||||
|
function cancelPending() {
|
||||||
|
graceTimer.stop();
|
||||||
|
hoverTracker.cancelPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBodyHover(over) {
|
||||||
|
bodyHovered = over;
|
||||||
|
if (over) {
|
||||||
|
graceTimer.stop();
|
||||||
|
} else if (dismissEnabled && !dismissSuspended && surfaceVisible) {
|
||||||
|
graceTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCursor(sceneX, sceneY) {
|
||||||
|
PopoutManager.updateHoverCursor(sceneX + globalOffsetX, sceneY + globalOffsetY);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDismissEnabledChanged: {
|
||||||
|
if (!dismissEnabled)
|
||||||
|
cancelPending();
|
||||||
|
}
|
||||||
|
onDismissSuspendedChanged: {
|
||||||
|
if (dismissSuspended)
|
||||||
|
graceTimer.stop();
|
||||||
|
else if (dismissEnabled && surfaceVisible && !bodyHovered)
|
||||||
|
graceTimer.restart();
|
||||||
|
}
|
||||||
|
onSurfaceVisibleChanged: {
|
||||||
|
if (!surfaceVisible)
|
||||||
|
cancelPending();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: graceTimer
|
||||||
|
interval: root.graceInterval
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (!root.dismissEnabled || root.dismissSuspended || !root.surfaceVisible || root.bodyHovered)
|
||||||
|
return;
|
||||||
|
if (PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY))
|
||||||
|
return;
|
||||||
|
root.dismissRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HoverDismissTracker {
|
||||||
|
id: hoverTracker
|
||||||
|
enabled: root.dismissEnabled && !root.dismissSuspended && root.surfaceVisible
|
||||||
|
shouldDismiss: function () {
|
||||||
|
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
|
||||||
|
}
|
||||||
|
onDismissRequested: root.dismissRequested()
|
||||||
|
onHoverMoved: (gx, gy) => root.updateCursor(gx, gy)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
"wallpaper"
|
"wallpaper"
|
||||||
],
|
],
|
||||||
"icon": "wallpaper",
|
"icon": "wallpaper",
|
||||||
"description": "Color shown for areas not covered by wallpaper (e.g. Fit or Pad modes)"
|
"description": "Color shown for areas not covered by wallpaper"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"section": "selectedMonitor",
|
"section": "selectedMonitor",
|
||||||
@@ -8139,6 +8139,36 @@
|
|||||||
],
|
],
|
||||||
"icon": "monitor"
|
"icon": "monitor"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": "frameLauncherEdgeHover",
|
||||||
|
"label": "Edge Hover Reveal",
|
||||||
|
"tabIndex": 33,
|
||||||
|
"category": "Frame",
|
||||||
|
"keywords": [
|
||||||
|
"app drawer",
|
||||||
|
"app menu",
|
||||||
|
"applications",
|
||||||
|
"border",
|
||||||
|
"connected",
|
||||||
|
"decoration",
|
||||||
|
"edge",
|
||||||
|
"emerge",
|
||||||
|
"frame",
|
||||||
|
"free",
|
||||||
|
"hover",
|
||||||
|
"hovering",
|
||||||
|
"launcher",
|
||||||
|
"open",
|
||||||
|
"panel",
|
||||||
|
"reveal",
|
||||||
|
"start menu",
|
||||||
|
"statusbar",
|
||||||
|
"taskbar",
|
||||||
|
"topbar",
|
||||||
|
"window"
|
||||||
|
],
|
||||||
|
"description": "Open the launcher by hovering the emerge edge (when free of bar and dock)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": "frameEnable",
|
"section": "frameEnable",
|
||||||
"label": "Enable Frame",
|
"label": "Enable Frame",
|
||||||
|
|||||||
Reference in New Issue
Block a user