From 1855eeca4709ea0bd657daccd0cb7ed126fd5891 Mon Sep 17 00:00:00 2001 From: Jake Weaver Date: Fri, 15 May 2026 12:38:15 -0400 Subject: [PATCH 1/5] Show multi-day forecast in weather widget and drop hover popup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hover-driven detail flyout with a click-toggled popup that includes a Noctalia-style row of upcoming days. Switching the wttr.in fetch to the j1 JSON gives us structured current conditions plus a 3-day forecast; each day's representative icon comes from the hourly entry nearest noon, mapped through the same code → glyph table as the bar pill. --- .../plugins/bar/widgets/weatherFlyout.qml | 222 +++++++++++++----- 1 file changed, 165 insertions(+), 57 deletions(-) diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml index e9f17c82..f3805215 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml @@ -13,59 +13,112 @@ Item { property bool popupOpen: false function closePopout() { popupOpen = false } - property string fullReport: "" + // Parsed wttr.in j1 response. Kept on failure so stale data stays visible. + property var report: null readonly property string label: bar ? bar.weatherText : "" readonly property string klass: bar ? bar.weatherClass : "" - // Pipe-delimited fields from wttr.in: location|condition|temp|feels|wind|humidity - readonly property var reportFields: String(fullReport || "").split("|") - readonly property string reportLocation: reportFields.length > 0 ? String(reportFields[0] || "").trim() : "" - readonly property string reportCondition: reportFields.length > 1 ? String(reportFields[1] || "").trim() : "" - readonly property string reportTemp: reportFields.length > 2 ? String(reportFields[2] || "").trim() : "" - readonly property string reportFeels: reportFields.length > 3 ? String(reportFields[3] || "").trim() : "" - readonly property string reportWind: reportFields.length > 4 ? String(reportFields[4] || "").trim() : "" - readonly property string reportHumidity: reportFields.length > 5 ? String(reportFields[5] || "").trim() : "" + readonly property var current: report && report.current_condition && report.current_condition[0] ? report.current_condition[0] : null + readonly property var areaInfo: report && report.nearest_area && report.nearest_area[0] ? report.nearest_area[0] : null + readonly property var forecastDays: report && report.weather ? report.weather : [] + + readonly property bool useImperial: { + var override = setting("unit", "") + if (override === "imperial") return true + if (override === "metric") return false + var name = String(Qt.locale().name || "") + return /^en_US/.test(name) || /^en_LR/.test(name) || /^my/.test(name) + } + + readonly property string reportLocation: areaInfo && areaInfo.areaName && areaInfo.areaName[0] ? areaInfo.areaName[0].value : "" + readonly property string reportCondition: current && current.weatherDesc && current.weatherDesc[0] ? current.weatherDesc[0].value : "" + readonly property string reportTemp: current ? formatTemp(useImperial ? current.temp_F : current.temp_C) : "" + readonly property string reportFeels: current ? formatTemp(useImperial ? current.FeelsLikeF : current.FeelsLikeC) : "" + readonly property string reportWind: current ? (useImperial ? (current.windspeedMiles + " mph") : (current.windspeedKmph + " km/h")) : "" + readonly property string reportHumidity: current ? (current.humidity + "%") : "" visible: label !== "" implicitWidth: button.implicitWidth + 8 implicitHeight: button.implicitHeight + function setting(name, fallback) { + var v = settings ? settings[name] : undefined + return v === undefined || v === null ? fallback : v + } + function refresh() { if (!forecastProc.running) forecastProc.running = true } - // Hover state across the trigger button and the popup. - property bool buttonHovered: false - property bool popupHovered: popup.containsMouse - - function showPopup() { - hideTimer.stop() - if (!popupOpen) refresh() - popupOpen = true + function formatTemp(value) { + if (value === undefined || value === null || value === "") return "" + return value + "°" + (useImperial ? "F" : "C") } - function scheduleHide() { - hideTimer.restart() + function dayName(dateString) { + if (!dateString) return "" + var d = new Date(dateString + "T12:00:00") + if (isNaN(d.getTime())) return "" + return Qt.formatDate(d, "ddd") } - Timer { - id: hideTimer - interval: 220 - onTriggered: { - if (!root.buttonHovered && !root.popupHovered) root.popupOpen = false + function maxTempForDay(day) { + if (!day) return "" + return formatTemp(useImperial ? day.maxtempF : day.maxtempC) + } + + function minTempForDay(day) { + if (!day) return "" + return formatTemp(useImperial ? day.mintempF : day.mintempC) + } + + // Representative icon for a forecast day: the hourly entry nearest noon. + function dayIcon(day) { + if (!day || !day.hourly || day.hourly.length === 0) return "" + var best = day.hourly[0] + var bestDist = 9999 + for (var i = 0; i < day.hourly.length; ++i) { + var t = parseInt(String(day.hourly[i].time || "0"), 10) + var dist = Math.abs(t - 1200) + if (dist < bestDist) { bestDist = dist; best = day.hourly[i] } + } + return iconForCode(best.weatherCode, false) + } + + // Mirrors omarchy-weather-icon's wttr.in code → nerd-font glyph mapping. + function iconForCode(code, night) { + var c = parseInt(String(code || "0"), 10) + switch (c) { + case 113: return night ? "" : "" + case 116: return night ? "" : "" + case 119: case 122: return "" + case 143: case 248: case 260: return "" + case 176: case 263: case 353: return night ? "" : "" + case 179: case 227: case 230: case 323: case 326: case 368: return night ? "" : "" + case 182: case 185: case 281: case 284: case 311: case 314: + case 317: case 320: case 350: case 362: case 365: case 374: case 377: return "" + case 200: case 386: case 389: case 392: case 395: return "" + case 266: case 293: case 296: case 299: case 302: case 305: case 308: case 356: case 359: return "" + case 329: case 332: case 335: case 338: case 371: return "" + default: return "" } } - onButtonHoveredChanged: buttonHovered ? showPopup() : scheduleHide() - onPopupHoveredChanged: popupHovered ? hideTimer.stop() : scheduleHide() - Process { id: forecastProc - command: ["bash", "-lc", "curl -fsS --max-time 5 'wttr.in/?T0&format=%l|%C|%t|%f|%w|%h' 2>/dev/null"] + command: ["bash", "-lc", "curl -fsS --max-time 5 'https://wttr.in/?format=j1' 2>/dev/null"] stdout: StdioCollector { waitForEnd: true - onStreamFinished: root.fullReport = String(text || "").trim() + onStreamFinished: { + var raw = String(text || "").trim() + if (!raw) return + try { + root.report = JSON.parse(raw) + } catch (e) { + // Keep last-good report on parse failure so the popup isn't blanked. + } + } } } @@ -83,55 +136,68 @@ Item { tooltipText: "" onPressed: function(b) { - if (b === Qt.RightButton) root.bar.run("omarchy-notification-send \"$(omarchy-weather-status)\"") - else if (b === Qt.MiddleButton) root.refresh() + if (b === Qt.RightButton) { + root.bar.run("omarchy-notification-send \"$(omarchy-weather-status)\"") + } else if (b === Qt.MiddleButton) { + root.refresh() + } else { + var willOpen = !root.popupOpen + root.popupOpen = willOpen + if (willOpen) root.refresh() + } } } - HoverHandler { - id: hoverHandler - target: button - onHoveredChanged: root.buttonHovered = hovered - } - Common.PopupCard { id: popup anchorItem: button owner: root bar: root.bar open: root.popupOpen - triggerMode: "hover" - contentWidth: 320 + triggerMode: "click" + contentWidth: 340 contentHeight: card.implicitHeight + 28 Column { id: card anchors.fill: parent - spacing: 12 + spacing: 14 Row { width: parent.width - spacing: 10 + spacing: 12 Text { id: glyph text: root.label || "—" color: root.bar.foreground font.family: root.bar.fontFamily - font.pixelSize: 24 - anchors.verticalCenter: location.verticalCenter + font.pixelSize: 30 + anchors.verticalCenter: headerCol.verticalCenter } - Text { - id: location - text: root.reportLocation || "Weather" - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 13 - font.bold: true - anchors.verticalCenter: parent.verticalCenter + Column { + id: headerCol width: parent.width - glyph.width - parent.spacing - elide: Text.ElideRight + spacing: 2 + + Text { + text: root.reportTemp || "—" + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 22 + font.bold: true + } + + Text { + visible: text !== "" + text: root.reportLocation + color: Qt.darker(root.bar.foreground, 1.4) + font.family: root.bar.fontFamily + font.pixelSize: 11 + elide: Text.ElideRight + width: parent.width + } } } @@ -147,7 +213,7 @@ Item { } Text { - visible: root.fullReport === "" + visible: !root.current text: "Fetching forecast…" color: Qt.darker(root.bar.foreground, 1.5) font.family: root.bar.fontFamily @@ -155,22 +221,21 @@ Item { font.italic: true } - // Key/value pairs — each metric on its own line for scanability. Grid { - visible: root.reportTemp !== "" + visible: !!root.current width: parent.width columns: 2 columnSpacing: 12 rowSpacing: 4 Text { - text: "Temperature" + text: "Feels like" color: Qt.darker(root.bar.foreground, 1.4) font.family: root.bar.fontFamily font.pixelSize: 11 } Text { - text: root.reportTemp + (root.reportFeels && root.reportFeels !== root.reportTemp ? " (feels " + root.reportFeels + ")" : "") + text: root.reportFeels color: root.bar.foreground font.family: root.bar.fontFamily font.pixelSize: 11 @@ -203,6 +268,49 @@ Item { } } + Row { + id: forecastRow + visible: root.forecastDays.length > 0 + width: parent.width + spacing: 6 + + Repeater { + model: root.forecastDays + + Column { + required property var modelData + required property int index + width: (forecastRow.width - forecastRow.spacing * Math.max(0, root.forecastDays.length - 1)) / Math.max(1, root.forecastDays.length) + spacing: 4 + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: root.dayName(modelData.date) + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 11 + font.bold: true + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: root.dayIcon(modelData) + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 22 + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + text: root.maxTempForDay(modelData) + " / " + root.minTempForDay(modelData) + color: Qt.darker(root.bar.foreground, 1.4) + font.family: root.bar.fontFamily + font.pixelSize: 10 + } + } + } + } + Row { width: parent.width spacing: 8 From 15c7cbfc91d660dbe63783e281cfc8f9e0fe490c Mon Sep 17 00:00:00 2001 From: Jake Weaver Date: Fri, 15 May 2026 13:43:31 -0400 Subject: [PATCH 2/5] Redesign weather popup and auto-refresh forecast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the inline detail flyout with a two-column hero: location chip and large temperature on the left, big condition icon on the right. Stats (Feels / Wind / Humidity) move below as caps-labeled columns, separated from the forecast by a thin divider. Forecast cells now lead with the day icon and put the day name above hi/lo temps. Drop the Refresh and wttr.in pill buttons; a Timer refreshes the forecast every refreshMinutes (default 15). The interval is exposed through the Settings panel — Bar -> Weather has a gear that opens a SpinBox-backed form, persisted as "settings": { "refreshMinutes": N } in shell.json. --- .../omarchy-shell/plugins/bar/Bar.qml | 2 +- .../plugins/bar/widgets/weatherFlyout.qml | 329 +++++++++++------- .../plugins/settings/SettingsPanel.qml | 28 ++ 3 files changed, 234 insertions(+), 125 deletions(-) diff --git a/default/quickshell/omarchy-shell/plugins/bar/Bar.qml b/default/quickshell/omarchy-shell/plugins/bar/Bar.qml index 2bd5c9aa..442718b4 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/Bar.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/Bar.qml @@ -312,7 +312,7 @@ Item { "calendar": { displayName: "Calendar", description: "Clock with month-grid popup", category: "Time", allowMultiple: false, settingsForm: "calendarSettings" }, "notificationCenter": { displayName: "Notification center", description: "Recent notifications + DND", category: "Status", allowMultiple: false }, "systemStats": { displayName: "System stats", description: "CPU icon — hover for graphs, click to open btop", category: "System", allowMultiple: false }, - "weatherFlyout": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false }, + "weatherFlyout": { displayName: "Weather", description: "Weather pill with detail popup", category: "Info", allowMultiple: false, settingsForm: "weatherSettings" }, "idleInhibitor": { displayName: "Keep awake", description: "Toggle idle inhibitor", category: "System", allowMultiple: false }, "microphone": { displayName: "Microphone", description: "Mic input state and mute toggle", category: "Audio", allowMultiple: false }, "activeWindow": { displayName: "Active window", description: "Title of the focused window", category: "Compositor", allowMultiple: false }, diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml index f3805215..41f9ec8a 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml @@ -31,12 +31,24 @@ Item { return /^en_US/.test(name) || /^en_LR/.test(name) || /^my/.test(name) } - readonly property string reportLocation: areaInfo && areaInfo.areaName && areaInfo.areaName[0] ? areaInfo.areaName[0].value : "" + // Auto-refresh interval in minutes; clamped to a sane minimum. + readonly property int refreshMinutes: Math.max(1, parseInt(setting("refreshMinutes", 15), 10) || 15) + + readonly property string reportLocation: areaInfo && areaInfo.areaName && areaInfo.areaName[0] ? areaInfo.areaName[0].value : "" readonly property string reportCondition: current && current.weatherDesc && current.weatherDesc[0] ? current.weatherDesc[0].value : "" readonly property string reportTemp: current ? formatTemp(useImperial ? current.temp_F : current.temp_C) : "" + readonly property string reportTempNum: current ? String(useImperial ? current.temp_F : current.temp_C) : "" + readonly property string tempUnit: "°" + (useImperial ? "F" : "C") readonly property string reportFeels: current ? formatTemp(useImperial ? current.FeelsLikeF : current.FeelsLikeC) : "" readonly property string reportWind: current ? (useImperial ? (current.windspeedMiles + " mph") : (current.windspeedKmph + " km/h")) : "" readonly property string reportHumidity: current ? (current.humidity + "%") : "" + readonly property string conditionLine: { + var parts = [] + if (reportCondition) parts.push(reportCondition) + var wd = windDescriptor() + if (wd) parts.push(wd) + return parts.join(" · ") + } visible: label !== "" implicitWidth: button.implicitWidth + 8 @@ -73,6 +85,32 @@ Item { return formatTemp(useImperial ? day.mintempF : day.mintempC) } + // Beaufort-ish descriptor based on current windspeed in mph. + function windDescriptor() { + if (!current) return "" + var mph = parseInt(String(current.windspeedMiles || "0"), 10) + if (isNaN(mph)) return "" + if (mph < 1) return "Calm" + if (mph < 4) return "Light air" + if (mph < 8) return "Light breeze" + if (mph < 13) return "Gentle breeze" + if (mph < 19) return "Moderate breeze" + if (mph < 25) return "Fresh breeze" + if (mph < 32) return "Strong breeze" + if (mph < 39) return "Near gale" + return "Gale" + } + + // Bare degree value (no unit letter), used in the forecast row. + function bareTempForDay(day, kind) { + if (!day) return "" + var v = useImperial + ? (kind === "max" ? day.maxtempF : day.mintempF) + : (kind === "max" ? day.maxtempC : day.mintempC) + if (v === undefined || v === null || v === "") return "" + return v + "°" + } + // Representative icon for a forecast day: the hourly entry nearest noon. function dayIcon(day) { if (!day || !day.hourly || day.hourly.length === 0) return "" @@ -122,6 +160,15 @@ Item { } } + Timer { + id: refreshTimer + interval: root.refreshMinutes * 60 * 1000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: root.refresh() + } + Common.WidgetButton { id: button anchors.left: parent.left @@ -155,61 +202,143 @@ Item { bar: root.bar open: root.popupOpen triggerMode: "click" - contentWidth: 340 + contentWidth: 460 contentHeight: card.implicitHeight + 28 Column { id: card anchors.fill: parent - spacing: 14 + spacing: 20 - Row { + // ---- Hero row: location/temp/condition on the left, big icon on the right. + Item { width: parent.width - spacing: 12 - - Text { - id: glyph - text: root.label || "—" - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 30 - anchors.verticalCenter: headerCol.verticalCenter - } + height: heroLeft.height Column { - id: headerCol - width: parent.width - glyph.width - parent.spacing - spacing: 2 + id: heroLeft + anchors.left: parent.left + anchors.top: parent.top + spacing: 6 - Text { - text: root.reportTemp || "—" - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 22 - font.bold: true + Row { + visible: root.reportLocation !== "" + spacing: 6 + + Text { + text: "" // nf-fa-map_marker + color: Qt.darker(root.bar.foreground, 1.4) + font.family: root.bar.fontFamily + font.pixelSize: 11 + anchors.verticalCenter: parent.verticalCenter + } + Text { + text: (root.reportLocation || "").toUpperCase() + color: Qt.darker(root.bar.foreground, 1.4) + font.family: root.bar.fontFamily + font.pixelSize: 11 + font.letterSpacing: 1 + anchors.verticalCenter: parent.verticalCenter + } + } + + Row { + spacing: 2 + + Text { + id: tempBig + text: root.reportTempNum || "—" + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 56 + font.bold: true + } + Text { + text: root.current ? root.tempUnit : "" + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 22 + anchors.top: tempBig.top + anchors.topMargin: 10 + } } Text { visible: text !== "" - text: root.reportLocation - color: Qt.darker(root.bar.foreground, 1.4) + text: root.conditionLine + color: Qt.darker(root.bar.foreground, 1.2) font.family: root.bar.fontFamily - font.pixelSize: 11 - elide: Text.ElideRight - width: parent.width + font.pixelSize: 12 } } + + Text { + id: heroIcon + anchors.right: parent.right + anchors.verticalCenter: heroLeft.verticalCenter + text: root.label || "—" + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 56 + } } - Text { - visible: text !== "" - text: root.reportCondition - color: Qt.darker(root.bar.foreground, 1.2) - font.family: root.bar.fontFamily - font.pixelSize: 11 - font.italic: true - wrapMode: Text.WordWrap + // ---- Stats row: three caps-labeled columns. + Row { + visible: !!root.current width: parent.width + spacing: 36 + + Column { + spacing: 4 + Text { + text: "FEELS" + color: Qt.darker(root.bar.foreground, 1.5) + font.family: root.bar.fontFamily + font.pixelSize: 10 + font.letterSpacing: 1 + } + Text { + text: root.reportFeels + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 13 + } + } + + Column { + spacing: 4 + Text { + text: "WIND" + color: Qt.darker(root.bar.foreground, 1.5) + font.family: root.bar.fontFamily + font.pixelSize: 10 + font.letterSpacing: 1 + } + Text { + text: root.reportWind + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 13 + } + } + + Column { + spacing: 4 + Text { + text: "HUMIDITY" + color: Qt.darker(root.bar.foreground, 1.5) + font.family: root.bar.fontFamily + font.pixelSize: 10 + font.letterSpacing: 1 + } + Text { + text: root.reportHumidity + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 13 + } + } } Text { @@ -221,118 +350,70 @@ Item { font.italic: true } - Grid { - visible: !!root.current + // ---- Divider between current conditions and forecast. + Rectangle { + visible: root.forecastDays.length > 0 width: parent.width - columns: 2 - columnSpacing: 12 - rowSpacing: 4 - - Text { - text: "Feels like" - color: Qt.darker(root.bar.foreground, 1.4) - font.family: root.bar.fontFamily - font.pixelSize: 11 - } - Text { - text: root.reportFeels - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 11 - } - - Text { - text: "Wind" - color: Qt.darker(root.bar.foreground, 1.4) - font.family: root.bar.fontFamily - font.pixelSize: 11 - } - Text { - text: root.reportWind - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 11 - } - - Text { - text: "Humidity" - color: Qt.darker(root.bar.foreground, 1.4) - font.family: root.bar.fontFamily - font.pixelSize: 11 - } - Text { - text: root.reportHumidity - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 11 - } + height: 1 + color: root.bar.foreground + opacity: 0.12 } + // ---- Forecast row: each cell has the day icon left of a day-name + hi/lo column. Row { id: forecastRow visible: root.forecastDays.length > 0 width: parent.width - spacing: 6 Repeater { model: root.forecastDays - Column { + Row { required property var modelData required property int index - width: (forecastRow.width - forecastRow.spacing * Math.max(0, root.forecastDays.length - 1)) / Math.max(1, root.forecastDays.length) - spacing: 4 + width: forecastRow.width / Math.max(1, root.forecastDays.length) + spacing: 10 Text { - anchors.horizontalCenter: parent.horizontalCenter - text: root.dayName(modelData.date) - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 11 - font.bold: true - } - - Text { - anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter text: root.dayIcon(modelData) color: root.bar.foreground font.family: root.bar.fontFamily - font.pixelSize: 22 + font.pixelSize: 24 } - Text { - anchors.horizontalCenter: parent.horizontalCenter - text: root.maxTempForDay(modelData) + " / " + root.minTempForDay(modelData) - color: Qt.darker(root.bar.foreground, 1.4) - font.family: root.bar.fontFamily - font.pixelSize: 10 + Column { + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + + Text { + text: root.dayName(modelData.date).toUpperCase() + color: Qt.darker(root.bar.foreground, 1.4) + font.family: root.bar.fontFamily + font.pixelSize: 10 + font.letterSpacing: 1 + } + + Row { + spacing: 6 + + Text { + text: root.bareTempForDay(modelData, "max") + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 12 + } + Text { + text: root.bareTempForDay(modelData, "min") + color: Qt.darker(root.bar.foreground, 1.5) + font.family: root.bar.fontFamily + font.pixelSize: 12 + } + } } } } } - - Row { - width: parent.width - spacing: 8 - - Common.PillButton { - iconText: "󰑐" - text: "Refresh" - foreground: root.bar.foreground - horizontalPadding: 12 - verticalPadding: 6 - onClicked: root.refresh() - } - - Common.PillButton { - iconText: "󰏌" - text: "wttr.in" - foreground: root.bar.foreground - horizontalPadding: 12 - verticalPadding: 6 - onClicked: { root.bar.run("xdg-open https://wttr.in"); root.popupOpen = false } - } - } } } } diff --git a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml index 7e201c4e..bf684db4 100644 --- a/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml +++ b/default/quickshell/omarchy-shell/plugins/settings/SettingsPanel.qml @@ -2497,6 +2497,7 @@ Item { switch (meta.settingsForm) { case "spacerSettings": return spacerSettingsComponent case "calendarSettings": return calendarSettingsComponent + case "weatherSettings": return weatherSettingsComponent } } var manifest = root.pluginRegistry ? root.pluginRegistry.installedPlugins[id] : null @@ -2644,6 +2645,33 @@ Item { } } + Component { + id: weatherSettingsComponent + + Column { + id: weatherForm + signal fieldChanged(string key, var value) + property var entry: ({}) + + spacing: 8 + width: parent ? parent.width : 0 + + Text { + text: "Auto-refresh interval (minutes)" + color: Qt.darker(root.foreground, 1.4) + font.family: root.fontFamily + font.pixelSize: 11 + } + + SpinBox { + from: 1 + to: 1440 + value: weatherForm.entry.refreshMinutes !== undefined ? weatherForm.entry.refreshMinutes : 15 + onValueModified: weatherForm.fieldChanged("refreshMinutes", value) + } + } + } + // ===================== plugins category ================================== component PluginManager: Column { id: pm From 8eb91cd06b3ffc3762f5f72674ebf24f794bc063 Mon Sep 17 00:00:00 2001 From: Jake Weaver Date: Fri, 15 May 2026 15:14:21 -0400 Subject: [PATCH 3/5] Enlarge hero icon and tighten popup spacing --- .../omarchy-shell/plugins/bar/widgets/weatherFlyout.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml index 41f9ec8a..7ed91ab5 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml @@ -208,7 +208,7 @@ Item { Column { id: card anchors.fill: parent - spacing: 20 + spacing: 14 // ---- Hero row: location/temp/condition on the left, big icon on the right. Item { @@ -279,7 +279,7 @@ Item { text: root.label || "—" color: root.bar.foreground font.family: root.bar.fontFamily - font.pixelSize: 56 + font.pixelSize: 80 } } From 9c859a4c9bc6f40adb3bfd4d304841a1a07c1ebf Mon Sep 17 00:00:00 2001 From: Jake Weaver Date: Fri, 15 May 2026 15:55:14 -0400 Subject: [PATCH 4/5] Reorganize weather popup hero layout Move the weather icon next to the big temperature on the left and stack the location and stats columns on the right, matching the new mockup. --- .../plugins/bar/widgets/weatherFlyout.qml | 212 ++++++++---------- 1 file changed, 93 insertions(+), 119 deletions(-) diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml index 7ed91ab5..8a218a69 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml @@ -42,13 +42,6 @@ Item { readonly property string reportFeels: current ? formatTemp(useImperial ? current.FeelsLikeF : current.FeelsLikeC) : "" readonly property string reportWind: current ? (useImperial ? (current.windspeedMiles + " mph") : (current.windspeedKmph + " km/h")) : "" readonly property string reportHumidity: current ? (current.humidity + "%") : "" - readonly property string conditionLine: { - var parts = [] - if (reportCondition) parts.push(reportCondition) - var wd = windDescriptor() - if (wd) parts.push(wd) - return parts.join(" · ") - } visible: label !== "" implicitWidth: button.implicitWidth + 8 @@ -85,22 +78,6 @@ Item { return formatTemp(useImperial ? day.mintempF : day.mintempC) } - // Beaufort-ish descriptor based on current windspeed in mph. - function windDescriptor() { - if (!current) return "" - var mph = parseInt(String(current.windspeedMiles || "0"), 10) - if (isNaN(mph)) return "" - if (mph < 1) return "Calm" - if (mph < 4) return "Light air" - if (mph < 8) return "Light breeze" - if (mph < 13) return "Gentle breeze" - if (mph < 19) return "Moderate breeze" - if (mph < 25) return "Fresh breeze" - if (mph < 32) return "Strong breeze" - if (mph < 39) return "Near gale" - return "Gale" - } - // Bare degree value (no unit letter), used in the forecast row. function bareTempForDay(day, kind) { if (!day) return "" @@ -202,7 +179,7 @@ Item { bar: root.bar open: root.popupOpen triggerMode: "click" - contentWidth: 460 + contentWidth: 520 contentHeight: card.implicitHeight + 28 Column { @@ -210,16 +187,54 @@ Item { anchors.fill: parent spacing: 14 - // ---- Hero row: location/temp/condition on the left, big icon on the right. + // ---- Hero row: big icon + temp on the left; location and stats stacked on the right. Item { width: parent.width - height: heroLeft.height + height: Math.max(heroLeft.height, heroRight.height) - Column { + Row { id: heroLeft anchors.left: parent.left - anchors.top: parent.top - spacing: 6 + anchors.verticalCenter: parent.verticalCenter + spacing: 12 + + Text { + id: heroIcon + anchors.verticalCenter: parent.verticalCenter + text: root.label || "—" + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 64 + } + + Row { + anchors.verticalCenter: parent.verticalCenter + spacing: 2 + + Text { + id: tempBig + text: root.reportTempNum || "—" + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 56 + font.bold: true + } + Text { + text: root.current ? root.tempUnit : "" + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 22 + anchors.top: tempBig.top + anchors.topMargin: 10 + } + } + } + + Column { + id: heroRight + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + spacing: 8 Row { visible: root.reportLocation !== "" @@ -243,100 +258,59 @@ Item { } Row { - spacing: 2 + visible: !!root.current + spacing: 22 - Text { - id: tempBig - text: root.reportTempNum || "—" - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 56 - font.bold: true + Column { + spacing: 4 + Text { + text: "FEELS" + color: Qt.darker(root.bar.foreground, 1.5) + font.family: root.bar.fontFamily + font.pixelSize: 10 + font.letterSpacing: 1 + } + Text { + text: root.reportFeels + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 13 + } } - Text { - text: root.current ? root.tempUnit : "" - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 22 - anchors.top: tempBig.top - anchors.topMargin: 10 + + Column { + spacing: 4 + Text { + text: "WIND" + color: Qt.darker(root.bar.foreground, 1.5) + font.family: root.bar.fontFamily + font.pixelSize: 10 + font.letterSpacing: 1 + } + Text { + text: root.reportWind + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 13 + } } - } - Text { - visible: text !== "" - text: root.conditionLine - color: Qt.darker(root.bar.foreground, 1.2) - font.family: root.bar.fontFamily - font.pixelSize: 12 - } - } - - Text { - id: heroIcon - anchors.right: parent.right - anchors.verticalCenter: heroLeft.verticalCenter - text: root.label || "—" - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 80 - } - } - - // ---- Stats row: three caps-labeled columns. - Row { - visible: !!root.current - width: parent.width - spacing: 36 - - Column { - spacing: 4 - Text { - text: "FEELS" - color: Qt.darker(root.bar.foreground, 1.5) - font.family: root.bar.fontFamily - font.pixelSize: 10 - font.letterSpacing: 1 - } - Text { - text: root.reportFeels - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 13 - } - } - - Column { - spacing: 4 - Text { - text: "WIND" - color: Qt.darker(root.bar.foreground, 1.5) - font.family: root.bar.fontFamily - font.pixelSize: 10 - font.letterSpacing: 1 - } - Text { - text: root.reportWind - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 13 - } - } - - Column { - spacing: 4 - Text { - text: "HUMIDITY" - color: Qt.darker(root.bar.foreground, 1.5) - font.family: root.bar.fontFamily - font.pixelSize: 10 - font.letterSpacing: 1 - } - Text { - text: root.reportHumidity - color: root.bar.foreground - font.family: root.bar.fontFamily - font.pixelSize: 13 + Column { + spacing: 4 + Text { + text: "HUMIDITY" + color: Qt.darker(root.bar.foreground, 1.5) + font.family: root.bar.fontFamily + font.pixelSize: 10 + font.letterSpacing: 1 + } + Text { + text: root.reportHumidity + color: root.bar.foreground + font.family: root.bar.fontFamily + font.pixelSize: 13 + } + } } } } From 66f902e50c076df21107cbe9c7edcd2ac6a17fe8 Mon Sep 17 00:00:00 2001 From: Jake Weaver Date: Fri, 15 May 2026 16:02:38 -0400 Subject: [PATCH 5/5] Widen and enlarge weather popup right-side details Bump location and stat label/value font sizes a couple pixels and add breathing room between the FEELS/WIND/HUMIDITY columns. --- .../plugins/bar/widgets/weatherFlyout.qml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml index 8a218a69..e2f733cf 100644 --- a/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml +++ b/default/quickshell/omarchy-shell/plugins/bar/widgets/weatherFlyout.qml @@ -234,7 +234,7 @@ Item { id: heroRight anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - spacing: 8 + spacing: 12 Row { visible: root.reportLocation !== "" @@ -244,14 +244,14 @@ Item { text: "" // nf-fa-map_marker color: Qt.darker(root.bar.foreground, 1.4) font.family: root.bar.fontFamily - font.pixelSize: 11 + font.pixelSize: 12 anchors.verticalCenter: parent.verticalCenter } Text { text: (root.reportLocation || "").toUpperCase() color: Qt.darker(root.bar.foreground, 1.4) font.family: root.bar.fontFamily - font.pixelSize: 11 + font.pixelSize: 12 font.letterSpacing: 1 anchors.verticalCenter: parent.verticalCenter } @@ -259,56 +259,56 @@ Item { Row { visible: !!root.current - spacing: 22 + spacing: 36 Column { - spacing: 4 + spacing: 5 Text { text: "FEELS" color: Qt.darker(root.bar.foreground, 1.5) font.family: root.bar.fontFamily - font.pixelSize: 10 + font.pixelSize: 11 font.letterSpacing: 1 } Text { text: root.reportFeels color: root.bar.foreground font.family: root.bar.fontFamily - font.pixelSize: 13 + font.pixelSize: 15 } } Column { - spacing: 4 + spacing: 5 Text { text: "WIND" color: Qt.darker(root.bar.foreground, 1.5) font.family: root.bar.fontFamily - font.pixelSize: 10 + font.pixelSize: 11 font.letterSpacing: 1 } Text { text: root.reportWind color: root.bar.foreground font.family: root.bar.fontFamily - font.pixelSize: 13 + font.pixelSize: 15 } } Column { - spacing: 4 + spacing: 5 Text { text: "HUMIDITY" color: Qt.darker(root.bar.foreground, 1.5) font.family: root.bar.fontFamily - font.pixelSize: 10 + font.pixelSize: 11 font.letterSpacing: 1 } Text { text: root.reportHumidity color: root.bar.foreground font.family: root.bar.fontFamily - font.pixelSize: 13 + font.pixelSize: 15 } } }