1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-24 21:42:51 -05:00

popout: fix excessive repaints

- Size content window to content size, buffer for shadow
- Add second window for click outside behavior
- User overriding the layer disables the click outside behavior

part of #716
This commit is contained in:
bbedward
2025-11-23 10:49:59 -05:00
parent fd20986cf8
commit 62845b470c
2 changed files with 355 additions and 313 deletions

View File

@@ -1,16 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls
import QtQuick.Effects
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Widgets
import qs.Common import qs.Common
import qs.Modules.ControlCenter
import qs.Modules.ControlCenter.Widgets
import qs.Modules.ControlCenter.Details import qs.Modules.ControlCenter.Details
import qs.Modules.DankBar
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
import qs.Modules.ControlCenter.Components import qs.Modules.ControlCenter.Components
@@ -32,39 +23,39 @@ DankPopout {
signal lockRequested signal lockRequested
function collapseAll() { function collapseAll() {
expandedSection = "" expandedSection = "";
expandedWidgetIndex = -1 expandedWidgetIndex = -1;
expandedWidgetData = null expandedWidgetData = null;
} }
onEditModeChanged: { onEditModeChanged: {
if (editMode) { if (editMode) {
collapseAll() collapseAll();
} }
} }
onVisibleChanged: { onVisibleChanged: {
if (!visible) { if (!visible) {
collapseAll() collapseAll();
} }
} }
readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) readonly property color _containerBg: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
function openWithSection(section) { function openWithSection(section) {
StateUtils.openWithSection(root, section) StateUtils.openWithSection(root, section);
} }
function toggleSection(section) { function toggleSection(section) {
StateUtils.toggleSection(root, section) StateUtils.toggleSection(root, section);
} }
popupWidth: 550 popupWidth: 550
popupHeight: { popupHeight: {
const screenHeight = (triggerScreen?.height ?? 1080) const screenHeight = (triggerScreen?.height ?? 1080);
const maxHeight = screenHeight - 100 const maxHeight = screenHeight - 100;
const contentHeight = contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400 const contentHeight = contentLoader.item && contentLoader.item.implicitHeight > 0 ? contentLoader.item.implicitHeight + 20 : 400;
return Math.min(maxHeight, contentHeight) return Math.min(maxHeight, contentHeight);
} }
triggerX: 0 triggerX: 0
triggerY: 0 triggerY: 0
@@ -75,12 +66,16 @@ DankPopout {
property bool credentialsPromptOpen: NetworkService.credentialsRequested property bool credentialsPromptOpen: NetworkService.credentialsRequested
WlrLayershell.keyboardFocus: { customKeyboardFocus: {
if (!shouldBeVisible) return WlrKeyboardFocus.None if (!shouldBeVisible)
if (powerMenuOpen) return WlrKeyboardFocus.None return WlrKeyboardFocus.None;
if (credentialsPromptOpen) return WlrKeyboardFocus.None if (powerMenuOpen)
if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand return WlrKeyboardFocus.None;
return WlrKeyboardFocus.Exclusive if (credentialsPromptOpen)
return WlrKeyboardFocus.None;
if (CompositorService.isHyprland)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
} }
onBackgroundClicked: close() onBackgroundClicked: close()
@@ -88,21 +83,21 @@ DankPopout {
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (shouldBeVisible) { if (shouldBeVisible) {
Qt.callLater(() => { Qt.callLater(() => {
if (NetworkService.activeService) { if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled;
} }
if (UserInfoService) if (UserInfoService)
UserInfoService.getUptime() UserInfoService.getUptime();
}) });
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {
if (NetworkService.activeService) { if (NetworkService.activeService) {
NetworkService.activeService.autoRefreshEnabled = false NetworkService.activeService.autoRefreshEnabled = false;
} }
if (BluetoothService.adapter && BluetoothService.adapter.discovering) if (BluetoothService.adapter && BluetoothService.adapter.discovering)
BluetoothService.adapter.discovering = false BluetoothService.adapter.discovering = false;
editMode = false editMode = false;
}) });
} }
} }
@@ -118,9 +113,9 @@ DankPopout {
property alias bluetoothCodecSelector: bluetoothCodecSelector property alias bluetoothCodecSelector: bluetoothCodecSelector
color: { color: {
const transparency = Theme.popupTransparency const transparency = Theme.popupTransparency;
const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1) const surface = Theme.surfaceContainer || Qt.rgba(0.1, 0.1, 0.1, 1);
return Qt.rgba(surface.r, surface.g, surface.b, transparency) return Qt.rgba(surface.r, surface.g, surface.b, transparency);
} }
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08) border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
@@ -157,20 +152,20 @@ DankPopout {
onEditModeToggled: root.editMode = !root.editMode onEditModeToggled: root.editMode = !root.editMode
onPowerButtonClicked: { onPowerButtonClicked: {
if (powerMenuModalLoader) { if (powerMenuModalLoader) {
powerMenuModalLoader.active = true powerMenuModalLoader.active = true;
if (powerMenuModalLoader.item) { if (powerMenuModalLoader.item) {
const popoutPos = controlContent.mapToItem(null, 0, 0) const popoutPos = controlContent.mapToItem(null, 0, 0);
const bounds = Qt.rect(popoutPos.x, popoutPos.y, controlContent.width, controlContent.height) const bounds = Qt.rect(popoutPos.x, popoutPos.y, controlContent.width, controlContent.height);
powerMenuModalLoader.item.openFromControlCenter(bounds, root.triggerScreen) powerMenuModalLoader.item.openFromControlCenter(bounds, root.triggerScreen);
} }
} }
} }
onLockRequested: { onLockRequested: {
root.close() root.close();
root.lockRequested() root.lockRequested();
} }
onSettingsButtonClicked: { onSettingsButtonClicked: {
root.close() root.close();
} }
} }
@@ -187,16 +182,16 @@ DankPopout {
screenName: root.triggerScreen?.name || "" screenName: root.triggerScreen?.name || ""
parentScreen: root.triggerScreen parentScreen: root.triggerScreen
onExpandClicked: (widgetData, globalIndex) => { onExpandClicked: (widgetData, globalIndex) => {
root.expandedWidgetIndex = globalIndex root.expandedWidgetIndex = globalIndex;
root.expandedWidgetData = widgetData root.expandedWidgetData = widgetData;
if (widgetData.id === "diskUsage") { if (widgetData.id === "diskUsage") {
root.toggleSection("diskUsage_" + (widgetData.instanceId || "default")) root.toggleSection("diskUsage_" + (widgetData.instanceId || "default"));
} else if (widgetData.id === "brightnessSlider") { } else if (widgetData.id === "brightnessSlider") {
root.toggleSection("brightnessSlider_" + (widgetData.instanceId || "default")) root.toggleSection("brightnessSlider_" + (widgetData.instanceId || "default"));
} else { } else {
root.toggleSection(widgetData.id) root.toggleSection(widgetData.id);
} }
} }
onRemoveWidget: index => widgetModel.removeWidget(index) onRemoveWidget: index => widgetModel.removeWidget(index)
onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex) onMoveWidget: (fromIndex, toIndex) => widgetModel.moveWidget(fromIndex, toIndex)
onToggleWidgetSize: index => widgetModel.toggleWidgetSize(index) onToggleWidgetSize: index => widgetModel.toggleWidgetSize(index)
@@ -209,10 +204,10 @@ DankPopout {
popoutContent: controlContent popoutContent: controlContent
availableWidgets: { availableWidgets: {
if (!editMode) if (!editMode)
return [] return [];
const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id) const existingIds = (SettingsData.controlCenterWidgets || []).map(w => w.id);
const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets()) const allWidgets = widgetModel.baseWidgetDefinitions.concat(widgetModel.getPluginWidgets());
return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id)) return allWidgets.filter(w => w.allowMultiple || !existingIds.includes(w.id));
} }
onAddWidget: widgetId => widgetModel.addWidget(widgetId) onAddWidget: widgetId => widgetModel.addWidget(widgetId)
onResetToDefault: () => widgetModel.resetToDefault() onResetToDefault: () => widgetModel.resetToDefault()
@@ -239,10 +234,10 @@ DankPopout {
id: bluetoothDetail id: bluetoothDetail
onShowCodecSelector: function (device) { onShowCodecSelector: function (device) {
if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) { if (contentLoader.item && contentLoader.item.bluetoothCodecSelector) {
contentLoader.item.bluetoothCodecSelector.show(device) contentLoader.item.bluetoothCodecSelector.show(device);
contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function (deviceAddress, codecName) { contentLoader.item.bluetoothCodecSelector.codecSelected.connect(function (deviceAddress, codecName) {
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName) bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName);
}) });
} }
} }
} }

View File

@@ -1,18 +1,15 @@
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import Quickshell import Quickshell
import Quickshell.Hyprland
import Quickshell.Wayland import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
PanelWindow { Item {
id: root id: root
property string layerNamespace: "dms:popout" property string layerNamespace: "dms:popout"
WlrLayershell.namespace: layerNamespace
property alias content: contentLoader.sourceComponent property alias content: contentLoader.sourceComponent
property alias contentLoader: contentLoader property alias contentLoader: contentLoader
property real popupWidth: 400 property real popupWidth: 400
@@ -28,24 +25,34 @@ PanelWindow {
property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial property list<real> animationEnterCurve: Theme.expressiveCurves.expressiveDefaultSpatial
property list<real> animationExitCurve: Theme.expressiveCurves.emphasized property list<real> animationExitCurve: Theme.expressiveCurves.emphasized
property bool shouldBeVisible: false property bool shouldBeVisible: false
property var customKeyboardFocus: null
property real storedBarThickness: Theme.barHeight - 4 property real storedBarThickness: Theme.barHeight - 4
property real storedBarSpacing: 4 property real storedBarSpacing: 4
property var storedBarConfig: null property var storedBarConfig: null
property var adjacentBarInfo: ({ "topBar": 0, "bottomBar": 0, "leftBar": 0, "rightBar": 0 }) property var adjacentBarInfo: ({
"topBar": 0,
visible: false "bottomBar": 0,
"leftBar": 0,
"rightBar": 0
})
property var screen: null
readonly property real effectiveBarThickness: { readonly property real effectiveBarThickness: {
const padding = storedBarConfig ? (storedBarConfig.innerPadding !== undefined ? storedBarConfig.innerPadding : 4) : 4 const padding = storedBarConfig ? (storedBarConfig.innerPadding !== undefined ? storedBarConfig.innerPadding : 4) : 4;
return Math.max(26 + padding * 0.6, Theme.barHeight - 4 - (8 - padding)) + storedBarSpacing return Math.max(26 + padding * 0.6, Theme.barHeight - 4 - (8 - padding)) + storedBarSpacing;
} }
readonly property var barBounds: { readonly property var barBounds: {
if (!root.screen) { if (!screen)
return { "x": 0, "y": 0, "width": 0, "height": 0, "wingSize": 0 } return {
} "x": 0,
return SettingsData.getBarBounds(root.screen, effectiveBarThickness, effectiveBarPosition, storedBarConfig) "y": 0,
"width": 0,
"height": 0,
"wingSize": 0
};
return SettingsData.getBarBounds(screen, effectiveBarThickness, effectiveBarPosition, storedBarConfig);
} }
readonly property real barX: barBounds.x readonly property real barX: barBounds.x
@@ -58,50 +65,57 @@ PanelWindow {
signal popoutClosed signal popoutClosed
signal backgroundClicked signal backgroundClicked
property int effectiveBarPosition: 0
property real effectiveBarBottomGap: 0
function setBarContext(position, bottomGap) { function setBarContext(position, bottomGap) {
effectiveBarPosition = position !== undefined ? position : 0 effectiveBarPosition = position !== undefined ? position : 0;
effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0 effectiveBarBottomGap = bottomGap !== undefined ? bottomGap : 0;
} }
function setTriggerPosition(x, y, width, section, screen, barPosition, barThickness, barSpacing, barConfig) { function setTriggerPosition(x, y, width, section, targetScreen, barPosition, barThickness, barSpacing, barConfig) {
triggerX = x triggerX = x;
triggerY = y triggerY = y;
triggerWidth = width triggerWidth = width;
triggerSection = section triggerSection = section;
root.screen = screen screen = targetScreen;
storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4) storedBarThickness = barThickness !== undefined ? barThickness : (Theme.barHeight - 4);
storedBarSpacing = barSpacing !== undefined ? barSpacing : 4 storedBarSpacing = barSpacing !== undefined ? barSpacing : 4;
storedBarConfig = barConfig storedBarConfig = barConfig;
const pos = barPosition !== undefined ? barPosition : 0 const pos = barPosition !== undefined ? barPosition : 0;
const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : 0) : 0 const bottomGap = barConfig ? (barConfig.bottomGap !== undefined ? barConfig.bottomGap : 0) : 0;
// Get adjacent bar info for proper positioning adjacentBarInfo = SettingsData.getAdjacentBarInfo(targetScreen, pos, barConfig);
adjacentBarInfo = SettingsData.getAdjacentBarInfo(screen, pos, barConfig) setBarContext(pos, bottomGap);
}
setBarContext(pos, bottomGap) readonly property bool useBackgroundWindow: {
const layerOverride = Quickshell.env("DMS_POPOUT_LAYER");
return !layerOverride || layerOverride === "overlay";
} }
function open() { function open() {
closeTimer.stop() if (!screen)
shouldBeVisible = true return;
visible = true closeTimer.stop();
PopoutManager.showPopout(root) shouldBeVisible = true;
opened() if (useBackgroundWindow)
backgroundWindow.visible = true;
contentWindow.visible = true;
PopoutManager.showPopout(root);
opened();
} }
function close() { function close() {
shouldBeVisible = false shouldBeVisible = false;
PopoutManager.popoutChanged() PopoutManager.popoutChanged();
closeTimer.restart() closeTimer.restart();
} }
function toggle() { function toggle() {
if (shouldBeVisible) shouldBeVisible ? close() : open();
close()
else
open()
} }
Timer { Timer {
@@ -109,277 +123,310 @@ PanelWindow {
interval: animationDuration interval: animationDuration
onTriggered: { onTriggered: {
if (!shouldBeVisible) { if (!shouldBeVisible) {
visible = false contentWindow.visible = false;
PopoutManager.hidePopout(root) if (useBackgroundWindow)
popoutClosed() backgroundWindow.visible = false;
PopoutManager.hidePopout(root);
popoutClosed();
} }
} }
} }
color: "transparent" readonly property real screenWidth: screen ? screen.width : 0
WlrLayershell.layer: { readonly property real screenHeight: screen ? screen.height : 0
switch (Quickshell.env("DMS_POPOUT_LAYER")) { readonly property real dpr: screen ? CompositorService.getScreenScale(screen) : 1
case "bottom":
return WlrLayershell.Bottom
case "overlay":
return WlrLayershell.Overlay
case "background":
return WlrLayershell.Background
default:
return WlrLayershell.Top
}
}
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: {
if (!shouldBeVisible) return WlrKeyboardFocus.None
if (CompositorService.isHyprland) return WlrKeyboardFocus.OnDemand
return WlrKeyboardFocus.Exclusive
}
anchors {
top: true
left: true
right: true
bottom: true
}
readonly property real screenWidth: root.screen.width
readonly property real screenHeight: root.screen.height
readonly property real dpr: CompositorService.getScreenScale(root.screen)
readonly property real shadowBuffer: 5
readonly property real alignedWidth: Theme.px(popupWidth, dpr) readonly property real alignedWidth: Theme.px(popupWidth, dpr)
readonly property real alignedHeight: Theme.px(popupHeight, dpr) readonly property real alignedHeight: Theme.px(popupHeight, dpr)
property int effectiveBarPosition: 0
property real effectiveBarBottomGap: 0
readonly property real alignedX: Theme.snap((() => { readonly property real alignedX: Theme.snap((() => {
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4 const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
let rawX = 0 switch (effectiveBarPosition) {
if (effectiveBarPosition === SettingsData.Position.Left) { case SettingsData.Position.Left:
rawX = triggerX return Math.max(popupGap, Math.min(screenWidth - popupWidth - popupGap, triggerX));
} else if (effectiveBarPosition === SettingsData.Position.Right) { case SettingsData.Position.Right:
rawX = triggerX - popupWidth return Math.max(popupGap, Math.min(screenWidth - popupWidth - popupGap, triggerX - popupWidth));
} else { default:
rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2) const rawX = triggerX + (triggerWidth / 2) - (popupWidth / 2);
const minX = adjacentBarInfo.leftBar > 0 ? adjacentBarInfo.leftBar : popupGap const minX = adjacentBarInfo.leftBar > 0 ? adjacentBarInfo.leftBar : popupGap;
const maxX = screenWidth - popupWidth - (adjacentBarInfo.rightBar > 0 ? adjacentBarInfo.rightBar : popupGap) const maxX = screenWidth - popupWidth - (adjacentBarInfo.rightBar > 0 ? adjacentBarInfo.rightBar : popupGap);
return Math.max(minX, Math.min(maxX, rawX)) return Math.max(minX, Math.min(maxX, rawX));
} }
return Math.max(popupGap, Math.min(screenWidth - popupWidth - popupGap, rawX)) })(), dpr)
})(), dpr)
readonly property real alignedY: Theme.snap((() => { readonly property real alignedY: Theme.snap((() => {
const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true const useAutoGaps = storedBarConfig?.popupGapsAuto !== undefined ? storedBarConfig.popupGapsAuto : true;
const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4 const manualGapValue = storedBarConfig?.popupGapsManual !== undefined ? storedBarConfig.popupGapsManual : 4;
const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue const popupGap = useAutoGaps ? Math.max(4, storedBarSpacing) : manualGapValue;
let rawY = 0 switch (effectiveBarPosition) {
if (effectiveBarPosition === SettingsData.Position.Bottom) { case SettingsData.Position.Bottom:
rawY = triggerY - popupHeight return Math.max(popupGap, Math.min(screenHeight - popupHeight - popupGap, triggerY - popupHeight));
} else if (effectiveBarPosition === SettingsData.Position.Top) { case SettingsData.Position.Top:
rawY = triggerY return Math.max(popupGap, Math.min(screenHeight - popupHeight - popupGap, triggerY));
} else { default:
rawY = triggerY - (popupHeight / 2) const rawY = triggerY - (popupHeight / 2);
const minY = adjacentBarInfo.topBar > 0 ? adjacentBarInfo.topBar : popupGap const minY = adjacentBarInfo.topBar > 0 ? adjacentBarInfo.topBar : popupGap;
const maxY = screenHeight - popupHeight - (adjacentBarInfo.bottomBar > 0 ? adjacentBarInfo.bottomBar : popupGap) const maxY = screenHeight - popupHeight - (adjacentBarInfo.bottomBar > 0 ? adjacentBarInfo.bottomBar : popupGap);
return Math.max(minY, Math.min(maxY, rawY)) return Math.max(minY, Math.min(maxY, rawY));
} }
return Math.max(popupGap, Math.min(screenHeight - popupHeight - popupGap, rawY)) })(), dpr)
})(), dpr)
readonly property real maskX: { readonly property real maskX: {
const triggeringBarX = (effectiveBarPosition === SettingsData.Position.Left && root.barWidth > 0) ? root.barWidth : 0 const triggeringBarX = (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? barWidth : 0;
const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0 const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0;
return Math.max(triggeringBarX, adjacentLeftBar) return Math.max(triggeringBarX, adjacentLeftBar);
} }
readonly property real maskY: { readonly property real maskY: {
const triggeringBarY = (effectiveBarPosition === SettingsData.Position.Top && root.barHeight > 0) ? root.barHeight : 0 const triggeringBarY = (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? barHeight : 0;
const adjacentTopBar = adjacentBarInfo?.topBar ?? 0 const adjacentTopBar = adjacentBarInfo?.topBar ?? 0;
return Math.max(triggeringBarY, adjacentTopBar) return Math.max(triggeringBarY, adjacentTopBar);
} }
readonly property real maskWidth: { readonly property real maskWidth: {
const triggeringBarRight = (effectiveBarPosition === SettingsData.Position.Right && root.barWidth > 0) ? root.barWidth : 0 const triggeringBarRight = (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? barWidth : 0;
const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0 const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0;
const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar) const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar);
return Math.max(100, root.width - maskX - rightExclusion) return Math.max(100, screenWidth - maskX - rightExclusion);
} }
readonly property real maskHeight: { readonly property real maskHeight: {
const triggeringBarBottom = (effectiveBarPosition === SettingsData.Position.Bottom && root.barHeight > 0) ? root.barHeight : 0 const triggeringBarBottom = (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? barHeight : 0;
const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0 const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0;
const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar) const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar);
return Math.max(100, root.height - maskY - bottomExclusion) return Math.max(100, screenHeight - maskY - bottomExclusion);
} }
mask: Region { PanelWindow {
item: Rectangle { id: backgroundWindow
screen: root.screen
visible: false
color: "transparent"
WlrLayershell.namespace: root.layerNamespace + ":background"
WlrLayershell.layer: WlrLayershell.Top
WlrLayershell.exclusiveZone: -1
WlrLayershell.keyboardFocus: WlrKeyboardFocus.None
anchors {
top: true
left: true
right: true
bottom: true
}
mask: Region {
item: Rectangle {
x: root.maskX
y: root.maskY
width: shouldBeVisible ? root.maskWidth : 0
height: shouldBeVisible ? root.maskHeight : 0
}
}
MouseArea {
x: root.maskX x: root.maskX
y: root.maskY y: root.maskY
width: root.maskWidth width: root.maskWidth
height: root.maskHeight height: root.maskHeight
enabled: shouldBeVisible
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
const clickX = mouse.x + root.maskX;
const clickY = mouse.y + root.maskY;
const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight;
if (!outsideContent)
return;
backgroundClicked();
}
} }
} }
MouseArea { PanelWindow {
x: maskX id: contentWindow
y: maskY screen: root.screen
width: maskWidth visible: false
height: maskHeight color: "transparent"
z: -1
enabled: shouldBeVisible
acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
onClicked: mouse => {
const clickX = mouse.x + maskX
const clickY = mouse.y + maskY
const outsideContent = clickX < alignedX || clickX > alignedX + alignedWidth ||
clickY < alignedY || clickY > alignedY + alignedHeight
if (!outsideContent) return WlrLayershell.namespace: root.layerNamespace
WlrLayershell.layer: {
backgroundClicked() switch (Quickshell.env("DMS_POPOUT_LAYER")) {
} case "bottom":
} return WlrLayershell.Bottom;
case "top":
Item { return WlrLayershell.Top;
id: contentContainer case "background":
x: alignedX return WlrLayershell.Background;
y: alignedY default:
width: alignedWidth return WlrLayershell.Overlay;
height: alignedHeight
readonly property bool barTop: effectiveBarPosition === SettingsData.Position.Top
readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
readonly property real offsetX: barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0)
readonly property real offsetY: barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0)
property real animX: 0
property real animY: 0
property real scaleValue: root.animationScaleCollapsed
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
Connections {
target: root
function onShouldBeVisibleChanged() {
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr)
contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, root.dpr)
contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed
} }
} }
WlrLayershell.exclusiveZone: -1
Behavior on animX { WlrLayershell.keyboardFocus: {
NumberAnimation { if (customKeyboardFocus !== null)
duration: root.animationDuration return customKeyboardFocus;
easing.type: Easing.BezierSpline if (!shouldBeVisible)
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve return WlrKeyboardFocus.None;
} if (CompositorService.isHyprland)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
} }
Behavior on animY { anchors {
NumberAnimation { left: true
duration: root.animationDuration top: true
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
} }
Behavior on scaleValue { WlrLayershell.margins {
NumberAnimation { left: root.alignedX - shadowBuffer
duration: root.animationDuration top: root.alignedY - shadowBuffer
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
} }
implicitWidth: root.alignedWidth + (shadowBuffer * 2)
implicitHeight: root.alignedHeight + (shadowBuffer * 2)
Item { Item {
id: contentWrapper id: contentContainer
anchors.centerIn: parent x: shadowBuffer
width: parent.width y: shadowBuffer
height: parent.height width: root.alignedWidth
opacity: shouldBeVisible ? 1 : 0 height: root.alignedHeight
visible: opacity > 0
scale: contentContainer.scaleValue
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
property real shadowBlurPx: 10 readonly property bool barTop: effectiveBarPosition === SettingsData.Position.Top
property real shadowSpreadPx: 0 readonly property bool barBottom: effectiveBarPosition === SettingsData.Position.Bottom
property real shadowBaseAlpha: 0.60 readonly property bool barLeft: effectiveBarPosition === SettingsData.Position.Left
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency readonly property bool barRight: effectiveBarPosition === SettingsData.Position.Right
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha * contentWrapper.opacity)) readonly property real offsetX: barLeft ? root.animationOffset : (barRight ? -root.animationOffset : 0)
readonly property real offsetY: barBottom ? -root.animationOffset : (barTop ? root.animationOffset : 0)
Behavior on opacity { property real animX: 0
property real animY: 0
property real scaleValue: root.animationScaleCollapsed
onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr)
onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr)
Connections {
target: root
function onShouldBeVisibleChanged() {
contentContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetX, root.dpr);
contentContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : contentContainer.offsetY, root.dpr);
contentContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed;
}
}
Behavior on animX {
NumberAnimation { NumberAnimation {
duration: animationDuration duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on animY {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
}
}
Behavior on scaleValue {
NumberAnimation {
duration: root.animationDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
} }
} }
Item { Item {
id: bgShadowLayer id: contentWrapper
anchors.fill: parent anchors.centerIn: parent
visible: contentWrapper.popupSurfaceAlpha >= 0.95 width: parent.width
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1" height: parent.height
layer.smooth: false opacity: shouldBeVisible ? 1 : 0
layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr)) visible: opacity > 0
layer.textureMirroring: ShaderEffectSource.MirrorVertically scale: contentContainer.scaleValue
x: Theme.snap(contentContainer.animX + (parent.width - width) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
y: Theme.snap(contentContainer.animY + (parent.height - height) * (1 - contentContainer.scaleValue) * 0.5, root.dpr)
readonly property int blurMax: 64 property real shadowBlurPx: 10
property real shadowSpreadPx: 0
property real shadowBaseAlpha: 0.60
readonly property real popupSurfaceAlpha: SettingsData.popupTransparency
readonly property real effectiveShadowAlpha: Math.max(0, Math.min(1, shadowBaseAlpha * popupSurfaceAlpha * contentWrapper.opacity))
layer.effect: MultiEffect { Behavior on opacity {
id: shadowFx NumberAnimation {
autoPaddingEnabled: true duration: animationDuration
shadowEnabled: true easing.type: Easing.BezierSpline
blurEnabled: false easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, contentWrapper.shadowBlurPx / bgShadowLayer.blurMax))
shadowScale: 1 + (2 * contentWrapper.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest
return Theme.withAlpha(baseColor, contentWrapper.effectiveShadowAlpha)
} }
} }
DankRectangle { Item {
id: bgShadowLayer
anchors.fill: parent anchors.fill: parent
radius: Theme.cornerRadius visible: contentWrapper.popupSurfaceAlpha >= 0.95
layer.enabled: Quickshell.env("DMS_DISABLE_LAYER") !== "true" && Quickshell.env("DMS_DISABLE_LAYER") !== "1"
layer.smooth: false
layer.textureSize: Qt.size(Math.round(width * root.dpr), Math.round(height * root.dpr))
layer.textureMirroring: ShaderEffectSource.MirrorVertically
readonly property int blurMax: 64
layer.effect: MultiEffect {
id: shadowFx
autoPaddingEnabled: true
shadowEnabled: true
blurEnabled: false
maskEnabled: false
shadowBlur: Math.max(0, Math.min(1, contentWrapper.shadowBlurPx / bgShadowLayer.blurMax))
shadowScale: 1 + (2 * contentWrapper.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, contentWrapper.effectiveShadowAlpha);
}
}
DankRectangle {
anchors.fill: parent
radius: Theme.cornerRadius
}
} }
}
Item { Item {
id: contentLoaderWrapper id: contentLoaderWrapper
anchors.fill: parent
x: Theme.snap(x, root.dpr)
y: Theme.snap(y, root.dpr)
Loader {
id: contentLoader
anchors.fill: parent anchors.fill: parent
active: root.visible x: Theme.snap(x, root.dpr)
asynchronous: false y: Theme.snap(y, root.dpr)
Loader {
id: contentLoader
anchors.fill: parent
active: contentWindow.visible
asynchronous: false
}
} }
} }
} }
}
Item { Item {
id: focusHelper id: focusHelper
parent: contentContainer parent: contentContainer
anchors.fill: parent anchors.fill: parent
focus: true focus: true
Keys.onPressed: event => { Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) { if (event.key === Qt.Key_Escape) {
close() close();
event.accepted = true event.accepted = true;
}
} }
} }
} }