Parent Directory | Revision Log
OSD
1 | harbaum | 71 | /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ |
2 | /* vim:set et sw=4 ts=4 cino=t0,(0: */ | ||
3 | harbaum | 70 | /* |
4 | * Copyright (C) Till Harbaum 2009 <till@harbaum.org> | ||
5 | * | ||
6 | * osm-gps-map is free software: you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License as published by the | ||
8 | * Free Software Foundation, either version 3 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * osm-gps-map is distributed in the hope that it will be useful, but | ||
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
14 | * See the GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program. If not, see <http://www.gnu.org/licenses/>. | ||
18 | */ | ||
19 | |||
20 | #include "config.h" | ||
21 | |||
22 | harbaum | 71 | /* parameters that can be overwritten from the config file: */ |
23 | /* OSM_GPS_MAP_OSD_DIAMETER */ | ||
24 | |||
25 | #ifndef USE_CAIRO | ||
26 | #error "OSD control display lacks a non-cairo implementation!" | ||
27 | harbaum | 70 | #endif |
28 | |||
29 | harbaum | 71 | #include <cairo.h> |
30 | |||
31 | #include "osm-gps-map.h" | ||
32 | |||
33 | harbaum | 70 | //the osd controls |
34 | typedef struct { | ||
35 | GdkPixmap *backup; | ||
36 | gint backup_x, backup_y; | ||
37 | harbaum | 71 | |
38 | cairo_surface_t *overlay; | ||
39 | |||
40 | harbaum | 70 | OsmGpsMapOsdGpsCallback cb; |
41 | gpointer data; | ||
42 | } osd_priv_t; | ||
43 | |||
44 | harbaum | 71 | /* the osd structure mainly contains various callbacks */ |
45 | /* required to draw and update the OSD */ | ||
46 | harbaum | 70 | typedef struct { |
47 | |||
48 | harbaum | 71 | void(enable_gps)(OsmGpsMap *, OsmGpsMapOsdGpsCallback, gpointer); |
49 | void(restore)(OsmGpsMap*); | ||
50 | void(draw)(OsmGpsMap *, gint, gint); | ||
51 | void(render)(OsmGpsMapPrivate *); | ||
52 | osd_button_t(*check)(gint, gint); /* check if x/y lies within OSD */ | ||
53 | void(free)(gpointer); | ||
54 | harbaum | 70 | |
55 | gpointer priv; | ||
56 | } osm_gps_map_osd_t; | ||
57 | |||
58 | harbaum | 71 | static osm_gps_map_osd_t osd_classic = { |
59 | .restore = osm_gps_map_osd_restore, | ||
60 | .draw = osm_gps_map_osd_draw_controls, | ||
61 | .check = osm_gps_map_osd_check, | ||
62 | .render = osm_gps_map_osd_render, | ||
63 | .enable_gps = osm_gps_map_osd_enable_gps, | ||
64 | .free = osm_gps_map_osd_free, | ||
65 | .priv = NULL; | ||
66 | } | ||
67 | |||
68 | /* this is the only function that's externally visible */ | ||
69 | osm_gps_map_osd_t * | ||
70 | osm_gps_map_osd_classic_init(void) | ||
71 | { | ||
72 | return osd_classic; | ||
73 | } | ||
74 | |||
75 | static void | ||
76 | osm_gps_map_osd_free(gpointer priv_ptr) | ||
77 | { | ||
78 | osd_priv_t *priv = (osd_priv_t *)priv_ptr; | ||
79 | |||
80 | |||
81 | g_free(priv); | ||
82 | } | ||
83 | |||
84 | harbaum | 70 | /* position and extent of bounding box */ |
85 | #define OSD_X (10) | ||
86 | #define OSD_Y (10) | ||
87 | |||
88 | #define OSD_COLOR 0.5, 0.5, 1 | ||
89 | #define OSD_COLOR_DISABLED 0.8, 0.8, 0.8 | ||
90 | |||
91 | /* parameters of the direction shape */ | ||
92 | #ifndef OSM_GPS_MAP_OSD_DIAMETER | ||
93 | #define D_RAD (30) // diameter of dpad | ||
94 | #else | ||
95 | #define D_RAD (OSM_GPS_MAP_OSD_DIAMETER) | ||
96 | #endif | ||
97 | #define D_TIP (4*D_RAD/5) // distance of arrow tip from dpad center | ||
98 | #define D_LEN (D_RAD/4) // length of arrow | ||
99 | #define D_WID (D_LEN) // width of arrow | ||
100 | |||
101 | /* parameters of the "zoom" pad */ | ||
102 | #define Z_STEP (D_RAD/4) // distance between dpad and zoom | ||
103 | #define Z_RAD (D_RAD/2) // radius of "caps" of zoom bar | ||
104 | |||
105 | /* shadow also depends on control size */ | ||
106 | #define OSD_SHADOW (D_RAD/6) | ||
107 | |||
108 | /* total width and height of controls incl. shadow */ | ||
109 | #define OSD_W (2*D_RAD + OSD_SHADOW) | ||
110 | #define OSD_H (2*D_RAD + Z_STEP + 2*Z_RAD + OSD_SHADOW) | ||
111 | |||
112 | #define OSD_LBL_SHADOW (OSD_SHADOW/2) | ||
113 | |||
114 | #define Z_TOP (2 * D_RAD + Z_STEP) | ||
115 | #define Z_MID (Z_TOP + Z_RAD) | ||
116 | #define Z_BOT (Z_MID + Z_RAD) | ||
117 | #define Z_LEFT (Z_RAD) | ||
118 | #define Z_RIGHT (2 * D_RAD - Z_RAD) | ||
119 | |||
120 | harbaum | 71 | |
121 | harbaum | 70 | /* create the cairo shape used for the zoom buttons */ |
122 | static void | ||
123 | harbaum | 71 | osm_gps_map_osd_zoom_shape(cairo_t *cr, gint x, gint y) |
124 | { | ||
125 | harbaum | 70 | cairo_move_to (cr, x+Z_LEFT, y+Z_TOP); |
126 | cairo_line_to (cr, x+Z_RIGHT, y+Z_TOP); | ||
127 | cairo_arc (cr, x+Z_RIGHT, y+Z_MID, Z_RAD, -M_PI/2, M_PI/2); | ||
128 | cairo_line_to (cr, x+Z_LEFT, y+Z_BOT); | ||
129 | cairo_arc (cr, x+Z_LEFT, y+Z_MID, Z_RAD, M_PI/2, -M_PI/2); | ||
130 | } | ||
131 | |||
132 | /* create the cairo shape used for the dpad */ | ||
133 | static void | ||
134 | harbaum | 71 | osm_gps_map_osd_dpad_shape(cairo_t *cr, gint x, gint y) |
135 | { | ||
136 | harbaum | 70 | cairo_arc (cr, x+D_RAD, y+D_RAD, D_RAD, 0, 2 * M_PI); |
137 | } | ||
138 | |||
139 | static gboolean | ||
140 | osm_gps_map_in_circle(gint x, gint y, gint cx, gint cy, gint rad) | ||
141 | { | ||
142 | return( pow(cx - x, 2) + pow(cy - y, 2) < rad * rad); | ||
143 | } | ||
144 | |||
145 | /* check whether x/y is within the dpad */ | ||
146 | static osd_button_t | ||
147 | osm_gps_map_osd_check_dpad(gint x, gint y) | ||
148 | { | ||
149 | /* within entire dpad circle */ | ||
150 | if( osm_gps_map_in_circle(x, y, OSD_X + D_RAD, OSD_Y + D_RAD, D_RAD)) | ||
151 | { | ||
152 | /* convert into position relative to dpads centre */ | ||
153 | x -= (OSD_X + D_RAD); | ||
154 | y -= (OSD_Y + D_RAD); | ||
155 | |||
156 | /* check for dpad center goes here! */ | ||
157 | if( osm_gps_map_in_circle(x, y, 0, 0, D_RAD/3)) | ||
158 | return OSD_GPS; | ||
159 | |||
160 | if( y < 0 && abs(x) < abs(y)) | ||
161 | return OSD_UP; | ||
162 | |||
163 | if( y > 0 && abs(x) < abs(y)) | ||
164 | return OSD_DOWN; | ||
165 | |||
166 | if( x < 0 && abs(y) < abs(x)) | ||
167 | return OSD_LEFT; | ||
168 | |||
169 | if( x > 0 && abs(y) < abs(x)) | ||
170 | return OSD_RIGHT; | ||
171 | |||
172 | return OSD_BG; | ||
173 | } | ||
174 | return OSD_NONE; | ||
175 | } | ||
176 | |||
177 | /* check whether x/y is within the zoom pads */ | ||
178 | static osd_button_t | ||
179 | osm_gps_map_osd_check_zoom(gint x, gint y) { | ||
180 | if( x > OSD_X && x < (OSD_X + OSD_W) && y > Z_TOP && y < (OSD_Y+Z_BOT)) { | ||
181 | |||
182 | /* within circle around (-) label */ | ||
183 | if( osm_gps_map_in_circle(x, y, OSD_X + Z_LEFT, OSD_Y + Z_MID, Z_RAD)) | ||
184 | return OSD_OUT; | ||
185 | |||
186 | /* between center of (-) button and center of entire zoom control area */ | ||
187 | if(x > OSD_LEFT && x < OSD_X + D_RAD) | ||
188 | return OSD_OUT; | ||
189 | |||
190 | /* within circle around (+) label */ | ||
191 | if( osm_gps_map_in_circle(x, y, OSD_X + Z_RIGHT, OSD_Y + Z_MID, Z_RAD)) | ||
192 | return OSD_IN; | ||
193 | |||
194 | /* between center of (+) button and center of entire zoom control area */ | ||
195 | if(x < OSD_RIGHT && x > OSD_X + D_RAD) | ||
196 | return OSD_IN; | ||
197 | } | ||
198 | |||
199 | return OSD_NONE; | ||
200 | } | ||
201 | |||
202 | static osd_button_t | ||
203 | osm_gps_map_osd_check(gint x, gint y) { | ||
204 | osd_button_t but = OSD_NONE; | ||
205 | |||
206 | /* first do a rough test for the OSD area. */ | ||
207 | /* this is just to avoid an unnecessary detailed test */ | ||
208 | if(x > OSD_X && x < OSD_X + OSD_W && | ||
209 | y > OSD_Y && y < OSD_Y + OSD_H) { | ||
210 | but = osm_gps_map_osd_check_dpad(x, y); | ||
211 | |||
212 | if(but == OSD_NONE) | ||
213 | but = osm_gps_map_osd_check_zoom(x, y); | ||
214 | } | ||
215 | |||
216 | return but; | ||
217 | } | ||
218 | |||
219 | static void | ||
220 | osm_gps_map_osd_shape_shadow(cairo_t *cr) { | ||
221 | cairo_set_source_rgba (cr, 0, 0, 0, 0.2); | ||
222 | cairo_fill (cr); | ||
223 | cairo_stroke (cr); | ||
224 | } | ||
225 | |||
226 | static void | ||
227 | osm_gps_map_osd_shape(cairo_t *cr) { | ||
228 | cairo_set_source_rgb (cr, 1, 1, 1); | ||
229 | cairo_fill_preserve (cr); | ||
230 | cairo_set_source_rgb (cr, OSD_COLOR); | ||
231 | cairo_set_line_width (cr, 1); | ||
232 | cairo_stroke (cr); | ||
233 | } | ||
234 | |||
235 | static void | ||
236 | osm_gps_map_osd_dpad_labels(cairo_t *cr, gint x, gint y) { | ||
237 | /* move reference to dpad center */ | ||
238 | x += D_RAD; | ||
239 | y += D_RAD; | ||
240 | |||
241 | const static gint offset[][3][2] = { | ||
242 | /* left arrow/triangle */ | ||
243 | { { -D_TIP+D_LEN, -D_WID }, { -D_LEN, D_WID }, { +D_LEN, D_WID } }, | ||
244 | /* right arrow/triangle */ | ||
245 | { { +D_TIP-D_LEN, -D_WID }, { +D_LEN, D_WID }, { -D_LEN, D_WID } }, | ||
246 | /* top arrow/triangle */ | ||
247 | { { -D_WID, -D_TIP+D_LEN }, { D_WID, -D_LEN }, { D_WID, +D_LEN } }, | ||
248 | /* bottom arrow/triangle */ | ||
249 | { { -D_WID, +D_TIP-D_LEN }, { D_WID, +D_LEN }, { D_WID, -D_LEN } } | ||
250 | }; | ||
251 | |||
252 | int i; | ||
253 | for(i=0;i<4;i++) { | ||
254 | cairo_move_to (cr, x + offset[i][0][0], y + offset[i][0][1]); | ||
255 | cairo_rel_line_to (cr, offset[i][1][0], offset[i][1][1]); | ||
256 | cairo_rel_line_to (cr, offset[i][2][0], offset[i][2][1]); | ||
257 | } | ||
258 | } | ||
259 | |||
260 | /* draw the sattelite dish icon in the center of the dpad */ | ||
261 | #define GPS_V0 (D_RAD/8) | ||
262 | #define GPS_V1 (D_RAD/10) | ||
263 | #define GPS_V2 (D_RAD/5) | ||
264 | |||
265 | /* draw a satellite receiver dish */ | ||
266 | static void | ||
267 | osm_gps_map_osd_dpad_gps(cairo_t *cr, gint x, gint y) { | ||
268 | /* move reference to dpad center */ | ||
269 | x += D_RAD; | ||
270 | y += D_RAD + GPS_V0; | ||
271 | |||
272 | cairo_move_to (cr, x-GPS_V0, y+GPS_V0); | ||
273 | cairo_rel_line_to (cr, +GPS_V0, -GPS_V0); | ||
274 | cairo_rel_line_to (cr, +GPS_V0, +GPS_V0); | ||
275 | cairo_close_path (cr); | ||
276 | |||
277 | cairo_move_to (cr, x+GPS_V1-GPS_V2, y-2*GPS_V2); | ||
278 | cairo_curve_to (cr, x-GPS_V2, y, x+GPS_V1, y+GPS_V1, x+GPS_V1+GPS_V2, y); | ||
279 | cairo_close_path (cr); | ||
280 | |||
281 | x += GPS_V1; | ||
282 | cairo_move_to (cr, x, y-GPS_V2); | ||
283 | cairo_rel_line_to (cr, +GPS_V1, -GPS_V1); | ||
284 | } | ||
285 | |||
286 | #define Z_LEN (2*Z_RAD/3) | ||
287 | |||
288 | static void | ||
289 | osm_gps_map_osd_zoom_labels(cairo_t *cr, gint x, gint y) { | ||
290 | cairo_move_to (cr, x + Z_LEFT - Z_LEN, y + Z_MID); | ||
291 | cairo_line_to (cr, x + Z_LEFT + Z_LEN, y + Z_MID); | ||
292 | |||
293 | cairo_move_to (cr, x + Z_RIGHT, y + Z_MID - Z_LEN); | ||
294 | cairo_line_to (cr, x + Z_RIGHT, y + Z_MID + Z_LEN); | ||
295 | cairo_move_to (cr, x + Z_RIGHT - Z_LEN, y + Z_MID); | ||
296 | cairo_line_to (cr, x + Z_RIGHT + Z_LEN, y + Z_MID); | ||
297 | } | ||
298 | |||
299 | static void | ||
300 | osm_gps_map_osd_labels(cairo_t *cr, gint width, gboolean enabled) { | ||
301 | if(enabled) cairo_set_source_rgb (cr, OSD_COLOR); | ||
302 | else cairo_set_source_rgb (cr, OSD_COLOR_DISABLED); | ||
303 | cairo_set_line_width (cr, width); | ||
304 | cairo_stroke (cr); | ||
305 | } | ||
306 | |||
307 | static void | ||
308 | osm_gps_map_osd_labels_shadow(cairo_t *cr, gint width, gboolean enabled) { | ||
309 | cairo_set_source_rgba (cr, 0, 0, 0, enabled?0.3:0.15); | ||
310 | cairo_set_line_width (cr, width); | ||
311 | cairo_stroke (cr); | ||
312 | } | ||
313 | |||
314 | static void | ||
315 | osm_gps_map_osd_render(OsmGpsMapPrivate *priv) { | ||
316 | |||
317 | /* first fill with transparency */ | ||
318 | cairo_t *cr = cairo_create(priv->osd.overlay); | ||
319 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); | ||
320 | cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0); | ||
321 | cairo_paint(cr); | ||
322 | |||
323 | cairo_set_operator(cr, CAIRO_OPERATOR_OVER); | ||
324 | |||
325 | /* --------- draw zoom and dpad shape shadow ----------- */ | ||
326 | gint x = 0, y = 0; | ||
327 | |||
328 | osm_gps_map_osd_zoom_shape(cr, x + OSD_SHADOW, y + OSD_SHADOW); | ||
329 | osm_gps_map_osd_shape_shadow(cr); | ||
330 | osm_gps_map_osd_dpad_shape(cr, x + OSD_SHADOW, y + OSD_SHADOW); | ||
331 | osm_gps_map_osd_shape_shadow(cr); | ||
332 | |||
333 | /* --------- draw zoom and dpad shape ----------- */ | ||
334 | |||
335 | osm_gps_map_osd_zoom_shape(cr, x, y); | ||
336 | osm_gps_map_osd_shape(cr); | ||
337 | osm_gps_map_osd_dpad_shape(cr, x, y); | ||
338 | osm_gps_map_osd_shape(cr); | ||
339 | |||
340 | /* --------- draw zoom and dpad labels --------- */ | ||
341 | |||
342 | osm_gps_map_osd_zoom_labels(cr, x + OSD_LBL_SHADOW, y + OSD_LBL_SHADOW); | ||
343 | osm_gps_map_osd_dpad_labels(cr, x + OSD_LBL_SHADOW, y + OSD_LBL_SHADOW); | ||
344 | osm_gps_map_osd_labels_shadow(cr, Z_RAD/3, TRUE); | ||
345 | osm_gps_map_osd_dpad_gps(cr, x + OSD_LBL_SHADOW, y + OSD_LBL_SHADOW); | ||
346 | osm_gps_map_osd_labels_shadow(cr, Z_RAD/6, priv->osd.cb != NULL); | ||
347 | |||
348 | osm_gps_map_osd_zoom_labels(cr, x, y); | ||
349 | osm_gps_map_osd_dpad_labels(cr, x, y); | ||
350 | osm_gps_map_osd_labels(cr, Z_RAD/3, TRUE); | ||
351 | osm_gps_map_osd_dpad_gps(cr, x, y); | ||
352 | osm_gps_map_osd_labels(cr, Z_RAD/6, priv->osd.cb != NULL); | ||
353 | |||
354 | cairo_destroy(cr); | ||
355 | } | ||
356 | |||
357 | static void | ||
358 | osm_gps_map_osd_draw_controls (OsmGpsMap *map, gint xoffset, gint yoffset) | ||
359 | { | ||
360 | OsmGpsMapPrivate *priv = map->priv; | ||
361 | |||
362 | /* backup previous contents */ | ||
363 | if(!priv->osd.backup) | ||
364 | priv->osd.backup = gdk_pixmap_new(priv->pixmap, OSD_W+2, OSD_H+2, -1); | ||
365 | |||
366 | gint x = OSD_X + EXTRA_BORDER + xoffset; | ||
367 | gint y = OSD_Y + EXTRA_BORDER + yoffset; | ||
368 | |||
369 | /* create backup of background */ | ||
370 | gdk_draw_drawable(priv->osd.backup, | ||
371 | GTK_WIDGET(map)->style->fg_gc[GTK_WIDGET_STATE(GTK_WIDGET(map))], | ||
372 | priv->pixmap, x-1, y-1, 0, 0, OSD_W+2, OSD_H+2); | ||
373 | |||
374 | priv->osd.backup_x = x-1; | ||
375 | priv->osd.backup_y = y-1; | ||
376 | |||
377 | /* OSD itself uses some off-screen rendering, so check if the */ | ||
378 | /* offscreen buffer is present and create it if not */ | ||
379 | if(!priv->osd.overlay) { | ||
380 | /* create overlay ... */ | ||
381 | priv->osd.overlay = | ||
382 | cairo_image_surface_create(CAIRO_FORMAT_ARGB32, OSD_W, OSD_H); | ||
383 | /* ... and render it */ | ||
384 | osm_gps_map_osd_render(priv); | ||
385 | } | ||
386 | |||
387 | // now draw this onto the original context | ||
388 | cairo_t *cr = gdk_cairo_create(priv->pixmap); | ||
389 | cairo_set_source_surface(cr, priv->osd.overlay, x, y); | ||
390 | cairo_paint(cr); | ||
391 | cairo_destroy(cr); | ||
392 | } | ||
393 | |||
394 | static void | ||
395 | osm_gps_map_osd_restore (OsmGpsMap *map) | ||
396 | { | ||
397 | OsmGpsMapPrivate *priv = map->priv; | ||
398 | |||
399 | /* restore backup of previous contents */ | ||
400 | if(priv->osd.backup) { | ||
401 | /* create backup of background */ | ||
402 | gdk_draw_drawable(priv->pixmap, | ||
403 | GTK_WIDGET(map)->style->fg_gc[GTK_WIDGET_STATE(GTK_WIDGET(map))], | ||
404 | priv->osd.backup, 0, 0, | ||
405 | priv->osd.backup_x, priv->osd.backup_y, OSD_W+2, OSD_H+2); | ||
406 | } | ||
407 | } | ||
408 | |||
409 | harbaum | 71 | static void |
410 | osm_gps_map_osd_enable_gps (OsmGpsMap *map, OsmGpsMapOsdGpsCallback cb, gpointer data) | ||
411 | harbaum | 70 | { |
412 | OsmGpsMapPrivate *priv; | ||
413 | |||
414 | g_return_if_fail (OSM_IS_GPS_MAP (map)); | ||
415 | priv = map->priv; | ||
416 | |||
417 | priv->osd.cb = cb; | ||
418 | priv->osd.data = data; | ||
419 | |||
420 | /* this may have changed the state of the gps button */ | ||
421 | /* we thus re-render the overlay */ | ||
422 | osm_gps_map_osd_render(priv); | ||
423 | |||
424 | osm_gps_map_map_redraw_idle(map); | ||
425 | } |