* 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>
142 lines
4.4 KiB
QML
142 lines
4.4 KiB
QML
import QtQuick
|
|
import Quickshell
|
|
import Quickshell.Io
|
|
import qs.Commons
|
|
import "GlobeModel.js" as Solar
|
|
import "Model.js" as Model
|
|
|
|
// A small drawn globe for where an icon would go. Drawn, not glyphed, so it
|
|
// can spin: `spin` is the longitude facing the viewer; rotate the item to lean it.
|
|
Item {
|
|
id: root
|
|
|
|
property real spin: 0
|
|
property color color: Color.foreground
|
|
// Below this size land turns to noise; draw the graticule alone.
|
|
readonly property bool showLand: width >= 22
|
|
|
|
property var land: []
|
|
|
|
// Heavier strokes and fuller fills, for a centerpiece rather than an inline icon.
|
|
property bool bold: false
|
|
|
|
// "You are here".
|
|
property bool showMarker: false
|
|
property real markerLat: 0
|
|
property real markerLon: 0
|
|
property color markerColor: Color.accent
|
|
|
|
readonly property real radius: Math.min(width, height) / 2 - 1
|
|
readonly property string pluginDir: Quickshell.env("OMARCHY_PATH") + "/shell/plugins/panels/elsewhen"
|
|
|
|
onSpinChanged: canvas.requestPaint()
|
|
onColorChanged: canvas.requestPaint()
|
|
onShowMarkerChanged: canvas.requestPaint()
|
|
onMarkerLatChanged: canvas.requestPaint()
|
|
onMarkerLonChanged: canvas.requestPaint()
|
|
|
|
FileView {
|
|
path: root.pluginDir + "/world.json"
|
|
printErrors: false
|
|
onLoaded: {
|
|
try { root.land = JSON.parse(text()); canvas.requestPaint() } catch (e) { }
|
|
}
|
|
}
|
|
|
|
Canvas {
|
|
id: canvas
|
|
anchors.fill: parent
|
|
renderStrategy: Canvas.Cooperative
|
|
|
|
function strokePath(ctx, pts) {
|
|
var segs = Solar.visibleSegments(pts, root.spin, 0, root.radius)
|
|
for (var i = 0; i < segs.length; i++) {
|
|
ctx.moveTo(segs[i][0].x, segs[i][0].y)
|
|
for (var j = 1; j < segs[i].length; j++) ctx.lineTo(segs[i][j].x, segs[i][j].y)
|
|
}
|
|
}
|
|
|
|
onPaint: {
|
|
var ctx = getContext("2d")
|
|
ctx.reset()
|
|
ctx.translate(width / 2, height / 2)
|
|
var r = root.radius
|
|
if (r <= 0) return
|
|
var c = root.color
|
|
var lat, lon, pts, i
|
|
|
|
// The ocean, opaque like the large globe.
|
|
ctx.beginPath()
|
|
ctx.arc(0, 0, r, 0, Math.PI * 2)
|
|
ctx.fillStyle = Model.mix(Color.popups.background, c, root.bold ? 0.16 : 0.13)
|
|
ctx.fill()
|
|
|
|
ctx.save()
|
|
ctx.beginPath()
|
|
ctx.arc(0, 0, r, 0, Math.PI * 2)
|
|
ctx.clip()
|
|
|
|
// Graticule first, so the land sits on top of it.
|
|
ctx.beginPath()
|
|
for (lon = -90; lon < 90; lon += 90) {
|
|
pts = []
|
|
for (lat = -90; lat <= 90; lat += 6) pts.push([lat, lon])
|
|
strokePath(ctx, pts)
|
|
}
|
|
pts = []
|
|
for (lon = -180; lon <= 180; lon += 6) pts.push([0, lon])
|
|
strokePath(ctx, pts)
|
|
ctx.lineWidth = Math.max(1, r * (root.bold ? 0.055 : 0.045))
|
|
ctx.strokeStyle = Util.alpha(c, root.showLand ? (root.bold ? 0.42 : 0.30) : 0.85)
|
|
ctx.stroke()
|
|
|
|
if (root.showLand && root.land.length > 0) {
|
|
ctx.beginPath()
|
|
for (i = 0; i < root.land.length; i++) {
|
|
var ring = root.land[i]
|
|
// Major landmasses only; islands read as dirt on the lens.
|
|
if (ring.length < 40) continue
|
|
var poly = Solar.clipRingToDisc(ring, root.spin, 0, root.radius)
|
|
if (poly.length < 3) continue
|
|
ctx.moveTo(poly[0].x, poly[0].y)
|
|
for (var q = 1; q < poly.length; q++) ctx.lineTo(poly[q].x, poly[q].y)
|
|
ctx.closePath()
|
|
}
|
|
ctx.fillStyle = Util.alpha(c, root.bold ? 1.0 : 0.85)
|
|
ctx.fill()
|
|
}
|
|
|
|
// "You are here", only while on the near side.
|
|
if (root.showMarker) {
|
|
var mp = Solar.project(root.markerLat, root.markerLon, root.spin, 0, r)
|
|
if (mp.visible) {
|
|
var mr = Math.max(1.6, r * 0.13)
|
|
ctx.beginPath()
|
|
ctx.arc(mp.x, mp.y, mr, 0, Math.PI * 2)
|
|
ctx.fillStyle = root.markerColor
|
|
ctx.fill()
|
|
// Edged so it does not dissolve into a continent of similar lightness.
|
|
ctx.lineWidth = Math.max(1, r * 0.04)
|
|
ctx.strokeStyle = Util.alpha(Color.background, 0.5)
|
|
ctx.stroke()
|
|
|
|
ctx.beginPath()
|
|
ctx.arc(mp.x, mp.y, mr * 1.9, 0, Math.PI * 2)
|
|
ctx.lineWidth = Math.max(1, r * 0.045)
|
|
ctx.strokeStyle = Util.alpha(root.markerColor, 0.65)
|
|
ctx.stroke()
|
|
}
|
|
}
|
|
|
|
ctx.restore()
|
|
|
|
// The rim last, so nothing spills over it.
|
|
ctx.beginPath()
|
|
ctx.arc(0, 0, r, 0, Math.PI * 2)
|
|
ctx.lineWidth = Math.max(1, r * (root.bold ? 0.095 : 0.08))
|
|
ctx.strokeStyle = Util.alpha(c, root.bold ? 1.0 : 0.95)
|
|
ctx.stroke()
|
|
}
|
|
}
|
|
}
|