Dynamic values
- Pass values to display in Slint UI - Actual weather data instead of fixed dummy data
This commit is contained in:
parent
3262745771
commit
4fe5878d36
@ -2,6 +2,7 @@
|
|||||||
name = "openweatherwidget"
|
name = "openweatherwidget"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
owm-rs = { git = "https://git.candifloss.cc/candifloss/OpenWeatherMapSDK.git" }
|
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"
|
toml = "0.9.7"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
serde_json = "1.0.145"
|
serde_json = "1.0.145"
|
||||||
slint = "1.13"
|
slint = "1.14.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
slint-build = "1.13.1"
|
slint-build = "1.14.1"
|
||||||
|
|||||||
3
widget/build.rs
Normal file
3
widget/build.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
slint_build::compile("ui/widget-popup.slint").unwrap();
|
||||||
|
}
|
||||||
@ -2,6 +2,9 @@ use owm_rs::free_api_v25::current::WeatherResponse;
|
|||||||
use owm_widg_config::config::Config;
|
use owm_widg_config::config::Config;
|
||||||
use owm_widg_config::general::ApiVersion;
|
use owm_widg_config::general::ApiVersion;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
use slint::SharedString;
|
||||||
|
|
||||||
|
slint::include_modules!();
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let path = dirs::config_dir()
|
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)
|
let toml_str = fs::read_to_string(&path)
|
||||||
.map_err(|_| format!("Failed to read config: {}", path.display()))?;
|
.map_err(|_| format!("Failed to read config: {}", path.display()))?;
|
||||||
|
|
||||||
// Deserialize whole config
|
|
||||||
let cfg: Config = toml::from_str(&toml_str)?;
|
let cfg: Config = toml::from_str(&toml_str)?;
|
||||||
|
|
||||||
match cfg.general.api_version() {
|
match cfg.general.api_version() {
|
||||||
ApiVersion::Free25 => {
|
ApiVersion::Free25 => {
|
||||||
// Read json data from cache file
|
|
||||||
let cache_path = dirs::cache_dir()
|
let cache_path = dirs::cache_dir()
|
||||||
.ok_or("No cache dir found")?
|
.ok_or("No cache dir found")?
|
||||||
.join("candydesktop/owm_widget.json");
|
.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 json_data = fs::read_to_string(&cache_path)?;
|
||||||
let resp: WeatherResponse = serde_json::from_str(&json_data)?;
|
let resp: WeatherResponse = serde_json::from_str(&json_data)?;
|
||||||
|
|
||||||
// Print city and country
|
let city = resp.name.clone().unwrap_or_else(|| "Unknown".into());
|
||||||
if let Some(city) = &resp.name {
|
let country = resp
|
||||||
print!("City: {city}");
|
.sys
|
||||||
if let Some(sys) = &resp.sys
|
.as_ref()
|
||||||
&& let Some(country) = &sys.country
|
.and_then(|s| s.country.clone())
|
||||||
{
|
.unwrap_or_else(|| "".into());
|
||||||
print!(", {country}");
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
let temp = resp.main.as_ref().and_then(|m| m.temp).unwrap_or(0.0);
|
||||||
if let Some(weather) = resp.weather.first() {
|
let temperature = format!("{temp:.1}");
|
||||||
println!("Weather: {}", weather.main);
|
let unit = "C";
|
||||||
println!("Icon: {}", weather.icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print temperature
|
// Create and show the UI
|
||||||
if let Some(main_data) = &resp.main
|
let ui = MainWindow::new()?;
|
||||||
&& let Some(temp) = main_data.temp
|
ui.set_city(SharedString::from(city));
|
||||||
{
|
ui.set_country(SharedString::from(country));
|
||||||
println!("Temperature: {temp:.1}°C");
|
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)));
|
||||||
other => {
|
ui.set_temperature(SharedString::from(temperature));
|
||||||
return Err(format!("Unsupported api_version: {other:?}").into());
|
ui.set_unit(SharedString::from(unit));
|
||||||
|
|
||||||
|
ui.run()?;
|
||||||
}
|
}
|
||||||
|
other => return Err(format!("Unsupported api_version: {other:?}").into()),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
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()
|
||||||
|
}
|
||||||
|
|||||||
@ -1,15 +1,25 @@
|
|||||||
export component MainWindow inherits Window {
|
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;
|
always-on-top: true;
|
||||||
no-frame: true;
|
no-frame: true;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 120px;
|
height: 124px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
default-font-family: "IosevkaTermSlab Nerd Font Mono";
|
||||||
|
|
||||||
weather_popup := Rectangle {
|
weather_popup := Rectangle {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: @linear-gradient(160deg, #246a8ac9, #c5edff75);
|
background: @linear-gradient(160deg, #236a8bc7, #c5edff5c);
|
||||||
drop-shadow-blur: 15px;
|
drop-shadow-blur: 15px;
|
||||||
drop-shadow-color: #00000066;
|
drop-shadow-color: #00000066;
|
||||||
|
|
||||||
@ -20,10 +30,10 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
/* Top: City */
|
/* Top: City */
|
||||||
Text {
|
Text {
|
||||||
text: "Province of Turin";
|
text: city + ", " + country;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: "IosevkaTermSlab Nerd Font Mono";
|
//font-family:
|
||||||
horizontal-alignment: TextHorizontalAlignment.left;
|
horizontal-alignment: TextHorizontalAlignment.left;
|
||||||
height: 18px; // Fixed height for consistent spacing
|
height: 18px; // Fixed height for consistent spacing
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@ -32,14 +42,14 @@ export component MainWindow inherits Window {
|
|||||||
/* Middle: icon on left, temp on right */
|
/* Middle: icon on left, temp on right */
|
||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
horizontal-stretch: 1.0;
|
horizontal-stretch: 1.0;
|
||||||
height: 46px; // Fixed height for main content area
|
height: 50px; // Fixed height for main content area
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
|
||||||
/* Icon */
|
/* Icon */
|
||||||
Text {
|
Text {
|
||||||
text: "";
|
text: weather_icon;
|
||||||
font-family: "IosevkaTermSlab Nerd Font Mono";
|
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
horizontal-alignment: TextHorizontalAlignment.left;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
vertical-alignment: TextVerticalAlignment.center;
|
vertical-alignment: TextVerticalAlignment.center;
|
||||||
width: 46px; // Fixed width for consistent alignment
|
width: 46px; // Fixed width for consistent alignment
|
||||||
@ -55,10 +65,10 @@ export component MainWindow inherits Window {
|
|||||||
HorizontalLayout {
|
HorizontalLayout {
|
||||||
spacing: 4px; // Tighter spacing for temperature elements
|
spacing: 4px; // Tighter spacing for temperature elements
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
//height: 50px;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "124.4";
|
text: temperature;
|
||||||
font-family: "IosevkaTermSlab Nerd Font Mono";
|
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
vertical-alignment: TextVerticalAlignment.center;
|
vertical-alignment: TextVerticalAlignment.center;
|
||||||
@ -66,28 +76,27 @@ export component MainWindow inherits Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VerticalLayout {
|
VerticalLayout {
|
||||||
height: 46px; // Match temperature text height
|
height: 50px; // Match temperature text height
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
spacing: 0px;
|
spacing: 0px;
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "o";
|
text: "o";
|
||||||
font-size: 20px;
|
font-size: 16px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: "IosevkaTermSlab Nerd Font Mono";
|
|
||||||
horizontal-alignment: TextHorizontalAlignment.left;
|
horizontal-alignment: TextHorizontalAlignment.left;
|
||||||
vertical-alignment: TextVerticalAlignment.bottom;
|
vertical-alignment: TextVerticalAlignment.center;
|
||||||
height: 20px;
|
height: 25px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
text: "F";
|
text: unit;
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: "IosevkaTermSlab Nerd Font Mono";
|
|
||||||
horizontal-alignment: TextHorizontalAlignment.left;
|
horizontal-alignment: TextHorizontalAlignment.left;
|
||||||
vertical-alignment: TextVerticalAlignment.top;
|
vertical-alignment: TextVerticalAlignment.center;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,10 +112,9 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
/* Summary */
|
/* Summary */
|
||||||
Text {
|
Text {
|
||||||
text: "Thunderstorm";
|
text: weather_main;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: "IosevkaTermSlab Nerd Font Mono";
|
|
||||||
vertical-alignment: TextVerticalAlignment.top;
|
vertical-alignment: TextVerticalAlignment.top;
|
||||||
horizontal-alignment: TextHorizontalAlignment.left;
|
horizontal-alignment: TextHorizontalAlignment.left;
|
||||||
width: 40%; // Fixed percentage width for consistent layout
|
width: 40%; // Fixed percentage width for consistent layout
|
||||||
@ -115,12 +123,11 @@ export component MainWindow inherits Window {
|
|||||||
|
|
||||||
/* Description */
|
/* Description */
|
||||||
Text {
|
Text {
|
||||||
text: "Thunderstorm with heavy drizzle";
|
text: weather_description;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #ffffffcc;
|
color: #ffffffcc;
|
||||||
font-family: "IosevkaTermSlab Nerd Font Mono";
|
horizontal-alignment: TextHorizontalAlignment.right;
|
||||||
horizontal-alignment: TextHorizontalAlignment.left;
|
vertical-alignment: TextVerticalAlignment.top;
|
||||||
vertical-alignment: TextVerticalAlignment.bottom;
|
|
||||||
horizontal-stretch: 1.0;
|
horizontal-stretch: 1.0;
|
||||||
wrap: word-wrap;
|
wrap: word-wrap;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user