diff options
Diffstat (limited to 'config/quickshell/qs-hyprview-trixie/layouts')
12 files changed, 1270 insertions, 0 deletions
diff --git a/config/quickshell/qs-hyprview-trixie/layouts/BandsLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/BandsLayout.qml new file mode 100644 index 00000000..386ef92e --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/BandsLayout.qml @@ -0,0 +1,174 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 0) return [] + + // Gap: 0.8% of screen, clamped between 12px and 24px + var rawGap = Math.min(outerWidth * 0.08, outerHeight * 0.08) + var gap = Math.max(12, Math.min(24, rawGap)) + + // Safe Area: 90% of the screen + var contentScale = 0.90 + var useW = outerWidth * contentScale + var useH = outerHeight * contentScale + + // Global offsets to center everything + var offX = (outerWidth - useW) / 2 + var offY = (outerHeight - useH) / 2 + + // Group by workspace + var groups = {} + var wsOrder = [] + + for (var i = 0; i < N; i++) { + var w = windowList[i] + var wsId = w.workspaceId + + if (!groups[wsId]) { + groups[wsId] = [] + wsOrder.push(wsId) + } + groups[wsId].push(w) + } + + var bandCount = wsOrder.length + if (bandCount === 0) return [] + + // Band height & max thumb height + + // Calculate the height allocated for each workspace band + var totalGapH = gap * (bandCount - 1) + var bandHeight = (useH - totalGapH) / bandCount + + // Aesthetic Cap: Even if we have only 1 workspace, + // windows shouldn't exceed 45% of screen height. + var absoluteMaxH = useH * 0.45 + + // The effective max height is the smaller of the two. + // If we have 10 bands, bandHeight will be small (e.g. 100px), so that rules. + // If we have 1 band, bandHeight is huge (1000px), so absoluteMaxH (450px) rules. + var localMaxH = Math.min(bandHeight, absoluteMaxH) + + // Minimum safety height to avoid division by zero errors + if (localMaxH < 10) localMaxH = 10 + + var result = [] + var currentY = offY + + // Process each band + for (var b = 0; b < bandCount; b++) { + var wsId = wsOrder[b] + var items = groups[wsId] + var itemCount = items.length + + // ROW LAYOUT CALCULATION (Justified) + var rows = [] + var currentRow = [] + var currentAspectSum = 0 + + for (var k = 0; k < itemCount; k++) { + var item = items[k] + var w0 = (item.width > 0) ? item.width : 100 + var h0 = (item.height > 0) ? item.height : 100 + var aspect = w0 / h0 + + var wrapper = { win: item.win, aspect: aspect } + + // Check overflow: (SumAspects * MaxH) + Gaps > Width + var hypotheticalWidth = (currentAspectSum + aspect) * localMaxH + (currentRow.length * gap) + + if (currentRow.length > 0 && hypotheticalWidth > useW) { + rows.push({ items: currentRow, aspectSum: currentAspectSum }) + currentRow = [] + currentAspectSum = 0 + } + + currentRow.push(wrapper) + currentAspectSum += aspect + } + if (currentRow.length > 0) { + rows.push({ items: currentRow, aspectSum: currentAspectSum }) + } + + // SCALE & FIT ROWS + // Calculate how tall the content actually is + var totalContentH = 0 + var finalRows = [] + + for (var r = 0; r < rows.length; r++) { + var rowObj = rows[r] + var rItems = rowObj.items + + // Optimal Height = (Available Width / Sum Aspects) + var availRowW = useW - (gap * (rItems.length - 1)) + var optimalH = availRowW / rowObj.aspectSum + + // Clamp to limits + if (optimalH > localMaxH) optimalH = localMaxH + + finalRows.push({ items: rItems, h: optimalH }) + totalContentH += optimalH + } + + // Add vertical gaps between rows inside the band + if (finalRows.length > 1) { + totalContentH += gap * (finalRows.length - 1) + } + + // If rows overflow the band height (rare, but possible with many windows), scale down + var scaleFactor = 1.0 + if (totalContentH > bandHeight) { + scaleFactor = bandHeight / totalContentH + totalContentH = bandHeight // Cap for centering math + } + + // GENERATE COORDINATES + // Center the content vertically within the band slot + // Note: If bandCount=1, bandHeight is huge (90% screen), but totalContentH is constrained by absoluteMaxH. + // This ensures the single row floats nicely in the middle. + var rowY = currentY + (bandHeight - totalContentH) / 2 + + for (var r2 = 0; r2 < finalRows.length; r2++) { + var fRow = finalRows[r2] + var rHeight = fRow.h * scaleFactor + var rItems2 = fRow.items + + // Calculate row width for horizontal centering + var actualRowW = 0 + for (var j = 0; j < rItems2.length; j++) { + actualRowW += (rItems2[j].aspect * rHeight) + } + actualRowW += gap * (rItems2.length - 1) + + var rowX = offX + (useW - actualRowW) / 2 + + for (var j2 = 0; j2 < rItems2.length; j2++) { + var it = rItems2[j2] + var finalW = it.aspect * rHeight + + result.push({ + win: it.win, + x: rowX, + y: rowY, + width: finalW, + height: rHeight + }) + + rowX += finalW + gap + } + + rowY += rHeight + (gap * scaleFactor) + } + + // Advance Y to the next band slot + currentY += bandHeight + gap + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/ColumnarLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/ColumnarLayout.qml new file mode 100644 index 00000000..feaa9afa --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/ColumnarLayout.qml @@ -0,0 +1,72 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 0) return [] + + var gap = Math.max(8, outerWidth * 0.005) + + // Safe Area: We use slightly more width here (95%) + // as vertical strips look better when filling the screen horizontally. + var useW = outerWidth * 0.95 + var useH = outerHeight * 0.90 + var offX = (outerWidth - useW) / 2 + var offY = (outerHeight - useH) / 2 + + // Calculate width of a single column + var colW = (useW - (gap * (N - 1))) / N + + // Safety: If columns become too narrow (e.g. < 200px), + // we clamp the width to keep them readable. + var minColW = 200 + if (colW < minColW) colW = minColW + + // Calculate the actual total width used + var totalW = N * colW + (N - 1) * gap + + // Center the group horizontally. + // If N is small, it centers. If N is large (clamped), it starts from left. + var startX = offX + if (totalW < useW) { + startX = offX + (useW - totalW) / 2 + } + + var result = [] + + for (var i = 0; i < N; i++) { + var item = windowList[i] + + var w0 = (item.width > 0) ? item.width : 100 + var h0 = (item.height > 0) ? item.height : 100 + + // In this layout, vertical space is abundant (useH). + // The constraining factor is usually the column width. + var sc = Math.min(colW / w0, useH / h0) + + var thumbW = w0 * sc + var thumbH = h0 * sc + + var xPos = startX + i * (colW + gap) + + // Center horizontally within the strip + var xCentered = xPos + (colW - thumbW) / 2 + + // Center vertically on screen + var yCentered = offY + (useH - thumbH) / 2 + + result.push({ + win: item.win, + x: xCentered, + y: yCentered, + width: thumbW, + height: thumbH + }) + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/HeroLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/HeroLayout.qml new file mode 100644 index 00000000..e45ecf2c --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/HeroLayout.qml @@ -0,0 +1,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 + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/JustifiedLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/JustifiedLayout.qml new file mode 100644 index 00000000..655147b5 --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/JustifiedLayout.qml @@ -0,0 +1,149 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 0) + return [] + + var containerWidth = outerWidth * 0.9 + var containerHeight = outerHeight * 0.9 + + // 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)) + + var maxThumbHeight = outerHeight * 0.3 + + if (containerWidth <= 0 || containerHeight <= 0) { + return windowList.map(function(item) { + return { + win: item.win, + x: 0, + y: 0, + width: 0, + height: 0 + } + }) + } + + var targetRowH = maxThumbHeight + var rows = [] + var currentRow = [] + var sumAspect = 0 + + function flushRow() { + if (currentRow.length === 0) + return + + var n = currentRow.length + var rowHeight = maxThumbHeight + if (sumAspect > 0) { + var totalGapWidth = gap * (n - 1) + var hFit = (containerWidth - totalGapWidth) / sumAspect + if (hFit < rowHeight) + rowHeight = hFit + } + + if (rowHeight > maxThumbHeight) + rowHeight = maxThumbHeight + if (rowHeight <= 0) + rowHeight = 1 + + rows.push({ + items: currentRow.slice(), + height: rowHeight, + sumAspect: sumAspect + }) + + currentRow = [] + sumAspect = 0 + } + + for (var i = 0; i < N; ++i) { + var item = windowList[i] + var w0 = item.width > 0 ? item.width : 1 + var h0 = item.height > 0 ? item.height : 1 + var a = w0 / h0 + item.aspect = a + + if (currentRow.length > 0 && + ((sumAspect + a) * targetRowH + gap * currentRow.length) > containerWidth) { + flushRow() + } + + currentRow.push(item) + sumAspect += a + } + + if (currentRow.length > 0) { + flushRow() + } + + var totalRawHeight = 0 + for (var r = 0; r < rows.length; ++r) { + totalRawHeight += rows[r].height + } + if (rows.length > 1) { + totalRawHeight += gap * (rows.length - 1) + } + + var sV = 1.0 + var availH = containerHeight + if (totalRawHeight > 0 && totalRawHeight > availH) { + sV = availH / totalRawHeight + } + if (sV <= 0) + sV = 0.1 + if (sV > 1.0) + sV = 1.0 + + var gridTotalHeightScaled = totalRawHeight * sV + var yAcc = (outerHeight - gridTotalHeightScaled) / 2 + if (!isFinite(yAcc) || yAcc < 0) + yAcc = 0 + + var result = [] + + for (var r2 = 0; r2 < rows.length; ++r2) { + var row = rows[r2] + var rowHeightScaled = row.height * sV + + var rowWidthNoGapsScaled = 0 + for (var j = 0; j < row.items.length; ++j) { + rowWidthNoGapsScaled += row.items[j].aspect * rowHeightScaled + } + var totalRowWidthScaled = rowWidthNoGapsScaled + gap * (row.items.length - 1) + + var xAcc = (outerWidth - totalRowWidthScaled) / 2 + if (!isFinite(xAcc)) + xAcc = 0 + + for (var j2 = 0; j2 < row.items.length; ++j2) { + var it2 = row.items[j2] + var wScaled = it2.aspect * rowHeightScaled + var hScaled = rowHeightScaled + + result.push({ + win: it2.win, + x: xAcc, + y: yAcc, + width: wScaled, + height: hScaled + }) + + xAcc += wScaled + gap + } + + yAcc += rowHeightScaled + if (r2 < rows.length - 1) { + yAcc += gap * sV + } + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/LayoutsManager.qml b/config/quickshell/qs-hyprview-trixie/layouts/LayoutsManager.qml new file mode 100644 index 00000000..8db0672d --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/LayoutsManager.qml @@ -0,0 +1,47 @@ +pragma Singleton +import Quickshell +import "." + +Singleton { + id: root + + function doLayout( layoutAlgorithm, windowList, width, height) { + var doLayout = null + switch (layoutAlgorithm) { + case 'smartgrid': + doLayout = SmartGridLayout.doLayout + break + case 'justified': + doLayout = JustifiedLayout.doLayout + break + case 'bands': + doLayout = BandsLayout.doLayout + break + case 'masonry': + doLayout = MasonryLayout.doLayout + break + case 'hero': + doLayout = HeroLayout.doLayout + break + case 'spiral': + doLayout = SpiralLayout.doLayout + break + case 'satellite': + doLayout = SatelliteLayout.doLayout + break + case 'staggered': + doLayout = StarggeredLayout.doLayout + break + case 'columnar': + doLayout = ColumnarLayout.doLayout + break + case 'vortex': + doLayout = VortexLayout.doLayout + break + default: + doLayout = SmartGridLayout.doLayout + } + + return doLayout( windowList, width, height) + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/MasonryLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/MasonryLayout.qml new file mode 100644 index 00000000..73192f17 --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/MasonryLayout.qml @@ -0,0 +1,139 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 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)) + + // Safe Area (90%) + // Define the bounding box for the content. + var contentScale = 0.90 + var useW = outerWidth * contentScale + var useH = outerHeight * contentScale + + // Find Best Column Count + // Standard logic: try to fit content in 1 col, then 2, etc. + var bestCols = N + + for (var cols = 1; cols <= N; cols++) { + var tryColWidth = (useW - (cols - 1) * gap) / cols + var tryColHeights = new Array(cols).fill(0) + + for (var i = 0; i < N; i++) { + var item = windowList[i] + var minH = Math.min.apply(null, tryColHeights) + var colIdx = tryColHeights.indexOf(minH) + + var w0 = (item.width && item.width > 0) ? item.width : 100 + var h0 = (item.height && item.height > 0) ? item.height : 100 + var scale = tryColWidth / w0 + + tryColHeights[colIdx] += (h0 * scale) + gap + } + + var currentMaxH = Math.max.apply(null, tryColHeights) + if (currentMaxH > 0) currentMaxH -= gap + + // If it fits vertically, we stop. + if (currentMaxH <= useH) { + bestCols = cols + break + } + } + + // Rigorous clamping + // We have chosen 'bestCols'. Now we calculate the theoretical column width. + // BUT, if N is small (e.g. 1), this width might produce a height > useH. + // We must calculate a "Global Downscale Factor" to ensure NO item exceeds useH. + + var rawColWidth = (useW - (bestCols - 1) * gap) / bestCols + var maxOverflowRatio = 1.0 // 1.0 means "fits perfectly" + + // Simulate again to find the worst offender (tallest item/column relative to screen) + // Note: In masonry, we care about the total column height, not just single item. + var clampHeights = new Array(bestCols).fill(0) + + for (var j = 0; j < N; j++) { + var it = windowList[j] + + // Standard masonry placement logic + var mH = Math.min.apply(null, clampHeights) + var cId = clampHeights.indexOf(mH) + + var wRaw = (it.width && it.width > 0) ? it.width : 100 + var hRaw = (it.height && it.height > 0) ? it.height : 100 + var sc = rawColWidth / wRaw + + clampHeights[cId] += (hRaw * sc) + gap + } + + // Find the tallest column produced by the raw width + var tallestCol = Math.max.apply(null, clampHeights) + if (tallestCol > 0) tallestCol -= gap + + // If the tallest column is taller than Safe Area, calculate reduction factor + if (tallestCol > useH) { + maxOverflowRatio = useH / tallestCol + } + + // Apply the reduction factor to the column width. + var finalColWidth = rawColWidth * maxOverflowRatio + + // Re-centering x + var finalGridW = (finalColWidth * bestCols) + (gap * (bestCols - 1)) + var finalOffX = (outerWidth - finalGridW) / 2 + + + // Final rendering + var colHeights = new Array(bestCols).fill(0) + var result = [] + + for (var k = 0; k < N; k++) { + var itemK = windowList[k] + + // 1. Find shortest column + var minH = Math.min.apply(null, colHeights) + var cIdx = colHeights.indexOf(minH) + + // 2. Dimensions + var wOrig = (itemK.width && itemK.width > 0) ? itemK.width : 100 + var hOrig = (itemK.height && itemK.height > 0) ? itemK.height : 100 + var s = finalColWidth / wOrig + var tH = hOrig * s + + // 3. Position (using Recalculated OffX) + var xPos = finalOffX + cIdx * (finalColWidth + gap) + var yPos = colHeights[cIdx] + + result.push({ + win: itemK.win, + x: xPos, + y: yPos, + width: finalColWidth, + height: tH, + colIndex: cIdx + }) + + colHeights[cIdx] += tH + gap + } + + // Vertical centering + var realGridH = Math.max.apply(null, colHeights) + if (realGridH > 0) realGridH -= gap + + var finalOffY = (outerHeight - realGridH) / 2 + + for (var m = 0; m < result.length; m++) { + result[m].y += finalOffY + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/SatelliteLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/SatelliteLayout.qml new file mode 100644 index 00000000..2efb703b --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/SatelliteLayout.qml @@ -0,0 +1,98 @@ +pragma Singleton +import Quickshell +import Quickshell.Hyprland + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 0) return [] + + // Move active window to the start of the list (Center Item) + 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 useW = outerWidth * 0.90 + var useH = outerHeight * 0.90 + var offX = (outerWidth - useW) / 2 + var offY = (outerHeight - useH) / 2 + + var result = [] + + // Center item (hero) + var centerItem = windowList[0] + + // The center item takes up roughly 35% of the screen dimensions + var centerW = useW * 0.35 + var centerH = useH * 0.35 + + // Aspect Fit for the center item + var w0 = (centerItem.width > 0) ? centerItem.width : 100 + var h0 = (centerItem.height > 0) ? centerItem.height : 100 + var sc0 = Math.min(centerW / w0, centerH / h0) + var finalCenterW = w0 * sc0 + var finalCenterH = h0 * sc0 + + result.push({ + win: centerItem.win, + x: offX + (useW - finalCenterW) / 2, + y: offY + (useH - finalCenterH) / 2, + width: finalCenterW, + height: finalCenterH, + isSatellite: false + }) + + // Orbit items (satellites) + var satellites = windowList.slice(1) + var numSat = satellites.length + + if (numSat > 0) { + // Orbit Radius (distance from center) + var radiusX = useW * 0.4 + var radiusY = useH * 0.4 + + // Max size for satellites. + // As the number of satellites increases, we shrink them to avoid overlap. + var maxSatW = (useW * 0.25) / Math.max(1, (numSat / 6)) + var maxSatH = (useH * 0.25) / Math.max(1, (numSat / 6)) + + // Start angle (-90 degrees = Top) + var startAngle = -Math.PI / 2 + var stepAngle = (2 * Math.PI) / numSat + + for (var i = 0; i < numSat; i++) { + var item = satellites[i] + var angle = startAngle + (i * stepAngle) + + // Calculate satellite center coordinates + var cx = (useW / 2) + radiusX * Math.cos(angle) + var cy = (useH / 2) + radiusY * Math.sin(angle) + + // Aspect Fit satellite + var ws = (item.width > 0) ? item.width : 100 + var hs = (item.height > 0) ? item.height : 100 + var scS = Math.min(maxSatW / ws, maxSatH / hs) + var finalSatW = ws * scS + var finalSatH = hs * scS + + result.push({ + win: item.win, + x: offX + cx - (finalSatW / 2), + y: offY + cy - (finalSatH / 2), + width: finalSatW, + height: finalSatH, + isSatellite: true + }) + } + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/SmartGridLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/SmartGridLayout.qml new file mode 100644 index 00000000..6207d19c --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/SmartGridLayout.qml @@ -0,0 +1,134 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 0) return [] + if (outerWidth <= 0 || outerHeight <= 0) return [] + + var gap = Math.min(outerWidth * 0.03, outerHeight * 0.03) + + // --- 0. DEFINIZIONE AREA SICURA (SCALATA) --- + // Riduciamo l'area di calcolo al 90% per lasciare spazio alle animazioni hover + var contentScale = 0.9 + var usableW = outerWidth * contentScale + var usableH = outerHeight * contentScale + + // --- 1. TROVARE LA SCALA OTTIMALE --- + // Usiamo usableW/H per decidere la dimensione delle finestre + var TARGET_ASPECT = 16.0 / 9.0 + var bestCols = 1 + var bestRows = 1 + var bestScale = 0 + + for (var cols = 1; cols <= N; cols++) { + var rows = Math.ceil(N / cols) + + // Calcoliamo lo spazio basandoci sull'area ridotta + var availW = usableW - gap * (cols - 1) + var availH = usableH - gap * (rows - 1) + + if (availW <= 0 || availH <= 0) continue + + var cellW = availW / cols + var cellH = availH / rows + + var scaleW = cellW / TARGET_ASPECT + var scaleH = cellH / 1.0 + var currentScale = Math.min(scaleW, scaleH) + + if (currentScale > bestScale) { + bestScale = currentScale + bestCols = cols + bestRows = rows + } + } + + // --- 2. CALCOLO DIMENSIONI REALI --- + + // Ricalcoliamo i limiti cella basati sull'area ridotta + var finalAvailW = usableW - gap * (bestCols - 1) + var finalAvailH = usableH - gap * (bestRows - 1) + var maxCellW = finalAvailW / bestCols + var maxCellH = finalAvailH / bestRows + + // --- 3. POSIZIONAMENTO (CENTRATO NELL'AREA TOTALE) --- + + // Calcoliamo l'altezza totale del blocco di contenuto + var totalGridContentH = bestRows * maxCellH + (bestRows - 1) * gap + + // Per centrare verticalmente, usiamo l'outerHeight REALE (al 100%) + // In questo modo il blocco ridotto (90%) finisce esattamente al centro dello schermo fisico + var startOffsetY = (outerHeight - totalGridContentH) / 2 + + var result = [] + + // Iteriamo per RIGA + for (var r = 0; r < bestRows; r++) { + var rowItems = [] + var startIndex = r * bestCols + var endIndex = Math.min(startIndex + bestCols, N) + + if (startIndex >= N) break + + var totalRowContentWidth = 0 + + // Fase 3a: Calcolo dimensioni miniature (Packed) + for (var i = startIndex; i < endIndex; i++) { + var item = windowList[i] + var w0 = (item.width && item.width > 0) ? item.width : 100 + var h0 = (item.height && item.height > 0) ? item.height : 100 + + // Scala calcolata sui limiti "sicuri" (90%) + var scale = Math.min(maxCellW / w0, maxCellH / h0) + + var thumbW = w0 * scale + var thumbH = h0 * scale + + rowItems.push({ + originalItem: item, + width: thumbW, + height: thumbH, + index: i, + col: i - startIndex + }) + + totalRowContentWidth += thumbW + } + + // Aggiungiamo i gap totali della riga + if (rowItems.length > 1) { + totalRowContentWidth += (rowItems.length - 1) * gap + } + + // Fase 3b: Posizionamento X + // Anche qui, usiamo outerWidth REALE per centrare il blocco riga nello schermo intero + var currentX = (outerWidth - totalRowContentWidth) / 2 + var cellAbsY = startOffsetY + r * (maxCellH + gap) + + for (var k = 0; k < rowItems.length; k++) { + var rItem = rowItems[k] + + // Centratura verticale nella fascia + var currentY = cellAbsY + (maxCellH - rItem.height) / 2 + + result.push({ + win: rItem.originalItem.win, + x: currentX, + y: currentY, + width: rItem.width, + height: rItem.height, + rowIndex: r, + colIndex: rItem.col + }) + + currentX += rItem.width + gap + } + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/SpiralLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/SpiralLayout.qml new file mode 100644 index 00000000..bcfc7abb --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/SpiralLayout.qml @@ -0,0 +1,155 @@ +pragma Singleton +import Quickshell +import Quickshell.Hyprland + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight, maxSplits) { + var N = windowList.length + if (N === 0) return [] + + if (maxSplits === undefined) maxSplits = 3 + + // Standard Gap: 0.8% of screen + var rawGap = outerWidth * 0.008 + var gap = Math.max(8, Math.min(24, rawGap)) + + // Primary Gap: The space between the first Big Window and the rest. + // We make it 3x larger than the standard gap for emphasis. + var primaryGap = gap * 3 + + // Safe Area (90%) + var contentScale = 0.90 + var useW = outerWidth * contentScale + var useH = outerHeight * contentScale + var offX = (outerWidth - useW) / 2 + var offY = (outerHeight - useH) / 2 + + // Move Active Window to start + 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])] + } + } + + var result = [] + + // Working area cursor + var curX = offX + var curY = offY + var curW = useW + var curH = useH + + // Items to process in Spiral mode + var spiralCount = Math.min(N - 1, maxSplits) + + // Spiral cuts + for (var k = 0; k < spiralCount; k++) { + var sItem = windowList[k] + var sBoxW, sBoxH + var sBoxX = curX + var sBoxY = curY + + // Logic change: Use 'primaryGap' only for the very first cut (k=0), + // otherwise use standard 'gap'. + var currentGap = (k === 0) ? primaryGap : gap + + if (curW > curH) { // Split Vertical + // Calculate width subtracting the specific gap for this iteration + sBoxW = (curW - currentGap) / 2 + sBoxH = curH + + // Shift working area for next items by the specific gap + curX += sBoxW + currentGap + curW -= (sBoxW + currentGap) + } else { // Split Horizontal + sBoxW = curW + sBoxH = (curH - currentGap) / 2 + + // Shift working area for next items by the specific gap + curY += sBoxH + currentGap + curH -= (sBoxH + currentGap) + } + + // Aspect Fit + var sw0 = (sItem.width > 0) ? sItem.width : 100 + var sh0 = (sItem.height > 0) ? sItem.height : 100 + var sScale = Math.min(sBoxW / sw0, sBoxH / sh0) + + result.push({ + win: sItem.win, + x: sBoxX + (sBoxW - (sw0 * sScale))/2, + y: sBoxY + (sBoxH - (sh0 * sScale))/2, + width: sw0 * sScale, + height: sh0 * sScale, + isSpiral: true, + index: k + }) + } + + // Overflow grid + var remainingItems = windowList.slice(spiralCount) + var remN = remainingItems.length + + if (remN > 0) { + // Standard Grid logic for the remaining box + var bestCols = 1 + var bestScale = 0 + var TARGET_ASPECT = 16.0/9.0 + + for (var c = 1; c <= remN; c++) { + var r = Math.ceil(remN / c) + var avW = curW - gap * (c - 1) + var avH = curH - gap * (r - 1) + if (avW <= 0 || avH <= 0) continue + + var cW = avW / c + var cH = avH / r + var sc = Math.min(cW / TARGET_ASPECT, cH) + + if (sc > bestScale) { + bestScale = sc + bestCols = c + } + } + + var remRows = Math.ceil(remN / bestCols) + var finalCellW = (curW - gap * (bestCols - 1)) / bestCols + var finalCellH = (curH - gap * (remRows - 1)) / remRows + + var gridContentH = remRows * finalCellH + (remRows - 1) * gap + var gridStartY = curY + (curH - gridContentH) / 2 + + for (var j = 0; j < remN; j++) { + var rItem = remainingItems[j] + var row = Math.floor(j / bestCols) + var col = j % bestCols + + var itemsInRow = Math.min((row + 1) * bestCols, remN) - (row * bestCols) + var rowW = itemsInRow * finalCellW + (itemsInRow - 1) * gap + var rowStartX = curX + (curW - rowW) / 2 + + var cellX = rowStartX + col * (finalCellW + gap) + var cellY = gridStartY + row * (finalCellH + gap) + + var rw0 = (rItem.width > 0) ? rItem.width : 100 + var rh0 = (rItem.height > 0) ? rItem.height : 100 + var rSc = Math.min(finalCellW / rw0, finalCellH / rh0) + + result.push({ + win: rItem.win, + x: cellX + (finalCellW - (rw0 * rSc))/2, + y: cellY + (finalCellH - (rh0 * rSc))/2, + width: rw0 * rSc, + height: rh0 * rSc, + isSpiral: false + }) + } + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/StarggeredLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/StarggeredLayout.qml new file mode 100644 index 00000000..6a9deb3a --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/StarggeredLayout.qml @@ -0,0 +1,64 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 0) return [] + + var gap = Math.max(10, outerWidth * 0.01) + + // Safe Area + var useW = outerWidth * 0.9 + var useH = outerHeight * 0.9 + var offX = (outerWidth - useW) / 2 + var offY = (outerHeight - useH) / 2 + + // Heuristic: roughly sqrt(N), but slightly weighted towards columns + // to accommodate 16:9 screens better. + var cols = Math.ceil(Math.sqrt(N * 1.5)) + var rows = Math.ceil(N / cols) + + // Calculate cell width. + // Note: In a staggered layout, the effective width needed is (cols + 0.5) + // because alternate rows are shifted by half a cell. + var cellW = (useW - (cols * gap)) / (cols + 0.5) + var cellH = (useH - (rows * gap)) / rows + + // Vertical centering of the whole block + var contentH = rows * cellH + (rows - 1) * gap + var startY = offY + (useH - contentH) / 2 + + var result = [] + + for (var i = 0; i < N; i++) { + var item = windowList[i] + + var r = Math.floor(i / cols) + var c = i % cols + + // Stagger offset: if row is odd, shift right by half cell width + var staggerOffset = (r % 2 === 1) ? (cellW / 2) : 0 + + var cellX = staggerOffset + c * (cellW + gap) + var cellY = r * (cellH + gap) + + // Aspect Fit + var w0 = (item.width > 0) ? item.width : 100 + var h0 = (item.height > 0) ? item.height : 100 + var sc = Math.min(cellW / w0, cellH / h0) + + // Center the thumbnail inside the calculated cell + result.push({ + win: item.win, + x: offX + cellX + (cellW - w0 * sc)/2, + y: startY + cellY + (cellH - h0 * sc)/2, + width: w0 * sc, + height: h0 * sc + }) + } + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/VortexLayout.qml b/config/quickshell/qs-hyprview-trixie/layouts/VortexLayout.qml new file mode 100644 index 00000000..6d5fef37 --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/VortexLayout.qml @@ -0,0 +1,82 @@ +pragma Singleton +import Quickshell + +Singleton { + id: root + + function doLayout(windowList, outerWidth, outerHeight) { + var N = windowList.length + if (N === 0) return [] + + // Safe Area (90%) + var contentScale = 0.90 + var useW = outerWidth * contentScale + var useH = outerHeight * contentScale + var offX = (outerWidth - useW) / 2 + var offY = (outerHeight - useH) / 2 + + var centerX = offX + useW / 2 + var centerY = offY + useH / 2 + + // Maximum radius (distance from center to the furthest edge of safe area) + var maxRadius = Math.min(useW, useH) / 2 + + var result = [] + + // --- THE VORTEX CONFIGURATION --- + + var goldenAngle = Math.PI * (3 - Math.sqrt(5)) + + // PARAMETER TWEAK 1: from 0.3 to 0.5 to keep distant windows readable + var minScale = 0.4 + + // PARAMETER TWEAK 2: from 0.4 to 0.6 (60% of screen height) + var baseSizeFactor = 0.5 + + for (var i = 0; i < N; i++) { + var item = windowList[i] + + var t = i / Math.max(1, N - 1) + if (N === 1) t = 0 + + // PARAMETER TWEAK 3: from 0.9 instead of 0.8 to accommodate larger thumbs + var currentRadius = (maxRadius * 0.85) * Math.sqrt(t) + var currentAngle = i * goldenAngle + var scale = 1.0 - (t * (1.0 - minScale)) + var tilt = (Math.cos(currentAngle) * 8) + + // Coordinates (Polar to Cartesian) + var cx = centerX + currentRadius * Math.cos(currentAngle) + var cy = centerY + currentRadius * Math.sin(currentAngle) + + // Dimensions (Aspect Fit) + var w0 = (item.width > 0) ? item.width : 100 + var h0 = (item.height > 0) ? item.height : 100 + + var baseBoxSize = Math.min(useW, useH) * baseSizeFactor + + var aspect = w0 / h0 + var thumbW, thumbH + + if (aspect > 1) { + thumbW = baseBoxSize * scale + thumbH = thumbW / aspect + } else { + thumbH = baseBoxSize * scale + thumbW = thumbH * aspect + } + + result.push({ + win: item.win, + x: cx - (thumbW / 2), + y: cy - (thumbH / 2), + width: thumbW, + height: thumbH, + rotation: tilt, + zIndex: N - i + }) + } + + return result + } +} diff --git a/config/quickshell/qs-hyprview-trixie/layouts/qmldir b/config/quickshell/qs-hyprview-trixie/layouts/qmldir new file mode 100644 index 00000000..653b90ca --- /dev/null +++ b/config/quickshell/qs-hyprview-trixie/layouts/qmldir @@ -0,0 +1,12 @@ +singleton HeroLayout 1.0 HeroLayout.qml +singleton JustifiedLayout 1.0 JustifiedLayout.qml +singleton MasonryLayout 1.0 MasonryLayout.qml +singleton SmartGridLayout 1.0 SmartGridLayout.qml +singleton SpiralLayout 1.0 SpiralLayout.qml +singleton BandsLayout 1.0 BandsLayout.qml +singleton SatelliteLayout 1.0 SatelliteLayout.qml +singleton StarggeredLayout 1.0 StarggeredLayout.qml +singleton ColumnarLayout 1.0 ColumnarLayout.qml +singleton VortexLayout 1.0 VortexLayout.qml + +singleton LayoutsManager 1.0 LayoutsManager.qml |
