mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-28 22:12:10 -04:00
feat(popouts): enhance hover functionality and introduce hover dismiss features
This commit is contained in:
@@ -388,53 +388,51 @@ Item {
|
||||
return "left";
|
||||
}
|
||||
|
||||
property string activeHoverTrigger: ""
|
||||
property real _lastHoverGlobalX: 0
|
||||
property real _lastHoverGlobalY: 0
|
||||
|
||||
readonly property bool hoverPopoutsEnabled: barConfig?.hoverPopouts ?? false
|
||||
readonly property int hoverPopoutDelay: Math.max(0, barConfig?.hoverPopoutDelay ?? 150)
|
||||
|
||||
// Clean up hover state and close transient popouts when the hover feature is disabled.
|
||||
onHoverPopoutsEnabledChanged: {
|
||||
if (hoverPopoutsEnabled)
|
||||
return;
|
||||
_cancelPendingHover();
|
||||
_hoverCloseTimer.stop();
|
||||
if (hasOpenHoverSurface() && !PopoutManager.isActivePopoutPinned(barWindow?.screen))
|
||||
closeHoverSurfaces();
|
||||
activeHoverTrigger = "";
|
||||
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
|
||||
}
|
||||
|
||||
property var _pendingHoverHit: null
|
||||
property string _pendingHoverTrigger: ""
|
||||
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() {
|
||||
_cancelPendingHover();
|
||||
_hoverCloseTimer.stop();
|
||||
_pendingPopoutOpenSpec = null;
|
||||
|
||||
const activePopout = PopoutManager.getActivePopout(barWindow?.screen);
|
||||
const hasTransientSurface = activeHoverTrigger !== "" || activePopout?.hoverDismissEnabled === true;
|
||||
if (hasTransientSurface && !PopoutManager.isActivePopoutPinned(barWindow?.screen))
|
||||
closeHoverSurfaces();
|
||||
else
|
||||
activeHoverTrigger = "";
|
||||
hoverController.resetForBarGeometryChange();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: _hoverIntentTimer
|
||||
interval: topBarContent.hoverPopoutDelay
|
||||
repeat: false
|
||||
onTriggered: topBarContent._commitPendingHover()
|
||||
}
|
||||
|
||||
// Grace timer to prevent flicker when crossing gaps.
|
||||
Timer {
|
||||
id: _hoverCloseTimer
|
||||
interval: 120
|
||||
repeat: false
|
||||
onTriggered: topBarContent._commitHoverClose()
|
||||
function _dashTriggerSource(section, tabIndex) {
|
||||
return hoverController.dashTriggerSource(section, tabIndex);
|
||||
}
|
||||
|
||||
function getBarPosition() {
|
||||
@@ -512,7 +510,7 @@ Item {
|
||||
topBarContent._pendingPopoutOpenSpec = null;
|
||||
topBarContent._finishWidgetPopoutOpen(pending, loader.item);
|
||||
if (pending.mode === "hover")
|
||||
topBarContent.checkHoverPopout(topBarContent._lastHoverGlobalX, topBarContent._lastHoverGlobalY);
|
||||
hoverController.recheckLatestPoint();
|
||||
};
|
||||
if (loader.item) {
|
||||
onLoaded();
|
||||
@@ -557,638 +555,6 @@ Item {
|
||||
return true;
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
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) {
|
||||
const layout = section.layoutLoader?.item;
|
||||
if (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 === section.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;
|
||||
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 (!_widgetSupportsHoverPopout(widgetId, widgetItem))
|
||||
return;
|
||||
if (!getWidgetVisible(widgetId))
|
||||
return;
|
||||
seen.add(widgetItem);
|
||||
candidates.push({
|
||||
widgetId,
|
||||
widgetItem,
|
||||
section: widgetItem.section || sectionHint || "right",
|
||||
wrapper: 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(c => c.widgetItem === entry.host.item);
|
||||
if (existing) {
|
||||
existing.wrapper = entry.wrapper;
|
||||
if (!existing.section)
|
||||
existing.section = entry.section;
|
||||
continue;
|
||||
}
|
||||
candidates.push({
|
||||
widgetId: entry.host.widgetId,
|
||||
widgetItem: entry.host.item,
|
||||
section: entry.host.item.section || entry.section,
|
||||
wrapper: entry.wrapper
|
||||
});
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function _globalItemBounds(item) {
|
||||
const topLeft = item.mapToItem(null, 0, 0);
|
||||
return {
|
||||
x: topLeft.x,
|
||||
y: topLeft.y,
|
||||
width: item.width,
|
||||
height: item.height
|
||||
};
|
||||
}
|
||||
|
||||
function _hitBoundsForWidget(widgetItem, wrapper) {
|
||||
if (!widgetItem?.visible)
|
||||
return null;
|
||||
|
||||
if (widgetItem.visualContent !== undefined) {
|
||||
const visual = widgetItem.visualContent;
|
||||
if (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);
|
||||
|
||||
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 findWidgetAtGlobalPoint(gx, gy) {
|
||||
const candidates = _collectHoverCandidates();
|
||||
let best = null;
|
||||
let bestArea = Infinity;
|
||||
for (let i = 0; i < candidates.length; i++) {
|
||||
const entry = candidates[i];
|
||||
const bounds = _hitBoundsForWidget(entry.widgetItem, entry.wrapper);
|
||||
if (!bounds || bounds.width <= 0 || bounds.height <= 0)
|
||||
continue;
|
||||
if (!_pointInBounds(gx, gy, bounds))
|
||||
continue;
|
||||
const area = bounds.width * bounds.height;
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
best = {
|
||||
widgetId: entry.widgetId,
|
||||
widgetItem: entry.widgetItem,
|
||||
section: entry.section
|
||||
};
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
function _dashTriggerSource(section, tabIndex) {
|
||||
return (barConfig?.id ?? "default") + "-" + section + "-" + tabIndex;
|
||||
}
|
||||
|
||||
function _notepadWidgetForScreen() {
|
||||
const screenName = barWindow?.screen?.name;
|
||||
const fromRegistry = screenName ? BarWidgetService.getWidget("notepadButton", screenName) : null;
|
||||
if (fromRegistry)
|
||||
return fromRegistry;
|
||||
const candidates = _collectHoverCandidates();
|
||||
for (let i = 0; i < candidates.length; i++) {
|
||||
if (candidates[i].widgetId === "notepadButton")
|
||||
return candidates[i].widgetItem;
|
||||
}
|
||||
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 cursorOverHoverChain(gx, gy) {
|
||||
if (PopoutManager.cursorOverBar(gx, gy))
|
||||
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();
|
||||
}
|
||||
|
||||
// Fade out the active popout in-place during morph switch transitions.
|
||||
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 inst = _notepadWidgetForScreen()?.notepadInstance;
|
||||
if (!inst?.isVisible)
|
||||
activeHoverTrigger = "";
|
||||
return;
|
||||
}
|
||||
if (activeHoverTrigger === "")
|
||||
return;
|
||||
if (!hasOpenHoverSurface())
|
||||
activeHoverTrigger = "";
|
||||
}
|
||||
|
||||
function hasOpenHoverSurface() {
|
||||
if (activeHoverTrigger === "")
|
||||
return false;
|
||||
if (activeHoverTrigger === "notepadButton") {
|
||||
const inst = _notepadWidgetForScreen()?.notepadInstance;
|
||||
return inst?.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 openHoverPopoutForHit(hit) {
|
||||
if (!hit?.widgetItem)
|
||||
return false;
|
||||
|
||||
const widgetId = hit.widgetId;
|
||||
const widgetItem = hit.widgetItem;
|
||||
const section = hit.section;
|
||||
const mode = "hover";
|
||||
const base = {
|
||||
widgetItem,
|
||||
section,
|
||||
mode
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
switch (widgetId) {
|
||||
case "launcherButton":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: appDrawerLoader,
|
||||
triggerSource: "appDrawer",
|
||||
visualItem: widgetItem
|
||||
}));
|
||||
case "clipboard":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: clipboardHistoryPopoutLoader,
|
||||
triggerSource: "clipboard",
|
||||
prepare: popout => {
|
||||
popout.activeTab = "recents";
|
||||
}
|
||||
}));
|
||||
case "clock":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: dankDashPopoutLoader,
|
||||
tabIndex: 0,
|
||||
triggerSource: _dashTriggerSource(section, 0),
|
||||
useCenterSection: true,
|
||||
setTriggerScreen: true
|
||||
}));
|
||||
case "music":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: dankDashPopoutLoader,
|
||||
tabIndex: 1,
|
||||
triggerSource: _dashTriggerSource(section, 1),
|
||||
useCenterSection: true,
|
||||
setTriggerScreen: true
|
||||
}));
|
||||
case "weather":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: dankDashPopoutLoader,
|
||||
tabIndex: 3,
|
||||
triggerSource: _dashTriggerSource(section, 3),
|
||||
useCenterSection: true,
|
||||
setTriggerScreen: true
|
||||
}));
|
||||
case "cpuUsage":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: processListPopoutLoader,
|
||||
triggerSource: "cpu"
|
||||
}));
|
||||
case "memUsage":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: processListPopoutLoader,
|
||||
triggerSource: "memory"
|
||||
}));
|
||||
case "cpuTemp":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: processListPopoutLoader,
|
||||
triggerSource: "cpu_temp"
|
||||
}));
|
||||
case "gpuTemp":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: processListPopoutLoader,
|
||||
triggerSource: "gpu_temp"
|
||||
}));
|
||||
case "notificationButton":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: notificationCenterLoader,
|
||||
triggerSource: "notifications",
|
||||
setTriggerScreen: true
|
||||
}));
|
||||
case "battery":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: batteryPopoutLoader,
|
||||
triggerSource: "battery"
|
||||
}));
|
||||
case "layout":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: layoutPopoutLoader,
|
||||
triggerSource: "layout"
|
||||
}));
|
||||
case "vpn":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: vpnPopoutLoader,
|
||||
triggerSource: "vpn"
|
||||
}));
|
||||
case "controlCenterButton":
|
||||
if (openWidgetPopout(Object.assign({}, base, {
|
||||
loader: controlCenterLoader,
|
||||
triggerSource: "controlCenter",
|
||||
setTriggerScreen: true
|
||||
}))) {
|
||||
if (controlCenterLoader.item?.shouldBeVisible && NetworkService.wifiEnabled)
|
||||
NetworkService.scanWifi();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case "systemUpdate":
|
||||
return openWidgetPopout(Object.assign({}, base, {
|
||||
loader: systemUpdateLoader,
|
||||
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();
|
||||
|
||||
// Ignore hover events when a popout is pinned open.
|
||||
if (PopoutManager.isActivePopoutPinned(barWindow?.screen))
|
||||
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 = "";
|
||||
}
|
||||
|
||||
// Maps widgets to their loaders to support in-place switching between triggers sharing a popout.
|
||||
function _loaderForWidgetId(widgetId) {
|
||||
switch (widgetId) {
|
||||
case "launcherButton":
|
||||
return appDrawerLoader;
|
||||
case "clipboard":
|
||||
return clipboardHistoryPopoutLoader;
|
||||
case "clock":
|
||||
case "music":
|
||||
case "weather":
|
||||
return dankDashPopoutLoader;
|
||||
case "cpuUsage":
|
||||
case "memUsage":
|
||||
case "cpuTemp":
|
||||
case "gpuTemp":
|
||||
return processListPopoutLoader;
|
||||
case "notificationButton":
|
||||
return notificationCenterLoader;
|
||||
case "battery":
|
||||
return batteryPopoutLoader;
|
||||
case "layout":
|
||||
return layoutPopoutLoader;
|
||||
case "vpn":
|
||||
return vpnPopoutLoader;
|
||||
case "controlCenterButton":
|
||||
return controlCenterLoader;
|
||||
case "systemUpdate":
|
||||
return systemUpdateLoader;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function _hitTargetsActivePopout(hit) {
|
||||
const active = PopoutManager.getActivePopout(barWindow?.screen);
|
||||
if (!active || !hit)
|
||||
return false;
|
||||
const loader = _loaderForWidgetId(hit.widgetId);
|
||||
if (!loader)
|
||||
return false;
|
||||
return _resolvePopoutFromLoader(loader) === active;
|
||||
}
|
||||
|
||||
function _commitPendingHover() {
|
||||
const hit = _pendingHoverHit;
|
||||
const triggerKey = _pendingHoverTrigger;
|
||||
_pendingHoverHit = null;
|
||||
_pendingHoverTrigger = "";
|
||||
if (!hit || !hoverPopoutsEnabled)
|
||||
return;
|
||||
if (PopoutManager.isActivePopoutPinned(barWindow?.screen))
|
||||
return;
|
||||
// Cursor may have left the bar before the timer fired.
|
||||
if (!PopoutManager.cursorOverBar(_lastHoverGlobalX, _lastHoverGlobalY))
|
||||
return;
|
||||
|
||||
const activePopout = PopoutManager.getActivePopout(barWindow?.screen);
|
||||
const targetLoader = _loaderForWidgetId(hit.widgetId);
|
||||
const targetPopout = _resolvePopoutFromLoader(targetLoader);
|
||||
const managerOwnsTransition = !!(activePopout && targetPopout);
|
||||
|
||||
// A different trigger backed by the same already-open popout swaps tab/position
|
||||
// in place. PopoutManager also owns handoff between loaded popouts, so only
|
||||
// pre-close special/unmanaged surfaces here.
|
||||
if (triggerKey !== activeHoverTrigger && activeHoverTrigger !== "" && !_hitTargetsActivePopout(hit)) {
|
||||
if (!managerOwnsTransition) {
|
||||
_beginSupersededCloseForActive();
|
||||
closeHoverSurfaces();
|
||||
}
|
||||
}
|
||||
|
||||
if (!openHoverPopoutForHit(hit)) {
|
||||
if (activeHoverTrigger !== "")
|
||||
closeHoverSurfaces();
|
||||
return;
|
||||
}
|
||||
|
||||
activeHoverTrigger = triggerKey;
|
||||
}
|
||||
|
||||
function scheduleHoverClose(gx, gy) {
|
||||
_cancelPendingHover();
|
||||
if (!hoverPopoutsEnabled)
|
||||
return;
|
||||
if (PopoutManager.isActivePopoutPinned(barWindow?.screen))
|
||||
return;
|
||||
if (cursorOverHoverChain(gx, gy))
|
||||
return;
|
||||
_hoverCloseTimer.restart();
|
||||
}
|
||||
|
||||
function _commitHoverClose() {
|
||||
const gx = PopoutManager.hoverCursorGlobalX;
|
||||
const gy = PopoutManager.hoverCursorGlobalY;
|
||||
if (PopoutManager.isActivePopoutPinned(barWindow?.screen))
|
||||
return;
|
||||
if (cursorOverHoverChain(gx, gy))
|
||||
return;
|
||||
closeHoverSurfaces();
|
||||
}
|
||||
|
||||
readonly property var widgetVisibility: ({
|
||||
"cpuUsage": DgopService.dgopAvailable,
|
||||
"memUsage": DgopService.dgopAvailable,
|
||||
|
||||
Reference in New Issue
Block a user