From 4df7fedc532dc41b431ac183a739b9836d332ffb Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Fri, 17 Oct 2025 19:52:46 +0200 Subject: [PATCH 01/11] NPanel: make panels open in overlay layer --- Widgets/NPanel.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 72e4905e..4b80964d 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -157,6 +157,7 @@ Loader { WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "noctalia-panel" + WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None Region { From 56dc0d70dec1019d3d2a47870937a9c3f39d128a Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 19 Oct 2025 13:17:22 +0200 Subject: [PATCH 02/11] NPanel: extract PanelWindow to separate reusable component --- Widgets/NPanel.qml | 349 +------------------------------------- Widgets/NPanelWindow.qml | 355 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 357 insertions(+), 347 deletions(-) create mode 100644 Widgets/NPanelWindow.qml diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 4b80964d..70931010 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -126,353 +126,8 @@ Loader { // ----------------------------------------- sourceComponent: Component { // PanelWindow has its own screen property inherited of QsWindow - PanelWindow { - id: panelWindow - - readonly property string barPosition: Settings.data.bar.position - readonly property bool isVertical: barPosition === "left" || barPosition === "right" - readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0)) - readonly property real verticalBarWidth: Style.barHeight - - Component.onCompleted: { - Logger.d("NPanel", "Opened", root.objectName, "on", screen.name) - dimmingOpacity = Style.opacityHeavy - } - - Connections { - target: panelWindow - function onScreenChanged() { - root.screen = screen - - // If called from IPC always reposition if screen is updated - if (buttonName) { - setPosition() - } - Logger.d("NPanel", "OnScreenChanged", root.screen.name) - } - } - - visible: true - color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent - - WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.namespace: "noctalia-panel" - WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None - - Region { - id: maskRegion - } - - Behavior on color { - ColorAnimation { - duration: Style.animationNormal - } - } - - anchors.top: true - anchors.left: true - anchors.right: true - anchors.bottom: true - - // Close any panel with Esc without requiring focus - Shortcut { - sequences: ["Escape"] - enabled: root.active - onActivated: root.close() - context: Qt.WindowShortcut - } - - // Clicking outside of the rectangle to close - MouseArea { - anchors.fill: parent - enabled: root.backgroundClickEnabled - onClicked: root.close() - } - - // The actual panel's content - Rectangle { - id: panelBackground - color: panelBackgroundColor - radius: Style.radiusL - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS) - // Dragging support - property bool draggable: root.draggable - property bool isDragged: false - property real manualX: 0 - property real manualY: 0 - width: { - var w - if (preferredWidthRatio !== undefined) { - w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth)) - } else { - w = preferredWidth - } - // Clamp width so it is never bigger than the screen - return Math.min(w, screen?.width - Style.marginL * 2) - } - height: { - var h - if (preferredHeightRatio !== undefined) { - h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight)) - } else { - h = preferredHeight - } - - // Clamp width so it is never bigger than the screen - return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2) - } - - scale: root.scaleValue - x: isDragged ? manualX : calculatedX - y: isDragged ? manualY : calculatedY - - // --------------------------------------------- - // Does not account for corners are they are negligible and helps keep the code clean. - // --------------------------------------------- - property real marginTop: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "top": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) - default: - return Style.marginS - } - } - - property real marginBottom: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "bottom": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) - default: - return Style.marginS - } - } - - property real marginLeft: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "left": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) - default: - return Style.marginS - } - } - - property real marginRight: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "right": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) - default: - return Style.marginS - } - } - - // --------------------------------------------- - property int calculatedX: { - // Priority to fixed anchoring - if (panelAnchorHorizontalCenter) { - // Center horizontally but respect bar margins - var centerX = Math.round((panelWindow.width - panelBackground.width) / 2) - var minX = marginLeft - var maxX = panelWindow.width - panelBackground.width - marginRight - return Math.round(Math.max(minX, Math.min(centerX, maxX))) - } else if (panelAnchorLeft) { - return marginLeft - } else if (panelAnchorRight) { - return Math.round(panelWindow.width - panelBackground.width - marginRight) - } - - // No fixed anchoring - if (isVertical) { - // Vertical bar - if (barPosition === "right") { - // To the left of the right bar - return Math.round(panelWindow.width - panelBackground.width - marginRight) - } else { - // To the right of the left bar - return marginLeft - } - } else { - // Horizontal bar - if (root.useButtonPosition) { - // Position panel relative to button - var targetX = buttonPosition.x + (buttonWidth / 2) - (panelBackground.width / 2) - // Keep panel within screen bounds - var maxX = panelWindow.width - panelBackground.width - marginRight - var minX = marginLeft - return Math.round(Math.max(minX, Math.min(targetX, maxX))) - } else { - // Fallback to center horizontally - return Math.round((panelWindow.width - panelBackground.width) / 2) - } - } - } - - // --------------------------------------------- - property int calculatedY: { - // Priority to fixed anchoring - if (panelAnchorVerticalCenter) { - // Center vertically but respect bar margins - var centerY = Math.round((panelWindow.height - panelBackground.height) / 2) - var minY = marginTop - var maxY = panelWindow.height - panelBackground.height - marginBottom - return Math.round(Math.max(minY, Math.min(centerY, maxY))) - } else if (panelAnchorTop) { - return marginTop - } else if (panelAnchorBottom) { - return Math.round(panelWindow.height - panelBackground.height - marginBottom) - } - - // No fixed anchoring - if (isVertical) { - // Vertical bar - if (useButtonPosition) { - // Position panel relative to button - var targetY = buttonPosition.y + (buttonHeight / 2) - (panelBackground.height / 2) - // Keep panel within screen bounds - var maxY = panelWindow.height - panelBackground.height - marginBottom - var minY = marginTop - return Math.round(Math.max(minY, Math.min(targetY, maxY))) - } else { - // Fallback to center vertically - return Math.round((panelWindow.height - panelBackground.height) / 2) - } - } else { - // Horizontal bar - if (barPosition === "bottom") { - // Above the bottom bar - return Math.round(panelWindow.height - panelBackground.height - marginBottom) - } else { - // Below the top bar - return marginTop - } - } - } - - // Animate in when component is completed - Component.onCompleted: { - root.scaleValue = 1.0 - } - - // Reset drag position when panel closes - Connections { - target: root - function onClosed() { - panelBackground.isDragged = false - } - } - - // Prevent closing when clicking in the panel bg - MouseArea { - anchors.fill: parent - } - - // Animation behaviors - Behavior on scale { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutExpo - } - } - - Behavior on opacity { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutQuad - } - } - - Loader { - id: panelContentLoader - anchors.fill: parent - sourceComponent: root.panelContent - } - - // Handle drag move on the whole panel area - DragHandler { - id: dragHandler - target: null - enabled: panelBackground.draggable - property real dragStartX: 0 - property real dragStartY: 0 - onActiveChanged: { - if (active) { - // Capture current position into manual coordinates BEFORE toggling isDragged - panelBackground.manualX = panelBackground.x - panelBackground.manualY = panelBackground.y - dragStartX = panelBackground.x - dragStartY = panelBackground.y - panelBackground.isDragged = true - if (root.enableBackgroundClick) - root.disableBackgroundClick() - } else { - // Keep isDragged true so we continue using the manual x/y after release - if (root.enableBackgroundClick) - root.enableBackgroundClick() - } - } - onTranslationChanged: { - // Proposed new coordinates from fixed drag origin - var nx = dragStartX + translation.x - var ny = dragStartY + translation.y - - // Calculate gaps so we never overlap the bar on any side - var baseGap = Style.marginS - var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL : 0 - var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL : 0 - - var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight + floatExtraH) : 0) - var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight + floatExtraH) : 0) - var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight + floatExtraV) : 0) - var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight + floatExtraV) : 0) - - // Clamp within screen bounds accounting for insets - var maxX = panelWindow.width - panelBackground.width - insetRight - var minX = insetLeft - var maxY = panelWindow.height - panelBackground.height - insetBottom - var minY = insetTop - - panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX))) - panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY))) - } - } - - // Drag indicator border - Rectangle { - anchors.fill: parent - anchors.margins: 0 - color: Color.transparent - border.color: Color.mPrimary - border.width: Math.max(2, Style.borderL) - radius: parent.radius - visible: panelBackground.isDragged && dragHandler.active - opacity: 0.8 - z: 3000 - - // Subtle glow effect - Rectangle { - anchors.fill: parent - anchors.margins: 0 - color: Color.transparent - border.color: Color.mPrimary - border.width: Math.max(1, Style.borderS) - radius: parent.radius - opacity: 0.3 - } - } - } + NPanelWindow { + loggerPrefix: "NPanel" } } } diff --git a/Widgets/NPanelWindow.qml b/Widgets/NPanelWindow.qml new file mode 100644 index 00000000..ae9129f6 --- /dev/null +++ b/Widgets/NPanelWindow.qml @@ -0,0 +1,355 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services + +PanelWindow { + id: panelWindow + + readonly property string barPosition: Settings.data.bar.position + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0)) + readonly property real verticalBarWidth: Style.barHeight + + property string loggerPrefix + + Component.onCompleted: { + Logger.d(loggerPrefix, "Opened", root.objectName, "on", screen.name) + dimmingOpacity = Style.opacityHeavy + } + + Connections { + target: panelWindow + function onScreenChanged() { + root.screen = screen + + // If called from IPC always reposition if screen is updated + if (buttonName) { + setPosition() + } + Logger.d(loggerPrefix, "OnScreenChanged", root.screen.name) + } + } + + visible: true + color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent + + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-panel" + WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + + Region { + id: maskRegion + } + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + + anchors.top: true + anchors.left: true + anchors.right: true + anchors.bottom: true + + // Close any panel with Esc without requiring focus + Shortcut { + sequences: ["Escape"] + enabled: root.active + onActivated: root.close() + context: Qt.WindowShortcut + } + + // Clicking outside of the rectangle to close + MouseArea { + anchors.fill: parent + enabled: root.backgroundClickEnabled + onClicked: root.close() + } + + // The actual panel's content + Rectangle { + id: panelBackground + color: panelBackgroundColor + radius: Style.radiusL + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS) + // Dragging support + property bool draggable: root.draggable + property bool isDragged: false + property real manualX: 0 + property real manualY: 0 + width: { + var w + if (preferredWidthRatio !== undefined) { + w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth)) + } else { + w = preferredWidth + } + // Clamp width so it is never bigger than the screen + return Math.min(w, screen?.width - Style.marginL * 2) + } + height: { + var h + if (preferredHeightRatio !== undefined) { + h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight)) + } else { + h = preferredHeight + } + + // Clamp width so it is never bigger than the screen + return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2) + } + + scale: root.scaleValue + x: isDragged ? manualX : calculatedX + y: isDragged ? manualY : calculatedY + + // --------------------------------------------- + // Does not account for corners are they are negligible and helps keep the code clean. + // --------------------------------------------- + property real marginTop: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "top": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) + default: + return Style.marginS + } + } + + property real marginBottom: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "bottom": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) + default: + return Style.marginS + } + } + + property real marginLeft: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "left": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) + default: + return Style.marginS + } + } + + property real marginRight: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "right": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) + default: + return Style.marginS + } + } + + // --------------------------------------------- + property int calculatedX: { + // Priority to fixed anchoring + if (panelAnchorHorizontalCenter) { + // Center horizontally but respect bar margins + var centerX = Math.round((panelWindow.width - panelBackground.width) / 2) + var minX = marginLeft + var maxX = panelWindow.width - panelBackground.width - marginRight + return Math.round(Math.max(minX, Math.min(centerX, maxX))) + } else if (panelAnchorLeft) { + return marginLeft + } else if (panelAnchorRight) { + return Math.round(panelWindow.width - panelBackground.width - marginRight) + } + + // No fixed anchoring + if (isVertical) { + // Vertical bar + if (barPosition === "right") { + // To the left of the right bar + return Math.round(panelWindow.width - panelBackground.width - marginRight) + } else { + // To the right of the left bar + return marginLeft + } + } else { + // Horizontal bar + if (root.useButtonPosition) { + // Position panel relative to button + var targetX = buttonPosition.x + (buttonWidth / 2) - (panelBackground.width / 2) + // Keep panel within screen bounds + var maxX = panelWindow.width - panelBackground.width - marginRight + var minX = marginLeft + return Math.round(Math.max(minX, Math.min(targetX, maxX))) + } else { + // Fallback to center horizontally + return Math.round((panelWindow.width - panelBackground.width) / 2) + } + } + } + + // --------------------------------------------- + property int calculatedY: { + // Priority to fixed anchoring + if (panelAnchorVerticalCenter) { + // Center vertically but respect bar margins + var centerY = Math.round((panelWindow.height - panelBackground.height) / 2) + var minY = marginTop + var maxY = panelWindow.height - panelBackground.height - marginBottom + return Math.round(Math.max(minY, Math.min(centerY, maxY))) + } else if (panelAnchorTop) { + return marginTop + } else if (panelAnchorBottom) { + return Math.round(panelWindow.height - panelBackground.height - marginBottom) + } + + // No fixed anchoring + if (isVertical) { + // Vertical bar + if (useButtonPosition) { + // Position panel relative to button + var targetY = buttonPosition.y + (buttonHeight / 2) - (panelBackground.height / 2) + // Keep panel within screen bounds + var maxY = panelWindow.height - panelBackground.height - marginBottom + var minY = marginTop + return Math.round(Math.max(minY, Math.min(targetY, maxY))) + } else { + // Fallback to center vertically + return Math.round((panelWindow.height - panelBackground.height) / 2) + } + } else { + // Horizontal bar + if (barPosition === "bottom") { + // Above the bottom bar + return Math.round(panelWindow.height - panelBackground.height - marginBottom) + } else { + // Below the top bar + return marginTop + } + } + } + + // Animate in when component is completed + Component.onCompleted: { + root.scaleValue = 1.0 + } + + // Reset drag position when panel closes + Connections { + target: root + function onClosed() { + panelBackground.isDragged = false + } + } + + // Prevent closing when clicking in the panel bg + MouseArea { + anchors.fill: parent + } + + // Animation behaviors + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutExpo + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + + Loader { + id: panelContentLoader + anchors.fill: parent + sourceComponent: root.panelContent + } + + // Handle drag move on the whole panel area + DragHandler { + id: dragHandler + target: null + enabled: panelBackground.draggable + property real dragStartX: 0 + property real dragStartY: 0 + onActiveChanged: { + if (active) { + // Capture current position into manual coordinates BEFORE toggling isDragged + panelBackground.manualX = panelBackground.x + panelBackground.manualY = panelBackground.y + dragStartX = panelBackground.x + dragStartY = panelBackground.y + panelBackground.isDragged = true + if (root.enableBackgroundClick) + root.disableBackgroundClick() + } else { + // Keep isDragged true so we continue using the manual x/y after release + if (root.enableBackgroundClick) + root.enableBackgroundClick() + } + } + onTranslationChanged: { + // Proposed new coordinates from fixed drag origin + var nx = dragStartX + translation.x + var ny = dragStartY + translation.y + + // Calculate gaps so we never overlap the bar on any side + var baseGap = Style.marginS + var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL : 0 + var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL : 0 + + var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight + floatExtraH) : 0) + var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight + floatExtraH) : 0) + var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight + floatExtraV) : 0) + var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight + floatExtraV) : 0) + + // Clamp within screen bounds accounting for insets + var maxX = panelWindow.width - panelBackground.width - insetRight + var minX = insetLeft + var maxY = panelWindow.height - panelBackground.height - insetBottom + var minY = insetTop + + panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX))) + panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY))) + } + } + + // Drag indicator border + Rectangle { + anchors.fill: parent + anchors.margins: 0 + color: Color.transparent + border.color: Color.mPrimary + border.width: Math.max(2, Style.borderL) + radius: parent.radius + visible: panelBackground.isDragged && dragHandler.active + opacity: 0.8 + z: 3000 + + // Subtle glow effect + Rectangle { + anchors.fill: parent + anchors.margins: 0 + color: Color.transparent + border.color: Color.mPrimary + border.width: Math.max(1, Style.borderS) + radius: parent.radius + opacity: 0.3 + } + } + } +} From 17ed483285ca509c4f5c08ba9a920f8a06143f3d Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 19 Oct 2025 13:17:49 +0200 Subject: [PATCH 03/11] NPanelOverlay: implement wrapper for NPanel that opens in overlay layer --- Widgets/NPanelOverlay.qml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Widgets/NPanelOverlay.qml diff --git a/Widgets/NPanelOverlay.qml b/Widgets/NPanelOverlay.qml new file mode 100644 index 00000000..9debdbcb --- /dev/null +++ b/Widgets/NPanelOverlay.qml @@ -0,0 +1,15 @@ +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Commons +import qs.Services + +NPanel { + sourceComponent: Component { + // PanelWindow has its own screen property inherited of QsWindow + NPanelWindow { + loggerPrefix: "NPanelOverlay" + WlrLayershell.layer: WlrLayer.Overlay + } + } +} From 8a79c298e2342c24176d79612a1bdc058839c4e9 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 19 Oct 2025 13:46:15 +0200 Subject: [PATCH 04/11] ControlCenterPanel: change NPanel to NPanelOverlay --- Modules/ControlCenter/ControlCenterPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index fcc13cb8..a60ffbce 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -7,7 +7,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanel { +NPanelOverlay { id: root panelKeyboardFocus: true From ab2e172f9c652a0fef853295fbaef5974bf4a2b8 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 19 Oct 2025 13:46:21 +0200 Subject: [PATCH 05/11] Launcher: change NPanel to NPanelOverlay --- Modules/Launcher/Launcher.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index f8ee5db1..443a70ef 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -7,7 +7,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanel { +NPanelOverlay { id: root // Panel configuration From ad1fe55772d05618cee6d32cbbd5d4d550a4d199 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 19 Oct 2025 13:46:34 +0200 Subject: [PATCH 06/11] NotificationHistoryPanel: change NPanel to NPanelOverlay --- Modules/Notification/NotificationHistoryPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Notification/NotificationHistoryPanel.qml b/Modules/Notification/NotificationHistoryPanel.qml index 8bf5583f..24950d27 100644 --- a/Modules/Notification/NotificationHistoryPanel.qml +++ b/Modules/Notification/NotificationHistoryPanel.qml @@ -9,7 +9,7 @@ import qs.Services import qs.Widgets // Notification History panel -NPanel { +NPanelOverlay { id: root preferredWidth: 380 From 1adf86d5937273f4354876571c6a8eb5e489e9c3 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 19 Oct 2025 13:46:44 +0200 Subject: [PATCH 07/11] SessionMenu: change NPanel to NPanelOverlay --- Modules/SessionMenu/SessionMenu.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index 83afaf5f..e5f536dc 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -10,7 +10,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanel { +NPanelOverlay { id: root preferredWidth: 320 * Style.uiScaleRatio From a1bde12f9a68744a92c2490f97854d40b6b041f0 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Sun, 19 Oct 2025 13:46:52 +0200 Subject: [PATCH 08/11] SettingsPanel: change NPanel to NPanelOverlay --- Modules/Settings/SettingsPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index e5b64566..7726b1d2 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -8,7 +8,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanel { +NPanelOverlay { id: root preferredWidth: 820 * Style.uiScaleRatio From eba6c7ec2743a5ea5ec99b871f5df70b647ee6ba Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 20 Oct 2025 21:44:19 +0200 Subject: [PATCH 09/11] NPanel: revert NPanelWindow change --- Modules/ControlCenter/ControlCenterPanel.qml | 2 +- Modules/Launcher/Launcher.qml | 2 +- .../Notification/NotificationHistoryPanel.qml | 2 +- Modules/SessionMenu/SessionMenu.qml | 2 +- Modules/Settings/SettingsPanel.qml | 2 +- Widgets/NPanel.qml | 349 ++++++++++++++++- Widgets/NPanelOverlay.qml | 15 - Widgets/NPanelWindow.qml | 355 ------------------ 8 files changed, 352 insertions(+), 377 deletions(-) delete mode 100644 Widgets/NPanelOverlay.qml delete mode 100644 Widgets/NPanelWindow.qml diff --git a/Modules/ControlCenter/ControlCenterPanel.qml b/Modules/ControlCenter/ControlCenterPanel.qml index a60ffbce..fcc13cb8 100644 --- a/Modules/ControlCenter/ControlCenterPanel.qml +++ b/Modules/ControlCenter/ControlCenterPanel.qml @@ -7,7 +7,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanelOverlay { +NPanel { id: root panelKeyboardFocus: true diff --git a/Modules/Launcher/Launcher.qml b/Modules/Launcher/Launcher.qml index 443a70ef..f8ee5db1 100644 --- a/Modules/Launcher/Launcher.qml +++ b/Modules/Launcher/Launcher.qml @@ -7,7 +7,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanelOverlay { +NPanel { id: root // Panel configuration diff --git a/Modules/Notification/NotificationHistoryPanel.qml b/Modules/Notification/NotificationHistoryPanel.qml index 24950d27..8bf5583f 100644 --- a/Modules/Notification/NotificationHistoryPanel.qml +++ b/Modules/Notification/NotificationHistoryPanel.qml @@ -9,7 +9,7 @@ import qs.Services import qs.Widgets // Notification History panel -NPanelOverlay { +NPanel { id: root preferredWidth: 380 diff --git a/Modules/SessionMenu/SessionMenu.qml b/Modules/SessionMenu/SessionMenu.qml index e5f536dc..83afaf5f 100644 --- a/Modules/SessionMenu/SessionMenu.qml +++ b/Modules/SessionMenu/SessionMenu.qml @@ -10,7 +10,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanelOverlay { +NPanel { id: root preferredWidth: 320 * Style.uiScaleRatio diff --git a/Modules/Settings/SettingsPanel.qml b/Modules/Settings/SettingsPanel.qml index 7726b1d2..e5b64566 100644 --- a/Modules/Settings/SettingsPanel.qml +++ b/Modules/Settings/SettingsPanel.qml @@ -8,7 +8,7 @@ import qs.Commons import qs.Services import qs.Widgets -NPanelOverlay { +NPanel { id: root preferredWidth: 820 * Style.uiScaleRatio diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 70931010..4b80964d 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -126,8 +126,353 @@ Loader { // ----------------------------------------- sourceComponent: Component { // PanelWindow has its own screen property inherited of QsWindow - NPanelWindow { - loggerPrefix: "NPanel" + PanelWindow { + id: panelWindow + + readonly property string barPosition: Settings.data.bar.position + readonly property bool isVertical: barPosition === "left" || barPosition === "right" + readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0)) + readonly property real verticalBarWidth: Style.barHeight + + Component.onCompleted: { + Logger.d("NPanel", "Opened", root.objectName, "on", screen.name) + dimmingOpacity = Style.opacityHeavy + } + + Connections { + target: panelWindow + function onScreenChanged() { + root.screen = screen + + // If called from IPC always reposition if screen is updated + if (buttonName) { + setPosition() + } + Logger.d("NPanel", "OnScreenChanged", root.screen.name) + } + } + + visible: true + color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent + + WlrLayershell.exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "noctalia-panel" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None + + Region { + id: maskRegion + } + + Behavior on color { + ColorAnimation { + duration: Style.animationNormal + } + } + + anchors.top: true + anchors.left: true + anchors.right: true + anchors.bottom: true + + // Close any panel with Esc without requiring focus + Shortcut { + sequences: ["Escape"] + enabled: root.active + onActivated: root.close() + context: Qt.WindowShortcut + } + + // Clicking outside of the rectangle to close + MouseArea { + anchors.fill: parent + enabled: root.backgroundClickEnabled + onClicked: root.close() + } + + // The actual panel's content + Rectangle { + id: panelBackground + color: panelBackgroundColor + radius: Style.radiusL + border.color: Color.mOutline + border.width: Math.max(1, Style.borderS) + // Dragging support + property bool draggable: root.draggable + property bool isDragged: false + property real manualX: 0 + property real manualY: 0 + width: { + var w + if (preferredWidthRatio !== undefined) { + w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth)) + } else { + w = preferredWidth + } + // Clamp width so it is never bigger than the screen + return Math.min(w, screen?.width - Style.marginL * 2) + } + height: { + var h + if (preferredHeightRatio !== undefined) { + h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight)) + } else { + h = preferredHeight + } + + // Clamp width so it is never bigger than the screen + return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2) + } + + scale: root.scaleValue + x: isDragged ? manualX : calculatedX + y: isDragged ? manualY : calculatedY + + // --------------------------------------------- + // Does not account for corners are they are negligible and helps keep the code clean. + // --------------------------------------------- + property real marginTop: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "top": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) + default: + return Style.marginS + } + } + + property real marginBottom: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "bottom": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) + default: + return Style.marginS + } + } + + property real marginLeft: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "left": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) + default: + return Style.marginS + } + } + + property real marginRight: { + if (!barIsVisible) { + return 0 + } + switch (barPosition) { + case "right": + return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) + default: + return Style.marginS + } + } + + // --------------------------------------------- + property int calculatedX: { + // Priority to fixed anchoring + if (panelAnchorHorizontalCenter) { + // Center horizontally but respect bar margins + var centerX = Math.round((panelWindow.width - panelBackground.width) / 2) + var minX = marginLeft + var maxX = panelWindow.width - panelBackground.width - marginRight + return Math.round(Math.max(minX, Math.min(centerX, maxX))) + } else if (panelAnchorLeft) { + return marginLeft + } else if (panelAnchorRight) { + return Math.round(panelWindow.width - panelBackground.width - marginRight) + } + + // No fixed anchoring + if (isVertical) { + // Vertical bar + if (barPosition === "right") { + // To the left of the right bar + return Math.round(panelWindow.width - panelBackground.width - marginRight) + } else { + // To the right of the left bar + return marginLeft + } + } else { + // Horizontal bar + if (root.useButtonPosition) { + // Position panel relative to button + var targetX = buttonPosition.x + (buttonWidth / 2) - (panelBackground.width / 2) + // Keep panel within screen bounds + var maxX = panelWindow.width - panelBackground.width - marginRight + var minX = marginLeft + return Math.round(Math.max(minX, Math.min(targetX, maxX))) + } else { + // Fallback to center horizontally + return Math.round((panelWindow.width - panelBackground.width) / 2) + } + } + } + + // --------------------------------------------- + property int calculatedY: { + // Priority to fixed anchoring + if (panelAnchorVerticalCenter) { + // Center vertically but respect bar margins + var centerY = Math.round((panelWindow.height - panelBackground.height) / 2) + var minY = marginTop + var maxY = panelWindow.height - panelBackground.height - marginBottom + return Math.round(Math.max(minY, Math.min(centerY, maxY))) + } else if (panelAnchorTop) { + return marginTop + } else if (panelAnchorBottom) { + return Math.round(panelWindow.height - panelBackground.height - marginBottom) + } + + // No fixed anchoring + if (isVertical) { + // Vertical bar + if (useButtonPosition) { + // Position panel relative to button + var targetY = buttonPosition.y + (buttonHeight / 2) - (panelBackground.height / 2) + // Keep panel within screen bounds + var maxY = panelWindow.height - panelBackground.height - marginBottom + var minY = marginTop + return Math.round(Math.max(minY, Math.min(targetY, maxY))) + } else { + // Fallback to center vertically + return Math.round((panelWindow.height - panelBackground.height) / 2) + } + } else { + // Horizontal bar + if (barPosition === "bottom") { + // Above the bottom bar + return Math.round(panelWindow.height - panelBackground.height - marginBottom) + } else { + // Below the top bar + return marginTop + } + } + } + + // Animate in when component is completed + Component.onCompleted: { + root.scaleValue = 1.0 + } + + // Reset drag position when panel closes + Connections { + target: root + function onClosed() { + panelBackground.isDragged = false + } + } + + // Prevent closing when clicking in the panel bg + MouseArea { + anchors.fill: parent + } + + // Animation behaviors + Behavior on scale { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutExpo + } + } + + Behavior on opacity { + NumberAnimation { + duration: Style.animationNormal + easing.type: Easing.OutQuad + } + } + + Loader { + id: panelContentLoader + anchors.fill: parent + sourceComponent: root.panelContent + } + + // Handle drag move on the whole panel area + DragHandler { + id: dragHandler + target: null + enabled: panelBackground.draggable + property real dragStartX: 0 + property real dragStartY: 0 + onActiveChanged: { + if (active) { + // Capture current position into manual coordinates BEFORE toggling isDragged + panelBackground.manualX = panelBackground.x + panelBackground.manualY = panelBackground.y + dragStartX = panelBackground.x + dragStartY = panelBackground.y + panelBackground.isDragged = true + if (root.enableBackgroundClick) + root.disableBackgroundClick() + } else { + // Keep isDragged true so we continue using the manual x/y after release + if (root.enableBackgroundClick) + root.enableBackgroundClick() + } + } + onTranslationChanged: { + // Proposed new coordinates from fixed drag origin + var nx = dragStartX + translation.x + var ny = dragStartY + translation.y + + // Calculate gaps so we never overlap the bar on any side + var baseGap = Style.marginS + var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL : 0 + var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL : 0 + + var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight + floatExtraH) : 0) + var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight + floatExtraH) : 0) + var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight + floatExtraV) : 0) + var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight + floatExtraV) : 0) + + // Clamp within screen bounds accounting for insets + var maxX = panelWindow.width - panelBackground.width - insetRight + var minX = insetLeft + var maxY = panelWindow.height - panelBackground.height - insetBottom + var minY = insetTop + + panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX))) + panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY))) + } + } + + // Drag indicator border + Rectangle { + anchors.fill: parent + anchors.margins: 0 + color: Color.transparent + border.color: Color.mPrimary + border.width: Math.max(2, Style.borderL) + radius: parent.radius + visible: panelBackground.isDragged && dragHandler.active + opacity: 0.8 + z: 3000 + + // Subtle glow effect + Rectangle { + anchors.fill: parent + anchors.margins: 0 + color: Color.transparent + border.color: Color.mPrimary + border.width: Math.max(1, Style.borderS) + radius: parent.radius + opacity: 0.3 + } + } + } } } } diff --git a/Widgets/NPanelOverlay.qml b/Widgets/NPanelOverlay.qml deleted file mode 100644 index 9debdbcb..00000000 --- a/Widgets/NPanelOverlay.qml +++ /dev/null @@ -1,15 +0,0 @@ -import QtQuick -import Quickshell -import Quickshell.Wayland -import qs.Commons -import qs.Services - -NPanel { - sourceComponent: Component { - // PanelWindow has its own screen property inherited of QsWindow - NPanelWindow { - loggerPrefix: "NPanelOverlay" - WlrLayershell.layer: WlrLayer.Overlay - } - } -} diff --git a/Widgets/NPanelWindow.qml b/Widgets/NPanelWindow.qml deleted file mode 100644 index ae9129f6..00000000 --- a/Widgets/NPanelWindow.qml +++ /dev/null @@ -1,355 +0,0 @@ -import QtQuick -import Quickshell -import Quickshell.Wayland -import qs.Commons -import qs.Services - -PanelWindow { - id: panelWindow - - readonly property string barPosition: Settings.data.bar.position - readonly property bool isVertical: barPosition === "left" || barPosition === "right" - readonly property bool barIsVisible: (screen !== null) && (Settings.data.bar.monitors.includes(screen.name) || (Settings.data.bar.monitors.length === 0)) - readonly property real verticalBarWidth: Style.barHeight - - property string loggerPrefix - - Component.onCompleted: { - Logger.d(loggerPrefix, "Opened", root.objectName, "on", screen.name) - dimmingOpacity = Style.opacityHeavy - } - - Connections { - target: panelWindow - function onScreenChanged() { - root.screen = screen - - // If called from IPC always reposition if screen is updated - if (buttonName) { - setPosition() - } - Logger.d(loggerPrefix, "OnScreenChanged", root.screen.name) - } - } - - visible: true - color: Settings.data.general.dimDesktop ? Qt.alpha(Color.mShadow, dimmingOpacity) : Color.transparent - - WlrLayershell.exclusionMode: ExclusionMode.Ignore - WlrLayershell.namespace: "noctalia-panel" - WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None - - Region { - id: maskRegion - } - - Behavior on color { - ColorAnimation { - duration: Style.animationNormal - } - } - - anchors.top: true - anchors.left: true - anchors.right: true - anchors.bottom: true - - // Close any panel with Esc without requiring focus - Shortcut { - sequences: ["Escape"] - enabled: root.active - onActivated: root.close() - context: Qt.WindowShortcut - } - - // Clicking outside of the rectangle to close - MouseArea { - anchors.fill: parent - enabled: root.backgroundClickEnabled - onClicked: root.close() - } - - // The actual panel's content - Rectangle { - id: panelBackground - color: panelBackgroundColor - radius: Style.radiusL - border.color: Color.mOutline - border.width: Math.max(1, Style.borderS) - // Dragging support - property bool draggable: root.draggable - property bool isDragged: false - property real manualX: 0 - property real manualY: 0 - width: { - var w - if (preferredWidthRatio !== undefined) { - w = Math.round(Math.max(screen?.width * preferredWidthRatio, preferredWidth)) - } else { - w = preferredWidth - } - // Clamp width so it is never bigger than the screen - return Math.min(w, screen?.width - Style.marginL * 2) - } - height: { - var h - if (preferredHeightRatio !== undefined) { - h = Math.round(Math.max(screen?.height * preferredHeightRatio, preferredHeight)) - } else { - h = preferredHeight - } - - // Clamp width so it is never bigger than the screen - return Math.min(h, screen?.height - Style.barHeight - Style.marginL * 2) - } - - scale: root.scaleValue - x: isDragged ? manualX : calculatedX - y: isDragged ? manualY : calculatedY - - // --------------------------------------------- - // Does not account for corners are they are negligible and helps keep the code clean. - // --------------------------------------------- - property real marginTop: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "top": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) - default: - return Style.marginS - } - } - - property real marginBottom: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "bottom": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginVertical * Style.marginXL : 0) - default: - return Style.marginS - } - } - - property real marginLeft: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "left": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) - default: - return Style.marginS - } - } - - property real marginRight: { - if (!barIsVisible) { - return 0 - } - switch (barPosition) { - case "right": - return (Style.barHeight + Style.marginS) + (Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * Style.marginXL : 0) - default: - return Style.marginS - } - } - - // --------------------------------------------- - property int calculatedX: { - // Priority to fixed anchoring - if (panelAnchorHorizontalCenter) { - // Center horizontally but respect bar margins - var centerX = Math.round((panelWindow.width - panelBackground.width) / 2) - var minX = marginLeft - var maxX = panelWindow.width - panelBackground.width - marginRight - return Math.round(Math.max(minX, Math.min(centerX, maxX))) - } else if (panelAnchorLeft) { - return marginLeft - } else if (panelAnchorRight) { - return Math.round(panelWindow.width - panelBackground.width - marginRight) - } - - // No fixed anchoring - if (isVertical) { - // Vertical bar - if (barPosition === "right") { - // To the left of the right bar - return Math.round(panelWindow.width - panelBackground.width - marginRight) - } else { - // To the right of the left bar - return marginLeft - } - } else { - // Horizontal bar - if (root.useButtonPosition) { - // Position panel relative to button - var targetX = buttonPosition.x + (buttonWidth / 2) - (panelBackground.width / 2) - // Keep panel within screen bounds - var maxX = panelWindow.width - panelBackground.width - marginRight - var minX = marginLeft - return Math.round(Math.max(minX, Math.min(targetX, maxX))) - } else { - // Fallback to center horizontally - return Math.round((panelWindow.width - panelBackground.width) / 2) - } - } - } - - // --------------------------------------------- - property int calculatedY: { - // Priority to fixed anchoring - if (panelAnchorVerticalCenter) { - // Center vertically but respect bar margins - var centerY = Math.round((panelWindow.height - panelBackground.height) / 2) - var minY = marginTop - var maxY = panelWindow.height - panelBackground.height - marginBottom - return Math.round(Math.max(minY, Math.min(centerY, maxY))) - } else if (panelAnchorTop) { - return marginTop - } else if (panelAnchorBottom) { - return Math.round(panelWindow.height - panelBackground.height - marginBottom) - } - - // No fixed anchoring - if (isVertical) { - // Vertical bar - if (useButtonPosition) { - // Position panel relative to button - var targetY = buttonPosition.y + (buttonHeight / 2) - (panelBackground.height / 2) - // Keep panel within screen bounds - var maxY = panelWindow.height - panelBackground.height - marginBottom - var minY = marginTop - return Math.round(Math.max(minY, Math.min(targetY, maxY))) - } else { - // Fallback to center vertically - return Math.round((panelWindow.height - panelBackground.height) / 2) - } - } else { - // Horizontal bar - if (barPosition === "bottom") { - // Above the bottom bar - return Math.round(panelWindow.height - panelBackground.height - marginBottom) - } else { - // Below the top bar - return marginTop - } - } - } - - // Animate in when component is completed - Component.onCompleted: { - root.scaleValue = 1.0 - } - - // Reset drag position when panel closes - Connections { - target: root - function onClosed() { - panelBackground.isDragged = false - } - } - - // Prevent closing when clicking in the panel bg - MouseArea { - anchors.fill: parent - } - - // Animation behaviors - Behavior on scale { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutExpo - } - } - - Behavior on opacity { - NumberAnimation { - duration: Style.animationNormal - easing.type: Easing.OutQuad - } - } - - Loader { - id: panelContentLoader - anchors.fill: parent - sourceComponent: root.panelContent - } - - // Handle drag move on the whole panel area - DragHandler { - id: dragHandler - target: null - enabled: panelBackground.draggable - property real dragStartX: 0 - property real dragStartY: 0 - onActiveChanged: { - if (active) { - // Capture current position into manual coordinates BEFORE toggling isDragged - panelBackground.manualX = panelBackground.x - panelBackground.manualY = panelBackground.y - dragStartX = panelBackground.x - dragStartY = panelBackground.y - panelBackground.isDragged = true - if (root.enableBackgroundClick) - root.disableBackgroundClick() - } else { - // Keep isDragged true so we continue using the manual x/y after release - if (root.enableBackgroundClick) - root.enableBackgroundClick() - } - } - onTranslationChanged: { - // Proposed new coordinates from fixed drag origin - var nx = dragStartX + translation.x - var ny = dragStartY + translation.y - - // Calculate gaps so we never overlap the bar on any side - var baseGap = Style.marginS - var floatExtraH = Settings.data.bar.floating ? Settings.data.bar.marginHorizontal * 2 * Style.marginXL : 0 - var floatExtraV = Settings.data.bar.floating ? Settings.data.bar.marginVertical * 2 * Style.marginXL : 0 - - var insetLeft = baseGap + ((barIsVisible && barPosition === "left") ? (Style.barHeight + floatExtraH) : 0) - var insetRight = baseGap + ((barIsVisible && barPosition === "right") ? (Style.barHeight + floatExtraH) : 0) - var insetTop = baseGap + ((barIsVisible && barPosition === "top") ? (Style.barHeight + floatExtraV) : 0) - var insetBottom = baseGap + ((barIsVisible && barPosition === "bottom") ? (Style.barHeight + floatExtraV) : 0) - - // Clamp within screen bounds accounting for insets - var maxX = panelWindow.width - panelBackground.width - insetRight - var minX = insetLeft - var maxY = panelWindow.height - panelBackground.height - insetBottom - var minY = insetTop - - panelBackground.manualX = Math.round(Math.max(minX, Math.min(nx, maxX))) - panelBackground.manualY = Math.round(Math.max(minY, Math.min(ny, maxY))) - } - } - - // Drag indicator border - Rectangle { - anchors.fill: parent - anchors.margins: 0 - color: Color.transparent - border.color: Color.mPrimary - border.width: Math.max(2, Style.borderL) - radius: parent.radius - visible: panelBackground.isDragged && dragHandler.active - opacity: 0.8 - z: 3000 - - // Subtle glow effect - Rectangle { - anchors.fill: parent - anchors.margins: 0 - color: Color.transparent - border.color: Color.mPrimary - border.width: Math.max(1, Style.borderS) - radius: parent.radius - opacity: 0.3 - } - } - } -} From 47bb77f1039acfd75dbb2c449f2b90c2cda107ec Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 20 Oct 2025 22:05:13 +0200 Subject: [PATCH 10/11] NPanel: add persistent useOverlay property --- Assets/settings-default.json | 3 ++- Commons/Settings.qml | 1 + Widgets/NPanel.qml | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Assets/settings-default.json b/Assets/settings-default.json index ce680d75..2c1ca7bb 100644 --- a/Assets/settings-default.json +++ b/Assets/settings-default.json @@ -207,7 +207,8 @@ "fontFixed": "DejaVu Sans Mono", "fontDefaultScale": 1, "fontFixedScale": 1, - "tooltipsEnabled": true + "tooltipsEnabled": true, + "panelsOverlayLayer": true }, "brightness": { "brightnessStep": 5 diff --git a/Commons/Settings.qml b/Commons/Settings.qml index 1f925537..62695685 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -343,6 +343,7 @@ Singleton { property real fontDefaultScale: 1.0 property real fontFixedScale: 1.0 property bool tooltipsEnabled: true + property bool panelsOverlayLayer: true } // brightness diff --git a/Widgets/NPanel.qml b/Widgets/NPanel.qml index 4b80964d..527f31c6 100644 --- a/Widgets/NPanel.qml +++ b/Widgets/NPanel.qml @@ -9,6 +9,8 @@ Loader { property ShellScreen screen + property bool useOverlay: Settings.data.ui.panelsOverlayLayer + property Component panelContent: null property real preferredWidth: 700 property real preferredHeight: 900 @@ -157,7 +159,7 @@ Loader { WlrLayershell.exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "noctalia-panel" - WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.layer: useOverlay ? WlrLayer.Overlay : WlrLayer.Top WlrLayershell.keyboardFocus: root.panelKeyboardFocus ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None Region { From b6c95b8ed5a786e044d0c34bd0335c840e33b9a0 Mon Sep 17 00:00:00 2001 From: Damian D'Souza Date: Mon, 20 Oct 2025 22:10:58 +0200 Subject: [PATCH 11/11] Settings: add panels overlay setting toggle un UI tab --- Assets/Translations/de.json | 4 ++++ Assets/Translations/en.json | 6 +++++- Assets/Translations/es.json | 4 ++++ Assets/Translations/fr.json | 4 ++++ Assets/Translations/pt.json | 4 ++++ Assets/Translations/zh-CN.json | 4 ++++ Modules/Settings/Tabs/UserInterfaceTab.qml | 7 +++++++ 7 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Assets/Translations/de.json b/Assets/Translations/de.json index aed4c10c..26bd0515 100644 --- a/Assets/Translations/de.json +++ b/Assets/Translations/de.json @@ -754,6 +754,10 @@ "description": "Deaktivieren Sie alle Animationen für eine schnellere und reaktionsfreudigere Erfahrung.", "label": "UI-Animationen deaktivieren" }, + "panels-overlay": { + "label": "Panels in Overlay-Ebene öffnen", + "description": "Panels werden über Vollbildfenstern angezeigt" + }, "animation-speed": { "description": "Globale Animationsgeschwindigkeit anpassen.", "label": "Animationsgeschwindigkeit", diff --git a/Assets/Translations/en.json b/Assets/Translations/en.json index f18e6ac9..65e46e63 100644 --- a/Assets/Translations/en.json +++ b/Assets/Translations/en.json @@ -785,6 +785,10 @@ "animation-disable": { "label": "Disable UI Animations", "description": "Disable all animations for a faster, more responsive experience." + }, + "panels-overlay": { + "label": "Open panels in overlay layer", + "description": "Panels will appear above fullscreen windows" } } }, @@ -1187,7 +1191,7 @@ "scan-again": "Scan again" } }, - + "tooltips": { "refresh": "Refresh", "close": "Close", diff --git a/Assets/Translations/es.json b/Assets/Translations/es.json index a00881fe..8a663fd1 100644 --- a/Assets/Translations/es.json +++ b/Assets/Translations/es.json @@ -754,6 +754,10 @@ "description": "Desactiva todas las animaciones para una experiencia más rápida y con mayor capacidad de respuesta.", "label": "Desactivar animaciones de la interfaz de usuario" }, + "panels-overlay": { + "label": "Abrir paneles en capa superpuesta", + "description": "Los paneles aparecerán sobre las ventanas en pantalla completa" + }, "animation-speed": { "description": "Ajustar la velocidad global de la animación.", "label": "Velocidad de animación", diff --git a/Assets/Translations/fr.json b/Assets/Translations/fr.json index ec9af8ac..190573f5 100644 --- a/Assets/Translations/fr.json +++ b/Assets/Translations/fr.json @@ -754,6 +754,10 @@ "description": "Désactiver toutes les animations pour une expérience plus rapide et plus réactive.", "label": "Désactiver les animations de l'interface utilisateur" }, + "panels-overlay": { + "label": "Ouvrir les panneaux en couche superposée", + "description": "Les panneaux apparaîtront au-dessus des fenêtres en plein écran" + }, "animation-speed": { "description": "Ajuster la vitesse globale de l'animation.", "label": "Vitesse d'animation", diff --git a/Assets/Translations/pt.json b/Assets/Translations/pt.json index 02979bf1..69f96962 100644 --- a/Assets/Translations/pt.json +++ b/Assets/Translations/pt.json @@ -754,6 +754,10 @@ "description": "Desative todas as animações para uma experiência mais rápida e responsiva.", "label": "Desativar animações da interface do usuário" }, + "panels-overlay": { + "label": "Abrir painéis na camada de sobreposição", + "description": "Os painéis aparecerão sobre janelas em tela cheia" + }, "animation-speed": { "description": "Ajustar a velocidade global da animação.", "label": "Velocidade da animação", diff --git a/Assets/Translations/zh-CN.json b/Assets/Translations/zh-CN.json index a251a5d4..c6c52c06 100644 --- a/Assets/Translations/zh-CN.json +++ b/Assets/Translations/zh-CN.json @@ -754,6 +754,10 @@ "description": "禁用所有动画以获得更快、更流畅的体验。", "label": "禁用 UI 动画" }, + "panels-overlay": { + "label": "在覆盖层中打开面板", + "description": "面板将显示在全屏窗口上方" + }, "animation-speed": { "description": "调整全局动画速度。", "label": "动画速度", diff --git a/Modules/Settings/Tabs/UserInterfaceTab.qml b/Modules/Settings/Tabs/UserInterfaceTab.qml index 13557c29..ec9a289d 100644 --- a/Modules/Settings/Tabs/UserInterfaceTab.qml +++ b/Modules/Settings/Tabs/UserInterfaceTab.qml @@ -40,6 +40,13 @@ ColumnLayout { onToggled: checked => Settings.data.general.compactLockScreen = checked } + NToggle { + label: I18n.tr("settings.user-interface.panels-overlay.label") + description: I18n.tr("settings.user-interface.panels-overlay.description") + checked: Settings.data.ui.panelsOverlayLayer + onToggled: checked => Settings.data.ui.panelsOverlayLayer = checked + } + NDivider { Layout.fillWidth: true Layout.topMargin: Style.marginL