Align window chrome with Sway title bars
Adds focused title-bar geometry to the window service and updates the Sway backend to derive frame data more reliably (including parent node fallback and content rect tracking for wrapped/floating views). Window chrome positioning now centers against the actual title bar instead of deco offsets, improving alignment. Also refreshes visual sizing/padding for traffic-light controls and Sway titlebar spacing to match the new chrome metrics.
This commit is contained in:
@@ -54,7 +54,7 @@ focus_follows_mouse yes
|
|||||||
# Layer-shell surfaces (desktop, top bar, launcher, window chrome) are not
|
# Layer-shell surfaces (desktop, top bar, launcher, window chrome) are not
|
||||||
# xdg_shell/XWayland views and are not decorated by these settings.
|
# xdg_shell/XWayland views and are not decorated by these settings.
|
||||||
font pango:Ubuntu 10
|
font pango:Ubuntu 10
|
||||||
titlebar_padding 12 8
|
titlebar_padding 14 10
|
||||||
titlebar_border_thickness 1
|
titlebar_border_thickness 1
|
||||||
default_border normal 1
|
default_border normal 1
|
||||||
default_floating_border normal 1
|
default_floating_border normal 1
|
||||||
|
|||||||
@@ -146,6 +146,16 @@ int WindowService::focusedWindowDecoHeight() const
|
|||||||
return m_focusedWindowDecoHeight;
|
return m_focusedWindowDecoHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowTitleBarY() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowTitleBarY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowTitleBarHeight() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowTitleBarHeight;
|
||||||
|
}
|
||||||
|
|
||||||
bool WindowService::focusWindow(const QString &windowId)
|
bool WindowService::focusWindow(const QString &windowId)
|
||||||
{
|
{
|
||||||
return m_backend->focusWindow(parseWindowId(windowId));
|
return m_backend->focusWindow(parseWindowId(windowId));
|
||||||
@@ -321,6 +331,8 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
|||||||
int decoY = 0;
|
int decoY = 0;
|
||||||
int decoWidth = 0;
|
int decoWidth = 0;
|
||||||
int decoHeight = 0;
|
int decoHeight = 0;
|
||||||
|
int titleBarY = 0;
|
||||||
|
int titleBarHeight = 0;
|
||||||
bool fullscreen = false;
|
bool fullscreen = false;
|
||||||
|
|
||||||
if (focused && !focused->minimized) {
|
if (focused && !focused->minimized) {
|
||||||
@@ -333,6 +345,17 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
|||||||
decoWidth = focused->decoWidth;
|
decoWidth = focused->decoWidth;
|
||||||
decoHeight = focused->decoHeight;
|
decoHeight = focused->decoHeight;
|
||||||
fullscreen = focused->fullscreen;
|
fullscreen = focused->fullscreen;
|
||||||
|
|
||||||
|
titleBarHeight = focused->decoHeight;
|
||||||
|
if (titleBarHeight <= 0)
|
||||||
|
titleBarHeight = focused->contentY;
|
||||||
|
if (titleBarHeight <= 0)
|
||||||
|
titleBarHeight = 32;
|
||||||
|
|
||||||
|
if (focused->decoHeight > 0 || focused->contentY > 0)
|
||||||
|
titleBarY = focused->y + focused->decoY;
|
||||||
|
else
|
||||||
|
titleBarY = focused->y - titleBarHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_focusedWindowFullscreen != fullscreen) {
|
if (m_focusedWindowFullscreen != fullscreen) {
|
||||||
@@ -347,7 +370,9 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
|||||||
|| m_focusedWindowDecoX != decoX
|
|| m_focusedWindowDecoX != decoX
|
||||||
|| m_focusedWindowDecoY != decoY
|
|| m_focusedWindowDecoY != decoY
|
||||||
|| m_focusedWindowDecoWidth != decoWidth
|
|| m_focusedWindowDecoWidth != decoWidth
|
||||||
|| m_focusedWindowDecoHeight != decoHeight) {
|
|| m_focusedWindowDecoHeight != decoHeight
|
||||||
|
|| m_focusedWindowTitleBarY != titleBarY
|
||||||
|
|| m_focusedWindowTitleBarHeight != titleBarHeight) {
|
||||||
m_focusedWindowX = x;
|
m_focusedWindowX = x;
|
||||||
m_focusedWindowY = y;
|
m_focusedWindowY = y;
|
||||||
m_focusedWindowWidth = width;
|
m_focusedWindowWidth = width;
|
||||||
@@ -356,6 +381,8 @@ void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
|||||||
m_focusedWindowDecoY = decoY;
|
m_focusedWindowDecoY = decoY;
|
||||||
m_focusedWindowDecoWidth = decoWidth;
|
m_focusedWindowDecoWidth = decoWidth;
|
||||||
m_focusedWindowDecoHeight = decoHeight;
|
m_focusedWindowDecoHeight = decoHeight;
|
||||||
|
m_focusedWindowTitleBarY = titleBarY;
|
||||||
|
m_focusedWindowTitleBarHeight = titleBarHeight;
|
||||||
emit focusedWindowGeometryChanged();
|
emit focusedWindowGeometryChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class WindowService : public QObject
|
|||||||
Q_PROPERTY(int focusedWindowDecoY READ focusedWindowDecoY NOTIFY focusedWindowGeometryChanged)
|
Q_PROPERTY(int focusedWindowDecoY READ focusedWindowDecoY NOTIFY focusedWindowGeometryChanged)
|
||||||
Q_PROPERTY(int focusedWindowDecoWidth READ focusedWindowDecoWidth NOTIFY focusedWindowGeometryChanged)
|
Q_PROPERTY(int focusedWindowDecoWidth READ focusedWindowDecoWidth NOTIFY focusedWindowGeometryChanged)
|
||||||
Q_PROPERTY(int focusedWindowDecoHeight READ focusedWindowDecoHeight NOTIFY focusedWindowGeometryChanged)
|
Q_PROPERTY(int focusedWindowDecoHeight READ focusedWindowDecoHeight NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowTitleBarY READ focusedWindowTitleBarY NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowTitleBarHeight READ focusedWindowTitleBarHeight NOTIFY focusedWindowGeometryChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum SnapEdge {
|
enum SnapEdge {
|
||||||
@@ -69,6 +71,8 @@ public:
|
|||||||
int focusedWindowDecoY() const;
|
int focusedWindowDecoY() const;
|
||||||
int focusedWindowDecoWidth() const;
|
int focusedWindowDecoWidth() const;
|
||||||
int focusedWindowDecoHeight() const;
|
int focusedWindowDecoHeight() const;
|
||||||
|
int focusedWindowTitleBarY() const;
|
||||||
|
int focusedWindowTitleBarHeight() const;
|
||||||
|
|
||||||
Q_INVOKABLE bool focusWindow(const QString &windowId);
|
Q_INVOKABLE bool focusWindow(const QString &windowId);
|
||||||
Q_INVOKABLE bool closeWindow(const QString &windowId);
|
Q_INVOKABLE bool closeWindow(const QString &windowId);
|
||||||
@@ -118,5 +122,7 @@ private:
|
|||||||
int m_focusedWindowDecoY = 0;
|
int m_focusedWindowDecoY = 0;
|
||||||
int m_focusedWindowDecoWidth = 0;
|
int m_focusedWindowDecoWidth = 0;
|
||||||
int m_focusedWindowDecoHeight = 0;
|
int m_focusedWindowDecoHeight = 0;
|
||||||
|
int m_focusedWindowTitleBarY = 0;
|
||||||
|
int m_focusedWindowTitleBarHeight = 0;
|
||||||
QTimer *m_geometryTimer = nullptr;
|
QTimer *m_geometryTimer = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ struct WindowInfo
|
|||||||
int decoY = 0;
|
int decoY = 0;
|
||||||
int decoWidth = 0;
|
int decoWidth = 0;
|
||||||
int decoHeight = 0;
|
int decoHeight = 0;
|
||||||
|
int contentX = 0;
|
||||||
|
int contentY = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WorkspaceInfo
|
struct WorkspaceInfo
|
||||||
|
|||||||
@@ -66,6 +66,41 @@ bool nodeLooksLikeView(const QJsonObject &node)
|
|||||||
return !properties.isEmpty();
|
return !properties.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void applyFrameGeometry(WindowInfo &window, const QJsonObject &node, const QJsonObject &parent)
|
||||||
|
{
|
||||||
|
QJsonObject rect = node.value(QStringLiteral("rect")).toObject();
|
||||||
|
QJsonObject deco = node.value(QStringLiteral("deco_rect")).toObject();
|
||||||
|
QJsonObject content = node.value(QStringLiteral("window_rect")).toObject();
|
||||||
|
|
||||||
|
int decoHeight = deco.value(QStringLiteral("height")).toInt();
|
||||||
|
int contentY = content.value(QStringLiteral("y")).toInt();
|
||||||
|
|
||||||
|
if (decoHeight <= 0 && contentY <= 0 && !parent.isEmpty()) {
|
||||||
|
const QJsonObject parentDeco = parent.value(QStringLiteral("deco_rect")).toObject();
|
||||||
|
const QJsonObject parentContent = parent.value(QStringLiteral("window_rect")).toObject();
|
||||||
|
const int parentDecoHeight = parentDeco.value(QStringLiteral("height")).toInt();
|
||||||
|
const int parentContentY = parentContent.value(QStringLiteral("y")).toInt();
|
||||||
|
if (parentDecoHeight > 0 || parentContentY > 0) {
|
||||||
|
rect = parent.value(QStringLiteral("rect")).toObject();
|
||||||
|
deco = parentDeco;
|
||||||
|
content = parentContent;
|
||||||
|
decoHeight = parentDecoHeight;
|
||||||
|
contentY = parentContentY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.x = rect.value(QStringLiteral("x")).toInt();
|
||||||
|
window.y = rect.value(QStringLiteral("y")).toInt();
|
||||||
|
window.width = rect.value(QStringLiteral("width")).toInt();
|
||||||
|
window.height = rect.value(QStringLiteral("height")).toInt();
|
||||||
|
window.decoX = deco.value(QStringLiteral("x")).toInt();
|
||||||
|
window.decoY = deco.value(QStringLiteral("y")).toInt();
|
||||||
|
window.decoWidth = deco.value(QStringLiteral("width")).toInt();
|
||||||
|
window.decoHeight = decoHeight;
|
||||||
|
window.contentX = content.value(QStringLiteral("x")).toInt();
|
||||||
|
window.contentY = contentY;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
SwayWindowBackend::SwayWindowBackend(QObject *parent)
|
SwayWindowBackend::SwayWindowBackend(QObject *parent)
|
||||||
@@ -247,7 +282,7 @@ bool SwayWindowBackend::updateFocusedGeometry()
|
|||||||
|
|
||||||
QVector<WindowInfo> windows;
|
QVector<WindowInfo> windows;
|
||||||
QVector<WorkspaceInfo> workspaces;
|
QVector<WorkspaceInfo> workspaces;
|
||||||
collect(tree.object(), {}, {}, false, &windows, &workspaces);
|
collect(tree.object(), {}, {}, {}, false, &windows, &workspaces);
|
||||||
|
|
||||||
for (WindowInfo &window : windows) {
|
for (WindowInfo &window : windows) {
|
||||||
if (window.id != m_focusedWindowId)
|
if (window.id != m_focusedWindowId)
|
||||||
@@ -263,6 +298,8 @@ bool SwayWindowBackend::updateFocusedGeometry()
|
|||||||
&& cached.decoY == window.decoY
|
&& cached.decoY == window.decoY
|
||||||
&& cached.decoWidth == window.decoWidth
|
&& cached.decoWidth == window.decoWidth
|
||||||
&& cached.decoHeight == window.decoHeight
|
&& cached.decoHeight == window.decoHeight
|
||||||
|
&& cached.contentX == window.contentX
|
||||||
|
&& cached.contentY == window.contentY
|
||||||
&& cached.fullscreen == window.fullscreen)
|
&& cached.fullscreen == window.fullscreen)
|
||||||
return false;
|
return false;
|
||||||
cached.x = window.x;
|
cached.x = window.x;
|
||||||
@@ -273,6 +310,8 @@ bool SwayWindowBackend::updateFocusedGeometry()
|
|||||||
cached.decoY = window.decoY;
|
cached.decoY = window.decoY;
|
||||||
cached.decoWidth = window.decoWidth;
|
cached.decoWidth = window.decoWidth;
|
||||||
cached.decoHeight = window.decoHeight;
|
cached.decoHeight = window.decoHeight;
|
||||||
|
cached.contentX = window.contentX;
|
||||||
|
cached.contentY = window.contentY;
|
||||||
cached.fullscreen = window.fullscreen;
|
cached.fullscreen = window.fullscreen;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -328,7 +367,7 @@ void SwayWindowBackend::refresh()
|
|||||||
|
|
||||||
QVector<WindowInfo> windows;
|
QVector<WindowInfo> windows;
|
||||||
QVector<WorkspaceInfo> workspaces;
|
QVector<WorkspaceInfo> workspaces;
|
||||||
collect(tree.object(), {}, {}, false, &windows, &workspaces);
|
collect(tree.object(), {}, {}, {}, false, &windows, &workspaces);
|
||||||
|
|
||||||
QHash<QString, int> counts;
|
QHash<QString, int> counts;
|
||||||
qint64 focused = 0;
|
qint64 focused = 0;
|
||||||
@@ -481,6 +520,7 @@ bool SwayWindowBackend::isShellChrome(const QJsonObject &node)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SwayWindowBackend::collect(const QJsonObject &node,
|
void SwayWindowBackend::collect(const QJsonObject &node,
|
||||||
|
const QJsonObject &parent,
|
||||||
const QString &workspace,
|
const QString &workspace,
|
||||||
const QString &output,
|
const QString &output,
|
||||||
bool onScratchpad,
|
bool onScratchpad,
|
||||||
@@ -530,27 +570,18 @@ void SwayWindowBackend::collect(const QJsonObject &node,
|
|||||||
window.workspace = nextWorkspace;
|
window.workspace = nextWorkspace;
|
||||||
window.output = nextOutput;
|
window.output = nextOutput;
|
||||||
window.focused = node.value(QStringLiteral("focused")).toBool();
|
window.focused = node.value(QStringLiteral("focused")).toBool();
|
||||||
window.floating = nodeIsFloating(node);
|
window.floating = nodeIsFloating(node) || nodeIsFloating(parent);
|
||||||
window.fullscreen = node.value(QStringLiteral("fullscreen_mode")).toInt() > 0;
|
window.fullscreen = node.value(QStringLiteral("fullscreen_mode")).toInt() > 0;
|
||||||
window.minimized = nextScratchpad;
|
window.minimized = nextScratchpad;
|
||||||
const QJsonObject rect = node.value(QStringLiteral("rect")).toObject();
|
applyFrameGeometry(window, node, parent);
|
||||||
window.x = rect.value(QStringLiteral("x")).toInt();
|
|
||||||
window.y = rect.value(QStringLiteral("y")).toInt();
|
|
||||||
window.width = rect.value(QStringLiteral("width")).toInt();
|
|
||||||
window.height = rect.value(QStringLiteral("height")).toInt();
|
|
||||||
const QJsonObject deco = node.value(QStringLiteral("deco_rect")).toObject();
|
|
||||||
window.decoX = deco.value(QStringLiteral("x")).toInt();
|
|
||||||
window.decoY = deco.value(QStringLiteral("y")).toInt();
|
|
||||||
window.decoWidth = deco.value(QStringLiteral("width")).toInt();
|
|
||||||
window.decoHeight = deco.value(QStringLiteral("height")).toInt();
|
|
||||||
windows->append(window);
|
windows->append(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QJsonArray nodes = node.value(QStringLiteral("nodes")).toArray();
|
const QJsonArray nodes = node.value(QStringLiteral("nodes")).toArray();
|
||||||
for (const QJsonValue &child : nodes)
|
for (const QJsonValue &child : nodes)
|
||||||
collect(child.toObject(), nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
collect(child.toObject(), node, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
||||||
|
|
||||||
const QJsonArray floating = node.value(QStringLiteral("floating_nodes")).toArray();
|
const QJsonArray floating = node.value(QStringLiteral("floating_nodes")).toArray();
|
||||||
for (const QJsonValue &child : floating)
|
for (const QJsonValue &child : floating)
|
||||||
collect(child.toObject(), nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
collect(child.toObject(), node, nextWorkspace, nextOutput, nextScratchpad, windows, workspaces);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ private:
|
|||||||
const WindowInfo *findWindow(qint64 windowId) const;
|
const WindowInfo *findWindow(qint64 windowId) const;
|
||||||
static bool isShellChrome(const QJsonObject &node);
|
static bool isShellChrome(const QJsonObject &node);
|
||||||
static void collect(const QJsonObject &node,
|
static void collect(const QJsonObject &node,
|
||||||
|
const QJsonObject &parent,
|
||||||
const QString &workspace,
|
const QString &workspace,
|
||||||
const QString &output,
|
const QString &output,
|
||||||
bool onScratchpad,
|
bool onScratchpad,
|
||||||
|
|||||||
@@ -52,9 +52,9 @@ QtObject {
|
|||||||
readonly property int topBarHeight: 40
|
readonly property int topBarHeight: 40
|
||||||
readonly property int controlHeight: 28
|
readonly property int controlHeight: 28
|
||||||
readonly property int searchHeight: 36
|
readonly property int searchHeight: 36
|
||||||
readonly property int trafficLightSize: 12
|
readonly property int trafficLightSize: 18
|
||||||
readonly property int trafficLightGap: 8
|
readonly property int trafficLightGap: 8
|
||||||
readonly property int windowChromeInset: 10
|
readonly property int windowChromeInset: 12
|
||||||
|
|
||||||
readonly property int fontSize: 13
|
readonly property int fontSize: 13
|
||||||
readonly property int fontSizeApp: 12
|
readonly property int fontSizeApp: 12
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ Item {
|
|||||||
|
|
||||||
signal clicked()
|
signal clicked()
|
||||||
|
|
||||||
implicitWidth: Theme.trafficLightSize + 2
|
implicitWidth: Theme.trafficLightSize + 6
|
||||||
implicitHeight: Theme.trafficLightSize + 2
|
implicitHeight: Theme.trafficLightSize + 6
|
||||||
Accessible.role: Accessible.Button
|
Accessible.role: Accessible.Button
|
||||||
Accessible.name: root.label
|
Accessible.name: root.label
|
||||||
Accessible.onPressAction: root.clicked()
|
Accessible.onPressAction: root.clicked()
|
||||||
@@ -50,17 +50,20 @@ Item {
|
|||||||
Canvas {
|
Canvas {
|
||||||
id: glyph
|
id: glyph
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: 8
|
width: Math.round(Theme.trafficLightSize * 0.58)
|
||||||
height: 8
|
height: width
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
opacity: root.showGlyph ? 1 : 0
|
opacity: root.showGlyph ? 1 : 0
|
||||||
visible: opacity > 0.01
|
visible: opacity > 0.01
|
||||||
|
onWidthChanged: requestPaint()
|
||||||
|
onHeightChanged: requestPaint()
|
||||||
onPaint: {
|
onPaint: {
|
||||||
const ctx = getContext("2d")
|
const ctx = getContext("2d")
|
||||||
ctx.reset()
|
ctx.reset()
|
||||||
|
ctx.scale(width / 8, height / 8)
|
||||||
ctx.strokeStyle = Qt.rgba(0.18, 0.12, 0.1, 0.78)
|
ctx.strokeStyle = Qt.rgba(0.18, 0.12, 0.1, 0.78)
|
||||||
ctx.fillStyle = ctx.strokeStyle
|
ctx.fillStyle = ctx.strokeStyle
|
||||||
ctx.lineWidth = 1.15
|
ctx.lineWidth = 1.35
|
||||||
ctx.lineCap = "round"
|
ctx.lineCap = "round"
|
||||||
ctx.lineJoin = "round"
|
ctx.lineJoin = "round"
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,12 @@ Window {
|
|||||||
readonly property bool active: WindowService.hasFocusedWindow
|
readonly property bool active: WindowService.hasFocusedWindow
|
||||||
&& !WindowService.focusedWindowFullscreen
|
&& !WindowService.focusedWindowFullscreen
|
||||||
&& !ShellState.launcherOpen
|
&& !ShellState.launcherOpen
|
||||||
readonly property int chromeX: WindowService.focusedWindowX
|
readonly property int chromeX: WindowService.focusedWindowX + Theme.windowChromeInset
|
||||||
+ WindowService.focusedWindowDecoX
|
|
||||||
+ Theme.windowChromeInset
|
|
||||||
readonly property int chromeY: {
|
readonly property int chromeY: {
|
||||||
const top = WindowService.focusedWindowY + WindowService.focusedWindowDecoY
|
const barY = WindowService.focusedWindowTitleBarY
|
||||||
const decoHeight = WindowService.focusedWindowDecoHeight
|
const barH = Math.max(WindowService.focusedWindowTitleBarHeight, height)
|
||||||
if (decoHeight <= 0)
|
const centered = barY + Math.round((barH - height) / 2)
|
||||||
return top + 6
|
return centered - Theme.topBarHeight
|
||||||
return top + Math.max(0, Math.round((decoHeight - height) / 2))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
title: qsTr("Nebula Window Chrome")
|
title: qsTr("Nebula Window Chrome")
|
||||||
|
|||||||
Reference in New Issue
Block a user