Add: Weather widget
- Use OWM daemon's cache file - OWM icon codes - Add dependency: serde_json
This commit is contained in:
parent
61f7020fd3
commit
2a1beab0e8
@ -11,6 +11,7 @@ chrono = "0.4.42"
|
||||
dirs = "6.0.0"
|
||||
i-slint-backend-winit = { version = "1.14.1", features = ["x11"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
slint = { version = "1.14.1", features = ["backend-winit"] }
|
||||
toml = "0.9.8"
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ mod common;
|
||||
mod datewidget;
|
||||
mod timewidget;
|
||||
mod volumewidget;
|
||||
mod weatherwidget;
|
||||
mod wifiwidget;
|
||||
|
||||
use crate::TopBar;
|
||||
@ -15,7 +16,8 @@ pub fn install_callbacks(ui: &TopBar) {
|
||||
volumewidget::install(ui);
|
||||
brightnesswidget::install(ui);
|
||||
wifiwidget::install(ui);
|
||||
weatherwidget::install(ui);
|
||||
|
||||
// In the future:
|
||||
// weatherwidget::install(ui);
|
||||
// notifwidget::install(ui);
|
||||
}
|
||||
|
||||
120
src/widgets/weatherwidget.rs
Normal file
120
src/widgets/weatherwidget.rs
Normal file
@ -0,0 +1,120 @@
|
||||
use super::common::run_cmd;
|
||||
use crate::TopBar;
|
||||
use serde::Deserialize;
|
||||
use slint::{ComponentHandle, SharedString, Timer, TimerMode};
|
||||
use std::{fs, path::PathBuf, time::Duration};
|
||||
|
||||
/// The subset of the JSON we actually need.
|
||||
#[derive(Deserialize)]
|
||||
struct WeatherJson {
|
||||
weather: Vec<WeatherEntry>,
|
||||
main: MainEntry,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct WeatherEntry {
|
||||
icon: String, // e.g. "09n"
|
||||
main: String, // e.g. "Mist"
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MainEntry {
|
||||
temp: f32, // e.g. 21.09
|
||||
}
|
||||
|
||||
/// Expand `~` to the user home dir
|
||||
fn expand_home(path: &str) -> PathBuf {
|
||||
if let Some(home) = dirs::home_dir()
|
||||
&& path.starts_with("~/")
|
||||
{
|
||||
return home.join(path.trim_start_matches("~/"));
|
||||
}
|
||||
PathBuf::from(path)
|
||||
}
|
||||
|
||||
/// Parse the JSON file; return (temp, cond, icon code)
|
||||
fn read_weather_file() -> Option<(f32, String, String)> {
|
||||
let path = expand_home("~/.cache/candydesktop/owm_widget.json");
|
||||
let data = fs::read_to_string(path).ok()?;
|
||||
let parsed: WeatherJson = serde_json::from_str(&data).ok()?;
|
||||
|
||||
let temp = parsed.main.temp;
|
||||
let cond = parsed.weather.first()?.main.clone(); // e.g. "Mist"
|
||||
let icon_code = parsed.weather.first()?.icon.clone(); // e.g. "09n"
|
||||
|
||||
Some((temp, cond, icon_code))
|
||||
}
|
||||
|
||||
/// Pick glyph based on OWM icon code
|
||||
fn icon_from_code(code: &str) -> &'static str {
|
||||
match code {
|
||||
// Thunderstorm
|
||||
"11d" | "11n" => "",
|
||||
|
||||
// Drizzle
|
||||
"09d" | "09n" => "",
|
||||
|
||||
// Rain
|
||||
"10d" => "",
|
||||
"10n" => "",
|
||||
|
||||
// Snow
|
||||
"13d" | "13n" => "",
|
||||
|
||||
// Atmosphere (mist, fog, haze, dust)
|
||||
"50d" | "50n" => "",
|
||||
|
||||
// Clouds
|
||||
"02d" | "03d" | "04d" => "",
|
||||
"02n" | "03n" | "04n" => "",
|
||||
|
||||
// Clear sky
|
||||
"01d" => "",
|
||||
"01n" => "",
|
||||
|
||||
// Fallback (unknown)
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// Format tooltip: "21.1 °C, Mist"
|
||||
fn tooltip(temp: f32, cond: &str) -> SharedString {
|
||||
SharedString::from(format!("{temp:.1} °C, {cond}"))
|
||||
}
|
||||
|
||||
/// Timer to refresh weather every 100 seconds
|
||||
fn start_weather_updater(ui: &TopBar) {
|
||||
let weak = ui.as_weak();
|
||||
|
||||
// One-shot update on startup
|
||||
if let Some(ui) = weak.upgrade()
|
||||
&& let Some((t, c, icon_code)) = read_weather_file()
|
||||
{
|
||||
ui.set_weather_icon(icon_from_code(&icon_code).into());
|
||||
ui.set_weather_tooltip(tooltip(t, &c).into());
|
||||
}
|
||||
|
||||
// Refresh every 100 sec
|
||||
let timer = Box::leak(Box::new(Timer::default()));
|
||||
timer.start(TimerMode::Repeated, Duration::from_secs(100), move || {
|
||||
if let Some(ui) = weak.upgrade()
|
||||
&& let Some((t, c, icon_code)) = read_weather_file()
|
||||
{
|
||||
ui.set_weather_icon(icon_from_code(&icon_code).into());
|
||||
ui.set_weather_tooltip(tooltip(t, &c));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Public entry: connect callback + start updater
|
||||
pub fn install(ui: &TopBar) {
|
||||
let weak = ui.as_weak();
|
||||
|
||||
ui.on_show_weather(move || {
|
||||
if weak.upgrade().is_some() {
|
||||
run_cmd("xclock"); // Fix later
|
||||
}
|
||||
});
|
||||
|
||||
start_weather_updater(ui);
|
||||
}
|
||||
@ -54,6 +54,15 @@ export component TopBar inherits Window {
|
||||
in property <color> wifi_bg_clicked: #4a5f70;*/
|
||||
callback show_wifi();
|
||||
|
||||
// Weather widget
|
||||
in property <string> weather_tooltip;
|
||||
in property <string> weather_icon;
|
||||
in property <color> weather_icon_color: #ffffff;
|
||||
in property <color> weather_bg_normal: #8e9162;
|
||||
in property <color> weather_bg_hover: #6d8a4d;
|
||||
in property <color> weather_bg_clicked: #4a5f70;
|
||||
callback show_weather();
|
||||
|
||||
title: "chocobar";
|
||||
width: bar_width *1px;
|
||||
height: bar_height *1px;
|
||||
@ -98,6 +107,18 @@ export component TopBar inherits Window {
|
||||
HorizontalLayout {
|
||||
alignment: end; // Right-align
|
||||
|
||||
// SquareIconWidget - Weather
|
||||
SquareIconWidget {
|
||||
bar_height: root.bar_height;
|
||||
icon_text: root.weather_icon;
|
||||
tooltip_text: root.weather_tooltip;
|
||||
icon_color: root.weather_icon_color;
|
||||
bg_normal: root.weather_bg_normal;
|
||||
bg_hover: root.weather_bg_hover;
|
||||
bg_clicked: root.weather_bg_clicked;
|
||||
square_btn_callback => root.show_weather();
|
||||
}
|
||||
|
||||
// SquareIconWidget - Wifi
|
||||
SquareIconWidget {
|
||||
bar_height: root.bar_height;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user