aboutsummaryrefslogtreecommitdiffstats
path: root/window-switcher
diff options
context:
space:
mode:
Diffstat (limited to 'window-switcher')
-rw-r--r--window-switcher/Switcher.qml108
1 files changed, 100 insertions, 8 deletions
diff --git a/window-switcher/Switcher.qml b/window-switcher/Switcher.qml
index d989e57..4d01b90 100644
--- a/window-switcher/Switcher.qml
+++ b/window-switcher/Switcher.qml
@@ -25,6 +25,10 @@ Scope {
function toggle() { root.open = !root.open; }
function close() { root.open = false; }
+ // Hyprland.toplevels reads empty until it is asked, and a window opened
+ // since the last showing would otherwise be missing from the grid.
+ onOpenChanged: if (open) Windows.refresh();
+
// A config with no visible window exits, reporting nothing. This 1x1
// click-through window is what holds the shell open while the overlay
// is hidden, which is almost always.
@@ -54,14 +58,41 @@ Scope {
// Keys reach a focused item, not a window: Keys.onEscapePressed on
// the PanelWindow above never fires. This item is what listens.
Item {
+ id: keyHandler
anchors.fill: parent
focus: true
- Keys.onEscapePressed: {
- console.log("SWITCHER escape");
- root.close();
+ // 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) {
+ Windows.focus(list[selectedIndex]);
+ root.close();
+ }
+ }
+
+ 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
color: Qt.rgba(Theme.base.r, Theme.base.g, Theme.base.b, 0.82)
@@ -71,12 +102,73 @@ Scope {
onClicked: root.close()
}
- Text {
+ // Nothing to switch to. A full-screen dim with nothing in
+ // it reads as a hang, so it says so instead.
+ Column {
+ anchors.centerIn: parent
+ spacing: 14
+ visible: Windows.windows.length === 0
+
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "Nothing here but hopes and dreams..."
+ color: Theme.text
+ font.family: Theme.fontFamily
+ font.pixelSize: 26
+ }
+ Text {
+ anchors.horizontalCenter: parent.horizontalCenter
+ text: "go create something beautiful!"
+ color: Theme.overlay
+ font.family: Theme.fontFamily
+ font.pixelSize: 32
+ }
+ }
+
+ Grid {
+ id: grid
anchors.centerIn: parent
- text: "press Escape"
- color: Theme.text
- font.family: Theme.fontFamily
- font.pixelSize: 28
+ 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 cardWidth: Math.min(
+ maxCardWidth,
+ Math.floor((parent.width * 0.92 - (columns - 1) * gap) / columns))
+
+ spacing: gap
+
+ Repeater {
+ model: Windows.windows
+
+ WindowCard {
+ required property int index
+ required property var modelData
+
+ toplevel: modelData
+ cardWidth: grid.cardWidth
+ selected: index === keyHandler.selectedIndex
+
+ onActivated: {
+ keyHandler.selectedIndex = index;
+ keyHandler.commit();
+ }
+ // Closing is a side errand, so the overlay
+ // stays open: a dismissal would mean reopening
+ // to close a second window. The card goes when
+ // Hyprland says it went.
+ onCloseRequested: Windows.closeWindow(modelData)
+ }
+ }
}
}
}