From 92e7b78b5ec74905bbfb1db9b7477054684fdb5e Mon Sep 17 00:00:00 2001 From: Marco Allegretti Date: Mon, 4 May 2026 11:15:21 +0200 Subject: [PATCH] kwin: add Shift Aurorae decoration Add a KWin decoration package under kwin/decorations and install it into share/kwin/decorations. The QML theme defines Shift titlebar colors, button rendering, and maximized border geometry. Wire the new decorations subdirectory from kwin/CMakeLists.txt. --- kwin/CMakeLists.txt | 1 + kwin/decorations/CMakeLists.txt | 7 + .../org.shift.decoration/contents/ui/main.qml | 207 ++++++++++++++++++ .../org.shift.decoration/metadata.json | 14 ++ 4 files changed, 229 insertions(+) create mode 100644 kwin/decorations/CMakeLists.txt create mode 100644 kwin/decorations/org.shift.decoration/contents/ui/main.qml create mode 100644 kwin/decorations/org.shift.decoration/metadata.json diff --git a/kwin/CMakeLists.txt b/kwin/CMakeLists.txt index 37ff1c6a..4d7189b6 100644 --- a/kwin/CMakeLists.txt +++ b/kwin/CMakeLists.txt @@ -2,4 +2,5 @@ # SPDX-License-Identifier: LGPL-2.1-or-later add_subdirectory(scripts) +add_subdirectory(decorations) add_subdirectory(mobiletaskswitcher) \ No newline at end of file diff --git a/kwin/decorations/CMakeLists.txt b/kwin/decorations/CMakeLists.txt new file mode 100644 index 00000000..164cbc27 --- /dev/null +++ b/kwin/decorations/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2025 SHIFT Contributors +# SPDX-License-Identifier: GPL-2.0-or-later + +install( + DIRECTORY org.shift.decoration + DESTINATION ${KDE_INSTALL_DATADIR}/kwin/decorations +) diff --git a/kwin/decorations/org.shift.decoration/contents/ui/main.qml b/kwin/decorations/org.shift.decoration/contents/ui/main.qml new file mode 100644 index 00000000..c26a6c6e --- /dev/null +++ b/kwin/decorations/org.shift.decoration/contents/ui/main.qml @@ -0,0 +1,207 @@ +// SPDX-FileCopyrightText: 2025 SHIFT Contributors +// SPDX-License-Identifier: GPL-2.0-or-later + +import QtQuick +import org.kde.kwin.decoration + +Decoration { + id: root + alpha: true + + // ── Palette ───────────────────────────────────────────────────────────── + readonly property color activeBar: "#1a1d2e" + readonly property color inactiveBar: "#141620" + readonly property color activeText: "#f0f0f8" + readonly property color inactiveText: "#505570" + + readonly property int barHeight: 30 + readonly property int btnSize: 16 + readonly property int btnSpacing: 8 + readonly property int btnSideMargin: 12 + readonly property int cornerRadius: decoration.client.maximized ? 0 : 8 + + Component.onCompleted: { + borders.top = barHeight; + borders.left = 0; + borders.right = 0; + borders.bottom = 0; + + // Keep titlebar controls available for maximized windows in desktop + // convergence mode. Mobile mode uses noBorder=true and bypasses this. + maximizedBorders.top = barHeight; + maximizedBorders.left = 0; + maximizedBorders.right = 0; + maximizedBorders.bottom = 0; + } + + DecorationOptions { + id: options + deco: decoration + } + + // ── Faint window outline ───────────────────────────────────────────────── + Rectangle { + anchors.fill: parent + color: "transparent" + radius: root.cornerRadius + border.width: decoration.client.maximized ? 0 : 1 + border.color: Qt.rgba(1, 1, 1, 0.08) + } + + // ── Title bar ──────────────────────────────────────────────────────────── + Rectangle { + id: bar + anchors { left: parent.left; right: parent.right; top: parent.top } + height: root.barHeight + radius: root.cornerRadius + color: decoration.client.active ? root.activeBar : root.inactiveBar + Behavior on color { ColorAnimation { duration: 120 } } + + // Square off bottom half — only top corners are rounded + Rectangle { + anchors { left: parent.left; right: parent.right; bottom: parent.bottom } + height: root.cornerRadius + color: parent.color + visible: !decoration.client.maximized + } + + // ── Title row ──────────────────────────────────────────────────────── + Item { + id: titleRow + anchors.fill: parent + + Row { + id: leftRow + spacing: root.btnSpacing + anchors { + left: parent.left + leftMargin: root.btnSideMargin + verticalCenter: parent.verticalCenter + } + Repeater { + model: options.titleButtonsLeft + delegate: ShiftButton { btnType: modelData } + } + } + + Text { + anchors { + left: leftRow.right; leftMargin: 6 + right: rightRow.left; rightMargin: 6 + verticalCenter: parent.verticalCenter + } + text: decoration.client.caption + color: decoration.client.active ? root.activeText : root.inactiveText + font: options.titleFont + elide: Text.ElideMiddle + horizontalAlignment: Text.AlignHCenter + renderType: Text.NativeRendering + Behavior on color { ColorAnimation { duration: 120 } } + } + + Row { + id: rightRow + spacing: root.btnSpacing + anchors { + right: parent.right + rightMargin: root.btnSideMargin + verticalCenter: parent.verticalCenter + } + Repeater { + model: options.titleButtonsRight + delegate: ShiftButton { btnType: modelData } + } + } + + Component.onCompleted: decoration.installTitleItem(titleRow) + } + } + + // ── Button component ───────────────────────────────────────────────────── + component ShiftButton: DecorationButton { + property int btnType: DecorationOptions.DecorationButtonNone + readonly property bool isSpacer: btnType === DecorationOptions.DecorationButtonExplicitSpacer + readonly property bool supported: { + switch (btnType) { + case DecorationOptions.DecorationButtonExplicitSpacer: + case DecorationOptions.DecorationButtonClose: + case DecorationOptions.DecorationButtonMinimize: + case DecorationOptions.DecorationButtonMaximizeRestore: + case DecorationOptions.DecorationButtonMenu: + case DecorationOptions.DecorationButtonApplicationMenu: + return true; + default: + return false; + } + } + buttonType: btnType + width: isSpacer ? root.btnSpacing * 2 : (supported ? root.btnSize : 0) + height: isSpacer ? 1 : (supported ? root.btnSize : 0) + visible: supported + + readonly property color normalColor: { + switch (btnType) { + case DecorationOptions.DecorationButtonClose: return "#C4455D"; + case DecorationOptions.DecorationButtonMenu: + case DecorationOptions.DecorationButtonApplicationMenu: + case DecorationOptions.DecorationButtonMinimize: + case DecorationOptions.DecorationButtonMaximizeRestore: return "#2b3246"; + default: return "#2b3246"; + } + } + readonly property color hoverColor: { + switch (btnType) { + case DecorationOptions.DecorationButtonClose: return "#E05D76"; + case DecorationOptions.DecorationButtonMinimize: + case DecorationOptions.DecorationButtonMaximizeRestore: + case DecorationOptions.DecorationButtonMenu: + case DecorationOptions.DecorationButtonApplicationMenu: return "#3b435c"; + default: return "#3b435c"; + } + } + readonly property color symbolColor: { + switch (btnType) { + case DecorationOptions.DecorationButtonClose: return "#ffffff"; + case DecorationOptions.DecorationButtonMenu: + case DecorationOptions.DecorationButtonApplicationMenu: + case DecorationOptions.DecorationButtonMinimize: + case DecorationOptions.DecorationButtonMaximizeRestore: return "#eaf2ff"; + default: return "#eaf2ff"; + } + } + readonly property string symbol: { + switch (btnType) { + case DecorationOptions.DecorationButtonClose: return "\u00d7"; + case DecorationOptions.DecorationButtonMinimize: return "\u2212"; + case DecorationOptions.DecorationButtonMaximizeRestore: + return decoration.client.maximized ? "\u25a3" : "\u25a1"; + case DecorationOptions.DecorationButtonMenu: + case DecorationOptions.DecorationButtonApplicationMenu: return "\u2261"; + default: return ""; + } + } + + Rectangle { + visible: !isSpacer + anchors.fill: parent + radius: width / 2 + antialiasing: true + border.width: 1 + border.color: Qt.rgba(1, 1, 1, 0.18) + color: parent.pressed ? Qt.darker(parent.hoverColor, 1.3) + : parent.hovered ? parent.hoverColor + : parent.normalColor + Behavior on color { ColorAnimation { duration: 100 } } + + Text { + anchors.centerIn: parent + text: parent.parent.symbol + color: parent.parent.symbolColor + font.pixelSize: Math.round(parent.width * 0.66) + font.weight: Font.Bold + opacity: 1.0 + Behavior on opacity { NumberAnimation { duration: 100 } } + } + } + } +} diff --git a/kwin/decorations/org.shift.decoration/metadata.json b/kwin/decorations/org.shift.decoration/metadata.json new file mode 100644 index 00000000..42ac81a6 --- /dev/null +++ b/kwin/decorations/org.shift.decoration/metadata.json @@ -0,0 +1,14 @@ +{ + "KPackageStructure": "KWin/Decoration", + "KPlugin": { + "Authors": [ + { + "Name": "SHIFT Contributors" + } + ], + "Description": "Shift window decoration", + "Id": "org.shift.decoration", + "License": "GPL-2.0-or-later", + "Name": "Shift" + } +}