diff --git a/settings/quickshell.nix b/settings/quickshell.nix index 9cacbf7..aa22396 100644 --- a/settings/quickshell.nix +++ b/settings/quickshell.nix @@ -230,10 +230,21 @@ in function score(name, extra, q) { let n = name.toLowerCase(); - if (n.startsWith(q)) return 3; - if (n.includes(" " + q)) return 2; - if (n.includes(q)) return 1; - if (extra && extra.toLowerCase().includes(q)) return 1; + if (n.startsWith(q)) return 5; + if (n.includes(" " + q)) return 4; + if (n.includes(q)) return 3; + if (extra && extra.toLowerCase().includes(q)) return 2; + // Fuzzy: q as an in-order subsequence of n (vktop → + // vesktop); fewer skipped characters scores higher. + let qi = 0, gaps = 0, last = -1; + for (let i = 0; i < n.length && qi < q.length; i++) { + if (n[i] === q[qi]) { + if (last >= 0) gaps += i - last - 1; + last = i; + qi++; + } + } + if (qi === q.length) return 1 / (1 + gaps); return 0; } @@ -575,8 +586,12 @@ in if (dd.visible && !dd.closing) { dd.animateClose(); } else { - if (activeDropdown && activeDropdown !== dd && activeDropdown.visible) { - activeDropdown.animateClose(); + // Retarget the chrome before closing the previous + // dropdown so it morphs instead of dipping closed. + const prev = activeDropdown; + activeDropdown = dd; + if (prev && prev !== dd && prev.visible) { + prev.animateClose(); } if (setupFn) setupFn(); if (dd.closing) { @@ -584,7 +599,6 @@ in } else { dd.visible = true; } - activeDropdown = dd; } } @@ -1080,6 +1094,11 @@ in if (!visible || closing) return; closing = true; open = false; + // Collapse the chrome immediately so the content fade + // and the panel animation run together (when switching + // dropdowns, toggleDropdown retargets activeDropdown + // first, so this doesn't fire and the chrome morphs). + if (bar.activeDropdown === dropdown) bar.activeDropdown = null; _autoClose.stop(); _closeDelay.start(); } @@ -1126,7 +1145,7 @@ in Timer { id: _closeDelay - interval: 230 + interval: 300 onTriggered: { dropdown.visible = false; dropdown.closing = false; if (bar.activeDropdown === dropdown) bar.activeDropdown = null; } } @@ -1137,23 +1156,26 @@ in } } - // Content clipped to the chrome's animated height so it - // reveals/hides with the morph; fades on open/close. + // Content is clipped to the chrome's ANIMATED geometry — + // revealed as the panel slides/grows over it and wiped as + // the panel leaves, instead of popping in place. The inner + // item counter-offsets so content stays put while the clip + // window moves across it. Item { id: _dropdownRect - anchors.left: parent.left - anchors.leftMargin: 8 - anchors.top: parent.top - width: dropdown.fullWidth + x: (chrome.x + 8) - dropdown.x + y: 0 + width: Math.max(0, chrome.width - (chrome.flushRight ? 8 : 16)) height: Math.min(dropdown.fullHeight, chrome.height) clip: true opacity: dropdown.open ? 1 : 0 Behavior on opacity { - NumberAnimation { duration: 150; easing.type: Easing.OutCubic } + NumberAnimation { duration: 200; easing.type: Easing.OutCubic } } Item { id: dropdownContent + x: 8 - _dropdownRect.x width: dropdown.fullWidth height: dropdown.fullHeight }