Skip to content

Commit 9357fe5

Browse files
committed
breeze: add wind indicator
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent b6ea2b5 commit 9357fe5

5 files changed

Lines changed: 142 additions & 0 deletions

File tree

breeze/animations.c

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,46 @@ static void update_particles(AnimState *state, double dt)
122122
}
123123
}
124124

125+
static void update_streaks(AnimState *state, double dt)
126+
{
127+
/* Wind streaks appear above ~5 m/s, scale up to max at ~15 m/s */
128+
double wind_ms = state->weather.windspeed / 3.6;
129+
int target = 0;
130+
131+
if (wind_ms >= 5.0) {
132+
double frac = (wind_ms - 5.0) / 10.0;
133+
134+
if (frac > 1.0) frac = 1.0;
135+
target = (int)(frac * ANIM_MAX_STREAKS);
136+
if (target < 1) target = 1;
137+
}
138+
139+
while (state->streak_count < target) {
140+
Particle *s = &state->streaks[state->streak_count];
141+
142+
s->x = -randf() * state->width * 0.3;
143+
s->y = randf() * state->height;
144+
s->speed = 150.0 + wind_ms * 20.0 + randf() * 100.0;
145+
s->size = 30.0 + randf() * 50.0; /* streak length */
146+
state->streak_count++;
147+
}
148+
149+
if (state->streak_count > target)
150+
state->streak_count = target;
151+
152+
for (int i = 0; i < state->streak_count; i++) {
153+
Particle *s = &state->streaks[i];
154+
155+
s->x += s->speed * dt;
156+
157+
if (s->x > state->width + s->size) {
158+
s->x = -s->size - randf() * state->width * 0.2;
159+
s->y = randf() * state->height;
160+
s->speed = 150.0 + wind_ms * 20.0 + randf() * 100.0;
161+
}
162+
}
163+
}
164+
125165
void anim_update(AnimState *state, double dt, const WeatherData *weather)
126166
{
127167
state->weather = *weather;
@@ -130,6 +170,7 @@ void anim_update(AnimState *state, double dt, const WeatherData *weather)
130170

131171
update_clouds(state, dt);
132172
update_particles(state, dt);
173+
update_streaks(state, dt);
133174
}
134175

135176
/* ------------------------------------------------------------------ */
@@ -265,11 +306,38 @@ static void draw_snow(const AnimState *state, cairo_t *cr)
265306
}
266307
}
267308

309+
static void draw_streaks(const AnimState *state, cairo_t *cr)
310+
{
311+
if (state->streak_count == 0)
312+
return;
313+
314+
cairo_set_line_width(cr, 1.0);
315+
316+
for (int i = 0; i < state->streak_count; i++) {
317+
const Particle *s = &state->streaks[i];
318+
/* Fade in/out at edges */
319+
double alpha = 0.12 + 0.06 * sin(state->time_accum * 1.5 + i);
320+
321+
cairo_pattern_t *grad = cairo_pattern_create_linear(
322+
s->x - s->size, s->y, s->x, s->y);
323+
cairo_pattern_add_color_stop_rgba(grad, 0.0, 1.0, 1.0, 1.0, 0.0);
324+
cairo_pattern_add_color_stop_rgba(grad, 0.3, 1.0, 1.0, 1.0, alpha);
325+
cairo_pattern_add_color_stop_rgba(grad, 1.0, 1.0, 1.0, 1.0, 0.0);
326+
327+
cairo_move_to(cr, s->x - s->size, s->y);
328+
cairo_line_to(cr, s->x, s->y);
329+
cairo_set_source(cr, grad);
330+
cairo_stroke(cr);
331+
cairo_pattern_destroy(grad);
332+
}
333+
}
334+
268335
void anim_draw(const AnimState *state, cairo_t *cr)
269336
{
270337
draw_sky(state, cr);
271338
draw_sun(state, cr);
272339
draw_clouds(state, cr);
340+
draw_streaks(state, cr);
273341

274342
bool rain = (state->weather.type == WEATHER_RAIN ||
275343
state->weather.type == WEATHER_DRIZZLE ||

breeze/animations.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#define ANIM_MAX_CLOUDS 20
99
#define ANIM_MAX_PARTICLES 300
10+
#define ANIM_MAX_STREAKS 15
1011

1112
typedef struct {
1213
double x, y;
@@ -34,6 +35,10 @@ typedef struct {
3435
Particle particles[ANIM_MAX_PARTICLES];
3536
int particle_count;
3637

38+
/* Wind streaks */
39+
Particle streaks[ANIM_MAX_STREAKS];
40+
int streak_count;
41+
3742
/* Screen dimensions */
3843
int width;
3944
int height;

breeze/breeze.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ typedef struct {
3131
GtkWidget *time_label;
3232
GtkWidget *temp_label;
3333
GtkWidget *desc_label;
34+
GtkWidget *wind_label;
3435
GtkWidget *sun_label;
3536
GtkWidget *web_view;
3637

@@ -108,6 +109,7 @@ static void update_weather_labels(void)
108109
if (!app.weather.valid) {
109110
gtk_label_set_text(GTK_LABEL(app.temp_label), "--\u00B0C");
110111
gtk_label_set_text(GTK_LABEL(app.desc_label), "No data");
112+
gtk_label_set_text(GTK_LABEL(app.wind_label), "");
111113
gtk_label_set_text(GTK_LABEL(app.sun_label), "");
112114
return;
113115
}
@@ -119,6 +121,15 @@ static void update_weather_labels(void)
119121
gtk_label_set_text(GTK_LABEL(app.desc_label),
120122
weather_description(app.weather.type));
121123

124+
/* Wind: API gives km/h, display in m/s with direction arrow and compass */
125+
double wind_ms = app.weather.windspeed / 3.6;
126+
char wind_buf[64];
127+
snprintf(wind_buf, sizeof(wind_buf), "%s %.0f m/s %s",
128+
weather_wind_arrow(app.weather.winddirection),
129+
wind_ms,
130+
weather_wind_compass(app.weather.winddirection));
131+
gtk_label_set_text(GTK_LABEL(app.wind_label), wind_buf);
132+
122133
char rise[8], set[8], sun_buf[64];
123134
weather_format_time(app.weather.sunrise, rise, sizeof(rise));
124135
weather_format_time(app.weather.sunset, set, sizeof(set));
@@ -258,6 +269,11 @@ static GtkWidget *create_weather_view(void)
258269
gtk_style_context_add_class(gtk_widget_get_style_context(app.desc_label),
259270
"overlay-text");
260271

272+
app.wind_label = gtk_label_new("");
273+
gtk_widget_set_halign(app.wind_label, GTK_ALIGN_CENTER);
274+
gtk_style_context_add_class(gtk_widget_get_style_context(app.wind_label),
275+
"overlay-small");
276+
261277
app.sun_label = gtk_label_new("");
262278
gtk_widget_set_halign(app.sun_label, GTK_ALIGN_CENTER);
263279
gtk_style_context_add_class(gtk_widget_get_style_context(app.sun_label),
@@ -270,6 +286,7 @@ static GtkWidget *create_weather_view(void)
270286
gtk_box_pack_start(GTK_BOX(vbox), app.time_label, FALSE, FALSE, 0);
271287
gtk_box_pack_start(GTK_BOX(vbox), app.temp_label, FALSE, FALSE, 0);
272288
gtk_box_pack_start(GTK_BOX(vbox), app.desc_label, FALSE, FALSE, 0);
289+
gtk_box_pack_start(GTK_BOX(vbox), app.wind_label, FALSE, FALSE, 0);
273290
gtk_box_pack_start(GTK_BOX(vbox), app.sun_label, FALSE, FALSE, 10);
274291

275292
/* Overlay: drawing area + labels on top */

breeze/weather.c

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,49 @@ void weather_format_time(double hours, char *buf, int bufsize)
121121
snprintf(buf, bufsize, "%02d:%02d", h % 24, m);
122122
}
123123

124+
const char *weather_wind_compass(double degrees)
125+
{
126+
/* Normalize to 0-360 */
127+
while (degrees < 0) degrees += 360;
128+
while (degrees >= 360) degrees -= 360;
129+
130+
/* 16-point compass */
131+
static const char *dirs[] = {
132+
"N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
133+
"S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"
134+
};
135+
int idx = (int)((degrees + 11.25) / 22.5) % 16;
136+
137+
return dirs[idx];
138+
}
139+
140+
const char *weather_wind_arrow(double degrees)
141+
{
142+
/*
143+
* Wind direction is where wind comes FROM.
144+
* Arrow should point the direction wind is BLOWING TO,
145+
* so rotate 180 degrees.
146+
*/
147+
double to = degrees + 180.0;
148+
149+
while (to >= 360) to -= 360;
150+
151+
/* 8 Unicode arrows, starting from N (up) going clockwise */
152+
static const char *arrows[] = {
153+
"\u2193", /* 0 / N -> blows south -> down arrow */
154+
"\u2199", /* 45 / NE -> blows SW */
155+
"\u2190", /* 90 / E -> blows west -> left arrow */
156+
"\u2196", /* 135 / SE -> blows NW */
157+
"\u2191", /* 180 / S -> blows north -> up arrow */
158+
"\u2197", /* 225 / SW -> blows NE */
159+
"\u2192", /* 270 / W -> blows east -> right arrow */
160+
"\u2198", /* 315 / NW -> blows SE */
161+
};
162+
int idx = (int)((to + 22.5) / 45.0) % 8;
163+
164+
return arrows[idx];
165+
}
166+
124167
WeatherData weather_fetch(double latitude, double longitude)
125168
{
126169
WeatherData data = { .valid = false };
@@ -177,11 +220,13 @@ WeatherData weather_fetch(double latitude, double longitude)
177220
if (current) {
178221
cJSON *temp = cJSON_GetObjectItem(current, "temperature");
179222
cJSON *wind = cJSON_GetObjectItem(current, "windspeed");
223+
cJSON *wdir = cJSON_GetObjectItem(current, "winddirection");
180224
cJSON *code = cJSON_GetObjectItem(current, "weathercode");
181225
cJSON *isday = cJSON_GetObjectItem(current, "is_day");
182226

183227
if (temp) data.temperature = temp->valuedouble;
184228
if (wind) data.windspeed = wind->valuedouble;
229+
if (wdir) data.winddirection = wdir->valuedouble;
185230
if (code) data.type = wmo_to_type(code->valueint, &data.intensity);
186231
if (isday) data.is_day = (isday->valueint != 0);
187232

breeze/weather.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ typedef enum {
1818
typedef struct {
1919
double temperature; /* Celsius */
2020
double windspeed; /* km/h */
21+
double winddirection; /* degrees, 0=N 90=E 180=S 270=W */
2122
WeatherType type;
2223
double intensity; /* 0.0 - 1.0 */
2324
int cloudcover; /* 0 - 100 percent */
@@ -40,6 +41,12 @@ const char *weather_description(WeatherType type);
4041
/* Format sunrise/sunset hours as HH:MM string into buf (>= 6 bytes) */
4142
void weather_format_time(double hours, char *buf, int bufsize);
4243

44+
/* Compass direction string from degrees (e.g. "N", "SW", "ENE") */
45+
const char *weather_wind_compass(double degrees);
46+
47+
/* Unicode arrow character for wind direction (points the way wind blows) */
48+
const char *weather_wind_arrow(double degrees);
49+
4350
/*
4451
* Geocode a location string to lat/lon using Open-Meteo's geocoding API.
4552
* Accepts "City" or "Country,City" (e.g., "Stockholm" or "Sweden,Stockholm").

0 commit comments

Comments
 (0)