Dynamic values

- Pass values to display in Slint UI
- Actual weather data instead of fixed dummy data
This commit is contained in:
Candifloss 2025-11-04 20:05:59 +05:30
parent 3262745771
commit 4fe5878d36
4 changed files with 88 additions and 52 deletions

View File

@ -2,6 +2,7 @@
name = "openweatherwidget"
version = "0.0.1"
edition = "2024"
build = "build.rs"
[dependencies]
owm-rs = { git = "https://git.candifloss.cc/candifloss/OpenWeatherMapSDK.git" }
@ -10,7 +11,7 @@ reqwest = {version = "0.12.23", features = ["blocking", "json"] }
toml = "0.9.7"
dirs = "6.0.0"
serde_json = "1.0.145"
slint = "1.13"
slint = "1.14.1"
[build-dependencies]
slint-build = "1.13.1"
slint-build = "1.14.1"

3
widget/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
slint_build::compile("ui/widget-popup.slint").unwrap();
}

View File

@ -2,6 +2,9 @@ use owm_rs::free_api_v25::current::WeatherResponse;
use owm_widg_config::config::Config;
use owm_widg_config::general::ApiVersion;
use std::fs;
use slint::SharedString;
slint::include_modules!();
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = dirs::config_dir()
@ -11,12 +14,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let toml_str = fs::read_to_string(&path)
.map_err(|_| format!("Failed to read config: {}", path.display()))?;
// Deserialize whole config
let cfg: Config = toml::from_str(&toml_str)?;
match cfg.general.api_version() {
ApiVersion::Free25 => {
// Read json data from cache file
let cache_path = dirs::cache_dir()
.ok_or("No cache dir found")?
.join("candydesktop/owm_widget.json");
@ -24,35 +25,59 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let json_data = fs::read_to_string(&cache_path)?;
let resp: WeatherResponse = serde_json::from_str(&json_data)?;
// Print city and country
if let Some(city) = &resp.name {
print!("City: {city}");
if let Some(sys) = &resp.sys
&& let Some(country) = &sys.country
{
print!(", {country}");
}
let city = resp.name.clone().unwrap_or_else(|| "Unknown".into());
let country = resp
.sys
.as_ref()
.and_then(|s| s.country.clone())
.unwrap_or_else(|| "".into());
println!();
}
let (weather_main, weather_description, icon) = if let Some(w) = resp.weather.first() {
(
w.main.clone(),
w.description.clone(),
w.icon.clone(),
)
} else {
("N/A".into(), "N/A".into(), "".into())
};
// Print weather description
if let Some(weather) = resp.weather.first() {
println!("Weather: {}", weather.main);
println!("Icon: {}", weather.icon);
}
let temp = resp.main.as_ref().and_then(|m| m.temp).unwrap_or(0.0);
let temperature = format!("{temp:.1}");
let unit = "C";
// Print temperature
if let Some(main_data) = &resp.main
&& let Some(temp) = main_data.temp
{
println!("Temperature: {temp:.1}°C");
}
}
other => {
return Err(format!("Unsupported api_version: {other:?}").into());
// Create and show the UI
let ui = MainWindow::new()?;
ui.set_city(SharedString::from(city));
ui.set_country(SharedString::from(country));
ui.set_weather_main(SharedString::from(weather_main));
ui.set_weather_description(SharedString::from(weather_description));
ui.set_weather_icon(SharedString::from(icon_to_nerd_font(&icon)));
ui.set_temperature(SharedString::from(temperature));
ui.set_unit(SharedString::from(unit));
ui.run()?;
}
other => return Err(format!("Unsupported api_version: {other:?}").into()),
}
Ok(())
}
/// Convert OWM icon codes (e.g. "01d", "09n") to Nerd Font weather glyphs.
fn icon_to_nerd_font(code: &str) -> String {
match code {
"01d" => "", // clear day
"01n" => "", // clear night
"02d" | "02n" => "", // few clouds
"03d" | "03n" => "", // scattered clouds
"04d" | "04n" => "", // broken clouds
"09d" | "09n" => "", // shower rain
"10d" | "10n" => "", // rain
"11d" | "11n" => "", // thunderstorm
"13d" | "13n" => "", // snow
"50d" | "50n" => "", // mist
_ => "".into(),
}
.into()
}

View File

@ -1,15 +1,25 @@
export component MainWindow inherits Window {
in property <string> city;
in property <string> country;
in property <string> weather_main;
in property <string> weather_description;
in property <string> weather_icon;
in property <string> temperature;
in property <string> unit;
always-on-top: true;
no-frame: true;
width: 300px;
height: 120px;
height: 124px;
background: transparent;
default-font-family: "IosevkaTermSlab Nerd Font Mono";
weather_popup := Rectangle {
width: 100%;
height: 100%;
border-radius: 12px;
background: @linear-gradient(160deg, #246a8ac9, #c5edff75);
background: @linear-gradient(160deg, #236a8bc7, #c5edff5c);
drop-shadow-blur: 15px;
drop-shadow-color: #00000066;
@ -20,10 +30,10 @@ export component MainWindow inherits Window {
/* Top: City */
Text {
text: "Province of Turin";
text: city + ", " + country;
font-size: 14px;
color: #fff;
font-family: "IosevkaTermSlab Nerd Font Mono";
//font-family:
horizontal-alignment: TextHorizontalAlignment.left;
height: 18px; // Fixed height for consistent spacing
font-weight: 600;
@ -32,14 +42,14 @@ export component MainWindow inherits Window {
/* Middle: icon on left, temp on right */
HorizontalLayout {
horizontal-stretch: 1.0;
height: 46px; // Fixed height for main content area
height: 50px; // Fixed height for main content area
padding: 0px;
/* Icon */
Text {
text: "";
font-family: "IosevkaTermSlab Nerd Font Mono";
text: weather_icon;
font-size: 40px;
horizontal-alignment: TextHorizontalAlignment.left;
color: #fff;
vertical-alignment: TextVerticalAlignment.center;
width: 46px; // Fixed width for consistent alignment
@ -55,10 +65,10 @@ export component MainWindow inherits Window {
HorizontalLayout {
spacing: 4px; // Tighter spacing for temperature elements
padding: 0px;
//height: 50px;
Text {
text: "124.4";
font-family: "IosevkaTermSlab Nerd Font Mono";
text: temperature;
font-size: 40px;
color: #fff;
vertical-alignment: TextVerticalAlignment.center;
@ -66,28 +76,27 @@ export component MainWindow inherits Window {
}
VerticalLayout {
height: 46px; // Match temperature text height
height: 50px; // Match temperature text height
padding: 0px;
spacing: 0px;
Text {
text: "o";
font-size: 20px;
font-size: 16px;
color: #fff;
font-family: "IosevkaTermSlab Nerd Font Mono";
horizontal-alignment: TextHorizontalAlignment.left;
vertical-alignment: TextVerticalAlignment.bottom;
height: 20px;
vertical-alignment: TextVerticalAlignment.center;
height: 25px;
font-weight: 800;
}
Text {
text: "F";
font-size: 18px;
text: unit;
font-size: 16px;
color: #fff;
font-family: "IosevkaTermSlab Nerd Font Mono";
horizontal-alignment: TextHorizontalAlignment.left;
vertical-alignment: TextVerticalAlignment.top;
vertical-alignment: TextVerticalAlignment.center;
height: 20px;
width: 20px;
font-weight: 800;
}
}
@ -103,10 +112,9 @@ export component MainWindow inherits Window {
/* Summary */
Text {
text: "Thunderstorm";
text: weather_main;
font-size: 18px;
color: #fff;
font-family: "IosevkaTermSlab Nerd Font Mono";
vertical-alignment: TextVerticalAlignment.top;
horizontal-alignment: TextHorizontalAlignment.left;
width: 40%; // Fixed percentage width for consistent layout
@ -115,12 +123,11 @@ export component MainWindow inherits Window {
/* Description */
Text {
text: "Thunderstorm with heavy drizzle";
text: weather_description;
font-size: 12px;
color: #ffffffcc;
font-family: "IosevkaTermSlab Nerd Font Mono";
horizontal-alignment: TextHorizontalAlignment.left;
vertical-alignment: TextVerticalAlignment.bottom;
horizontal-alignment: TextHorizontalAlignment.right;
vertical-alignment: TextVerticalAlignment.top;
horizontal-stretch: 1.0;
wrap: word-wrap;
height: 100%;