diff options
Diffstat (limited to 'window-switcher/Switcher.qml')
| -rw-r--r-- | window-switcher/Switcher.qml | 95 |
1 files changed, 29 insertions, 66 deletions
diff --git a/window-switcher/Switcher.qml b/window-switcher/Switcher.qml index d907ab7..bd5c607 100644 --- a/window-switcher/Switcher.qml +++ b/window-switcher/Switcher.qml @@ -12,6 +12,7 @@ import Quickshell import Quickshell.Wayland import QtQuick +import "Expose.js" as Expose Scope { id: root @@ -80,39 +81,11 @@ Scope { // Keys reach a focused item, not a window: Keys.onEscapePressed on // the PanelWindow above never fires. This item is what listens. + // Escape is the only key: the expose is a pointer feature. Item { - id: keyHandler anchors.fill: parent focus: true - - // The LazyLoader destroys the overlay when it closes and - // builds a new one on the next opening, verified by a build - // counter: two openings, two constructions. So this starts at - // 0 every time on its own, and there is nothing to reset. A - // Component.onCompleted assigning 0 here would be a no-op. - property int selectedIndex: 0 - - function commit() { - const list = Windows.windows; - if (selectedIndex >= 0 && selectedIndex < list.length) - root.focusAfterClose(list[selectedIndex]); - } - - function move(delta) { - const n = Windows.windows.length; - if (n === 0) return; - selectedIndex = (selectedIndex + delta + n) % n; - } - Keys.onEscapePressed: root.close() - Keys.onReturnPressed: commit() - Keys.onEnterPressed: commit() - Keys.onLeftPressed: move(-1) - Keys.onRightPressed: move(1) - Keys.onUpPressed: move(-grid.columns) - Keys.onDownPressed: move(grid.columns) - Keys.onTabPressed: move(1) - Keys.onBacktabPressed: move(-1) Rectangle { anchors.fill: parent @@ -146,38 +119,26 @@ Scope { } } - Grid { - id: grid - anchors.centerIn: parent - visible: Windows.windows.length > 0 - - readonly property int count: Windows.windows.length - readonly property int gap: 30 - readonly property int maxCardWidth: 640 - - // Columns are the window count capped at six, so one - // card sits dead centre and two straddle the middle. - columns: Math.max(1, Math.min(count, 6)) - - // Cards grow to fill their row, up to a ceiling. - // Without the ceiling a lone preview becomes a - // full-screen mirror of the window it stands for. - readonly property int rows: Math.ceil(count / columns) - - // The three text lines and the spacing above them, - // which the box height has to leave room for. - readonly property int textHeight: 54 - - // Clamped by the height as well as the width. Without - // the second term a busy desktop pushes rows off the - // top and bottom, and a centred Grid has no way to - // scroll to them, so the cards shrink instead. - readonly property int cardWidth: Math.min( - maxCardWidth, - Math.floor((parent.width * 0.92 - (columns - 1) * gap) / columns), - Math.floor(((parent.height * 0.92 - (rows - 1) * gap) / rows - textHeight) * 16 / 10)) + // The scatter. Recomputed whenever the window list or a + // window's size changes, so closing one moves the rest. + Item { + id: scatter + anchors.fill: parent - spacing: gap + // Real sizes come from the raw hyprctl object. A window + // that has none yet stands in at 1280x800 until it does. + readonly property var rects: Expose.layout( + Windows.windows.map(t => { + const o = t.lastIpcObject; + const s = o && o.size; + return s ? { w: s[0], h: s[1] } : { w: 1280, h: 800 }; + }), width, height) + + // ponytail: a fade, not a flight from each window's real + // position; add the flight if the fade feels abrupt. + opacity: 0 + Component.onCompleted: opacity = 1 + Behavior on opacity { NumberAnimation { duration: 150 } } Repeater { model: Windows.windows @@ -186,14 +147,16 @@ Scope { required property int index required property var modelData + readonly property var rect: scatter.rects[index] + ?? { x: 0, y: 0, w: 0, h: 0 } + toplevel: modelData - cardWidth: grid.cardWidth - selected: index === keyHandler.selectedIndex + x: rect.x + y: rect.y + width: rect.w + height: rect.h - onActivated: { - keyHandler.selectedIndex = index; - keyHandler.commit(); - } + onActivated: root.focusAfterClose(modelData) // Closing is a side errand, so the overlay // stays open: a dismissal would mean reopening // to close a second window. The card goes when |
