From 10733383d326579de7e9284984b2f8b32d15e01a Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:20:31 +0100 Subject: [PATCH] Add basic weather plugin --- .../BuiltInPlugins/WeatherPlugin.cs | 207 ++++++++++++++++++ src/DesktopMagic/MainWindow.xaml.cs | 5 +- 2 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs diff --git a/src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs b/src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs new file mode 100644 index 0000000..e7298b2 --- /dev/null +++ b/src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs @@ -0,0 +1,207 @@ +using DesktopMagic.Api; +using DesktopMagic.Api.Settings; + +using System; +using System.Diagnostics; +using System.Drawing; +using System.Globalization; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; + +namespace DesktopMagic.BuiltInPlugins; + +public class WeatherPlugin : Plugin +{ + [Setting("city-name", "City Name:")] + private readonly TextBox cityInput = new TextBox("Berlin"); + + [Setting("search-btn")] + private readonly Button searchButton = new Button("Update Location"); + + [Setting("show-city", "Show City Name")] + private readonly CheckBox showCity = new CheckBox(true); + + [Setting("show-temp", "Show Temperature")] + private readonly CheckBox showTemp = new CheckBox(true); + + [Setting("show-status", "Show Weather Status")] + private readonly CheckBox showStatus = new CheckBox(true); + + [Setting("show-time", "Show Last Updated")] + private readonly CheckBox showTime = new CheckBox(false); + + [Setting("font-size", "Base Font Size")] + private readonly Slider fontSizeSlider = new Slider(10, 100, 30); + + public override int UpdateInterval { get; set; } = 900000; // 15 Minutes + + private readonly HttpClient httpClient = new HttpClient(); + + private string? cachedLat; + private string? cachedLon; + private string currentTemp = "--"; + private string weatherStatus = "Enter city and search"; + private string foundCityName = "No City Selected"; + private string lastUpdated = ""; + private bool isLoading = false; + + public override async void Start() + { + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (DesktopMagic)"); + + // Setup Event Handlers for instant UI updates when settings change + showCity.OnValueChanged += Application.UpdateWindow; + showTemp.OnValueChanged += Application.UpdateWindow; + showStatus.OnValueChanged += Application.UpdateWindow; + showTime.OnValueChanged += Application.UpdateWindow; + fontSizeSlider.OnValueChanged += Application.UpdateWindow; + + await UpdateLocationAndWeather(); + Application.UpdateWindow(); + + searchButton.OnClick += () => + { + isLoading = true; + Application.UpdateWindow(); + + _ = Task.Run(async () => + { + await UpdateLocationAndWeather(); + isLoading = false; + Application.UpdateWindow(); + }); + }; + } + + public override Bitmap Main() + { + // Periodic background update + if (!isLoading && cachedLat != null && cachedLon != null) + { + _ = UpdateWeatherOnly(cachedLat, cachedLon); + } + + Bitmap bmp = new Bitmap(1200, 800); + bmp.SetResolution(300, 300); + + using (Graphics g = Graphics.FromImage(bmp)) + { + g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; + g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit; + g.Clear(Color.Transparent); + + int lineCount = 0; + int baseSize = (int)fontSizeSlider.Value; + + if (isLoading) + { + DrawLine(bmp, g, "Fetching...", ref lineCount, baseSize, FontStyle.Italic); + } + else + { + if (showCity.Value) + { + DrawLine(bmp, g, foundCityName.ToUpper(), ref lineCount, baseSize, FontStyle.Bold); + } + + if (showTemp.Value) + { + DrawLine(bmp, g, currentTemp, ref lineCount, baseSize, FontStyle.Bold); + } + + if (showStatus.Value) + { + DrawLine(bmp, g, weatherStatus, ref lineCount, baseSize, FontStyle.Regular); + } + + if (showTime.Value && !string.IsNullOrEmpty(lastUpdated)) + { + DrawLine(bmp, g, $"Updated: {lastUpdated}", ref lineCount, baseSize, FontStyle.Italic); + } + } + } + + return bmp; + } + + private void DrawLine(Bitmap bitmap, Graphics graphics, string text, ref int lineCount, int fontSize, FontStyle style) + { + using Font font = new Font(Application.Theme.Font, fontSize, style); + SizeF textSize = graphics.MeasureString(text, font); + + float x = (bitmap.Width - textSize.Width) / 2; + // Adjust vertical spacing based on font size + float y = (lineCount == 0) ? 60 : (lineCount * (textSize.Height + 10)) + 60; + + using Brush brush = new SolidBrush(Application.Theme.PrimaryColor); + graphics.DrawString(text, font, brush, x, y); + + lineCount++; + } + + private async Task UpdateLocationAndWeather() + { + try + { + string geoUrl = $"https://geocoding-api.open-meteo.com/v1/search?name={Uri.EscapeDataString(cityInput.Value.Trim())}&count=1&language=en&format=json"; + string geoJson = await httpClient.GetStringAsync(geoUrl); + + using JsonDocument geoDoc = JsonDocument.Parse(geoJson); + if (!geoDoc.RootElement.TryGetProperty("results", out JsonElement results) || results.GetArrayLength() == 0) + { + foundCityName = cityInput.Value; + currentTemp = "--"; + weatherStatus = "City not found"; + return; + } + + JsonElement location = results[0]; + cachedLat = location.GetProperty("latitude").GetDouble().ToString(CultureInfo.InvariantCulture); + cachedLon = location.GetProperty("longitude").GetDouble().ToString(CultureInfo.InvariantCulture); + foundCityName = location.GetProperty("name").GetString() ?? cityInput.Value; + + // We update the textbox to the "official" name found by the API + cityInput.Value = foundCityName; + + await UpdateWeatherOnly(cachedLat, cachedLon); + } + catch (Exception ex) + { + Debug.WriteLine($"Error: {ex.Message}"); + weatherStatus = "Connection Error"; + } + } + + private async Task UpdateWeatherOnly(string lat, string lon) + { + try + { + string weatherUrl = $"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}¤t_weather=true"; + string weatherJson = await httpClient.GetStringAsync(weatherUrl); + + using JsonDocument weatherDoc = JsonDocument.Parse(weatherJson); + JsonElement current = weatherDoc.RootElement.GetProperty("current_weather"); + + currentTemp = $"{current.GetProperty("temperature").GetDouble()}°C"; + weatherStatus = MapWeatherCode(current.GetProperty("weathercode").GetInt32()); + lastUpdated = DateTime.Now.ToString("HH:mm"); + } + catch { /* Ignore background update errors */ } + } + + private string MapWeatherCode(int code) + { + return code switch + { + 0 => "Clear Sky", + 1 or 2 or 3 => "Partly Cloudy", + 45 or 48 => "Foggy", + 51 or 53 or 55 => "Drizzle", + 61 or 63 or 65 => "Rainy", + 71 or 73 or 75 => "Snowy", + 95 or 96 or 99 => "Thunderstorm", + _ => "Cloudy" + }; + } +} \ No newline at end of file diff --git a/src/DesktopMagic/MainWindow.xaml.cs b/src/DesktopMagic/MainWindow.xaml.cs index 4cc4f42..ea3a1a6 100644 --- a/src/DesktopMagic/MainWindow.xaml.cs +++ b/src/DesktopMagic/MainWindow.xaml.cs @@ -27,7 +27,8 @@ namespace DesktopMagic {new((string)App.LanguageDictionary["musicVisualizer"], 1), typeof(MusicVisualizerPlugin)}, {new((string)App.LanguageDictionary["time"],2), typeof(TimePlugin)}, {new((string)App.LanguageDictionary["date"],3), typeof(DatePlugin)}, - {new((string)App.LanguageDictionary["cpuUsage"], 4), typeof(CpuMonitorPlugin)} + {new((string)App.LanguageDictionary["cpuUsage"], 4), typeof(CpuMonitorPlugin)}, + {new("Weather", 5), typeof(WeatherPlugin)}, }; private bool loaded = false; @@ -376,7 +377,7 @@ namespace DesktopMagic TextBlock textBlock = new() { - Text = $"{settingElement.Name}:", + Text = string.IsNullOrWhiteSpace(settingElement.Name) ? string.Empty : $"{settingElement.Name}:", Padding = new Thickness(0, 0, 3, 0), VerticalAlignment = VerticalAlignment.Center };