1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
|
pragma Singleton
import Quickshell
import Quickshell.Hyprland
Singleton {
id: root
function doLayout(windowList, outerWidth, outerHeight) {
if (windowList.length === 0) return []
// Gap: 0.8% of screen, clamped between 12px and 32px
var rawGap = Math.min(outerWidth * 0.08, outerHeight * 0.08)
var gap = Math.max(12, Math.min(32, rawGap))
// Move active window to the head of windowList
var activeAddr = Hyprland.activeToplevel?.lastIpcObject?.address
if (activeAddr) {
var activeIdx = windowList.findIndex(it => it.lastIpcObject.address === activeAddr)
if (activeIdx !== -1) {
windowList = [windowList[activeIdx], ...windowList.filter(it => it !== windowList[activeIdx])]
}
}
// Safe area definition (90%)
var contentScale = 0.90
var useW = outerWidth * contentScale
var useH = outerHeight * contentScale
// Global offset - center Safe area
var offX = (outerWidth - useW) / 2
var offY = (outerHeight - useH) / 2
var result = []
// Screen zones (Hero/Stack)
var heroRatio = 0.40 // 40% Hero
var heroAreaW = useW * heroRatio
var stackAreaW = useW - heroAreaW - gap // 60% Stack
var heroItem = windowList[0]
// Aspect Fit
var hScale = Math.min(heroAreaW / heroItem.width, useH / heroItem.height)
var hW = heroItem.width * hScale
var hH = heroItem.height * hScale
result.push({
win: heroItem.win,
x: offX + (heroAreaW - hW) / 2,
y: offY + (useH - hH) / 2,
width: hW,
height: hH,
isHero: true
})
var others = windowList.slice(1)
var N = others.length
if (N > 0) {
var stackStartX = offX + heroAreaW + gap
// Evaluate col number
var bestCols = 1
var bestRows = N
// Windows height on a single column
var oneColH = (useH - (gap * (N - 1))) / N
// TOLERANCE THRESHOLD (0.15 = 15% of screen height)
// If the windows are at least 15% of the screen height, we stay on 1 column.
// With 4 windows we are at ~25% -> OK (1 Column)
// With 7 windows we are at ~14% -> NO (Go to grid calculation)
var useSingleCol = oneColH > (useH * 0.15)
if (!useSingleCol) {
// If space is limited, we look for the optimal grid starting with 2 columns.
var bestScale = 0
var TARGET_ASPECT = 16.0 / 9.0
for (var cols = 2; cols <= N; cols++) {
var rows = Math.ceil(N / cols)
var availW = stackAreaW - (gap * (cols - 1))
var availH = useH - (gap * (rows - 1))
if (availW <= 0 || availH <= 0) continue
var cellW = availW / cols
var cellH = availH / rows
// Size score
var sW = cellW / TARGET_ASPECT
var sH = cellH / 1.0
var currentScale = Math.min(sW, sH)
if (currentScale > bestScale) {
bestScale = currentScale
bestCols = cols
bestRows = rows
}
}
}
// Evaluation of the final dimensions of the selected grid
var finalAvailW = stackAreaW - (gap * (bestCols - 1))
var finalAvailH = useH - (gap * (bestRows - 1))
var finalCellW = finalAvailW / bestCols
var finalCellH = finalAvailH / bestRows
// Vertical centering of the total stack
var totalGridH = bestRows * finalCellH + (bestRows - 1) * gap
var stackStartY = offY + (useH - totalGridH) / 2
// Items positioning
for (var i = 0; i < N; ++i) {
var item = others[i]
var row = Math.floor(i / bestCols)
var col = i % bestCols
// Cell coords (Standard Grid Alignment)
// No “rowOffsetX”, cell 0 always starts on the left
var cellAbsX = stackStartX + col * (finalCellW + gap)
var cellAbsY = stackStartY + row * (finalCellH + gap)
// Thumb aspect Fit
var sc = Math.min(finalCellW / item.width, finalCellH / item.height)
var w = item.width * sc
var h = item.height * sc
result.push({
win: item.win,
x: cellAbsX + (finalCellW - w) / 2,
y: cellAbsY + (finalCellH - h) / 2,
width: w,
height: h,
isHero: false
})
}
}
return result
}
}
|