Add floating traffic-light window chrome
Introduces a dedicated layer-shell `WindowChromeSurface` that renders macOS-style traffic-light controls over the focused Sway window titlebar, replacing the old top-bar window controls. Adds theme tokens and a reusable `TrafficLightButton` component, updates shell QML/CMake wiring, and adjusts Sway titlebar padding/title format so window titles do not overlap the overlay. Window tracking was extended with focused-window geometry/deco/fullscreen properties plus backend refresh support so the chrome follows window movement and state changes in near real time.
This commit is contained in:
@@ -46,17 +46,20 @@ focus_follows_mouse yes
|
|||||||
# Sway draws a restrained title bar and border on normal application windows
|
# Sway draws a restrained title bar and border on normal application windows
|
||||||
# so they can be identified, dragged, and resized with the mouse.
|
# so they can be identified, dragged, and resized with the mouse.
|
||||||
#
|
#
|
||||||
# This is temporary infrastructure. Nebula does not yet own client/server-side
|
# Nebula overlays traffic-light controls on the focused window title bar.
|
||||||
# decorations. Do not treat these colours or metrics as the final design.
|
# Horizontal padding keeps the Sway title text clear of those buttons.
|
||||||
|
# Menu-style chrome (application name, future File/Edit) stays in the top bar.
|
||||||
#
|
#
|
||||||
# Layer-shell surfaces (desktop, top bar, launcher) are not xdg_shell/XWayland
|
# This is temporary infrastructure until Nebula owns server-side decorations.
|
||||||
# views and are not decorated by these settings.
|
# Layer-shell surfaces (desktop, top bar, launcher, window chrome) are not
|
||||||
|
# xdg_shell/XWayland views and are not decorated by these settings.
|
||||||
font pango:Ubuntu 10
|
font pango:Ubuntu 10
|
||||||
titlebar_padding 7 3
|
titlebar_padding 12 8
|
||||||
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
|
||||||
hide_edge_borders none
|
hide_edge_borders none
|
||||||
|
title_format " %title"
|
||||||
|
|
||||||
# class border backgr. text indicator child_border
|
# class border backgr. text indicator child_border
|
||||||
client.focused #3d4d62 #1a222e #e8ecf2 #7ea0c4 #3d4d62
|
client.focused #3d4d62 #1a222e #e8ecf2 #7ea0c4 #3d4d62
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public:
|
|||||||
virtual bool resizeWindow(qint64 windowId, int width, int height) = 0;
|
virtual bool resizeWindow(qint64 windowId, int width, int height) = 0;
|
||||||
virtual bool snapWindow(qint64 windowId, SnapEdge edge) = 0;
|
virtual bool snapWindow(qint64 windowId, SnapEdge edge) = 0;
|
||||||
virtual bool switchWorkspace(const QString &workspaceId) = 0;
|
virtual bool switchWorkspace(const QString &workspaceId) = 0;
|
||||||
|
virtual bool updateFocusedGeometry() { return false; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectedChanged();
|
void connectedChanged();
|
||||||
|
|||||||
@@ -27,11 +27,15 @@ WindowService::WindowService(QObject *parent)
|
|||||||
, m_backend(new SwayWindowBackend(this))
|
, m_backend(new SwayWindowBackend(this))
|
||||||
, m_model(new WindowModel(this))
|
, m_model(new WindowModel(this))
|
||||||
, m_workspaces(new WorkspaceModel(this))
|
, m_workspaces(new WorkspaceModel(this))
|
||||||
|
, m_geometryTimer(new QTimer(this))
|
||||||
{
|
{
|
||||||
connect(m_backend, &WindowBackend::connectedChanged, this, &WindowService::connectedChanged);
|
connect(m_backend, &WindowBackend::connectedChanged, this, &WindowService::connectedChanged);
|
||||||
connect(m_backend, &WindowBackend::connectedChanged, this, &WindowService::syncFromBackend);
|
connect(m_backend, &WindowBackend::connectedChanged, this, &WindowService::syncFromBackend);
|
||||||
connect(m_backend, &WindowBackend::windowsChanged, this, &WindowService::syncFromBackend);
|
connect(m_backend, &WindowBackend::windowsChanged, this, &WindowService::syncFromBackend);
|
||||||
|
|
||||||
|
m_geometryTimer->setInterval(33);
|
||||||
|
connect(m_geometryTimer, &QTimer::timeout, this, &WindowService::syncFocusedGeometry);
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this]() {
|
QTimer::singleShot(0, this, [this]() {
|
||||||
bindApplicationService();
|
bindApplicationService();
|
||||||
if (m_applications)
|
if (m_applications)
|
||||||
@@ -97,6 +101,51 @@ bool WindowService::focusedWindowMaximized() const
|
|||||||
return m_focusedWindowMaximized;
|
return m_focusedWindowMaximized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool WindowService::focusedWindowFullscreen() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowFullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowX() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowX;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowY() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowWidth() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowHeight() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowDecoX() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowDecoX;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowDecoY() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowDecoY;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowDecoWidth() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowDecoWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
int WindowService::focusedWindowDecoHeight() const
|
||||||
|
{
|
||||||
|
return m_focusedWindowDecoHeight;
|
||||||
|
}
|
||||||
|
|
||||||
bool WindowService::focusWindow(const QString &windowId)
|
bool WindowService::focusWindow(const QString &windowId)
|
||||||
{
|
{
|
||||||
return m_backend->focusWindow(parseWindowId(windowId));
|
return m_backend->focusWindow(parseWindowId(windowId));
|
||||||
@@ -197,15 +246,24 @@ void WindowService::syncFromBackend()
|
|||||||
QString appId;
|
QString appId;
|
||||||
QString appName;
|
QString appName;
|
||||||
bool maximized = false;
|
bool maximized = false;
|
||||||
|
const WindowInfo *focusedInfo = nullptr;
|
||||||
|
|
||||||
if (const WindowInfo *focused = m_model->windowById(focusedId)) {
|
if (const WindowInfo *focused = m_model->windowById(focusedId)) {
|
||||||
if (!focused->minimized) {
|
if (!focused->minimized)
|
||||||
windowId = QString::number(focused->id);
|
focusedInfo = focused;
|
||||||
title = focused->title;
|
}
|
||||||
appId = focused->appId.isEmpty() ? focused->windowClass : focused->appId;
|
if (!focusedInfo && !m_focusedWindowId.isEmpty()) {
|
||||||
appName = focused->displayName;
|
const WindowInfo *previous = m_model->windowById(parseWindowId(m_focusedWindowId));
|
||||||
maximized = focused->maximized;
|
if (previous && !previous->minimized)
|
||||||
}
|
focusedInfo = previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusedInfo) {
|
||||||
|
windowId = QString::number(focusedInfo->id);
|
||||||
|
title = focusedInfo->title;
|
||||||
|
appId = focusedInfo->appId.isEmpty() ? focusedInfo->windowClass : focusedInfo->appId;
|
||||||
|
appName = focusedInfo->displayName;
|
||||||
|
maximized = focusedInfo->maximized;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_focusedWindowId != windowId
|
if (m_focusedWindowId != windowId
|
||||||
@@ -220,6 +278,86 @@ void WindowService::syncFromBackend()
|
|||||||
m_focusedWindowMaximized = maximized;
|
m_focusedWindowMaximized = maximized;
|
||||||
emit focusChanged();
|
emit focusChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishFocusedGeometry(focusedInfo);
|
||||||
|
|
||||||
|
if (hasFocusedWindow()) {
|
||||||
|
if (!m_geometryTimer->isActive())
|
||||||
|
m_geometryTimer->start();
|
||||||
|
} else if (m_geometryTimer->isActive()) {
|
||||||
|
m_geometryTimer->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowService::syncFocusedGeometry()
|
||||||
|
{
|
||||||
|
if (m_focusedWindowId.isEmpty()) {
|
||||||
|
m_geometryTimer->stop();
|
||||||
|
publishFocusedGeometry(nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_backend->updateFocusedGeometry();
|
||||||
|
|
||||||
|
const QVector<WindowInfo> windows = m_backend->windows();
|
||||||
|
const qint64 id = parseWindowId(m_focusedWindowId);
|
||||||
|
const WindowInfo *focused = nullptr;
|
||||||
|
for (const WindowInfo &window : windows) {
|
||||||
|
if (window.id == id) {
|
||||||
|
focused = &window;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publishFocusedGeometry(focused);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowService::publishFocusedGeometry(const WindowInfo *focused)
|
||||||
|
{
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int decoX = 0;
|
||||||
|
int decoY = 0;
|
||||||
|
int decoWidth = 0;
|
||||||
|
int decoHeight = 0;
|
||||||
|
bool fullscreen = false;
|
||||||
|
|
||||||
|
if (focused && !focused->minimized) {
|
||||||
|
x = focused->x;
|
||||||
|
y = focused->y;
|
||||||
|
width = focused->width;
|
||||||
|
height = focused->height;
|
||||||
|
decoX = focused->decoX;
|
||||||
|
decoY = focused->decoY;
|
||||||
|
decoWidth = focused->decoWidth;
|
||||||
|
decoHeight = focused->decoHeight;
|
||||||
|
fullscreen = focused->fullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_focusedWindowFullscreen != fullscreen) {
|
||||||
|
m_focusedWindowFullscreen = fullscreen;
|
||||||
|
emit focusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_focusedWindowX != x
|
||||||
|
|| m_focusedWindowY != y
|
||||||
|
|| m_focusedWindowWidth != width
|
||||||
|
|| m_focusedWindowHeight != height
|
||||||
|
|| m_focusedWindowDecoX != decoX
|
||||||
|
|| m_focusedWindowDecoY != decoY
|
||||||
|
|| m_focusedWindowDecoWidth != decoWidth
|
||||||
|
|| m_focusedWindowDecoHeight != decoHeight) {
|
||||||
|
m_focusedWindowX = x;
|
||||||
|
m_focusedWindowY = y;
|
||||||
|
m_focusedWindowWidth = width;
|
||||||
|
m_focusedWindowHeight = height;
|
||||||
|
m_focusedWindowDecoX = decoX;
|
||||||
|
m_focusedWindowDecoY = decoY;
|
||||||
|
m_focusedWindowDecoWidth = decoWidth;
|
||||||
|
m_focusedWindowDecoHeight = decoHeight;
|
||||||
|
emit focusedWindowGeometryChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowService::resolveNames(QVector<WindowInfo> &windows) const
|
void WindowService::resolveNames(QVector<WindowInfo> &windows) const
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <QJSEngine>
|
#include <QJSEngine>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
#include <QTimer>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QtQml/qqmlregistration.h>
|
#include <QtQml/qqmlregistration.h>
|
||||||
|
|
||||||
@@ -27,6 +28,15 @@ class WindowService : public QObject
|
|||||||
Q_PROPERTY(QString focusedApplicationId READ focusedApplicationId NOTIFY focusChanged)
|
Q_PROPERTY(QString focusedApplicationId READ focusedApplicationId NOTIFY focusChanged)
|
||||||
Q_PROPERTY(QString focusedApplicationName READ focusedApplicationName NOTIFY focusChanged)
|
Q_PROPERTY(QString focusedApplicationName READ focusedApplicationName NOTIFY focusChanged)
|
||||||
Q_PROPERTY(bool focusedWindowMaximized READ focusedWindowMaximized NOTIFY focusChanged)
|
Q_PROPERTY(bool focusedWindowMaximized READ focusedWindowMaximized NOTIFY focusChanged)
|
||||||
|
Q_PROPERTY(bool focusedWindowFullscreen READ focusedWindowFullscreen NOTIFY focusChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowX READ focusedWindowX NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowY READ focusedWindowY NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowWidth READ focusedWindowWidth NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowHeight READ focusedWindowHeight NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowDecoX READ focusedWindowDecoX NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowDecoY READ focusedWindowDecoY NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowDecoWidth READ focusedWindowDecoWidth NOTIFY focusedWindowGeometryChanged)
|
||||||
|
Q_PROPERTY(int focusedWindowDecoHeight READ focusedWindowDecoHeight NOTIFY focusedWindowGeometryChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum SnapEdge {
|
enum SnapEdge {
|
||||||
@@ -50,6 +60,15 @@ public:
|
|||||||
QString focusedApplicationId() const;
|
QString focusedApplicationId() const;
|
||||||
QString focusedApplicationName() const;
|
QString focusedApplicationName() const;
|
||||||
bool focusedWindowMaximized() const;
|
bool focusedWindowMaximized() const;
|
||||||
|
bool focusedWindowFullscreen() const;
|
||||||
|
int focusedWindowX() const;
|
||||||
|
int focusedWindowY() const;
|
||||||
|
int focusedWindowWidth() const;
|
||||||
|
int focusedWindowHeight() const;
|
||||||
|
int focusedWindowDecoX() const;
|
||||||
|
int focusedWindowDecoY() const;
|
||||||
|
int focusedWindowDecoWidth() const;
|
||||||
|
int focusedWindowDecoHeight() 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);
|
||||||
@@ -69,9 +88,12 @@ signals:
|
|||||||
void connectedChanged();
|
void connectedChanged();
|
||||||
void windowCountChanged();
|
void windowCountChanged();
|
||||||
void focusChanged();
|
void focusChanged();
|
||||||
|
void focusedWindowGeometryChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void syncFromBackend();
|
void syncFromBackend();
|
||||||
|
void syncFocusedGeometry();
|
||||||
|
void publishFocusedGeometry(const WindowInfo *focused);
|
||||||
void resolveNames(QVector<WindowInfo> &windows) const;
|
void resolveNames(QVector<WindowInfo> &windows) const;
|
||||||
void bindApplicationService();
|
void bindApplicationService();
|
||||||
static qint64 parseWindowId(const QString &windowId);
|
static qint64 parseWindowId(const QString &windowId);
|
||||||
@@ -87,4 +109,14 @@ private:
|
|||||||
QString m_focusedApplicationId;
|
QString m_focusedApplicationId;
|
||||||
QString m_focusedApplicationName;
|
QString m_focusedApplicationName;
|
||||||
bool m_focusedWindowMaximized = false;
|
bool m_focusedWindowMaximized = false;
|
||||||
|
bool m_focusedWindowFullscreen = false;
|
||||||
|
int m_focusedWindowX = 0;
|
||||||
|
int m_focusedWindowY = 0;
|
||||||
|
int m_focusedWindowWidth = 0;
|
||||||
|
int m_focusedWindowHeight = 0;
|
||||||
|
int m_focusedWindowDecoX = 0;
|
||||||
|
int m_focusedWindowDecoY = 0;
|
||||||
|
int m_focusedWindowDecoWidth = 0;
|
||||||
|
int m_focusedWindowDecoHeight = 0;
|
||||||
|
QTimer *m_geometryTimer = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ struct WindowInfo
|
|||||||
int y = 0;
|
int y = 0;
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
|
int decoX = 0;
|
||||||
|
int decoY = 0;
|
||||||
|
int decoWidth = 0;
|
||||||
|
int decoHeight = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WorkspaceInfo
|
struct WorkspaceInfo
|
||||||
|
|||||||
@@ -236,6 +236,52 @@ bool SwayWindowBackend::switchWorkspace(const QString &workspaceId)
|
|||||||
return run(QStringLiteral("workspace \"%1\"").arg(quoteCriteriaValue(workspaceId)));
|
return run(QStringLiteral("workspace \"%1\"").arg(quoteCriteriaValue(workspaceId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SwayWindowBackend::updateFocusedGeometry()
|
||||||
|
{
|
||||||
|
if (!m_ipc->isConnected() || m_focusedWindowId == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const QJsonDocument tree = m_ipc->request(SwayIpcClient::GetTree);
|
||||||
|
if (!tree.isObject())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QVector<WindowInfo> windows;
|
||||||
|
QVector<WorkspaceInfo> workspaces;
|
||||||
|
collect(tree.object(), {}, {}, false, &windows, &workspaces);
|
||||||
|
|
||||||
|
for (WindowInfo &window : windows) {
|
||||||
|
if (window.id != m_focusedWindowId)
|
||||||
|
continue;
|
||||||
|
for (WindowInfo &cached : m_windows) {
|
||||||
|
if (cached.id != window.id)
|
||||||
|
continue;
|
||||||
|
if (cached.x == window.x
|
||||||
|
&& cached.y == window.y
|
||||||
|
&& cached.width == window.width
|
||||||
|
&& cached.height == window.height
|
||||||
|
&& cached.decoX == window.decoX
|
||||||
|
&& cached.decoY == window.decoY
|
||||||
|
&& cached.decoWidth == window.decoWidth
|
||||||
|
&& cached.decoHeight == window.decoHeight
|
||||||
|
&& cached.fullscreen == window.fullscreen)
|
||||||
|
return false;
|
||||||
|
cached.x = window.x;
|
||||||
|
cached.y = window.y;
|
||||||
|
cached.width = window.width;
|
||||||
|
cached.height = window.height;
|
||||||
|
cached.decoX = window.decoX;
|
||||||
|
cached.decoY = window.decoY;
|
||||||
|
cached.decoWidth = window.decoWidth;
|
||||||
|
cached.decoHeight = window.decoHeight;
|
||||||
|
cached.fullscreen = window.fullscreen;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void SwayWindowBackend::tryConnect()
|
void SwayWindowBackend::tryConnect()
|
||||||
{
|
{
|
||||||
if (m_ipc->isConnected())
|
if (m_ipc->isConnected())
|
||||||
@@ -422,7 +468,8 @@ bool SwayWindowBackend::isShellChrome(const QJsonObject &node)
|
|||||||
const QString name = node.value(QStringLiteral("name")).toString();
|
const QString name = node.value(QStringLiteral("name")).toString();
|
||||||
if (name == QLatin1String("Nebula Desktop")
|
if (name == QLatin1String("Nebula Desktop")
|
||||||
|| name == QLatin1String("Nebula Top Bar")
|
|| name == QLatin1String("Nebula Top Bar")
|
||||||
|| name == QLatin1String("Nebula Launcher")) {
|
|| name == QLatin1String("Nebula Launcher")
|
||||||
|
|| name == QLatin1String("Nebula Window Chrome")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,6 +538,11 @@ void SwayWindowBackend::collect(const QJsonObject &node,
|
|||||||
window.y = rect.value(QStringLiteral("y")).toInt();
|
window.y = rect.value(QStringLiteral("y")).toInt();
|
||||||
window.width = rect.value(QStringLiteral("width")).toInt();
|
window.width = rect.value(QStringLiteral("width")).toInt();
|
||||||
window.height = rect.value(QStringLiteral("height")).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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public:
|
|||||||
bool resizeWindow(qint64 windowId, int width, int height) override;
|
bool resizeWindow(qint64 windowId, int width, int height) override;
|
||||||
bool snapWindow(qint64 windowId, SnapEdge edge) override;
|
bool snapWindow(qint64 windowId, SnapEdge edge) override;
|
||||||
bool switchWorkspace(const QString &workspaceId) override;
|
bool switchWorkspace(const QString &workspaceId) override;
|
||||||
|
bool updateFocusedGeometry() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void tryConnect();
|
void tryConnect();
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ QtObject {
|
|||||||
readonly property color pressedFill: Qt.rgba(1, 1, 1, 0.10)
|
readonly property color pressedFill: Qt.rgba(1, 1, 1, 0.10)
|
||||||
readonly property color dangerSoft: Qt.rgba(201 / 255, 136 / 255, 136 / 255, 0.18)
|
readonly property color dangerSoft: Qt.rgba(201 / 255, 136 / 255, 136 / 255, 0.18)
|
||||||
readonly property color dangerSoftStrong: Qt.rgba(201 / 255, 136 / 255, 136 / 255, 0.28)
|
readonly property color dangerSoftStrong: Qt.rgba(201 / 255, 136 / 255, 136 / 255, 0.28)
|
||||||
|
readonly property color trafficClose: "#ff5f57"
|
||||||
|
readonly property color trafficMinimise: "#febc2e"
|
||||||
|
readonly property color trafficZoom: "#28c840"
|
||||||
readonly property color tileHover: Qt.rgba(1, 1, 1, 0.05)
|
readonly property color tileHover: Qt.rgba(1, 1, 1, 0.05)
|
||||||
readonly property color iconWell: Qt.rgba(1, 1, 1, 0.05)
|
readonly property color iconWell: Qt.rgba(1, 1, 1, 0.05)
|
||||||
readonly property color iconWellHighlight: Qt.rgba(1, 1, 1, 0.06)
|
readonly property color iconWellHighlight: Qt.rgba(1, 1, 1, 0.06)
|
||||||
@@ -49,6 +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 trafficLightGap: 8
|
||||||
|
readonly property int windowChromeInset: 10
|
||||||
|
|
||||||
readonly property int fontSize: 13
|
readonly property int fontSize: 13
|
||||||
readonly property int fontSizeApp: 12
|
readonly property int fontSizeApp: 12
|
||||||
|
|||||||
@@ -46,9 +46,11 @@ set(NEBULA_SHELL_QML_FILES
|
|||||||
qml/ShellState.qml
|
qml/ShellState.qml
|
||||||
qml/surfaces/DesktopSurface.qml
|
qml/surfaces/DesktopSurface.qml
|
||||||
qml/surfaces/TopBarSurface.qml
|
qml/surfaces/TopBarSurface.qml
|
||||||
|
qml/surfaces/WindowChromeSurface.qml
|
||||||
qml/surfaces/LauncherSurface.qml
|
qml/surfaces/LauncherSurface.qml
|
||||||
qml/components/TopBar.qml
|
qml/components/TopBar.qml
|
||||||
qml/components/WindowControls.qml
|
qml/components/WindowControls.qml
|
||||||
|
qml/components/TrafficLightButton.qml
|
||||||
qml/components/NebulaButton.qml
|
qml/components/NebulaButton.qml
|
||||||
qml/components/Clock.qml
|
qml/components/Clock.qml
|
||||||
qml/components/SystemArea.qml
|
qml/components/SystemArea.qml
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ QtObject {
|
|||||||
|
|
||||||
readonly property DesktopSurface desktop: DesktopSurface {}
|
readonly property DesktopSurface desktop: DesktopSurface {}
|
||||||
readonly property TopBarSurface topBar: TopBarSurface {}
|
readonly property TopBarSurface topBar: TopBarSurface {}
|
||||||
|
readonly property WindowChromeSurface windowChrome: WindowChromeSurface {}
|
||||||
readonly property LauncherSurface launcher: LauncherSurface {}
|
readonly property LauncherSurface launcher: LauncherSurface {}
|
||||||
|
|
||||||
readonly property Shortcut closeLauncherShortcut: Shortcut {
|
readonly property Shortcut closeLauncherShortcut: Shortcut {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Item {
|
|||||||
id: left
|
id: left
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 6
|
anchors.leftMargin: 6
|
||||||
anchors.right: right.left
|
anchors.right: systemArea.left
|
||||||
anchors.rightMargin: 12
|
anchors.rightMargin: 12
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 8
|
spacing: 8
|
||||||
@@ -54,28 +54,10 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
SystemArea {
|
||||||
id: right
|
id: systemArea
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: windowControls.visible ? 8 : 0
|
|
||||||
|
|
||||||
WindowControls {
|
|
||||||
id: windowControls
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: windowControls.visible
|
|
||||||
width: visible ? 1 : 0
|
|
||||||
height: 14
|
|
||||||
color: Theme.borderStrong
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemArea {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Nebula.UI
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property string label: ""
|
||||||
|
property color fill: Theme.trafficClose
|
||||||
|
property string glyph: "close"
|
||||||
|
property bool showGlyph: false
|
||||||
|
readonly property bool hovered: hover.containsMouse
|
||||||
|
readonly property bool pressed: hover.pressed
|
||||||
|
|
||||||
|
signal clicked()
|
||||||
|
|
||||||
|
implicitWidth: Theme.trafficLightSize + 2
|
||||||
|
implicitHeight: Theme.trafficLightSize + 2
|
||||||
|
Accessible.role: Accessible.Button
|
||||||
|
Accessible.name: root.label
|
||||||
|
Accessible.onPressAction: root.clicked()
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: disc
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: Theme.trafficLightSize
|
||||||
|
height: Theme.trafficLightSize
|
||||||
|
radius: width / 2
|
||||||
|
color: hover.pressed ? Qt.darker(root.fill, 1.18) : root.fill
|
||||||
|
border.width: 1
|
||||||
|
border.color: Qt.rgba(0, 0, 0, 0.22)
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 1
|
||||||
|
width: parent.width - 4
|
||||||
|
height: Math.max(2, parent.height * 0.38)
|
||||||
|
radius: height / 2
|
||||||
|
color: Qt.rgba(1, 1, 1, 0.28)
|
||||||
|
Accessible.ignored: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: glyph
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 8
|
||||||
|
height: 8
|
||||||
|
antialiasing: true
|
||||||
|
opacity: root.showGlyph ? 1 : 0
|
||||||
|
visible: opacity > 0.01
|
||||||
|
onPaint: {
|
||||||
|
const ctx = getContext("2d")
|
||||||
|
ctx.reset()
|
||||||
|
ctx.strokeStyle = Qt.rgba(0.18, 0.12, 0.1, 0.78)
|
||||||
|
ctx.fillStyle = ctx.strokeStyle
|
||||||
|
ctx.lineWidth = 1.15
|
||||||
|
ctx.lineCap = "round"
|
||||||
|
ctx.lineJoin = "round"
|
||||||
|
|
||||||
|
switch (root.glyph) {
|
||||||
|
case "minimize":
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(1.2, 4)
|
||||||
|
ctx.lineTo(6.8, 4)
|
||||||
|
ctx.stroke()
|
||||||
|
break
|
||||||
|
case "zoom":
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(1.4, 4)
|
||||||
|
ctx.lineTo(6.6, 4)
|
||||||
|
ctx.moveTo(4, 1.4)
|
||||||
|
ctx.lineTo(4, 6.6)
|
||||||
|
ctx.stroke()
|
||||||
|
break
|
||||||
|
case "restore":
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.rect(2.3, 0.7, 4.3, 4.3)
|
||||||
|
ctx.stroke()
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.rect(0.7, 2.3, 4.3, 4.3)
|
||||||
|
ctx.stroke()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(1.6, 1.6)
|
||||||
|
ctx.lineTo(6.4, 6.4)
|
||||||
|
ctx.moveTo(6.4, 1.6)
|
||||||
|
ctx.lineTo(1.6, 6.4)
|
||||||
|
ctx.stroke()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.animationFast
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onGlyphChanged: glyph.requestPaint()
|
||||||
|
onShowGlyphChanged: if (showGlyph) glyph.requestPaint()
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: hover
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: root.clicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
NebulaToolTip {
|
||||||
|
text: root.label
|
||||||
|
active: hover.containsMouse && !hover.pressed && root.label.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,71 +6,43 @@ Item {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool maximized: WindowService.focusedWindowMaximized
|
readonly property bool maximized: WindowService.focusedWindowMaximized
|
||||||
|
readonly property bool hovered: closeButton.hovered || minimizeButton.hovered || zoomButton.hovered
|
||||||
|
|
||||||
visible: WindowService.hasFocusedWindow
|
implicitWidth: row.implicitWidth
|
||||||
implicitWidth: well.implicitWidth
|
implicitHeight: row.implicitHeight
|
||||||
implicitHeight: Theme.controlHeight
|
|
||||||
width: visible ? implicitWidth : 0
|
|
||||||
Accessible.role: Accessible.Grouping
|
Accessible.role: Accessible.Grouping
|
||||||
Accessible.name: qsTr("Window controls")
|
Accessible.name: qsTr("Window controls")
|
||||||
|
|
||||||
Rectangle {
|
Row {
|
||||||
id: well
|
id: row
|
||||||
anchors.fill: parent
|
anchors.centerIn: parent
|
||||||
implicitWidth: row.implicitWidth + 2
|
spacing: Theme.trafficLightGap
|
||||||
implicitHeight: Theme.controlHeight
|
|
||||||
radius: Theme.radiusSmall
|
|
||||||
color: Theme.iconWell
|
|
||||||
border.width: 1
|
|
||||||
border.color: Theme.border
|
|
||||||
|
|
||||||
Row {
|
TrafficLightButton {
|
||||||
id: row
|
id: closeButton
|
||||||
anchors.centerIn: parent
|
fill: Theme.trafficClose
|
||||||
spacing: 0
|
glyph: "close"
|
||||||
|
label: qsTr("Close")
|
||||||
|
showGlyph: root.hovered
|
||||||
|
onClicked: WindowService.closeFocusedWindow()
|
||||||
|
}
|
||||||
|
|
||||||
NebulaIconButton {
|
TrafficLightButton {
|
||||||
id: minimizeButton
|
id: minimizeButton
|
||||||
width: 26
|
fill: Theme.trafficMinimise
|
||||||
height: 26
|
glyph: "minimize"
|
||||||
label: qsTr("Minimise")
|
label: qsTr("Minimise")
|
||||||
onClicked: WindowService.minimizeFocusedWindow()
|
showGlyph: root.hovered
|
||||||
|
onClicked: WindowService.minimizeFocusedWindow()
|
||||||
|
}
|
||||||
|
|
||||||
NebulaIcon {
|
TrafficLightButton {
|
||||||
name: "minimize"
|
id: zoomButton
|
||||||
size: 14
|
fill: Theme.trafficZoom
|
||||||
color: minimizeButton.iconColor
|
glyph: root.maximized ? "restore" : "zoom"
|
||||||
}
|
label: root.maximized ? qsTr("Restore") : qsTr("Maximise")
|
||||||
}
|
showGlyph: root.hovered
|
||||||
|
onClicked: WindowService.toggleMaximizeFocusedWindow()
|
||||||
NebulaIconButton {
|
|
||||||
id: maximizeButton
|
|
||||||
width: 26
|
|
||||||
height: 26
|
|
||||||
label: root.maximized ? qsTr("Restore") : qsTr("Maximise")
|
|
||||||
onClicked: WindowService.toggleMaximizeFocusedWindow()
|
|
||||||
|
|
||||||
NebulaIcon {
|
|
||||||
name: root.maximized ? "restore" : "maximize"
|
|
||||||
size: 14
|
|
||||||
color: maximizeButton.iconColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NebulaIconButton {
|
|
||||||
id: closeButton
|
|
||||||
width: 26
|
|
||||||
height: 26
|
|
||||||
label: qsTr("Close")
|
|
||||||
danger: true
|
|
||||||
onClicked: WindowService.closeFocusedWindow()
|
|
||||||
|
|
||||||
NebulaIcon {
|
|
||||||
name: "close"
|
|
||||||
size: 14
|
|
||||||
color: closeButton.iconColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Window
|
||||||
|
import org.kde.layershell 1.0 as LayerShell
|
||||||
|
import Nebula.UI
|
||||||
|
import Nebula.Windows
|
||||||
|
|
||||||
|
Window {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property bool active: WindowService.hasFocusedWindow
|
||||||
|
&& !WindowService.focusedWindowFullscreen
|
||||||
|
&& !ShellState.launcherOpen
|
||||||
|
readonly property int chromeX: WindowService.focusedWindowX
|
||||||
|
+ WindowService.focusedWindowDecoX
|
||||||
|
+ Theme.windowChromeInset
|
||||||
|
readonly property int chromeY: {
|
||||||
|
const top = WindowService.focusedWindowY + WindowService.focusedWindowDecoY
|
||||||
|
const decoHeight = WindowService.focusedWindowDecoHeight
|
||||||
|
if (decoHeight <= 0)
|
||||||
|
return top + 6
|
||||||
|
return top + Math.max(0, Math.round((decoHeight - height) / 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
title: qsTr("Nebula Window Chrome")
|
||||||
|
color: "transparent"
|
||||||
|
flags: Qt.FramelessWindowHint | Qt.WindowDoesNotAcceptFocus
|
||||||
|
visible: root.active
|
||||||
|
width: Math.max(1, controls.implicitWidth)
|
||||||
|
height: Math.max(1, controls.implicitHeight)
|
||||||
|
|
||||||
|
LayerShell.Window.scope: "nebula-window-chrome"
|
||||||
|
LayerShell.Window.layer: LayerShell.Window.LayerTop
|
||||||
|
LayerShell.Window.anchors: LayerShell.Window.AnchorLeft | LayerShell.Window.AnchorTop
|
||||||
|
LayerShell.Window.margins.left: Math.max(0, chromeX)
|
||||||
|
LayerShell.Window.margins.top: Math.max(0, chromeY)
|
||||||
|
LayerShell.Window.exclusionZone: 0
|
||||||
|
LayerShell.Window.keyboardInteractivity: LayerShell.Window.KeyboardInteractivityNone
|
||||||
|
|
||||||
|
WindowControls {
|
||||||
|
id: controls
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user