From 36d9004473008bb0ac9cc89da1336089e53fd74b Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Sat, 23 May 2026 08:53:02 +0200 Subject: [PATCH] Round normal window decorations Reserve side and bottom decoration borders for normal windows and draw a rounded decoration frame so windows match the convergence workspace shape. Add static guards for the decoration and existing workspace-frame corner geometry. --- .../org.shift.decoration/contents/ui/main.qml | 41 +++++++++++++++++-- tests/check-convergence-dock-invariant.sh | 4 ++ tests/check-dynamic-tiles-motion.sh | 6 +++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/kwin/decorations/org.shift.decoration/contents/ui/main.qml b/kwin/decorations/org.shift.decoration/contents/ui/main.qml index 20d5e86d..392a9a61 100644 --- a/kwin/decorations/org.shift.decoration/contents/ui/main.qml +++ b/kwin/decorations/org.shift.decoration/contents/ui/main.qml @@ -2,6 +2,7 @@ // SPDX-License-Identifier: EUPL-1.2 import QtQuick +import QtQuick.Shapes import org.kde.kwin.decoration import org.kde.plasma.private.mobileshell as MobileShell import org.kde.plasma.private.mobileshell.shellsettingsplugin as ShellSettings @@ -20,7 +21,9 @@ Decoration { readonly property int btnSize: 16 readonly property int btnSpacing: 8 readonly property int btnSideMargin: 12 - readonly property int cornerRadius: decoration.client.maximized ? 0 : 8 + readonly property int normalCornerRadius: 8 + readonly property int cornerRadius: decoration.client.maximized ? 0 : normalCornerRadius + readonly property int frameThickness: decoration.client.maximized ? 0 : normalCornerRadius readonly property int shortAnimationDuration: MobileShell.Motion.duration(MobileShell.Motion.EffectsFast) readonly property bool windowMenuAllowed: !ShellSettings.Settings.convergenceModeEnabled || ShellSettings.Settings.gamingModeEnabled @@ -28,9 +31,9 @@ Decoration { Component.onCompleted: { borders.top = barHeight; - borders.left = 0; - borders.right = 0; - borders.bottom = 0; + borders.left = normalCornerRadius; + borders.right = normalCornerRadius; + borders.bottom = normalCornerRadius; // Keep titlebar controls available for maximized windows in desktop // convergence mode. Mobile mode uses noBorder=true and bypasses this. @@ -45,6 +48,36 @@ Decoration { deco: decoration } + readonly property color frameColor: decoration.client.active ? root.activeBar : root.inactiveBar + + Shape { + anchors.fill: parent + visible: !decoration.client.maximized + + ShapePath { + fillColor: root.frameColor + fillRule: ShapePath.OddEvenFill + strokeWidth: 0 + + startX: root.cornerRadius + startY: 0 + PathLine { x: root.width - root.cornerRadius; y: 0 } + PathArc { x: root.width; y: root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius } + PathLine { x: root.width; y: root.height - root.cornerRadius } + PathArc { x: root.width - root.cornerRadius; y: root.height; radiusX: root.cornerRadius; radiusY: root.cornerRadius } + PathLine { x: root.cornerRadius; y: root.height } + PathArc { x: 0; y: root.height - root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius } + PathLine { x: 0; y: root.cornerRadius } + PathArc { x: root.cornerRadius; y: 0; radiusX: root.cornerRadius; radiusY: root.cornerRadius } + + PathMove { x: root.frameThickness; y: root.barHeight } + PathLine { x: root.width - root.frameThickness; y: root.barHeight } + PathLine { x: root.width - root.frameThickness; y: root.height - root.frameThickness } + PathLine { x: root.frameThickness; y: root.height - root.frameThickness } + PathLine { x: root.frameThickness; y: root.barHeight } + } + } + // ── Faint window outline ───────────────────────────────────────────────── Rectangle { anchors.fill: parent diff --git a/tests/check-convergence-dock-invariant.sh b/tests/check-convergence-dock-invariant.sh index 10e02291..be1dc876 100644 --- a/tests/check-convergence-dock-invariant.sh +++ b/tests/check-convergence-dock-invariant.sh @@ -53,6 +53,10 @@ require_line "$folio_main" "readonly property real workAreaY: topReservedHeight require_line "$folio_main" "readonly property real workAreaWidth: Math.max(0, width - frameThickness * 2)" require_line "$folio_main" "readonly property real workAreaHeight: Math.max(0, height - topReservedHeight - bottomReservedHeight - frameThickness * 2)" require_line "$folio_main" "fillRule: ShapePath.OddEvenFill" +require_line "$folio_main" "PathLine { x: workspaceFrame.width; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }" +require_line "$folio_main" "PathLine { x: 0; y: workspaceFrame.height - workspaceFrame.bottomReservedHeight }" +require_line "$folio_main" "PathArc { x: workspaceFrame.workAreaX + workspaceFrame.workAreaWidth - workspaceFrame.frameRadius; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }" +require_line "$folio_main" "PathArc { x: workspaceFrame.workAreaX; y: workspaceFrame.workAreaY + workspaceFrame.workAreaHeight - workspaceFrame.frameRadius; radiusX: workspaceFrame.frameRadius; radiusY: workspaceFrame.frameRadius }" require_line "$folio_main" "readonly property real popupTopY: MobileShell.Constants.topPanelHeight" require_line "$folio_main" "+ MobileShell.Constants.convergenceWorkspaceFrameThickness" require_line "$folio_main" "readonly property real popupBottomY: parent.height" diff --git a/tests/check-dynamic-tiles-motion.sh b/tests/check-dynamic-tiles-motion.sh index 039a6ff6..6bacd273 100644 --- a/tests/check-dynamic-tiles-motion.sh +++ b/tests/check-dynamic-tiles-motion.sh @@ -118,6 +118,12 @@ require_line "$decoration_qml" "readonly property bool windowMenuAllowed: !Shell require_line "$decoration_qml" "return root.windowMenuAllowed;" require_line "$decoration_qml" "enabled: !root.windowMenuAllowed" require_line "$decoration_qml" "acceptedButtons: Qt.RightButton" +require_line "$decoration_qml" "readonly property int frameThickness: decoration.client.maximized ? 0 : normalCornerRadius" +require_line "$decoration_qml" "borders.left = normalCornerRadius;" +require_line "$decoration_qml" "borders.right = normalCornerRadius;" +require_line "$decoration_qml" "borders.bottom = normalCornerRadius;" +require_line "$decoration_qml" "PathArc { x: root.width - root.cornerRadius; y: root.height; radiusX: root.cornerRadius; radiusY: root.cornerRadius }" +require_line "$decoration_qml" "PathArc { x: 0; y: root.height - root.cornerRadius; radiusX: root.cornerRadius; radiusY: root.cornerRadius }" reject_line "$tiling_script" "targetKey = lastLeafKey(rootNode)" reject_line "$tiling_script" "function lastLeafKey(node)"