Move Elsewhen into Omarchy as omarchy.elsewhen (#13429)
* Move Elsewhen into Omarchy as omarchy.elsewhen The world clock ships in the shell tree instead of its own package. A migration renames existing bar entries with their settings and removes the retired package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Remove Elsewhen's shelved Earth row, overlap band and sky tint Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tidy Elsewhen's models, data helper and docs, and drop currency Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Restructure Elsewhen's panel and globe on the shared shell components Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Show Elsewhen's bar globe upright Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Use the shared city search for Elsewhen's globe jump bar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep legacy Elsewhen entries single and retry offline geocodes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fetch Elsewhen's weather with curl instead of a Python helper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c7cef1e78
commit
e7218e36f2
37 files changed
+6780
-155
No files matched your search
@@ -0,0 +1,223 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Commons
|
||||
import qs.Ui
|
||||
import "Model.js" as Model
|
||||
import "Sun.js" as Sun
|
||||
|
||||
// A city's 24 hours, midnight to midnight: its real daylight lit, a marker at
|
||||
// now (the sun by day, tonight's moon by night), and sunrise/sunset arrows
|
||||
// while the row is hovered. Dragging along it scrubs every clock.
|
||||
Item {
|
||||
id: strip
|
||||
|
||||
property bool ready: false
|
||||
property int offsetMinutes: 0
|
||||
property real progress: 0
|
||||
// The fixed civil band's answer, for a city with no coordinates yet.
|
||||
property bool fallbackLit: false
|
||||
// { lat, lon } from the fetcher's geocode, or null.
|
||||
property var place: null
|
||||
property double nowMs: 0
|
||||
property bool hovered: false
|
||||
property bool hour24: false
|
||||
property real moonPhase: 0
|
||||
property color foreground: Color.foreground
|
||||
property color fainter: Qt.darker(foreground, 2.1)
|
||||
property color daylightMarker: "#E5C736"
|
||||
property string fontFamily: Style.font.family
|
||||
|
||||
readonly property int trackHeight: Math.max(2, Style.space(3))
|
||||
|
||||
// Which arrow's time is showing: its index, or Model.NO_CHIP.
|
||||
property int shownChip: Model.NO_CHIP
|
||||
|
||||
signal scrubStarted(real fraction)
|
||||
signal scrubMoved(real fraction)
|
||||
signal scrubEnded()
|
||||
signal moonShowRequested()
|
||||
|
||||
function dismissChips() { shownChip = Model.NO_CHIP }
|
||||
|
||||
// Decided against the value read on press, so either delivery order of a
|
||||
// tap and the release underneath it lands the same way.
|
||||
function dismissUnlessChanged(atPress) {
|
||||
shownChip = Model.chipAfterRelease(shownChip, atPress)
|
||||
}
|
||||
|
||||
onHoveredChanged: if (!hovered) dismissChips()
|
||||
|
||||
height: Style.space(18)
|
||||
|
||||
// Keyed to local midnight, not the ticking clock, so the sun and the
|
||||
// Repeaters below rebuild once a day rather than once a second.
|
||||
readonly property double dayMs: ready ? Sun.localMidnightMs(nowMs, offsetMinutes) : 0
|
||||
|
||||
readonly property var sun: {
|
||||
if (!ready || !place || place.lat === undefined || place.lon === undefined) return null
|
||||
// Local noon: the instant furthest from either edge of the day.
|
||||
return Sun.sunTimes(place.lat, place.lon, dayMs + 43200000, offsetMinutes)
|
||||
}
|
||||
|
||||
// Empty in polar day and night, when the sun does not cross the horizon.
|
||||
readonly property var sunMarks: {
|
||||
if (!sun || sun.kind !== "normal") return []
|
||||
var out = []
|
||||
var up = Sun.eventMark(sun.riseMinutes)
|
||||
var down = Sun.eventMark(sun.setMinutes)
|
||||
if (up !== null) out.push({ x: up, minutes: sun.riseMinutes, rising: true, name: "Sunrise" })
|
||||
if (down !== null) out.push({ x: down, minutes: sun.setMinutes, rising: false, name: "Sunset" })
|
||||
return out
|
||||
}
|
||||
|
||||
// Read from the band that is drawn, so the sun never sits in the dark.
|
||||
readonly property bool litNow: sun ? Sun.litAt(sun, ready ? progress * 1440 : 0)
|
||||
: (ready && fallbackLit)
|
||||
|
||||
Rectangle {
|
||||
id: track
|
||||
visible: strip.ready
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
height: strip.trackHeight
|
||||
radius: height / 2
|
||||
color: Util.alpha(strip.foreground, 0.10)
|
||||
|
||||
// Polar night has no band at all, rather than a zero-width one.
|
||||
Repeater {
|
||||
model: strip.sun ? Sun.litSpans(strip.sun)
|
||||
: [{ x0: Model.daylightStart(), x1: Model.daylightEnd() }]
|
||||
|
||||
Rectangle {
|
||||
required property var modelData
|
||||
x: parent.width * modelData.x0
|
||||
width: Math.max(1, parent.width * (modelData.x1 - modelData.x0))
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Util.alpha(strip.foreground, 0.28)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: nowMarker
|
||||
width: Math.max(8, Style.space(10))
|
||||
height: width
|
||||
radius: width / 2
|
||||
x: Math.round(parent.width * (strip.ready ? strip.progress : 0) - width / 2)
|
||||
y: (parent.height - height) / 2
|
||||
color: strip.litNow ? strip.daylightMarker : "transparent"
|
||||
|
||||
MoonDot {
|
||||
anchors.fill: parent
|
||||
visible: !strip.litNow
|
||||
phase: strip.moonPhase
|
||||
color: strip.foreground
|
||||
}
|
||||
|
||||
Behavior on x { NumberAnimation { duration: 400; easing.type: Easing.OutCubic } }
|
||||
}
|
||||
}
|
||||
|
||||
// Scrub, or Shift-click the moon to run its phase show. The arrows below are
|
||||
// later siblings, so their taps never reach this.
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.SizeHorCursor
|
||||
preventStealing: true
|
||||
enabled: strip.ready
|
||||
|
||||
property bool showing: false
|
||||
property int chipAtPress: Model.NO_CHIP
|
||||
|
||||
function onMoon(mouse) {
|
||||
if (strip.litNow) return false
|
||||
return Math.abs(mouse.x - (nowMarker.x + nowMarker.width / 2)) <= nowMarker.width
|
||||
}
|
||||
|
||||
function finish() {
|
||||
if (!showing) strip.scrubEnded()
|
||||
strip.dismissUnlessChanged(chipAtPress)
|
||||
showing = false
|
||||
}
|
||||
|
||||
onPressed: function(mouse) {
|
||||
showing = (mouse.modifiers & Qt.ShiftModifier) !== 0 && onMoon(mouse)
|
||||
chipAtPress = strip.shownChip
|
||||
if (showing) strip.moonShowRequested()
|
||||
else strip.scrubStarted(mouse.x / width)
|
||||
}
|
||||
onPositionChanged: function(mouse) {
|
||||
if (!showing) strip.scrubMoved(mouse.x / width)
|
||||
}
|
||||
onReleased: finish()
|
||||
onCanceled: finish()
|
||||
}
|
||||
|
||||
// Sunrise and sunset, just outside the lit band they point at.
|
||||
Repeater {
|
||||
model: strip.sunMarks
|
||||
|
||||
Item {
|
||||
id: arrow
|
||||
required property var modelData
|
||||
required property int index
|
||||
readonly property bool rising: modelData.rising
|
||||
|
||||
// A finger-sized box around a caption-sized glyph, tucked towards the band.
|
||||
width: Style.space(14)
|
||||
height: Style.space(16)
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
x: Model.arrowBox(arrow.modelData.x, strip.width, arrow.width, Style.space(3), arrow.rising)
|
||||
|
||||
// Hidden while the now marker stands on it. nowMarker.x, not progress,
|
||||
// so it reappears exactly as the eased marker clears.
|
||||
readonly property bool covered: {
|
||||
var markerX = nowMarker.x
|
||||
if (!strip.ready) return false
|
||||
return Model.arrowCovered(arrow.x, arrow.width, markerX + nowMarker.width / 2,
|
||||
nowMarker.width, Style.space(4))
|
||||
}
|
||||
|
||||
// Gone rather than faded, so a hidden arrow is also untappable.
|
||||
opacity: strip.hovered && !arrow.covered ? 1 : 0
|
||||
visible: opacity > 0
|
||||
Behavior on opacity { NumberAnimation { duration: 160 } }
|
||||
|
||||
// Sunrise sits a little high and sunset a little low: a cue that needs no reading.
|
||||
transform: Translate { y: arrow.rising ? -Style.space(2) : Style.space(2) }
|
||||
|
||||
onCoveredChanged: if (arrow.covered && strip.shownChip === arrow.index) strip.dismissChips()
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
textFormat: Text.PlainText
|
||||
text: arrow.rising ? "\u2191" : "\u2193"
|
||||
color: arrowHover.hovered || strip.shownChip === arrow.index ? strip.foreground : strip.fainter
|
||||
font.family: strip.fontFamily
|
||||
font.pixelSize: Style.font.caption
|
||||
}
|
||||
|
||||
HoverHandler {
|
||||
id: arrowHover
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
// Read on press, so a dismissal that already ran underneath cannot
|
||||
// make an open chip look shut.
|
||||
property int atPress: Model.NO_CHIP
|
||||
onPressedChanged: if (pressed) atPress = strip.shownChip
|
||||
onTapped: strip.shownChip = Model.chipAfterTap(atPress, arrow.index)
|
||||
}
|
||||
|
||||
PanelToolTip {
|
||||
visible: strip.shownChip === arrow.index
|
||||
delay: 0
|
||||
closePolicy: Popup.NoAutoClose
|
||||
text: arrow.modelData.name + " " + Model.formatMinuteOfDay(arrow.modelData.minutes, strip.hour24)
|
||||
fontFamily: strip.fontFamily
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user