mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 01:00:06 +02:00
- The decoration's "bottom" property clashed with Item's (KWin refused it). - No On All Desktops button with one virtual desktop, like Breeze. - decoration/apply.sh finds the session's D-Bus through plasmashell (KWin's environment isn't readable), and installs each try under a new name: KWin keeps a decoration's QML by name until it restarts. - ft-floatd starts its script with Scripting.start: after a reload, the new script gets the old one's id while the old one is still being deleted, so run() on /Scripting/Script<id> went to the old script and the new one never ran (a second ft-floatd start left floating windows without their script). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
271 lines
9.2 KiB
QML
271 lines
9.2 KiB
QML
/*
|
|
Frametop's window decoration: Breeze's flat title bar, drawn in QML for KWin's Aurorae
|
|
engine, plus a button left of Close that floats the window in VR (docs/floating-windows.md,
|
|
decision 25). Aurorae loads QML without compiling anything, so this keeps working across
|
|
SteamOS's KWin updates, which a C++ decoration wouldn't.
|
|
|
|
The float button is the Keep Below button (FtButton.qml): Frametop's KWin script floats a
|
|
window when keep-below is set and docks it when it's cleared. A Keep Below button in the
|
|
configured button order is left out, since the float button stands in for it.
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
import QtQuick
|
|
import org.kde.kwin.decoration
|
|
|
|
Decoration {
|
|
id: root
|
|
alpha: false
|
|
|
|
DecorationOptions {
|
|
id: options
|
|
deco: decoration
|
|
}
|
|
TextMetrics {
|
|
id: metrics
|
|
font: options.titleFont
|
|
text: "Mj"
|
|
}
|
|
|
|
readonly property bool maximized: decoration.client.maximized
|
|
readonly property int buttonSize: Math.max(16, Math.round(metrics.height * 1.25))
|
|
readonly property int titleHeight: buttonSize + 8
|
|
readonly property int borderSize: decorationSettings.borderSize
|
|
readonly property int side: {
|
|
switch (borderSize) {
|
|
case DecorationOptions.BorderNone:
|
|
case DecorationOptions.BorderNoSides: return 0;
|
|
case DecorationOptions.BorderTiny: return 2;
|
|
case DecorationOptions.BorderLarge: return 6;
|
|
case DecorationOptions.BorderVeryLarge: return 8;
|
|
case DecorationOptions.BorderHuge: return 12;
|
|
case DecorationOptions.BorderVeryHuge: return 18;
|
|
case DecorationOptions.BorderOversized: return 27;
|
|
default: return 4;
|
|
}
|
|
}
|
|
readonly property int bottomBorder: borderSize === DecorationOptions.BorderNone ? 0
|
|
: borderSize === DecorationOptions.BorderNoSides ? 4 : side
|
|
readonly property color outline: Qt.tint(options.titleBarColor, Qt.rgba(0, 0, 0, 0.35))
|
|
|
|
function applyBorders() {
|
|
borders.left = side;
|
|
borders.right = side;
|
|
borders.bottom = bottomBorder;
|
|
borders.top = titleHeight;
|
|
maximizedBorders.top = titleHeight;
|
|
// Without visible side borders, keep a strip to grab for resizing.
|
|
extendedBorders.left = side ? 0 : 4;
|
|
extendedBorders.right = side ? 0 : 4;
|
|
extendedBorders.bottom = bottomBorder ? 0 : 4;
|
|
}
|
|
onTitleHeightChanged: applyBorders()
|
|
onSideChanged: applyBorders()
|
|
onBottomBorderChanged: applyBorders()
|
|
Component.onCompleted: applyBorders()
|
|
|
|
// The configured buttons, with the float button left of Close (or first on the right
|
|
// when there's no Close), and no Keep Below of their own.
|
|
function order(list, right) {
|
|
const out = [];
|
|
let placed = false;
|
|
for (let i = 0; i < list.length; ++i) {
|
|
const t = list[i];
|
|
if (t === DecorationOptions.DecorationButtonKeepBelow)
|
|
continue;
|
|
if (t === DecorationOptions.DecorationButtonClose && !placed) {
|
|
if (right) {
|
|
out.push(DecorationOptions.DecorationButtonKeepBelow, t);
|
|
} else {
|
|
out.push(t, DecorationOptions.DecorationButtonKeepBelow);
|
|
}
|
|
placed = true;
|
|
continue;
|
|
}
|
|
out.push(t);
|
|
}
|
|
return {buttons: out, placed: placed};
|
|
}
|
|
readonly property var leftOrder: order(options.titleButtonsLeft || [], false)
|
|
readonly property var rightOrder: {
|
|
const r = order(options.titleButtonsRight || [], true);
|
|
if (!r.placed && !leftOrder.placed)
|
|
r.buttons.unshift(DecorationOptions.DecorationButtonKeepBelow);
|
|
return r;
|
|
}
|
|
|
|
function componentFor(t) {
|
|
switch (t) {
|
|
case DecorationOptions.DecorationButtonMenu: return menuButton;
|
|
case DecorationOptions.DecorationButtonExplicitSpacer: return spacer;
|
|
case DecorationOptions.DecorationButtonClose: return closeButton;
|
|
case DecorationOptions.DecorationButtonMaximizeRestore: return maximizeButton;
|
|
case DecorationOptions.DecorationButtonMinimize: return minimizeButton;
|
|
case DecorationOptions.DecorationButtonKeepBelow: return floatButton;
|
|
case DecorationOptions.DecorationButtonKeepAbove: return keepAboveButton;
|
|
case DecorationOptions.DecorationButtonShade: return shadeButton;
|
|
case DecorationOptions.DecorationButtonOnAllDesktops: return allDesktopsButton;
|
|
case DecorationOptions.DecorationButtonQuickHelp: return helpButton;
|
|
case DecorationOptions.DecorationButtonApplicationMenu: return appMenuButton;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
color: options.titleBarColor
|
|
border.width: root.maximized ? 0 : 1
|
|
border.color: root.outline
|
|
}
|
|
|
|
Item {
|
|
id: titleBar
|
|
x: root.maximized ? 0 : Math.max(root.side, 1)
|
|
y: root.maximized ? 0 : 1
|
|
width: root.width - 2 * x
|
|
height: root.titleHeight - y
|
|
|
|
Row {
|
|
id: leftButtons
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 4
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 4
|
|
Repeater {
|
|
model: root.leftOrder.buttons
|
|
delegate: Loader {
|
|
required property var modelData
|
|
sourceComponent: root.componentFor(modelData)
|
|
}
|
|
}
|
|
}
|
|
Row {
|
|
id: rightButtons
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 4
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
spacing: 4
|
|
layoutDirection: Qt.LeftToRight
|
|
Repeater {
|
|
model: root.rightOrder.buttons
|
|
delegate: Loader {
|
|
required property var modelData
|
|
sourceComponent: root.componentFor(modelData)
|
|
}
|
|
}
|
|
}
|
|
// Centred over the whole bar, like Breeze, but never under the buttons.
|
|
Text {
|
|
id: caption
|
|
readonly property real free: rightButtons.x - (leftButtons.x + leftButtons.width) - 16
|
|
width: Math.min(implicitWidth, free)
|
|
x: Math.max(leftButtons.x + leftButtons.width + 8,
|
|
Math.min((parent.width - width) / 2, rightButtons.x - 8 - width))
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: decoration.client.caption
|
|
textFormat: Text.PlainText
|
|
font: options.titleFont
|
|
color: options.fontColor
|
|
elide: Text.ElideRight
|
|
renderType: Text.NativeRendering
|
|
}
|
|
Component.onCompleted: decoration.installTitleItem(titleBar)
|
|
}
|
|
|
|
Component {
|
|
id: menuButton
|
|
MenuButton {
|
|
width: root.buttonSize
|
|
height: root.buttonSize
|
|
}
|
|
}
|
|
Component {
|
|
id: spacer
|
|
Item {
|
|
width: root.buttonSize
|
|
height: root.buttonSize
|
|
}
|
|
}
|
|
Component {
|
|
id: closeButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonClose
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: maximizeButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonMaximizeRestore
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: minimizeButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonMinimize
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: floatButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonKeepBelow
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: keepAboveButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonKeepAbove
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: shadeButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonShade
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: allDesktopsButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonOnAllDesktops
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: helpButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonQuickHelp
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
Component {
|
|
id: appMenuButton
|
|
FtButton {
|
|
buttonType: DecorationOptions.DecorationButtonApplicationMenu
|
|
size: root.buttonSize
|
|
fg: options.fontColor
|
|
bg: options.titleBarColor
|
|
}
|
|
}
|
|
}
|