mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-06-28 05:55:21 -04:00
feat(popouts): enhance hover functionality and introduce hover dismiss features
This commit is contained in:
@@ -25,12 +25,12 @@ Singleton {
|
|||||||
hoverCursorGlobalY = gy;
|
hoverCursorGlobalY = gy;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cursorOverBar(gx, gy, padding) {
|
function cursorOverBar(gx, gy, padding, excludedWindow) {
|
||||||
const pad = padding !== undefined ? padding : 16;
|
const pad = padding !== undefined ? padding : 16;
|
||||||
const bars = KeyboardFocus.barWindows || [];
|
const bars = KeyboardFocus.barWindows || [];
|
||||||
for (let i = 0; i < bars.length; i++) {
|
for (let i = 0; i < bars.length; i++) {
|
||||||
const w = bars[i];
|
const w = bars[i];
|
||||||
if (!w?.visible)
|
if (!w?.visible || w === excludedWindow)
|
||||||
continue;
|
continue;
|
||||||
if (typeof w.containsGlobalPoint === "function") {
|
if (typeof w.containsGlobalPoint === "function") {
|
||||||
if (w.containsGlobalPoint(gx, gy, pad))
|
if (w.containsGlobalPoint(gx, gy, pad))
|
||||||
@@ -199,104 +199,22 @@ Singleton {
|
|||||||
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 hover popout pins it open rather than toggling it closed
|
|
||||||
|
// Clicking a transient popout pins it instead of toggling it closed.
|
||||||
const wasTransient = popout.hoverDismissEnabled === true;
|
const wasTransient = popout.hoverDismissEnabled === true;
|
||||||
if (popout.hoverDismissEnabled !== undefined)
|
if (!hoverRequest && popout.hoverDismissEnabled !== undefined)
|
||||||
popout.hoverDismissEnabled = false;
|
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) {
|
|
||||||
popoutOpening();
|
|
||||||
}
|
|
||||||
|
|
||||||
let movedFromOtherScreen = false;
|
|
||||||
for (const otherScreenName in currentPopoutsByScreen) {
|
|
||||||
if (otherScreenName === screenName)
|
|
||||||
continue;
|
|
||||||
const otherPopout = currentPopoutsByScreen[otherScreenName];
|
|
||||||
if (!otherPopout)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (_isStale(otherPopout)) {
|
|
||||||
currentPopoutsByScreen[otherScreenName] = null;
|
|
||||||
currentPopoutTriggers[otherScreenName] = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherPopout === popout) {
|
|
||||||
movedFromOtherScreen = true;
|
|
||||||
currentPopoutsByScreen[otherScreenName] = null;
|
|
||||||
currentPopoutTriggers[otherScreenName] = null;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_closePopout(otherPopout);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPopout && currentPopout !== popout) {
|
|
||||||
if (_isStale(currentPopout)) {
|
|
||||||
currentPopoutsByScreen[screenName] = null;
|
|
||||||
currentPopoutTriggers[screenName] = null;
|
|
||||||
} else {
|
|
||||||
_closePopout(currentPopout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPopout === popout && popout.shouldBeVisible && !movedFromOtherScreen) {
|
|
||||||
const sameTrigger = triggerId === undefined || currentPopoutTriggers[screenName] === triggerId;
|
|
||||||
|
|
||||||
if (sameTrigger) {
|
|
||||||
if (!wasTransient) {
|
|
||||||
_closePopout(popout);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (popout.updateSurfacePosition)
|
|
||||||
popout.updateSurfacePosition();
|
|
||||||
if (triggerId !== undefined)
|
|
||||||
currentPopoutTriggers[screenName] = triggerId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
|
|
||||||
popout.currentTabIndex = tabIndex;
|
|
||||||
}
|
|
||||||
if (popout.updateSurfacePosition)
|
|
||||||
popout.updateSurfacePosition();
|
|
||||||
currentPopoutTriggers[screenName] = triggerId;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPopoutTriggers[screenName] = triggerId;
|
|
||||||
currentPopoutsByScreen[screenName] = popout;
|
|
||||||
popoutChanged();
|
|
||||||
|
|
||||||
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
|
|
||||||
popout.currentTabIndex = tabIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPopout !== popout) {
|
|
||||||
ModalManager.closeAllModalsExcept(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
_openPopout(popout);
|
|
||||||
}
|
|
||||||
|
|
||||||
function requestHoverPopout(popout, tabIndex, triggerSource) {
|
|
||||||
if (!popout || !popout.screen)
|
|
||||||
return;
|
|
||||||
screenshotActive = false;
|
|
||||||
const screenName = popout.screen.name;
|
|
||||||
const currentPopout = currentPopoutsByScreen[screenName];
|
|
||||||
const triggerId = triggerSource !== undefined ? triggerSource : tabIndex;
|
|
||||||
|
|
||||||
const willOpen = !(currentPopout === popout && _isPopoutPresented(popout) && triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId);
|
|
||||||
if (willOpen)
|
if (willOpen)
|
||||||
popoutOpening();
|
popoutOpening();
|
||||||
|
|
||||||
@@ -329,23 +247,36 @@ Singleton {
|
|||||||
currentPopoutsByScreen[screenName] = null;
|
currentPopoutsByScreen[screenName] = null;
|
||||||
currentPopoutTriggers[screenName] = null;
|
currentPopoutTriggers[screenName] = null;
|
||||||
} else {
|
} else {
|
||||||
// Signal the active popout to fade in-place when morphed
|
if (hoverRequest && typeof currentPopout.beginSupersededClose === "function")
|
||||||
if (typeof currentPopout.beginSupersededClose === "function")
|
|
||||||
currentPopout.beginSupersededClose();
|
currentPopout.beginSupersededClose();
|
||||||
_closePopout(currentPopout);
|
_closePopout(currentPopout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPopout === popout && _isPopoutPresented(popout) && !movedFromOtherScreen) {
|
if (alreadyPresented && !movedFromOtherScreen) {
|
||||||
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId)
|
const sameDefinedTrigger = triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId;
|
||||||
|
if (hoverRequest && sameDefinedTrigger)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (tabIndex !== undefined && popout.currentTabIndex !== undefined)
|
if (!hoverRequest && (triggerId === undefined || sameDefinedTrigger)) {
|
||||||
|
if (!wasTransient) {
|
||||||
|
_closePopout(popout);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (popout.updateSurfacePosition)
|
||||||
|
popout.updateSurfacePosition();
|
||||||
|
if (triggerId !== undefined)
|
||||||
|
currentPopoutTriggers[screenName] = triggerId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
|
||||||
popout.currentTabIndex = tabIndex;
|
popout.currentTabIndex = tabIndex;
|
||||||
|
}
|
||||||
if (popout.updateSurfacePosition)
|
if (popout.updateSurfacePosition)
|
||||||
popout.updateSurfacePosition();
|
popout.updateSurfacePosition();
|
||||||
currentPopoutTriggers[screenName] = triggerId;
|
currentPopoutTriggers[screenName] = triggerId;
|
||||||
if (popout.hoverDismissEnabled !== undefined)
|
if (hoverRequest && popout.hoverDismissEnabled !== undefined)
|
||||||
popout.hoverDismissEnabled = true;
|
popout.hoverDismissEnabled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -354,15 +285,25 @@ Singleton {
|
|||||||
currentPopoutsByScreen[screenName] = popout;
|
currentPopoutsByScreen[screenName] = popout;
|
||||||
popoutChanged();
|
popoutChanged();
|
||||||
|
|
||||||
if (tabIndex !== undefined && popout.currentTabIndex !== undefined)
|
if (tabIndex !== undefined && popout.currentTabIndex !== undefined) {
|
||||||
popout.currentTabIndex = tabIndex;
|
popout.currentTabIndex = tabIndex;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentPopout !== popout)
|
if (currentPopout !== popout) {
|
||||||
ModalManager.closeAllModalsExcept(null);
|
ModalManager.closeAllModalsExcept(null);
|
||||||
|
}
|
||||||
|
|
||||||
if (popout.hoverDismissEnabled !== undefined)
|
if (hoverRequest && popout.hoverDismissEnabled !== undefined)
|
||||||
popout.hoverDismissEnabled = true;
|
popout.hoverDismissEnabled = true;
|
||||||
|
|
||||||
_openPopout(popout);
|
_openPopout(popout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function requestPopout(popout, tabIndex, triggerSource) {
|
||||||
|
_requestPopout(popout, tabIndex, triggerSource, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requestHoverPopout(popout, tabIndex, triggerSource) {
|
||||||
|
_requestPopout(popout, tabIndex, triggerSource, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -388,53 +388,51 @@ Item {
|
|||||||
return "left";
|
return "left";
|
||||||
}
|
}
|
||||||
|
|
||||||
property string activeHoverTrigger: ""
|
DankBarHoverController {
|
||||||
property real _lastHoverGlobalX: 0
|
id: hoverController
|
||||||
property real _lastHoverGlobalY: 0
|
barContent: topBarContent
|
||||||
|
barWindow: topBarContent.barWindow
|
||||||
readonly property bool hoverPopoutsEnabled: barConfig?.hoverPopouts ?? false
|
barConfig: topBarContent.barConfig
|
||||||
readonly property int hoverPopoutDelay: Math.max(0, barConfig?.hoverPopoutDelay ?? 150)
|
hLeftSection: topBarContent.hLeftSection
|
||||||
|
hCenterSection: topBarContent.hCenterSection
|
||||||
// Clean up hover state and close transient popouts when the hover feature is disabled.
|
hRightSection: topBarContent.hRightSection
|
||||||
onHoverPopoutsEnabledChanged: {
|
vLeftSection: topBarContent.vLeftSection
|
||||||
if (hoverPopoutsEnabled)
|
vCenterSection: topBarContent.vCenterSection
|
||||||
return;
|
vRightSection: topBarContent.vRightSection
|
||||||
_cancelPendingHover();
|
leftWidgetsModel: topBarContent.leftWidgetsModel
|
||||||
_hoverCloseTimer.stop();
|
centerWidgetsModel: topBarContent.centerWidgetsModel
|
||||||
if (hasOpenHoverSurface() && !PopoutManager.isActivePopoutPinned(barWindow?.screen))
|
rightWidgetsModel: topBarContent.rightWidgetsModel
|
||||||
closeHoverSurfaces();
|
|
||||||
activeHoverTrigger = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property var _pendingHoverHit: null
|
readonly property string activeHoverTrigger: hoverController.activeHoverTrigger
|
||||||
property string _pendingHoverTrigger: ""
|
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() {
|
function resetHoverForBarGeometryChange() {
|
||||||
_cancelPendingHover();
|
hoverController.resetForBarGeometryChange();
|
||||||
_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 = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
function _dashTriggerSource(section, tabIndex) {
|
||||||
id: _hoverIntentTimer
|
return hoverController.dashTriggerSource(section, tabIndex);
|
||||||
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 getBarPosition() {
|
function getBarPosition() {
|
||||||
@@ -512,7 +510,7 @@ Item {
|
|||||||
topBarContent._pendingPopoutOpenSpec = null;
|
topBarContent._pendingPopoutOpenSpec = null;
|
||||||
topBarContent._finishWidgetPopoutOpen(pending, loader.item);
|
topBarContent._finishWidgetPopoutOpen(pending, loader.item);
|
||||||
if (pending.mode === "hover")
|
if (pending.mode === "hover")
|
||||||
topBarContent.checkHoverPopout(topBarContent._lastHoverGlobalX, topBarContent._lastHoverGlobalY);
|
hoverController.recheckLatestPoint();
|
||||||
};
|
};
|
||||||
if (loader.item) {
|
if (loader.item) {
|
||||||
onLoaded();
|
onLoaded();
|
||||||
@@ -557,638 +555,6 @@ Item {
|
|||||||
return true;
|
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: ({
|
readonly property var widgetVisibility: ({
|
||||||
"cpuUsage": DgopService.dgopAvailable,
|
"cpuUsage": DgopService.dgopAvailable,
|
||||||
"memUsage": DgopService.dgopAvailable,
|
"memUsage": DgopService.dgopAvailable,
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1123,13 +1123,11 @@ PanelWindow {
|
|||||||
const gp = barUnitInset.mapToItem(null, point.position.x, point.position.y);
|
const gp = barUnitInset.mapToItem(null, point.position.x, point.position.y);
|
||||||
lastGlobalX = gp.x;
|
lastGlobalX = gp.x;
|
||||||
lastGlobalY = gp.y;
|
lastGlobalY = gp.y;
|
||||||
topBarContent.checkHoverPopout(gp.x, gp.y);
|
topBarContent.queueHoverPopout(gp.x, gp.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
onHoveredChanged: {
|
onHoveredChanged: {
|
||||||
if (hovered)
|
topBarContent.updateHoverBarHovered(hovered);
|
||||||
return;
|
|
||||||
topBarContent.scheduleHoverClose(lastGlobalX, lastGlobalY);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ Item {
|
|||||||
scrollXBehavior: defaultBar.scrollXBehavior ?? "column",
|
scrollXBehavior: defaultBar.scrollXBehavior ?? "column",
|
||||||
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace",
|
scrollYBehavior: defaultBar.scrollYBehavior ?? "workspace",
|
||||||
hoverPopouts: defaultBar.hoverPopouts ?? false,
|
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",
|
||||||
|
|||||||
@@ -42,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
|
||||||
@@ -131,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
|
||||||
@@ -234,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.pubBodyX,
|
"x": bodyX,
|
||||||
"y": root.pubBodyY,
|
"y": bodyY,
|
||||||
"width": root.pubBodyW,
|
"width": bodyW,
|
||||||
"height": root.pubBodyH
|
"height": bodyH
|
||||||
};
|
};
|
||||||
const animationOffset = {
|
const animationOffset = {
|
||||||
"x": _connectedChromeAnimX(),
|
"x": _connectedChromeAnimX(),
|
||||||
@@ -255,10 +268,10 @@ Item {
|
|||||||
"animationOffset": animationOffset,
|
"animationOffset": animationOffset,
|
||||||
"scale": 1,
|
"scale": 1,
|
||||||
"opacity": Theme.connectedSurfaceColor.a,
|
"opacity": Theme.connectedSurfaceColor.a,
|
||||||
"bodyX": root.pubBodyX,
|
"bodyX": bodyX,
|
||||||
"bodyY": root.pubBodyY,
|
"bodyY": bodyY,
|
||||||
"bodyW": root.pubBodyW,
|
"bodyW": bodyW,
|
||||||
"bodyH": root.pubBodyH,
|
"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 : "",
|
||||||
@@ -270,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -312,7 +330,26 @@ Item {
|
|||||||
return;
|
return;
|
||||||
if (!contentWindow.visible && !shouldBeVisible)
|
if (!contentWindow.visible && !shouldBeVisible)
|
||||||
return;
|
return;
|
||||||
chromeLease.updateBody(root.pubBodyX, root.pubBodyY, root.pubBodyW, root.pubBodyH);
|
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
|
||||||
@@ -357,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 {
|
||||||
@@ -412,8 +452,7 @@ Item {
|
|||||||
property bool hoverDismissSuspended: false
|
property bool hoverDismissSuspended: false
|
||||||
|
|
||||||
function cancelHoverDismiss() {
|
function cancelHoverDismiss() {
|
||||||
hoverDismissTracker.cancelPending();
|
hoverDismissController.cancelPending();
|
||||||
_hoverDismissGrace.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeFromHoverDismiss() {
|
function closeFromHoverDismiss() {
|
||||||
@@ -428,6 +467,7 @@ Item {
|
|||||||
function open() {
|
function open() {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
|
_resetPublishedBody();
|
||||||
closeTimer.stop();
|
closeTimer.stop();
|
||||||
isClosing = false;
|
isClosing = false;
|
||||||
animationsEnabled = false;
|
animationsEnabled = false;
|
||||||
@@ -485,6 +525,7 @@ Item {
|
|||||||
_freezeMorphTravel();
|
_freezeMorphTravel();
|
||||||
else
|
else
|
||||||
_endMorphTravel();
|
_endMorphTravel();
|
||||||
|
_resetPublishedBody();
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
shouldBeVisible = false;
|
shouldBeVisible = false;
|
||||||
_primeContent = false;
|
_primeContent = false;
|
||||||
@@ -875,40 +916,6 @@ Item {
|
|||||||
readonly property real maskWidth: _dismissZone.width
|
readonly property real maskWidth: _dismissZone.width
|
||||||
readonly property real maskHeight: _dismissZone.height
|
readonly property real maskHeight: _dismissZone.height
|
||||||
|
|
||||||
// Track body hover to initiate grace timer for transient dismissal.
|
|
||||||
property bool _hoverOverBody: false
|
|
||||||
|
|
||||||
function _onBodyHoverChanged(over) {
|
|
||||||
_hoverOverBody = over;
|
|
||||||
if (over)
|
|
||||||
_hoverDismissGrace.stop();
|
|
||||||
else if (root.hoverDismissEnabled && !root.hoverDismissSuspended && root.shouldBeVisible)
|
|
||||||
_hoverDismissGrace.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
onHoverDismissSuspendedChanged: {
|
|
||||||
if (hoverDismissSuspended) {
|
|
||||||
_hoverDismissGrace.stop();
|
|
||||||
} else if (hoverDismissEnabled && shouldBeVisible && !_hoverOverBody) {
|
|
||||||
_hoverDismissGrace.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: _hoverDismissGrace
|
|
||||||
interval: 150
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (!root.hoverDismissEnabled || root.hoverDismissSuspended || !root.shouldBeVisible)
|
|
||||||
return;
|
|
||||||
if (root._hoverOverBody)
|
|
||||||
return;
|
|
||||||
if (PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY))
|
|
||||||
return;
|
|
||||||
root.closeFromHoverDismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DismissZone {
|
DismissZone {
|
||||||
id: _dismissZone
|
id: _dismissZone
|
||||||
barPosition: root.effectiveBarPosition
|
barPosition: root.effectiveBarPosition
|
||||||
@@ -927,26 +934,13 @@ Item {
|
|||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
MouseArea {
|
PopoutHoverDismiss {
|
||||||
|
id: hoverDismissController
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
z: -1
|
dismissEnabled: root.hoverDismissEnabled
|
||||||
acceptedButtons: Qt.NoButton
|
dismissSuspended: root.hoverDismissSuspended
|
||||||
hoverEnabled: true
|
surfaceVisible: root.shouldBeVisible
|
||||||
onPositionChanged: mouse => {
|
|
||||||
const gp = mapToItem(null, mouse.x, mouse.y);
|
|
||||||
PopoutManager.updateHoverCursor(gp.x, gp.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverDismissTracker {
|
|
||||||
id: hoverDismissTracker
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.hoverDismissEnabled && !root.hoverDismissSuspended && root.shouldBeVisible
|
|
||||||
shouldDismiss: function () {
|
|
||||||
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
|
|
||||||
}
|
|
||||||
onDismissRequested: root.closeFromHoverDismiss()
|
onDismissRequested: root.closeFromHoverDismiss()
|
||||||
onHoverMoved: (gx, gy) => PopoutManager.updateHoverCursor(gx, gy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowBlur {
|
WindowBlur {
|
||||||
@@ -1103,17 +1097,9 @@ Item {
|
|||||||
|
|
||||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||||
|
|
||||||
// Ancestor HoverHandler to capture body hover reliably.
|
PopoutHoverBodyTracker {
|
||||||
HoverHandler {
|
controller: hoverDismissController
|
||||||
id: bodyHoverHandler
|
trackingEnabled: root.hoverDismissEnabled && root.shouldBeVisible
|
||||||
enabled: root.hoverDismissEnabled && root.shouldBeVisible
|
|
||||||
onHoveredChanged: root._onBodyHoverChanged(hovered)
|
|
||||||
onPointChanged: {
|
|
||||||
if (!bodyHoverHandler.hovered)
|
|
||||||
return;
|
|
||||||
const gp = contentContainer.mapToItem(null, bodyHoverHandler.point.position.x, bodyHoverHandler.point.position.y);
|
|
||||||
PopoutManager.updateHoverCursor(gp.x, gp.y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
|
|||||||
@@ -40,8 +40,7 @@ Item {
|
|||||||
property bool hoverDismissSuspended: false
|
property bool hoverDismissSuspended: false
|
||||||
|
|
||||||
function cancelHoverDismiss() {
|
function cancelHoverDismiss() {
|
||||||
hoverDismissTracker.cancelPending();
|
hoverDismissController.cancelPending();
|
||||||
_hoverDismissGrace.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeFromHoverDismiss() {
|
function closeFromHoverDismiss() {
|
||||||
@@ -53,40 +52,6 @@ Item {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track body hover to initiate grace timer for transient dismissal.
|
|
||||||
property bool _hoverOverBody: false
|
|
||||||
|
|
||||||
function _onBodyHoverChanged(over) {
|
|
||||||
_hoverOverBody = over;
|
|
||||||
if (over)
|
|
||||||
_hoverDismissGrace.stop();
|
|
||||||
else if (root.hoverDismissEnabled && !root.hoverDismissSuspended && root.shouldBeVisible)
|
|
||||||
_hoverDismissGrace.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
onHoverDismissSuspendedChanged: {
|
|
||||||
if (hoverDismissSuspended) {
|
|
||||||
_hoverDismissGrace.stop();
|
|
||||||
} else if (hoverDismissEnabled && shouldBeVisible && !_hoverOverBody) {
|
|
||||||
_hoverDismissGrace.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: _hoverDismissGrace
|
|
||||||
interval: 150
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (!root.hoverDismissEnabled || root.hoverDismissSuspended || !root.shouldBeVisible)
|
|
||||||
return;
|
|
||||||
if (root._hoverOverBody)
|
|
||||||
return;
|
|
||||||
if (PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY))
|
|
||||||
return;
|
|
||||||
root.closeFromHoverDismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
property bool backgroundInteractive: true
|
property bool backgroundInteractive: true
|
||||||
property bool contentHandlesKeys: false
|
property bool contentHandlesKeys: false
|
||||||
@@ -637,26 +602,15 @@ Item {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
|
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
|
||||||
|
|
||||||
MouseArea {
|
PopoutHoverDismiss {
|
||||||
|
id: hoverDismissController
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
z: -1
|
dismissEnabled: root.hoverDismissEnabled
|
||||||
acceptedButtons: Qt.NoButton
|
dismissSuspended: root.hoverDismissSuspended
|
||||||
hoverEnabled: true
|
surfaceVisible: root.shouldBeVisible
|
||||||
onPositionChanged: mouse => {
|
globalOffsetX: root._surfaceMarginLeft
|
||||||
const gp = mapToItem(null, mouse.x, mouse.y);
|
globalOffsetY: root._fullHeight ? 0 : root._surfaceMarginTop
|
||||||
PopoutManager.updateHoverCursor(gp.x, gp.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverDismissTracker {
|
|
||||||
id: hoverDismissTracker
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: root.hoverDismissEnabled && !root.hoverDismissSuspended && root.shouldBeVisible
|
|
||||||
shouldDismiss: function () {
|
|
||||||
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
|
|
||||||
}
|
|
||||||
onDismissRequested: root.closeFromHoverDismiss()
|
onDismissRequested: root.closeFromHoverDismiss()
|
||||||
onHoverMoved: (gx, gy) => PopoutManager.updateHoverCursor(gx, gy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowBlur {
|
WindowBlur {
|
||||||
@@ -776,17 +730,9 @@ Item {
|
|||||||
|
|
||||||
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
readonly property real computedScaleCollapsed: root.animationScaleCollapsed
|
||||||
|
|
||||||
// Ancestor HoverHandler to capture body hover reliably.
|
PopoutHoverBodyTracker {
|
||||||
HoverHandler {
|
controller: hoverDismissController
|
||||||
id: bodyHoverHandler
|
trackingEnabled: root.hoverDismissEnabled && root.shouldBeVisible
|
||||||
enabled: root.hoverDismissEnabled && root.shouldBeVisible
|
|
||||||
onHoveredChanged: root._onBodyHoverChanged(hovered)
|
|
||||||
onPointChanged: {
|
|
||||||
if (!bodyHoverHandler.hovered)
|
|
||||||
return;
|
|
||||||
const gp = contentContainer.mapToItem(null, bodyHoverHandler.point.position.x, bodyHoverHandler.point.position.y);
|
|
||||||
PopoutManager.updateHoverCursor(gp.x, gp.y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
|
// openProgress: 0 = closed (at offset, scaleCollapsed), 1 = open (at 0, scale 1).
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ PanelWindow {
|
|||||||
|
|
||||||
property bool isVisible: false
|
property bool isVisible: false
|
||||||
property bool hoverDismissEnabled: 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
|
||||||
@@ -26,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
|
||||||
@@ -48,6 +50,8 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function hideFromHoverDismiss() {
|
function hideFromHoverDismiss() {
|
||||||
|
if (hoverDismissSuspended)
|
||||||
|
return;
|
||||||
hoverDismissEnabled = false;
|
hoverDismissEnabled = false;
|
||||||
slideAnimation.duration = Math.round(Theme.expressiveDurations.expressiveDefaultSpatial);
|
slideAnimation.duration = Math.round(Theme.expressiveDurations.expressiveDefaultSpatial);
|
||||||
hide();
|
hide();
|
||||||
@@ -62,7 +66,8 @@ PanelWindow {
|
|||||||
return false;
|
return false;
|
||||||
const padding = 24;
|
const padding = 24;
|
||||||
const topLeft = slideContainer.mapToItem(null, 0, 0);
|
const topLeft = slideContainer.mapToItem(null, 0, 0);
|
||||||
return gx >= topLeft.x - padding && gx < topLeft.x + slideContainer.width + padding && gy >= topLeft.y - padding && gy < topLeft.y + slideContainer.height + padding;
|
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() {
|
||||||
@@ -86,25 +91,15 @@ PanelWindow {
|
|||||||
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
z: -1
|
|
||||||
acceptedButtons: Qt.NoButton
|
|
||||||
hoverEnabled: true
|
|
||||||
onPositionChanged: mouse => {
|
|
||||||
const gp = mapToItem(null, mouse.x, mouse.y);
|
|
||||||
PopoutManager.updateHoverCursor(gp.x, gp.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HoverDismissTracker {
|
HoverDismissTracker {
|
||||||
id: hoverDismissTracker
|
id: hoverDismissTracker
|
||||||
anchors.fill: parent
|
parent: root.contentItem
|
||||||
enabled: root.hoverDismissEnabled && root.isVisible
|
enabled: root.hoverDismissEnabled && !root.hoverDismissSuspended && root.isVisible
|
||||||
shouldDismiss: function () {
|
shouldDismiss: function () {
|
||||||
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
|
return !PopoutManager.cursorOverBar(PopoutManager.hoverCursorGlobalX, PopoutManager.hoverCursorGlobalY);
|
||||||
}
|
}
|
||||||
onDismissRequested: root.hideFromHoverDismiss()
|
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
|
||||||
|
|||||||
@@ -2,34 +2,27 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
Item {
|
HoverHandler {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool enabled: false
|
|
||||||
property var shouldDismiss: null
|
property var shouldDismiss: null
|
||||||
|
|
||||||
signal dismissRequested
|
signal dismissRequested
|
||||||
// Emitted on every hover move; passive to avoid blocking overlapping MouseAreas
|
// Emitted on every hover move; passive to avoid blocking overlapping MouseAreas
|
||||||
signal hoverMoved(real gx, real gy)
|
signal hoverMoved(real gx, real gy)
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
HoverHandler {
|
|
||||||
id: hoverHandler
|
|
||||||
enabled: root.enabled
|
|
||||||
onPointChanged: {
|
onPointChanged: {
|
||||||
if (!root.enabled || !hoverHandler.hovered)
|
if (!enabled || !hovered)
|
||||||
return;
|
return;
|
||||||
const gp = root.mapToItem(null, hoverHandler.point.position.x, hoverHandler.point.position.y);
|
const gp = parent.mapToItem(null, point.position.x, point.position.y);
|
||||||
root.hoverMoved(gp.x, gp.y);
|
hoverMoved(gp.x, gp.y);
|
||||||
}
|
}
|
||||||
onHoveredChanged: {
|
onHoveredChanged: {
|
||||||
if (hoverHandler.hovered || !root.enabled)
|
if (hovered || !enabled)
|
||||||
return;
|
return;
|
||||||
if (typeof root.shouldDismiss === "function" && !root.shouldDismiss())
|
if (typeof shouldDismiss === "function" && !shouldDismiss())
|
||||||
return;
|
return;
|
||||||
root.dismissRequested();
|
dismissRequested();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelPending() {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user