Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
Layout.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 fuddlesworth
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
9#include <phosphorzones_export.h>
10#include <QObject>
11#include <QVariantMap>
12#include <QVector>
13#include <QUuid>
14#include <QString>
15#include <QJsonArray>
16#include <QJsonObject>
17#include <functional>
18#include <memory>
19
20namespace PhosphorZones {
21
28struct PHOSPHORZONES_EXPORT AppRule
29{
30 QString pattern; // Window class or app name pattern (case-insensitive substring match)
31 int zoneNumber = 0; // 1-based zone number to snap to
32 QString targetScreen; // Optional: snap to zone on this screen instead of current
33
34 // C++20 synthesises operator!= from operator==.
35 bool operator==(const AppRule& other) const = default;
36
37 // Serialization helpers (centralized to avoid DRY violations)
38 QJsonObject toJson() const;
39 static AppRule fromJson(const QJsonObject& obj);
40 static QVector<AppRule> fromJsonArray(const QJsonArray& array);
41};
42
46struct PHOSPHORZONES_EXPORT AppRuleMatch
47{
48 int zoneNumber = 0;
49 QString targetScreen;
50 bool matched() const
51 {
52 return zoneNumber > 0;
53 }
54};
55
61enum class LayoutCategory {
62 Manual = 0,
63 Autotile = 1
64};
65
73class PHOSPHORZONES_EXPORT Layout : public QObject
74{
75 Q_OBJECT
76
77 Q_PROPERTY(QUuid id READ id CONSTANT)
78 Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
79 Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
80 Q_PROPERTY(int zonePadding READ zonePadding WRITE setZonePadding NOTIFY zonePaddingChanged)
81 Q_PROPERTY(int outerGap READ outerGap WRITE setOuterGap NOTIFY outerGapChanged)
82 Q_PROPERTY(bool hasZonePaddingOverride READ hasZonePaddingOverride NOTIFY zonePaddingChanged)
83 Q_PROPERTY(bool hasOuterGapOverride READ hasOuterGapOverride NOTIFY outerGapChanged)
84 Q_PROPERTY(bool usePerSideOuterGap READ usePerSideOuterGap WRITE setUsePerSideOuterGap NOTIFY outerGapChanged)
85 Q_PROPERTY(int outerGapTop READ outerGapTop WRITE setOuterGapTop NOTIFY outerGapChanged)
86 Q_PROPERTY(int outerGapBottom READ outerGapBottom WRITE setOuterGapBottom NOTIFY outerGapChanged)
87 Q_PROPERTY(int outerGapLeft READ outerGapLeft WRITE setOuterGapLeft NOTIFY outerGapChanged)
88 Q_PROPERTY(int outerGapRight READ outerGapRight WRITE setOuterGapRight NOTIFY outerGapChanged)
89 Q_PROPERTY(bool hasPerSideOuterGapOverride READ hasPerSideOuterGapOverride NOTIFY outerGapChanged)
90 Q_PROPERTY(bool showZoneNumbers READ showZoneNumbers WRITE setShowZoneNumbers NOTIFY showZoneNumbersChanged)
91 Q_PROPERTY(
92 int overlayDisplayMode READ overlayDisplayMode WRITE setOverlayDisplayMode NOTIFY overlayDisplayModeChanged)
93 Q_PROPERTY(bool hasOverlayDisplayModeOverride READ hasOverlayDisplayModeOverride NOTIFY overlayDisplayModeChanged)
94 Q_PROPERTY(int zoneCount READ zoneCount NOTIFY zonesChanged)
95 Q_PROPERTY(QString sourcePath READ sourcePath WRITE setSourcePath NOTIFY sourcePathChanged)
96 Q_PROPERTY(bool isSystemLayout READ isSystemLayout NOTIFY sourcePathChanged)
97 Q_PROPERTY(QString shaderId READ shaderId WRITE setShaderId NOTIFY shaderIdChanged)
98 Q_PROPERTY(QVariantMap shaderParams READ shaderParams WRITE setShaderParams NOTIFY shaderParamsChanged)
99
100 // App-to-zone rules
101 Q_PROPERTY(QVariantList appRules READ appRulesVariant WRITE setAppRulesVariant NOTIFY appRulesChanged)
102
103 // Auto-assign: new windows fill first empty zone
104 Q_PROPERTY(bool autoAssign READ autoAssign WRITE setAutoAssign NOTIFY autoAssignChanged)
105
106 // Geometry mode: use full screen or available (panel-excluded) geometry
107 Q_PROPERTY(bool useFullScreenGeometry READ useFullScreenGeometry WRITE setUseFullScreenGeometry NOTIFY
108 useFullScreenGeometryChanged)
109
110 // Aspect ratio classification
111 Q_PROPERTY(
112 int aspectRatioClass READ aspectRatioClassInt WRITE setAspectRatioClassInt NOTIFY aspectRatioClassChanged)
113 Q_PROPERTY(qreal minAspectRatio READ minAspectRatio WRITE setMinAspectRatio NOTIFY aspectRatioClassChanged)
114 Q_PROPERTY(qreal maxAspectRatio READ maxAspectRatio WRITE setMaxAspectRatio NOTIFY aspectRatioClassChanged)
115
116 // Visibility filtering
117 Q_PROPERTY(
118 bool hiddenFromSelector READ hiddenFromSelector WRITE setHiddenFromSelector NOTIFY hiddenFromSelectorChanged)
119 Q_PROPERTY(QStringList allowedScreens READ allowedScreens WRITE setAllowedScreens NOTIFY allowedScreensChanged)
120 Q_PROPERTY(QList<int> allowedDesktops READ allowedDesktops WRITE setAllowedDesktops NOTIFY allowedDesktopsChanged)
121 Q_PROPERTY(
122 QStringList allowedActivities READ allowedActivities WRITE setAllowedActivities NOTIFY allowedActivitiesChanged)
123
124public:
125 explicit Layout(QObject* parent = nullptr);
126 explicit Layout(const QString& name, QObject* parent = nullptr);
127 Layout(const Layout& other);
128 ~Layout() override;
129
130 Layout& operator=(const Layout& other);
131
132 // Identification
133 QUuid id() const
134 {
135 return m_id;
136 }
137 QString name() const
138 {
139 return m_name;
140 }
141 void setName(const QString& name);
142
143 QString description() const
144 {
145 return m_description;
146 }
147 void setDescription(const QString& description);
148
149 // Layout settings (per-layout gap overrides, -1 = use global setting)
150 int zonePadding() const
151 {
152 return m_zonePadding;
153 }
154 void setZonePadding(int padding);
156 {
157 return m_zonePadding >= 0;
158 }
160
161 int outerGap() const
162 {
163 return m_outerGap;
164 }
165 void setOuterGap(int gap);
167 {
168 return m_outerGap >= 0;
169 }
171
172 // Per-side outer gap overrides (-1 = use global setting)
174 {
175 return m_usePerSideOuterGap;
176 }
177 void setUsePerSideOuterGap(bool enabled);
178 int outerGapTop() const
179 {
180 return m_outerGapTop;
181 }
182 void setOuterGapTop(int gap);
183 int outerGapBottom() const
184 {
185 return m_outerGapBottom;
186 }
187 void setOuterGapBottom(int gap);
188 int outerGapLeft() const
189 {
190 return m_outerGapLeft;
191 }
192 void setOuterGapLeft(int gap);
193 int outerGapRight() const
194 {
195 return m_outerGapRight;
196 }
197 void setOuterGapRight(int gap);
199 {
200 return m_usePerSideOuterGap
201 && (m_outerGapTop >= 0 || m_outerGapBottom >= 0 || m_outerGapLeft >= 0 || m_outerGapRight >= 0);
202 }
209 {
210 return {m_outerGapTop, m_outerGapBottom, m_outerGapLeft, m_outerGapRight};
211 }
212
213 bool showZoneNumbers() const
214 {
215 return m_showZoneNumbers;
216 }
217 void setShowZoneNumbers(bool show);
218
220 {
221 return m_overlayDisplayMode;
222 }
223 void setOverlayDisplayMode(int mode);
225 {
226 return m_overlayDisplayMode >= 0;
227 }
229
230 // Source path tracking - determines if layout is from system or user directory
231 QString sourcePath() const
232 {
233 return m_sourcePath;
234 }
235 void setSourcePath(const QString& path);
236
237 // Returns true if layout was loaded from a system directory (not user's .local)
238 // This determines whether the layout can be edited/deleted in place
239 bool isSystemLayout() const;
240
241 // Original system layout path — set when a user override replaces a system layout.
242 // Persisted in user JSON so deletion can restore the system original without scanning.
243 QString systemSourcePath() const
244 {
245 return m_systemSourcePath;
246 }
247 void setSystemSourcePath(const QString& path)
248 {
249 m_systemSourcePath = path;
250 }
251 bool hasSystemOrigin() const
252 {
253 return !m_systemSourcePath.isEmpty();
254 }
255
256 // Shader support
257 QString shaderId() const
258 {
259 return m_shaderId;
260 }
261 void setShaderId(const QString& id);
262 QVariantMap shaderParams() const
263 {
264 return m_shaderParams;
265 }
266 void setShaderParams(const QVariantMap& params);
267
268 // Aspect ratio classification
270 {
271 return m_aspectRatioClass;
272 }
275 {
276 return static_cast<int>(m_aspectRatioClass);
277 }
279 qreal minAspectRatio() const
280 {
281 return m_minAspectRatio;
282 }
283 void setMinAspectRatio(qreal ratio);
284 qreal maxAspectRatio() const
285 {
286 return m_maxAspectRatio;
287 }
288 void setMaxAspectRatio(qreal ratio);
289
295 bool matchesAspectRatio(qreal screenAspectRatio) const;
296
297 // Visibility filtering
299 {
300 return m_hiddenFromSelector;
301 }
302 void setHiddenFromSelector(bool hidden);
303 QStringList allowedScreens() const
304 {
305 return m_allowedScreens;
306 }
307 void setAllowedScreens(const QStringList& screens);
308 QList<int> allowedDesktops() const
309 {
310 return m_allowedDesktops;
311 }
312 void setAllowedDesktops(const QList<int>& desktops);
313 QStringList allowedActivities() const
314 {
315 return m_allowedActivities;
316 }
317 void setAllowedActivities(const QStringList& activities);
318
319 // App-to-zone rules
320 QVector<AppRule> appRules() const
321 {
322 return m_appRules;
323 }
324 void setAppRules(const QVector<AppRule>& rules);
325 QVariantList appRulesVariant() const;
326 void setAppRulesVariant(const QVariantList& rules);
327 AppRuleMatch matchAppRule(const QString& windowClass) const;
328
329 // Auto-assign: new windows fill first empty zone
330 bool autoAssign() const
331 {
332 return m_autoAssign;
333 }
334 void setAutoAssign(bool enabled);
335
336 // Geometry mode: when true, zones span the full screen including panel/taskbar areas
338 {
339 return m_useFullScreenGeometry;
340 }
341 void setUseFullScreenGeometry(bool enabled);
342
345
348 QRectF fixedZoneBoundingBox() const;
349
358
359 // Optional load order for "default" layout when defaultLayoutId is not set (lower = first)
360 int defaultOrder() const
361 {
362 return m_defaultOrder;
363 }
364 void setDefaultOrder(int order)
365 {
366 m_defaultOrder = order;
367 }
368
369 // Zone management
370 int zoneCount() const
371 {
372 return m_zones.size();
373 }
374 QVector<Zone*> zones() const
375 {
376 return m_zones;
377 }
378 Zone* zone(int index) const;
379 Zone* zoneById(const QUuid& id) const;
380 Zone* zoneByNumber(int number) const;
381
382 Q_INVOKABLE void addZone(Zone* zone);
383 Q_INVOKABLE void removeZone(Zone* zone);
384 Q_INVOKABLE void removeZoneAt(int index);
385 Q_INVOKABLE void clearZones();
386 Q_INVOKABLE void moveZone(int fromIndex, int toIndex);
387
388 // Zone detection
389 Q_INVOKABLE Zone* zoneAtPoint(const QPointF& point) const;
390 Q_INVOKABLE Zone* nearestZone(const QPointF& point, qreal maxDistance = -1) const;
391 Q_INVOKABLE QVector<Zone*> zonesInRect(const QRectF& rect) const;
392 Q_INVOKABLE QVector<Zone*> adjacentZones(const QPointF& point, qreal threshold = 20) const;
393
394 // Geometry calculations
395 Q_INVOKABLE void renumberZones();
396 QRectF lastRecalcGeometry() const
397 {
398 return m_lastRecalcGeometry;
399 }
400 void setLastRecalcGeometry(const QRectF& geom)
401 {
402 m_lastRecalcGeometry = geom;
403 }
404
405 // Serialization
406 QJsonObject toJson() const;
407 static Layout* fromJson(const QJsonObject& json, QObject* parent = nullptr);
408
421 using ScreenIdResolver = std::function<QString(const QString&)>;
429
430 // Predefined layouts (templates)
431 static Layout* createColumnsLayout(int columns, QObject* parent = nullptr);
432 static Layout* createRowsLayout(int rows, QObject* parent = nullptr);
433 static Layout* createGridLayout(int columns, int rows, QObject* parent = nullptr);
434 static Layout* createPriorityGridLayout(QObject* parent = nullptr);
435 static Layout* createFocusLayout(QObject* parent = nullptr);
436
437 // Dirty tracking for copy-on-write saving
438 bool isDirty() const
439 {
440 return m_dirty;
441 }
443 {
444 m_dirty = true;
445 }
447 {
448 m_dirty = false;
449 }
452
453Q_SIGNALS:
472 void zoneAdded(Zone* zone);
473 void zoneRemoved(Zone* zone);
475
476public:
485 void recalculateZoneGeometries(const QRectF& screenGeometry);
486
487private:
488 void emitModifiedIfNotBatched();
489
496 void initFromJson(const QJsonObject& json);
497
498 QUuid m_id;
499 QString m_name;
500 QString m_description;
501 int m_zonePadding = -1; // -1 = use global setting
502 int m_outerGap = -1; // -1 = use global setting
503 bool m_usePerSideOuterGap = false;
504 int m_outerGapTop = -1; // -1 = use global setting
505 int m_outerGapBottom = -1;
506 int m_outerGapLeft = -1;
507 int m_outerGapRight = -1;
508 bool m_showZoneNumbers = true;
509 int m_overlayDisplayMode = -1; // -1 = use global setting
510 QString m_sourcePath; // Path where layout was loaded from (empty for new layouts)
511 QString m_systemSourcePath; // Original system path if this is a user override of a system layout
512 int m_defaultOrder = 999; // Optional: lower values appear first when choosing default (999 = not set)
513 QVector<Zone*> m_zones;
514
515 // App-to-zone rules
516 QVector<AppRule> m_appRules;
517
518 // Auto-assign: new windows fill first empty zone
519 bool m_autoAssign = false;
520
521 // Geometry mode: zones use full screen (true) or available area excluding panels (false)
522 bool m_useFullScreenGeometry = false;
523
524 // Shader support
525 QString m_shaderId; // Shader effect ID (empty = no shader)
526 QVariantMap m_shaderParams; // Shader-specific parameters
527
528 // Aspect ratio classification
530 qreal m_minAspectRatio = 0.0; // 0 = not set (use class matching)
531 qreal m_maxAspectRatio = 0.0; // 0 = not set (use class matching)
532
533 // Visibility filtering
534 bool m_hiddenFromSelector = false;
535 QStringList m_allowedScreens; // empty = all screens
536 QList<int> m_allowedDesktops; // empty = all desktops
537 QStringList m_allowedActivities; // empty = all activities
538
539 // Cache last geometry used for recalculation to avoid redundant work
540 mutable QRectF m_lastRecalcGeometry;
541
542 // Cached classification derived from m_sourcePath. Recomputed in
543 // setSourcePath so isSystemLayout() is O(1) — QStandardPaths lookups add
544 // up on hot paths like QML preview rebuilds.
545 bool m_isSystemLayout = false;
546
547 // Dirty tracking for copy-on-write saving
548 bool m_dirty = false;
549 int m_batchModifyDepth = 0;
550};
551
552} // namespace PhosphorZones
Represents a collection of zones that form a layout.
Definition Layout.h:74
bool hasOuterGapOverride() const
Definition Layout.h:166
int overlayDisplayMode() const
Definition Layout.h:219
Zone * nearestZone(const QPointF &point, qreal maxDistance=-1) const
int zonePadding() const
Definition Layout.h:150
QList< int > allowedDesktops() const
Definition Layout.h:308
int outerGapLeft() const
Definition Layout.h:188
::PhosphorLayout::EdgeGaps rawOuterGaps() const
Raw per-side gap overrides.
Definition Layout.h:208
void addZone(Zone *zone)
bool usePerSideOuterGap() const
Definition Layout.h:173
bool isSystemLayout() const
bool isDirty() const
Definition Layout.h:438
void moveZone(int fromIndex, int toIndex)
Zone * zoneById(const QUuid &id) const
void setAppRules(const QVector< AppRule > &rules)
void setSourcePath(const QString &path)
QVector< AppRule > appRules() const
Definition Layout.h:320
void setAllowedActivities(const QStringList &activities)
QString systemSourcePath() const
Definition Layout.h:243
int defaultOrder() const
Definition Layout.h:360
void setShaderId(const QString &id)
void zoneRemoved(Zone *zone)
static Layout * createPriorityGridLayout(QObject *parent=nullptr)
int outerGapTop() const
Definition Layout.h:178
void setDefaultOrder(int order)
Definition Layout.h:364
bool hasFixedGeometryZones() const
Returns true if any zone uses fixed (pixel) geometry mode.
Layout(const Layout &other)
void setAspectRatioClass(::PhosphorLayout::AspectRatioClass cls)
void setOuterGapTop(int gap)
void setLastRecalcGeometry(const QRectF &geom)
Definition Layout.h:400
void setUseFullScreenGeometry(bool enabled)
void setHiddenFromSelector(bool hidden)
qreal maxAspectRatio() const
Definition Layout.h:284
QRectF fixedZoneBoundingBox() const
Bounding box of all fixed-geometry zones in pixel coordinates, anchored at (0, 0).
void markDirty()
Definition Layout.h:442
void overlayDisplayModeChanged()
QRectF fixedZoneReferenceGeometry() const
Reference geometry suitable for normalizing fixed-pixel zones to 0–1 relative coordinates.
void setOuterGap(int gap)
bool matchesAspectRatio(qreal screenAspectRatio) const
Check if this layout is suitable for a screen with the given aspect ratio.
void setUsePerSideOuterGap(bool enabled)
bool hasZonePaddingOverride() const
Definition Layout.h:155
Layout(const QString &name, QObject *parent=nullptr)
QVariantMap shaderParams() const
Definition Layout.h:262
void setAppRulesVariant(const QVariantList &rules)
void setZonePadding(int padding)
QString sourcePath() const
Definition Layout.h:231
void setAutoAssign(bool enabled)
QVector< Zone * > adjacentZones(const QPointF &point, qreal threshold=20) const
bool hasSystemOrigin() const
Definition Layout.h:251
static void setScreenIdResolver(ScreenIdResolver resolver)
int zoneCount() const
Definition Layout.h:370
void recalculateZoneGeometries(const QRectF &screenGeometry)
Recalculate every zone's absolute geometry against screenGeometry.
QJsonObject toJson() const
Zone * zoneByNumber(int number) const
void hiddenFromSelectorChanged()
bool hasPerSideOuterGapOverride() const
Definition Layout.h:198
bool hasOverlayDisplayModeOverride() const
Definition Layout.h:224
Zone * zone(int index) const
std::function< QString(const QString &)> ScreenIdResolver
Screen-id resolver.
Definition Layout.h:421
QStringList allowedActivities() const
Definition Layout.h:313
void setOverlayDisplayMode(int mode)
int outerGapRight() const
Definition Layout.h:193
int outerGap() const
Definition Layout.h:161
static Layout * createGridLayout(int columns, int rows, QObject *parent=nullptr)
QVector< Zone * > zonesInRect(const QRectF &rect) const
static Layout * createFocusLayout(QObject *parent=nullptr)
Layout(QObject *parent=nullptr)
bool useFullScreenGeometry() const
Definition Layout.h:337
void setDescription(const QString &description)
bool hiddenFromSelector() const
Definition Layout.h:298
::PhosphorLayout::AspectRatioClass aspectRatioClass() const
Definition Layout.h:269
void setMinAspectRatio(qreal ratio)
QRectF lastRecalcGeometry() const
Definition Layout.h:396
void setSystemSourcePath(const QString &path)
Definition Layout.h:247
Zone * zoneAtPoint(const QPointF &point) const
QStringList allowedScreens() const
Definition Layout.h:303
bool showZoneNumbers() const
Definition Layout.h:213
void setAspectRatioClassInt(int cls)
void removeZone(Zone *zone)
void setAllowedScreens(const QStringList &screens)
QVariantList appRulesVariant() const
QString shaderId() const
Definition Layout.h:257
static ScreenIdResolver screenIdResolver()
Returns a copy of the currently-installed resolver (empty if none).
AppRuleMatch matchAppRule(const QString &windowClass) const
void setName(const QString &name)
void setOuterGapRight(int gap)
void clearOverlayDisplayModeOverride()
void setOuterGapBottom(int gap)
void setOuterGapLeft(int gap)
void removeZoneAt(int index)
QString name() const
Definition Layout.h:137
QUuid id() const
Definition Layout.h:133
void setMaxAspectRatio(qreal ratio)
QString description() const
Definition Layout.h:143
Layout & operator=(const Layout &other)
qreal minAspectRatio() const
Definition Layout.h:279
void useFullScreenGeometryChanged()
void setAllowedDesktops(const QList< int > &desktops)
static Layout * fromJson(const QJsonObject &json, QObject *parent=nullptr)
static Layout * createRowsLayout(int rows, QObject *parent=nullptr)
QVector< Zone * > zones() const
Definition Layout.h:374
int outerGapBottom() const
Definition Layout.h:183
bool autoAssign() const
Definition Layout.h:330
void zoneAdded(Zone *zone)
void setShaderParams(const QVariantMap &params)
void setShowZoneNumbers(bool show)
int aspectRatioClassInt() const
Definition Layout.h:274
static Layout * createColumnsLayout(int columns, QObject *parent=nullptr)
void clearDirty()
Definition Layout.h:446
Represents a single zone within a layout.
Definition Zone.h:44
AspectRatioClass
Screen aspect-ratio classification.
Definition AspectRatioClass.h:21
@ Any
Suitable for all aspect ratios (default)
Definition IWindowTrackingService.h:22
LayoutCategory
Category for layout type.
Definition Layout.h:61
@ Autotile
Dynamic auto-tiling algorithm.
@ Manual
Traditional zone-based layout.
Per-side edge gap values (resolved, non-negative pixel values)
Definition EdgeGaps.h:27
Result of matching a window class against app rules.
Definition Layout.h:47
QString targetScreen
Definition Layout.h:49
bool matched() const
Definition Layout.h:50
App-to-zone auto-snap rule.
Definition Layout.h:29
QJsonObject toJson() const
static AppRule fromJson(const QJsonObject &obj)
static QVector< AppRule > fromJsonArray(const QJsonArray &array)
bool operator==(const AppRule &other) const =default
QString pattern
Definition Layout.h:30
QString targetScreen
Definition Layout.h:32