1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-01 19:18:28 -04:00
Files
DankMaterialShell/quickshell/Modules/DankBar/DankBarBody.qml
T
2026-07-26 15:16:48 -04:00

1250 lines
50 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import QtQuick
import Quickshell
import qs.Common
import qs.Services
Item {
id: barWindow
readonly property var log: Log.scoped("DankBarBody")
required property var hostWindow
required property var rootWindow
required property var barConfig
required property var modelData
readonly property var screen: modelData
property var hyprlandOverviewLoader: rootWindow ? rootWindow.hyprlandOverviewLoader : null
property var leftWidgetsModel
property var centerWidgetsModel
property var rightWidgetsModel
readonly property bool barRevealed: inputMask.showing
property var controlCenterButtonRef: null
property var clockButtonRef: null
property var systemUpdateButtonRef: null
function triggerSystemUpdate() {
const loader = PopoutService.systemUpdateLoader;
if (!loader)
return;
loader.active = true;
if (!loader.item)
return;
const popout = loader.item;
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
if (systemUpdateButtonRef && popout.setTriggerPosition) {
const screenPos = systemUpdateButtonRef.mapToItem(null, 0, 0);
const pos = SettingsData.getPopupTriggerPosition(screenPos, barWindow.screen, barWindow.effectiveBarThickness, systemUpdateButtonRef.width, barConfig?.spacing ?? 4, barPosition, barConfig);
const section = systemUpdateButtonRef.section || "right";
popout.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
} else {
popout.screen = barWindow.screen;
}
PopoutManager.requestPopout(popout, undefined, "systemUpdate");
}
function triggerControlCenter() {
const loader = PopoutService.controlCenterLoader;
if (!loader)
return;
loader.active = true;
if (!loader.item) {
return;
}
if (controlCenterButtonRef && loader.item.setTriggerPosition) {
const screenPos = controlCenterButtonRef.mapToItem(null, 0, 0);
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
const pos = SettingsData.getPopupTriggerPosition(screenPos, barWindow.screen, barWindow.effectiveBarThickness, controlCenterButtonRef.width, barConfig?.spacing ?? 4, barPosition, barConfig);
const section = controlCenterButtonRef.section || "right";
loader.item.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
} else {
loader.item.triggerScreen = barWindow.screen;
}
loader.item.toggle();
if (loader.item.shouldBeVisible && NetworkService.wifiEnabled) {
NetworkService.scanWifi();
}
}
function dashSectionAnchor(section) {
let item;
switch (section) {
case "left":
item = barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection;
break;
case "right":
item = barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection;
break;
default:
item = barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection;
}
if (!item)
return null;
if (barWindow.isVertical)
return {
"pos": item.mapToItem(null, 0, item.height / 2),
"width": item.height
};
return {
"pos": item.mapToItem(null, 0, 0),
"width": item.width
};
}
function positionDash(popout, position) {
if (!popout.setTriggerPosition) {
popout.triggerScreen = barWindow.screen;
return "center";
}
const explicit = position === "left" || position === "center" || position === "right";
const section = explicit ? position : (clockButtonRef?.section || "center");
const clockAnchor = clockButtonRef?.visualContent ? {
"pos": clockButtonRef.visualContent.mapToItem(null, 0, 0),
"width": clockButtonRef.visualWidth
} : null;
let anchor;
if (!explicit && section !== "center" && clockAnchor)
anchor = clockAnchor;
else
anchor = dashSectionAnchor(section) || clockAnchor;
if (!anchor) {
popout.triggerScreen = barWindow.screen;
return section;
}
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
const pos = SettingsData.getPopupTriggerPosition(anchor.pos, barWindow.screen, barWindow.effectiveBarThickness, anchor.width, barConfig?.spacing ?? 4, barPosition, barConfig);
popout.setTriggerPosition(pos.x, pos.y, pos.width, section, barWindow.screen, barPosition, barWindow.effectiveBarThickness, barConfig?.spacing ?? 4, barConfig);
return section;
}
function triggerDashTab(tabId, position) {
const loader = PopoutService.dankDashPopoutLoader;
if (!loader)
return false;
loader.active = true;
if (!loader.item) {
return false;
}
const section = positionDash(loader.item, position);
if (loader.item.requestTab)
loader.item.requestTab(tabId);
PopoutManager.requestPopout(loader.item, undefined, (barConfig?.id ?? "default") + "-" + section + "-" + tabId);
return true;
}
function triggerWallpaperBrowser() {
triggerDashTab("wallpaper");
}
property var blurRegion: null
property var _blurWidgetItems: []
function registerBlurWidget(item) {
if (_blurWidgetItems.indexOf(item) >= 0)
return;
_blurWidgetItems = _blurWidgetItems.concat([item]);
_blurRebuildTimer.restart();
}
function unregisterBlurWidget(item) {
const idx = _blurWidgetItems.indexOf(item);
if (idx < 0)
return;
const arr = _blurWidgetItems.slice();
arr.splice(idx, 1);
_blurWidgetItems = arr;
_blurRebuildTimer.restart();
}
Timer {
id: _blurRebuildTimer
interval: 1
onTriggered: barBlur.rebuild()
}
Connections {
target: barWindow
function onUsesConnectedFrameChromeChanged() {
_blurRebuildTimer.restart();
}
function onUsesFrameBarChromeChanged() {
// Rebuild immediately so the bar region never overlaps FrameWindow's during chrome handoff
barBlur.rebuild();
}
function onBarRevealedChanged() {
barBlur.rebuild();
}
}
Component {
id: blurRegionComp
Region {}
}
Component {
id: blurSubRegionComp
Region {
property Item w
item: w
radius: Theme.cornerRadius
}
}
Component {
id: blurWingRegionComp
// r×r square at a bar end minus a quarter-disc — the swoop BarCanvas paints
Region {
id: wingRegion
property bool atEnd: false
readonly property real r: barBackground.wing
readonly property real bx: topBarMouseArea.x + barUnitInset.x + topBarSlide.x
readonly property real by: topBarMouseArea.y + barUnitInset.y + topBarSlide.y
readonly property real bw: barUnitInset.width
readonly property real bh: barUnitInset.height
x: {
switch (barPos) {
case SettingsData.Position.Left:
return bx + bw;
case SettingsData.Position.Right:
return bx - r;
default:
return atEnd ? bx + bw - r : bx;
}
}
y: {
switch (barPos) {
case SettingsData.Position.Top:
return by + bh;
case SettingsData.Position.Bottom:
return by - r;
default:
return atEnd ? by + bh - r : by;
}
}
width: r
height: r
Region {
intersection: Intersection.Subtract
radius: wingRegion.r
width: wingRegion.r * 2
height: wingRegion.r * 2
x: {
switch (barPos) {
case SettingsData.Position.Left:
return wingRegion.bx + wingRegion.bw;
case SettingsData.Position.Right:
return wingRegion.bx - wingRegion.r * 2;
default:
return wingRegion.atEnd ? wingRegion.bx + wingRegion.bw - wingRegion.r * 2 : wingRegion.bx;
}
}
y: {
switch (barPos) {
case SettingsData.Position.Top:
return wingRegion.by + wingRegion.bh;
case SettingsData.Position.Bottom:
return wingRegion.by - wingRegion.r * 2;
default:
return wingRegion.atEnd ? wingRegion.by + wingRegion.bh - wingRegion.r * 2 : wingRegion.by;
}
}
}
}
}
Item {
id: barBlur
visible: false
readonly property bool barHasTransparency: barWindow._backgroundAlpha > 0 && barWindow._backgroundAlpha < 1
function rebuild() {
teardown();
if (!BlurService.enabled || !BlurService.available)
return;
if (!barWindow.barRevealed && CompositorService.isHyprland)
return;
// FrameWindow owns the blur region for the whole edge while the bar wears frame chrome
if (FrameTransitionState.effectiveFrameEnabled && barWindow.usesFrameBarChrome)
return;
const widgets = barWindow._blurWidgetItems.filter(w => w && w.visible && w.width > 0 && w.height > 0);
const hasBar = barHasTransparency;
if (!hasBar && widgets.length === 0)
return;
const region = blurRegionComp.createObject(barWindow);
if (!region) {
log.warn("BarBlur: Failed to create blur region");
return;
}
if (hasBar) {
region.x = Qt.binding(() => topBarMouseArea.x + barUnitInset.x + topBarSlide.x);
region.y = Qt.binding(() => topBarMouseArea.y + barUnitInset.y + topBarSlide.y);
region.width = Qt.binding(() => barUnitInset.width);
region.height = Qt.binding(() => barUnitInset.height);
region.radius = Qt.binding(() => barBackground.rt);
}
const subRegions = [];
for (let i = 0; i < widgets.length; i++) {
const sub = blurSubRegionComp.createObject(region, {
w: widgets[i]
});
if (sub)
subRegions.push(sub);
}
if (hasBar && barBackground.gothEnabled && barWindow._wingR > 0) {
for (const atEnd of [false, true]) {
const wing = blurWingRegionComp.createObject(region, {
atEnd: atEnd
});
if (wing)
subRegions.push(wing);
}
}
region.regions = subRegions;
barWindow.blurRegion = region;
}
function teardown() {
const old = barWindow.blurRegion;
if (!old)
return;
barWindow.blurRegion = null;
old.destroy();
}
onBarHasTransparencyChanged: _blurRebuildTimer.restart()
Connections {
target: BlurService
function onEnabledChanged() {
barBlur.rebuild();
}
}
Connections {
target: FrameTransitionState
function onEffectiveFrameEnabledChanged() {
barBlur.rebuild();
}
}
Connections {
target: topBarSlide
function onXChanged() {
if (barWindow.blurRegion)
barWindow.blurRegion.changed();
}
function onYChanged() {
if (barWindow.blurRegion)
barWindow.blurRegion.changed();
}
}
Connections {
target: barBackground
function onGothEnabledChanged() {
_blurRebuildTimer.restart();
}
function onWingChanged() {
if (barWindow.blurRegion)
barWindow.blurRegion.changed();
}
}
Component.onCompleted: rebuild()
Component.onDestruction: teardown()
}
signal colorPickerRequested
onColorPickerRequested: rootWindow.colorPickerRequested()
property alias axis: axis
AxisContext {
id: axis
edge: {
switch (barConfig?.position ?? 0) {
case SettingsData.Position.Top:
return "top";
case SettingsData.Position.Bottom:
return "bottom";
case SettingsData.Position.Left:
return "left";
case SettingsData.Position.Right:
return "right";
default:
return "top";
}
}
}
readonly property bool isVertical: axis.isVertical
readonly property color _surfaceContainer: Theme.surfaceContainer
readonly property string _barId: barConfig?.id ?? "default"
property real _backgroundAlpha: barConfig?.transparency ?? 1.0
readonly property color _bgColor: (FrameTransitionState.effectiveFrameEnabled && usesFrameBarChrome) ? Theme.withAlpha(SettingsData.effectiveFrameColor, SettingsData.frameOpacity) : Theme.withAlpha(_surfaceContainer, _backgroundAlpha)
function _updateBackgroundAlpha() {
const live = SettingsData.barConfigs.find(c => c.id === _barId);
_backgroundAlpha = (live ?? barConfig)?.transparency ?? 1.0;
}
readonly property real _dpr: CompositorService.getScreenScale(barWindow.screen)
property string screenName: modelData.name
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screenName)
readonly property bool usesFrameBarChrome: CompositorService.frameWindowVisibleForScreen(screenName)
readonly property var renderBarConfig: SettingsData.effectiveBarConfigForRender(barConfig, usesFrameBarChrome)
property bool gothCornersEnabled: renderBarConfig?.gothCornersEnabled ?? false
property real wingtipsRadius: renderBarConfig?.gothCornerRadiusOverride ? (renderBarConfig?.gothCornerRadiusValue ?? 12) : Theme.cornerRadius
readonly property real _wingR: Math.max(0, wingtipsRadius)
// Shadow buffer: extra window space for shadow to render beyond bar bounds
readonly property bool _shadowActive: (Theme.elevationEnabled && (typeof SettingsData !== "undefined" ? (SettingsData.barElevationEnabled ?? true) : false)) || (renderBarConfig?.shadowIntensity ?? 0) > 0
readonly property real _shadowBuffer: {
if (!_shadowActive)
return 0;
const hasOverride = (renderBarConfig?.shadowIntensity ?? 0) > 0;
if (hasOverride) {
const blur = (renderBarConfig.shadowIntensity ?? 0) * 0.2;
const offset = blur * 0.5;
return Theme.snap(Math.max(16, blur + offset + 8), _dpr);
}
return Theme.snap(Theme.elevationRenderPadding(Theme.elevationLevel2, "top", 4, 8, 16), _dpr);
}
// Flatten/spacing collapse for maximized windows only applies to frame-integrated layout
readonly property bool flattenForMaximizedWindow: !FrameTransitionState.effectiveFrameEnabled || usesFrameBarChrome
property bool hasMaximizedToplevel: false
property bool shouldHideForWindows: false
function _updateHasMaximizedToplevel() {
if (!(barConfig?.maximizeDetection ?? true)) {
hasMaximizedToplevel = false;
return;
}
if (CompositorService.isMango) {
const out = MangoService.outputs[screenName];
const active = new Set((out?.activeTags) || []);
const wins = MangoService.windows || [];
for (let i = 0; i < wins.length; i++) {
const w = wins[i];
if (!w || w.monitor !== screenName || w.is_minimized)
continue;
if (active.size > 0 && !(w.tags || []).some(t => active.has(t)))
continue;
if (w.is_maximized || w.is_fullscreen) {
hasMaximizedToplevel = true;
return;
}
}
hasMaximizedToplevel = false;
return;
}
if (!CompositorService.isHyprland && !CompositorService.isNiri) {
hasMaximizedToplevel = false;
return;
}
const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
for (let i = 0; i < filtered.length; i++) {
if (filtered[i]?.maximized) {
hasMaximizedToplevel = true;
return;
}
}
hasMaximizedToplevel = false;
}
function _updateShouldHideForWindows() {
if (!(barConfig?.showOnWindowsOpen ?? false)) {
shouldHideForWindows = false;
return;
}
if (!(barConfig?.autoHide ?? false)) {
shouldHideForWindows = false;
return;
}
if (!CompositorService.isNiri && !CompositorService.isHyprland && !CompositorService.isMango) {
shouldHideForWindows = false;
return;
}
if (CompositorService.isNiri) {
let currentWorkspaceId = null;
for (let i = 0; i < NiriService.allWorkspaces.length; i++) {
const ws = NiriService.allWorkspaces[i];
if (ws.output === screenName && ws.is_active) {
currentWorkspaceId = ws.id;
break;
}
}
if (currentWorkspaceId === null) {
shouldHideForWindows = false;
return;
}
let hasTiled = false;
let hasFloatingTouchingBar = false;
const pos = barConfig?.position ?? 0;
const barThickness = barWindow.effectiveBarThickness + (barConfig?.spacing ?? 4);
for (let i = 0; i < NiriService.windows.length; i++) {
const win = NiriService.windows[i];
if (win.workspace_id !== currentWorkspaceId)
continue;
if (!win.is_floating) {
hasTiled = true;
continue;
}
const tilePos = win.layout?.tile_pos_in_workspace_view;
const winSize = win.layout?.window_size || win.layout?.tile_size;
if (!tilePos || !winSize)
continue;
switch (pos) {
case SettingsData.Position.Top:
if (tilePos[1] < barThickness)
hasFloatingTouchingBar = true;
break;
case SettingsData.Position.Bottom:
const screenHeight = barWindow.screen?.height ?? 0;
if (tilePos[1] + winSize[1] > screenHeight - barThickness)
hasFloatingTouchingBar = true;
break;
case SettingsData.Position.Left:
if (tilePos[0] < barThickness)
hasFloatingTouchingBar = true;
break;
case SettingsData.Position.Right:
const screenWidth = barWindow.screen?.width ?? 0;
if (tilePos[0] + winSize[0] > screenWidth - barThickness)
hasFloatingTouchingBar = true;
break;
}
}
shouldHideForWindows = hasTiled || hasFloatingTouchingBar;
return;
}
const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
shouldHideForWindows = filtered.length > 0;
}
property real effectiveSpacing: (FrameTransitionState.effectiveFrameEnabled && usesFrameBarChrome) ? 0 : ((flattenForMaximizedWindow && hasMaximizedToplevel) ? 0 : (barConfig?.spacing ?? 4))
Behavior on effectiveSpacing {
enabled: barWindow.hostWindow?.visible ?? false
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
readonly property int notificationCount: NotificationService.notifications.length
readonly property real effectiveBarThickness: (FrameTransitionState.effectiveFrameEnabled && usesFrameBarChrome) ? SettingsData.frameBarSize : Theme.snap(Math.max(barWindow.widgetThickness + (barConfig?.innerPadding ?? 4) + 4, Theme.barHeight - 4 - (8 - (barConfig?.innerPadding ?? 4))), _dpr)
readonly property bool effectiveOpenOnOverview: FrameTransitionState.effectiveFrameEnabled ? SettingsData.frameShowOnOverview : (barConfig?.openOnOverview ?? false)
readonly property real widgetThickness: Theme.snap(Math.max(20, 26 + (barConfig?.innerPadding ?? 4) * 0.6), _dpr)
readonly property bool hasAdjacentTopBar: {
if (barConfig?.autoHide ?? false)
return false;
if (!isVertical)
return false;
return SettingsData.barConfigs.some(bc => {
if (!bc.enabled || bc.id === barConfig?.id)
return false;
if (bc.autoHide)
return false;
if (!(bc.visible ?? true))
return false;
if (bc.position !== SettingsData.Position.Top && bc.position !== 0)
return false;
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
if (!onThisScreen)
return false;
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
return false;
return true;
});
}
readonly property bool hasAdjacentBottomBar: {
if (barConfig?.autoHide ?? false)
return false;
if (!isVertical)
return false;
const result = SettingsData.barConfigs.some(bc => {
if (!bc.enabled || bc.id === barConfig?.id)
return false;
if (bc.autoHide)
return false;
if (!(bc.visible ?? true))
return false;
if (bc.position !== SettingsData.Position.Bottom && bc.position !== 1)
return false;
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
if (!onThisScreen)
return false;
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
return false;
return true;
});
return result;
}
readonly property bool hasAdjacentLeftBar: {
if (barConfig?.autoHide ?? false)
return false;
if (isVertical)
return false;
const result = SettingsData.barConfigs.some(bc => {
if (!bc.enabled || bc.id === barConfig?.id)
return false;
if (bc.autoHide)
return false;
if (!(bc.visible ?? true))
return false;
if (bc.position !== SettingsData.Position.Left && bc.position !== 2)
return false;
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
if (!onThisScreen)
return false;
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
return false;
return true;
});
return result;
}
readonly property bool hasAdjacentRightBar: {
if (barConfig?.autoHide ?? false)
return false;
if (isVertical)
return false;
const result = SettingsData.barConfigs.some(bc => {
if (!bc.enabled || bc.id === barConfig?.id)
return false;
if (bc.autoHide)
return false;
if (!(bc.visible ?? true))
return false;
if (bc.position !== SettingsData.Position.Right && bc.position !== 3)
return false;
const onThisScreen = bc.screenPreferences.includes(screenName) || bc.screenPreferences.length === 0 || bc.screenPreferences.includes("all");
if (!onThisScreen)
return false;
if (bc.showOnLastDisplay && screenName !== barWindow.screenName)
return false;
return true;
});
return result;
}
readonly property real surfaceImplicitHeight: !isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
readonly property real surfaceImplicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((renderBarConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
Component.onCompleted: {
updateGpuTempConfig();
_updateBackgroundAlpha();
_updateHasMaximizedToplevel();
_updateShouldHideForWindows();
}
Connections {
target: PluginService
function onPluginLoaded(pluginId) {
log.info("DankBar: Plugin loaded:", pluginId);
SettingsData.widgetDataChanged();
}
function onPluginUnloaded(pluginId) {
log.info("DankBar: Plugin unloaded:", pluginId);
SettingsData.widgetDataChanged();
}
}
function updateGpuTempConfig() {
const leftWidgets = barConfig?.leftWidgets || [];
const centerWidgets = barConfig?.centerWidgets || [];
const rightWidgets = barConfig?.rightWidgets || [];
const allWidgets = [...leftWidgets, ...centerWidgets, ...rightWidgets];
const hasGpuTempWidget = allWidgets.some(widget => {
const widgetId = typeof widget === "string" ? widget : widget.id;
const widgetEnabled = typeof widget === "string" ? true : (widget.enabled !== false);
return widgetId === "gpuTemp" && widgetEnabled;
});
DgopService.gpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled || SessionData.nonNvidiaGpuTempEnabled;
DgopService.nvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nvidiaGpuTempEnabled;
DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled;
}
Connections {
function onBarConfigChanged() {
barWindow.updateGpuTempConfig();
barWindow._updateBackgroundAlpha();
barWindow._updateHasMaximizedToplevel();
barWindow._updateShouldHideForWindows();
}
target: rootWindow
}
Connections {
target: SettingsData
function onBarConfigsChanged() {
barWindow._updateBackgroundAlpha();
}
}
Connections {
target: CompositorService
function onToplevelsChanged() {
barWindow._updateHasMaximizedToplevel();
barWindow._updateShouldHideForWindows();
}
}
Connections {
target: NiriService
function onAllWorkspacesChanged() {
barWindow._updateHasMaximizedToplevel();
barWindow._updateShouldHideForWindows();
}
}
Connections {
function onNvidiaGpuTempEnabledChanged() {
barWindow.updateGpuTempConfig();
}
function onNonNvidiaGpuTempEnabledChanged() {
barWindow.updateGpuTempConfig();
}
target: SessionData
}
readonly property int barPos: barConfig?.position ?? 0
readonly property bool reserveExclusiveWhenAutoHidden: FrameTransitionState.effectiveFrameEnabled && usesFrameBarChrome && !!barWindow.screen && SettingsData.isScreenInPreferences(barWindow.screen, SettingsData.frameScreenPreferences)
readonly property real surfaceExclusiveZone: (!(barConfig?.visible ?? true) || (topBarCore.autoHide && !barWindow.reserveExclusiveWhenAutoHidden)) ? -1 : (barWindow.effectiveBarThickness + effectiveSpacing + (usesFrameBarChrome ? 0 : (barConfig?.bottomGap ?? 0)))
readonly property alias inputMaskItem: inputMask
Item {
id: inputMask
readonly property int barThickness: Theme.px(barWindow.effectiveBarThickness + barWindow.effectiveSpacing, barWindow._dpr)
readonly property bool inOverviewWithShow: CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview
readonly property bool effectiveVisible: (barConfig?.visible ?? true) || inOverviewWithShow
readonly property bool showing: effectiveVisible && (topBarCore.reveal || inOverviewWithShow || !topBarCore.autoHide)
readonly property int maskThickness: showing ? barThickness : 1
x: {
if (!axis.isVertical) {
return 0;
} else {
switch (barPos) {
case SettingsData.Position.Left:
return 0;
case SettingsData.Position.Right:
return parent.width - maskThickness;
default:
return 0;
}
}
}
y: {
if (axis.isVertical) {
return 0;
} else {
switch (barPos) {
case SettingsData.Position.Top:
return 0;
case SettingsData.Position.Bottom:
return parent.height - maskThickness;
default:
return 0;
}
}
}
width: axis.isVertical ? maskThickness : parent.width
height: axis.isVertical ? parent.height : maskThickness
}
readonly property bool clickThroughEnabled: barConfig?.clickThrough ?? false
readonly property var _leftSection: topBarContent ? (barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection) : null
readonly property var _centerSection: topBarContent ? (barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection) : null
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
readonly property real _revealProgress: topBarSlide.x + topBarSlide.y
function containsGlobalPoint(gx, gy, padding) {
const pad = padding !== undefined ? padding : 16;
if (!inputMask.showing)
return false;
const topLeft = inputMask.mapToItem(null, 0, 0);
return gx >= topLeft.x - pad && gx < topLeft.x + inputMask.width + pad && gy >= topLeft.y - pad && gy < topLeft.y + inputMask.height + pad;
}
function sectionRect(section, isCenter, _dep) {
if (!section)
return {
"x": 0,
"y": 0,
"w": 0,
"h": 0
};
const pos = section.mapToItem(barWindow.hostWindow.contentItem, 0, 0);
const implW = section.implicitWidth || 0;
const implH = section.implicitHeight || 0;
const contentSize = isCenter ? (section.contentSize || 0) : 0;
let offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
let offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
let w = implW;
let h = implH;
// index centering lays content out asymmetrically; use the real extent
if (contentSize > 0) {
if (barWindow.isVertical) {
offsetY = section.contentStart;
h = contentSize;
} else {
offsetX = section.contentStart;
w = contentSize;
}
}
const edgePad = 2;
return {
"x": pos.x + offsetX - edgePad,
"y": pos.y + offsetY - edgePad,
"w": w + edgePad * 2,
"h": h + edgePad * 2
};
}
Item {
id: topBarCore
anchors.fill: parent
layer.enabled: false
property bool autoHide: barConfig?.autoHide ?? false
property bool revealSticky: false
// In click-through mode the hidden bar's input mask covers the full
// band while the revealed bar's mask covers only the widget sections,
// so the pointer position is unknowable while it is over a gap. An
// enter on the hidden bar therefore only reveals once the pointer
// reaches a thin strip at the screen edge; anything else (including
// the spurious enter generated by the mask expanding underneath a
// resting pointer) keeps the bar hidden.
property bool gapEnterSuppressed: false
readonly property bool hoverReveal: topBarMouseArea.containsMouse && !gapEnterSuppressed
readonly property bool ipcReveal: !!SettingsData.barIpcRevealStates[barConfig?.id ?? ""]
onRevealChanged: {
if (reveal && barWindow.clickThroughEnabled)
revealSettle.restart();
}
// The input mask updates lag reveal transitions, generating spurious
// enter/leave pairs. Hides are deferred until the transition settles;
// the timer re-evaluates against the post-transition mask.
Timer {
id: revealSettle
interval: 600
repeat: false
onTriggered: topBarCore.evaluateReveal()
}
function inEdgeStrip(x, y) {
const band = barWindow.isVertical ? topBarMouseArea.width : topBarMouseArea.height;
const strip = Math.max(8, band * 0.15);
switch (barPos) {
case SettingsData.Position.Bottom:
return y >= band - strip;
case SettingsData.Position.Left:
return x <= strip;
case SettingsData.Position.Right:
return x >= band - strip;
default:
return y <= strip;
}
}
Timer {
id: revealHold
interval: barConfig?.autoHideDelay ?? 250
repeat: false
onTriggered: {
if (!topBarCore.hoverReveal && !topBarCore.popoutPinsReveal)
topBarCore.revealSticky = false;
}
}
property bool hasActivePopout: false
readonly property bool popoutPinsReveal: !!(hasActivePopout && !(barConfig?.autoHideStrict ?? false))
onHasActivePopoutChanged: evaluateReveal()
onPopoutPinsRevealChanged: evaluateReveal()
function updateActivePopoutState() {
if (!barWindow.screen)
return;
const screenName = barWindow.screen.name;
const activePopout = PopoutManager.currentPopoutsByScreen[screenName];
const activeTrayMenu = TrayMenuManager.activeTrayMenus[screenName];
const trayOpen = rootWindow.systemTrayMenuOpen;
const hasVisiblePopout = activePopout && activePopout.shouldBeVisible;
topBarCore.hasActivePopout = !!(hasVisiblePopout || activeTrayMenu || trayOpen);
}
Connections {
target: PopoutManager
function onPopoutChanged() {
topBarCore.updateActivePopoutState();
}
function onPopoutOpening() {
topBarCore.evaluateReveal();
}
}
Connections {
target: TrayMenuManager
function onActiveTrayMenusChanged() {
topBarCore.updateActivePopoutState();
}
}
property bool reveal: {
const inOverviewWithShow = CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview;
if (inOverviewWithShow)
return true;
const showOnWindowsSetting = barConfig?.showOnWindowsOpen ?? false;
if (showOnWindowsSetting && autoHide && (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango)) {
if (barWindow.shouldHideForWindows)
return hoverReveal || popoutPinsReveal || revealSticky || ipcReveal;
return true;
}
if (CompositorService.isNiri && NiriService.inOverview)
return hoverReveal || popoutPinsReveal || revealSticky || ipcReveal;
return (barConfig?.visible ?? true) && (!autoHide || hoverReveal || popoutPinsReveal || revealSticky || ipcReveal);
}
Connections {
function onBarConfigChanged() {
topBarCore.autoHide = barConfig?.autoHide ?? false;
topBarCore.evaluateReveal();
}
target: rootWindow
}
Component.onCompleted: topBarCore.updateActivePopoutState()
function evaluateReveal() {
if (!autoHide)
return;
if (hoverReveal) {
SettingsData.setBarIpcReveal(barConfig?.id ?? "", false);
revealSticky = true;
revealHold.stop();
return;
}
if (popoutPinsReveal) {
revealSticky = true;
revealHold.stop();
return;
}
if (revealSettle.running)
return;
revealHold.interval = barConfig?.autoHideDelay ?? 250;
revealHold.restart();
}
Connections {
target: topBarMouseArea
function onContainsMouseChanged() {
if (!topBarMouseArea.containsMouse) {
topBarCore.gapEnterSuppressed = false;
} else if (barWindow.clickThroughEnabled && !topBarCore.reveal) {
topBarCore.gapEnterSuppressed = true;
}
topBarCore.evaluateReveal();
}
}
MouseArea {
id: topBarMouseArea
y: !barWindow.isVertical ? (barPos === SettingsData.Position.Bottom ? parent.height - height : 0) : 0
x: barWindow.isVertical ? (barPos === SettingsData.Position.Right ? parent.width - width : 0) : 0
height: !barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + barWindow.effectiveSpacing, barWindow._dpr) : undefined
width: barWindow.isVertical ? Theme.px(barWindow.effectiveBarThickness + barWindow.effectiveSpacing, barWindow._dpr) : undefined
anchors {
left: !barWindow.isVertical ? parent.left : (barPos === SettingsData.Position.Left ? parent.left : undefined)
right: !barWindow.isVertical ? parent.right : (barPos === SettingsData.Position.Right ? parent.right : undefined)
top: barWindow.isVertical ? parent.top : undefined
bottom: barWindow.isVertical ? parent.bottom : undefined
}
readonly property bool inOverview: CompositorService.isNiri && NiriService.inOverview && barWindow.effectiveOpenOnOverview
hoverEnabled: (barConfig?.autoHide ?? false) && !inOverview && !topBarCore.popoutPinsReveal
acceptedButtons: Qt.NoButton
enabled: (barConfig?.autoHide ?? false) && !inOverview
onPositionChanged: mouse => {
if (!topBarCore.gapEnterSuppressed)
return;
if (!topBarCore.inEdgeStrip(mouse.x, mouse.y))
return;
topBarCore.gapEnterSuppressed = false;
topBarCore.evaluateReveal();
}
Item {
id: topBarContainer
anchors.fill: parent
transform: Translate {
id: topBarSlide
x: barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (barPos === SettingsData.Position.Right ? barWindow.surfaceImplicitWidth : -barWindow.surfaceImplicitWidth), barWindow._dpr) : 0
y: !barWindow.isVertical ? Theme.snap(topBarCore.reveal ? 0 : (barPos === SettingsData.Position.Bottom ? barWindow.surfaceImplicitHeight : -barWindow.surfaceImplicitHeight), barWindow._dpr) : 0
Behavior on x {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
Behavior on y {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Easing.OutCubic
}
}
}
Item {
id: barUnitInset
property int spacingPx: Theme.px(barWindow.effectiveSpacing, barWindow._dpr)
anchors.fill: parent
anchors.leftMargin: !barWindow.isVertical ? spacingPx : (axis.edge === "left" ? spacingPx : 0)
anchors.rightMargin: !barWindow.isVertical ? spacingPx : (axis.edge === "right" ? spacingPx : 0)
anchors.topMargin: barWindow.isVertical ? (barWindow.hasAdjacentTopBar ? 0 : spacingPx) : (axis.outerVisualEdge() === "bottom" ? 0 : spacingPx)
anchors.bottomMargin: barWindow.isVertical ? (barWindow.hasAdjacentBottomBar ? 0 : spacingPx) : (axis.outerVisualEdge() === "bottom" ? spacingPx : 0)
BarCanvas {
id: barBackground
barWindow: barWindow
axis: axis
barConfig: barWindow.renderBarConfig
}
MouseArea {
anchors.fill: parent
z: -2
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: {
const screenName = barWindow.screen?.name;
if (!screenName)
return;
if (PopoutManager.currentPopoutsByScreen[screenName])
PopoutManager.closeAllPopouts();
if (ModalManager.currentModalsByScreen[screenName])
ModalManager.closeAllModalsExcept(null);
TrayMenuManager.closeAllMenus();
}
}
MouseArea {
id: scrollArea
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
property real touchpadAccumulatorY: 0
property real touchpadAccumulatorX: 0
property real mouseAccumulatorY: 0
property real mouseAccumulatorX: 0
property bool actionInProgress: false
Timer {
id: cooldownTimer
interval: 100
onTriggered: parent.actionInProgress = false
}
function handleScrollAction(behavior, direction) {
switch (behavior) {
case "workspace":
topBarContent.switchWorkspace(direction);
return true;
case "column":
if (!CompositorService.isNiri)
return false;
if (direction > 0)
NiriService.moveColumnRight(barWindow.screenName);
else
NiriService.moveColumnLeft(barWindow.screenName);
return true;
default:
return false;
}
}
function processWheel(wheel) {
if (!(barConfig?.scrollEnabled ?? true) || actionInProgress) {
wheel.accepted = false;
return;
}
const deltaY = wheel.angleDelta.y;
const deltaX = wheel.angleDelta.x;
const isTouchpadY = wheel.pixelDelta && wheel.pixelDelta.y !== 0;
const isTouchpadX = wheel.pixelDelta && wheel.pixelDelta.x !== 0;
const xBehavior = barConfig?.scrollXBehavior ?? "column";
const yBehavior = barConfig?.scrollYBehavior ?? "workspace";
const reverse = SettingsData.reverseScrolling ? -1 : 1;
if (CompositorService.isNiri && xBehavior !== "none" && Math.abs(deltaX) > Math.abs(deltaY)) {
if (isTouchpadX) {
touchpadAccumulatorX += deltaX;
if (Math.abs(touchpadAccumulatorX) >= 500) {
const direction = touchpadAccumulatorX * reverse < 0 ? 1 : -1;
if (handleScrollAction(xBehavior, direction)) {
actionInProgress = true;
cooldownTimer.restart();
}
touchpadAccumulatorX = 0;
}
} else {
mouseAccumulatorX += deltaX;
if (Math.abs(mouseAccumulatorX) >= 120) {
const direction = mouseAccumulatorX * reverse < 0 ? 1 : -1;
if (handleScrollAction(xBehavior, direction)) {
actionInProgress = true;
cooldownTimer.restart();
}
mouseAccumulatorX = 0;
}
}
wheel.accepted = false;
return;
}
if (yBehavior === "none") {
wheel.accepted = false;
return;
}
if (isTouchpadY) {
touchpadAccumulatorY += deltaY;
if (Math.abs(touchpadAccumulatorY) >= 500) {
const direction = touchpadAccumulatorY * reverse < 0 ? 1 : -1;
if (handleScrollAction(yBehavior, direction)) {
actionInProgress = true;
cooldownTimer.restart();
}
touchpadAccumulatorY = 0;
}
} else {
mouseAccumulatorY += deltaY;
if (Math.abs(mouseAccumulatorY) >= 120) {
const direction = mouseAccumulatorY * reverse < 0 ? 1 : -1;
if (handleScrollAction(yBehavior, direction)) {
actionInProgress = true;
cooldownTimer.restart();
}
mouseAccumulatorY = 0;
}
}
wheel.accepted = false;
}
onWheel: wheel => processWheel(wheel)
}
DankBarContent {
id: topBarContent
barWindow: barWindow
rootWindow: barWindow.rootWindow
barConfig: barWindow.barConfig
leftWidgetsModel: barWindow.leftWidgetsModel
centerWidgetsModel: barWindow.centerWidgetsModel
rightWidgetsModel: barWindow.rightWidgetsModel
}
// Passive: tracks cursor without intercepting clicks or scroll
HoverHandler {
id: hoverPopoutHandler
enabled: (barConfig?.hoverPopouts ?? false) && !barWindow.clickThroughEnabled
property real lastGlobalX: 0
property real lastGlobalY: 0
onPointChanged: {
const gp = barUnitInset.mapToItem(null, point.position.x, point.position.y);
lastGlobalX = gp.x;
lastGlobalY = gp.y;
topBarContent.queueHoverPopout(gp.x, gp.y);
}
onHoveredChanged: {
topBarContent.updateHoverBarHovered(hovered);
}
}
}
}
}
}
}