Files
omarchy/shell/plugins/panels/elsewhen/CitySearch.qml
T
David Heinemeier HanssonandClaude Opus 5.5 773e19e145 Drive Elsewhen from the keyboard and summon it with Super+Ctrl+Alt+E (#13445)
* Navigate Elsewhen from the keyboard and summon it with Super+Ctrl+Alt+E

Up and down walk home and the cities, lighting the picked row and turning
the globe to it. Left and right move the clocks an hour, held until Escape
or close. Escape now clears a shifted time before leaving the globe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Reach Add a city from the keyboard and shift time past a day

Down past the last city selects Add a city, where Return or Space opens the
search. The arrow keys no longer stop at twelve hours, and shifts past a day
read in days.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Delete the picked Elsewhen city from the keyboard

Delete (or x) removes the picked city and moves the cursor to the one that
takes its place. The picked row shows its × so the target is plain. Delete
now reaches every panel's deleteRequested, as it already does in the
clipboard and the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Center bar glyphs on the open-panel underline

The 16px icon canvas in the 27px slot was snapped from 5.5 to 6, and the
glyph's fractional centering correction was snapped too, so glyphs sat up to
0.9px right of the underline, which centers on the slot. Both are now placed
exactly, and the geometry test measures against the slot to within 0.05px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:05:41 +02:00

102 lines
3.2 KiB
QML

import QtQuick
import qs.Commons
import qs.Ui
import "Model.js" as Model
// A button that opens into a city search: a field, then the matching cities
// with their zone and offset. Arrows move the selection, Return picks it,
// Escape backs out. Inline rather than a dropdown so the results grow the
// panel instead of running off the bottom of the screen.
Column {
id: search
// [{ label, value }] from Model.zoneOptions.
property var options: []
property int limit: 6
// Zone id -> offset text; read inside a binding, so its dependencies track.
property var offsetLabel: function(zoneId) { return "" }
property bool loading: false
property string buttonText: "+ Add a city"
property string placeholderText: "Search cities\u2026"
property string loadingText: "Loading zones\u2026"
property real fontSize: Style.font.bodySmall
property color foreground: Color.foreground
property color dim: Qt.darker(foreground, 1.55)
property color fainter: Qt.darker(foreground, 2.1)
property string fontFamily: Style.font.family
// False when the host shows CityMatches elsewhere.
property bool inlineResults: true
// The host's keyboard cursor is on the button.
property bool hasCursor: false
property bool active: false
readonly property string query: field.text
readonly property var matches: active ? Model.searchZones(options, query, limit) : []
// An index, not the match itself: the list is rebuilt on every keystroke.
property int selectedIndex: 0
signal picked(string label, string id)
signal dismissed()
onQueryChanged: selectedIndex = 0
onMatchesChanged: if (selectedIndex >= matches.length) selectedIndex = 0
function start() {
selectedIndex = 0
active = true
Qt.callLater(function() { field.text = ""; field.forceActiveFocus() })
}
// The hidden field would keep the keyboard, so the host takes it back on dismissed.
function stop() {
active = false
field.text = ""
dismissed()
}
function pick(match) {
picked(match.label, match.value)
stop()
}
function pickSelected() {
if (matches.length > 0) pick(matches[Util.clamp(selectedIndex, 0, matches.length - 1)])
}
spacing: Style.spacing.md
Button {
width: parent.width
// Matches the field, so opening the search does not shift the layout.
height: field.implicitHeight
visible: !search.active
text: search.buttonText
fontSize: search.fontSize
foreground: search.foreground
fontFamily: search.fontFamily
bordered: true
hasCursor: search.hasCursor
onClicked: search.start()
}
TextField {
id: field
visible: search.active
width: parent.width
placeholderText: search.placeholderText
foreground: search.foreground
Keys.onEscapePressed: search.stop()
Keys.onReturnPressed: search.pickSelected()
Keys.onEnterPressed: search.pickSelected()
Keys.onUpPressed: search.selectedIndex = Model.moveSelection(search.selectedIndex, -1, search.matches.length)
Keys.onDownPressed: search.selectedIndex = Model.moveSelection(search.selectedIndex, 1, search.matches.length)
}
CityMatches {
width: parent.width
visible: search.active && search.inlineResults
citySearch: search
}
}