--- trunk/src/osm-gps-map-osd-classic.c 2009/08/21 13:20:40 71 +++ trunk/src/osm-gps-map-osd-classic.c 2009/09/09 11:50:50 103 @@ -17,10 +17,19 @@ * with this program. If not, see . */ -#include "config.h" +#include "config.h" +#include // abs +#include // M_PI/cos() /* parameters that can be overwritten from the config file: */ -/* OSM_GPS_MAP_OSD_DIAMETER */ +/* OSD_DIAMETER */ +/* OSD_X, OSD_Y */ + +#ifndef OSD_SCALE_FONT_SIZE +#define OSD_SCALE_FONT_SIZE 12 +#endif +#define OSD_SCALE_W (10*OSD_SCALE_FONT_SIZE) +#define OSD_SCALE_H (5*OSD_SCALE_FONT_SIZE/2) #ifndef USE_CAIRO #error "OSD control display lacks a non-cairo implementation!" @@ -29,70 +38,43 @@ #include #include "osm-gps-map.h" +#include "osm-gps-map-osd-classic.h" //the osd controls typedef struct { - GdkPixmap *backup; - gint backup_x, backup_y; - + /* the offscreen representation of the OSD */ cairo_surface_t *overlay; - OsmGpsMapOsdGpsCallback cb; - gpointer data; -} osd_priv_t; - -/* the osd structure mainly contains various callbacks */ -/* required to draw and update the OSD */ -typedef struct { - - void(enable_gps)(OsmGpsMap *, OsmGpsMapOsdGpsCallback, gpointer); - void(restore)(OsmGpsMap*); - void(draw)(OsmGpsMap *, gint, gint); - void(render)(OsmGpsMapPrivate *); - osd_button_t(*check)(gint, gint); /* check if x/y lies within OSD */ - void(free)(gpointer); - - gpointer priv; -} osm_gps_map_osd_t; - -static osm_gps_map_osd_t osd_classic = { - .restore = osm_gps_map_osd_restore, - .draw = osm_gps_map_osd_draw_controls, - .check = osm_gps_map_osd_check, - .render = osm_gps_map_osd_render, - .enable_gps = osm_gps_map_osd_enable_gps, - .free = osm_gps_map_osd_free, - .priv = NULL; -} - -/* this is the only function that's externally visible */ -osm_gps_map_osd_t * -osm_gps_map_osd_classic_init(void) -{ - return osd_classic; -} - -static void -osm_gps_map_osd_free(gpointer priv_ptr) -{ - osd_priv_t *priv = (osd_priv_t *)priv_ptr; +#ifdef OSD_SCALE + cairo_surface_t *scale; + int scale_zoom; +#endif +#ifdef OSD_SOURCE_SEL + /* values to handle the "source" menu */ + cairo_surface_t *map_source; + gboolean expanded; + gint shift, dir, count; + gint handler_id; + gint width, height; +#endif - g_free(priv); -} +} osd_priv_t; /* position and extent of bounding box */ +#ifndef OSD_X #define OSD_X (10) -#define OSD_Y (10) +#endif -#define OSD_COLOR 0.5, 0.5, 1 -#define OSD_COLOR_DISABLED 0.8, 0.8, 0.8 +#ifndef OSD_Y +#define OSD_Y (10) +#endif /* parameters of the direction shape */ -#ifndef OSM_GPS_MAP_OSD_DIAMETER +#ifndef OSD_DIAMETER #define D_RAD (30) // diameter of dpad #else -#define D_RAD (OSM_GPS_MAP_OSD_DIAMETER) +#define D_RAD (OSD_DIAMETER) #endif #define D_TIP (4*D_RAD/5) // distance of arrow tip from dpad center #define D_LEN (D_RAD/4) // length of arrow @@ -102,25 +84,44 @@ #define Z_STEP (D_RAD/4) // distance between dpad and zoom #define Z_RAD (D_RAD/2) // radius of "caps" of zoom bar +#ifdef OSD_SHADOW_ENABLE /* shadow also depends on control size */ #define OSD_SHADOW (D_RAD/6) +#else +#define OSD_SHADOW (0) +#endif + +/* normally the GPS button is in the center of the dpad. if there's */ +/* no dpad it will go into the zoom area */ +#if defined(OSD_GPS_BUTTON) && defined(OSD_NO_DPAD) +#define Z_GPS 1 +#else +#define Z_GPS 0 +#endif /* total width and height of controls incl. shadow */ -#define OSD_W (2*D_RAD + OSD_SHADOW) +#define OSD_W (2*D_RAD + OSD_SHADOW + Z_GPS * 2 * Z_RAD) +#if !Z_GPS #define OSD_H (2*D_RAD + Z_STEP + 2*Z_RAD + OSD_SHADOW) +#else +#define OSD_H (2*Z_RAD + OSD_SHADOW) +#endif +#ifdef OSD_SHADOW_ENABLE #define OSD_LBL_SHADOW (OSD_SHADOW/2) +#endif + +#define Z_TOP ((1-Z_GPS) * (2 * D_RAD + Z_STEP)) -#define Z_TOP (2 * D_RAD + Z_STEP) #define Z_MID (Z_TOP + Z_RAD) #define Z_BOT (Z_MID + Z_RAD) #define Z_LEFT (Z_RAD) -#define Z_RIGHT (2 * D_RAD - Z_RAD) - +#define Z_RIGHT (2 * D_RAD - Z_RAD + Z_GPS * 2 * Z_RAD) +#define Z_CENTER ((Z_RIGHT + Z_LEFT)/2) /* create the cairo shape used for the zoom buttons */ static void -osm_gps_map_osd_zoom_shape(cairo_t *cr, gint x, gint y) +osd_zoom_shape(cairo_t *cr, gint x, gint y) { cairo_move_to (cr, x+Z_LEFT, y+Z_TOP); cairo_line_to (cr, x+Z_RIGHT, y+Z_TOP); @@ -129,12 +130,73 @@ cairo_arc (cr, x+Z_LEFT, y+Z_MID, Z_RAD, M_PI/2, -M_PI/2); } +/* ------------------- color/shadow functions ----------------- */ + +#ifndef OSD_COLOR +/* if no color has been specified we just use the gdks default colors */ +static void +osd_labels(cairo_t *cr, gint width, gboolean enabled, + GdkColor *fg, GdkColor *disabled) { + if(enabled) gdk_cairo_set_source_color(cr, fg); + else gdk_cairo_set_source_color(cr, disabled); + cairo_set_line_width (cr, width); +} +#else +static void +osd_labels(cairo_t *cr, gint width, gboolean enabled) { + if(enabled) cairo_set_source_rgb (cr, OSD_COLOR); + else cairo_set_source_rgb (cr, OSD_COLOR_DISABLED); + cairo_set_line_width (cr, width); +} +#endif + +#ifdef OSD_SHADOW_ENABLE +static void +osd_labels_shadow(cairo_t *cr, gint width, gboolean enabled) { + cairo_set_source_rgba (cr, 0, 0, 0, enabled?0.3:0.15); + cairo_set_line_width (cr, width); +} +#endif + +#ifndef OSD_NO_DPAD /* create the cairo shape used for the dpad */ static void -osm_gps_map_osd_dpad_shape(cairo_t *cr, gint x, gint y) +osd_dpad_shape(cairo_t *cr, gint x, gint y) { cairo_arc (cr, x+D_RAD, y+D_RAD, D_RAD, 0, 2 * M_PI); } +#endif + +#ifdef OSD_SHADOW_ENABLE +static void +osd_shape_shadow(cairo_t *cr) { + cairo_set_source_rgba (cr, 0, 0, 0, 0.2); + cairo_fill (cr); + cairo_stroke (cr); +} +#endif + +#ifndef OSD_COLOR +/* if no color has been specified we just use the gdks default colors */ +static void +osd_shape(cairo_t *cr, GdkColor *bg, GdkColor *fg) { + gdk_cairo_set_source_color(cr, bg); + cairo_fill_preserve (cr); + gdk_cairo_set_source_color(cr, fg); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); +} +#else +static void +osd_shape(cairo_t *cr) { + cairo_set_source_rgb (cr, OSD_COLOR_BG); + cairo_fill_preserve (cr); + cairo_set_source_rgb (cr, OSD_COLOR); + cairo_set_line_width (cr, 1); + cairo_stroke (cr); +} +#endif + static gboolean osm_gps_map_in_circle(gint x, gint y, gint cx, gint cy, gint rad) @@ -142,20 +204,23 @@ return( pow(cx - x, 2) + pow(cy - y, 2) < rad * rad); } +#ifndef OSD_NO_DPAD /* check whether x/y is within the dpad */ static osd_button_t -osm_gps_map_osd_check_dpad(gint x, gint y) +osd_check_dpad(gint x, gint y) { /* within entire dpad circle */ - if( osm_gps_map_in_circle(x, y, OSD_X + D_RAD, OSD_Y + D_RAD, D_RAD)) + if( osm_gps_map_in_circle(x, y, D_RAD, D_RAD, D_RAD)) { /* convert into position relative to dpads centre */ - x -= (OSD_X + D_RAD); - y -= (OSD_Y + D_RAD); + x -= D_RAD; + y -= D_RAD; +#ifdef OSD_GPS_BUTTON /* check for dpad center goes here! */ if( osm_gps_map_in_circle(x, y, 0, 0, D_RAD/3)) return OSD_GPS; +#endif if( y < 0 && abs(x) < abs(y)) return OSD_UP; @@ -173,67 +238,430 @@ } return OSD_NONE; } +#endif /* check whether x/y is within the zoom pads */ static osd_button_t -osm_gps_map_osd_check_zoom(gint x, gint y) { - if( x > OSD_X && x < (OSD_X + OSD_W) && y > Z_TOP && y < (OSD_Y+Z_BOT)) { +osd_check_zoom(gint x, gint y) { + if( x > 0 && x < OSD_W && y > Z_TOP && y < Z_BOT) { /* within circle around (-) label */ - if( osm_gps_map_in_circle(x, y, OSD_X + Z_LEFT, OSD_Y + Z_MID, Z_RAD)) - return OSD_OUT; - - /* between center of (-) button and center of entire zoom control area */ - if(x > OSD_LEFT && x < OSD_X + D_RAD) + if( osm_gps_map_in_circle(x, y, Z_LEFT, Z_MID, Z_RAD)) return OSD_OUT; /* within circle around (+) label */ - if( osm_gps_map_in_circle(x, y, OSD_X + Z_RIGHT, OSD_Y + Z_MID, Z_RAD)) + if( osm_gps_map_in_circle(x, y, Z_RIGHT, Z_MID, Z_RAD)) return OSD_IN; +#if Z_GPS == 1 + /* within square around center */ + if( x > Z_CENTER - Z_RAD && x < Z_CENTER + Z_RAD) + return OSD_GPS; +#endif + + /* between center of (-) button and center of entire zoom control area */ + if(x > OSD_LEFT && x < D_RAD) + return OSD_OUT; + /* between center of (+) button and center of entire zoom control area */ - if(x < OSD_RIGHT && x > OSD_X + D_RAD) + if(x < OSD_RIGHT && x > D_RAD) return OSD_IN; } return OSD_NONE; } +#ifdef OSD_SOURCE_SEL + +/* place source selection at right border */ +#define OSD_S_RAD (Z_RAD) +#define OSD_S_X (-OSD_X) +#define OSD_S_Y (OSD_Y) +#define OSD_S_PW (2 * Z_RAD) +#define OSD_S_W (OSD_S_PW) +#define OSD_S_PH (2 * Z_RAD) +#define OSD_S_H (OSD_S_PH + OSD_SHADOW) + +/* size of usable area when expanded */ +#define OSD_S_AREA_W (priv->width) +#define OSD_S_AREA_H (priv->height) +#define OSD_S_EXP_W (OSD_S_PW + OSD_S_AREA_W + OSD_SHADOW) +#define OSD_S_EXP_H (OSD_S_AREA_H + OSD_SHADOW) + +/* internal value to draw the arrow on the "puller" */ +#define OSD_S_D0 (OSD_S_RAD/2) +#ifndef OSD_FONT_SIZE +#define OSD_FONT_SIZE 16.0 +#endif +#define OSD_TEXT_BORDER (OSD_FONT_SIZE/2) +#define OSD_TEXT_SKIP (OSD_FONT_SIZE/8) + +/* draw the shape of the source selection OSD, either only the puller (not expanded) */ +/* or the entire menu incl. the puller (expanded) */ +static void +osd_source_shape(osd_priv_t *priv, cairo_t *cr, gint x, gint y) { + if(!priv->expanded) { + /* just draw the puller */ + cairo_move_to (cr, x + OSD_S_PW, y + OSD_S_PH); + cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2); + cairo_line_to (cr, x + OSD_S_PW, y); + } else { + /* draw the puller and the area itself */ + cairo_move_to (cr, x + OSD_S_PW + OSD_S_AREA_W, y + OSD_S_AREA_H); + cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_AREA_H); + if(OSD_S_Y > 0) { + cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_PH); + cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2); + } else { + cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_AREA_H-OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2); + cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_AREA_H - OSD_S_PH); + cairo_line_to (cr, x + OSD_S_PW, y); + } + cairo_line_to (cr, x + OSD_S_PW + OSD_S_AREA_W, y); + cairo_close_path (cr); + } +} + +static void +osd_source_content(osm_gps_map_osd_t *osd, cairo_t *cr, gint offset) { + osd_priv_t *priv = (osd_priv_t*)osd->priv; + + int py = offset + OSD_S_RAD - OSD_S_D0; + + if(!priv->expanded) { + /* draw the "puller" open (<) arrow */ + cairo_move_to (cr, offset + OSD_S_RAD + OSD_S_D0/2, py); + cairo_rel_line_to (cr, -OSD_S_D0, +OSD_S_D0); + cairo_rel_line_to (cr, +OSD_S_D0, +OSD_S_D0); + } else { + if(OSD_S_Y < 0) + py += OSD_S_AREA_H - OSD_S_PH; + + /* draw the "puller" close (>) arrow */ + cairo_move_to (cr, offset + OSD_S_RAD - OSD_S_D0/2, py); + cairo_rel_line_to (cr, +OSD_S_D0, +OSD_S_D0); + cairo_rel_line_to (cr, -OSD_S_D0, +OSD_S_D0); + cairo_stroke(cr); + + /* don't draw a shadow for the text content */ + if(offset == 1) { + gint source; + g_object_get(osd->widget, "map-source", &source, NULL); + + cairo_select_font_face (cr, "Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size (cr, OSD_FONT_SIZE); + + int i, step = (priv->height - 2*OSD_TEXT_BORDER) / + OSM_GPS_MAP_SOURCE_LAST; + for(i=OSM_GPS_MAP_SOURCE_NULL+1;i<=OSM_GPS_MAP_SOURCE_LAST;i++) { + cairo_text_extents_t extents; + const char *src = osm_gps_map_source_get_friendly_name(i); + cairo_text_extents (cr, src, &extents); + + int x = offset + OSD_S_PW + OSD_TEXT_BORDER; + int y = offset + step * (i-1) + OSD_TEXT_BORDER; + + /* draw filled rectangle if selected */ + if(source == i) { + cairo_rectangle(cr, x - OSD_TEXT_BORDER/2, + y - OSD_TEXT_SKIP, + priv->width - OSD_TEXT_BORDER, + step + OSD_TEXT_SKIP); + cairo_fill(cr); + + /* temprarily draw with background color */ +#ifndef OSD_COLOR + GdkColor bg = osd->widget->style->bg[GTK_STATE_NORMAL]; + gdk_cairo_set_source_color(cr, &bg); +#else + cairo_set_source_rgb (cr, OSD_COLOR_BG); +#endif + } + + cairo_move_to (cr, x, y + OSD_TEXT_SKIP - extents.y_bearing); + cairo_show_text (cr, src); + + /* restore color */ + if(source == i) { +#ifndef OSD_COLOR + GdkColor fg = osd->widget->style->fg[GTK_STATE_NORMAL]; + gdk_cairo_set_source_color(cr, &fg); +#else + cairo_set_source_rgb (cr, OSD_COLOR); +#endif + } + } + } + } +} + +static void +osd_render_source_sel(osm_gps_map_osd_t *osd) { + osd_priv_t *priv = (osd_priv_t*)osd->priv; + +#ifndef OSD_COLOR + GdkColor bg = GTK_WIDGET(osd->widget)->style->bg[GTK_STATE_NORMAL]; + GdkColor fg = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_NORMAL]; + GdkColor da = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_INSENSITIVE]; +#endif + + /* draw source selector */ + cairo_t *cr = cairo_create(priv->map_source); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + +#ifdef OSD_SHADOW_ENABLE + osd_source_shape(priv, cr, 1+OSD_SHADOW, 1+OSD_SHADOW); + osd_shape_shadow(cr); +#endif + + osd_source_shape(priv, cr, 1, 1); +#ifndef OSD_COLOR + osd_shape(cr, &bg, &fg); +#else + osd_shape(cr); +#endif + +#ifdef OSD_SHADOW_ENABLE + osd_labels_shadow(cr, Z_RAD/3, TRUE); + osd_source_content(osd, cr, 1+OSD_LBL_SHADOW); + cairo_stroke (cr); +#endif +#ifndef OSD_COLOR + osd_labels(cr, Z_RAD/3, TRUE, &fg, &da); +#else + osd_labels(cr, Z_RAD/3, TRUE); +#endif + osd_source_content(osd, cr, 1); + cairo_stroke (cr); + + cairo_destroy(cr); +} + +/* re-allocate the buffer used to draw the menu. This is used */ +/* to collapse/expand the buffer */ +static void +osd_source_reallocate(osm_gps_map_osd_t *osd) { + osd_priv_t *priv = (osd_priv_t*)osd->priv; + + /* re-allocate offscreen bitmap */ + g_assert (priv->map_source); + + int w = OSD_S_W, h = OSD_S_H; + if(priv->expanded) { + cairo_text_extents_t extents; + + /* determine content size */ + cairo_t *cr = cairo_create(priv->map_source); + cairo_select_font_face (cr, "Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size (cr, OSD_FONT_SIZE); + + /* calculate menu size */ + int i, max_h = 0, max_w = 0; + for(i=OSM_GPS_MAP_SOURCE_NULL+1;i<=OSM_GPS_MAP_SOURCE_LAST;i++) { + const char *src = osm_gps_map_source_get_friendly_name(i); + cairo_text_extents (cr, src, &extents); + + if(extents.width > max_w) max_w = extents.width; + if(extents.height > max_h) max_h = extents.height; + } + cairo_destroy(cr); + + priv->width = max_w + 2*OSD_TEXT_BORDER; + priv->height = OSM_GPS_MAP_SOURCE_LAST * + (max_h + 2*OSD_TEXT_SKIP) + 2*OSD_TEXT_BORDER; + + w = OSD_S_EXP_W; + h = OSD_S_EXP_H; + } + + cairo_surface_destroy(priv->map_source); + priv->map_source = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w+2, h+2); + + osd_render_source_sel(osd); +} + +#define OSD_HZ 15 +#define OSD_TIME 500 + +static gboolean osd_source_animate(gpointer data) { + osm_gps_map_osd_t *osd = (osm_gps_map_osd_t*)data; + osd_priv_t *priv = (osd_priv_t*)osd->priv; + int diff = OSD_S_EXP_W - OSD_S_W - OSD_S_X; + gboolean done = FALSE; + priv->count += priv->dir; + + /* shifting in */ + if(priv->dir < 0) { + if(priv->count <= 0) { + priv->count = 0; + done = TRUE; + } + } else { + if(priv->count >= 1000) { + priv->expanded = FALSE; + osd_source_reallocate(osd); + + priv->count = 1000; + done = TRUE; + } + } + + + /* count runs linearly from 0 to 1000, map this nicely onto a position */ + + /* nicer sinoid mapping */ + float m = 0.5-cos(priv->count * M_PI / 1000.0)/2; + priv->shift = (osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X) + + m * diff; + + osm_gps_map_repaint(OSM_GPS_MAP(osd->widget)); + + if(done) + priv->handler_id = 0; + + return !done; +} + +/* switch between expand and collapse mode of source selection */ +static void +osd_source_toggle(osm_gps_map_osd_t *osd) +{ + osd_priv_t *priv = (osd_priv_t*)osd->priv; + + /* ignore clicks while animation is running */ + if(priv->handler_id) + return; + + /* expand immediately, collapse is handle at the end of the collapse animation */ + if(!priv->expanded) { + priv->expanded = TRUE; + osd_source_reallocate(osd); + + priv->count = 1000; + priv->shift = osd->widget->allocation.width - OSD_S_W; + priv->dir = -1000/OSD_HZ; + } else { + priv->count = 0; + priv->shift = osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X; + priv->dir = +1000/OSD_HZ; + } + + priv->handler_id = gtk_timeout_add(OSD_TIME/OSD_HZ, osd_source_animate, osd); +} + +/* check if the user clicked inside the source selection area */ +static osd_button_t +osd_source_check(osm_gps_map_osd_t *osd, gint x, gint y) { + osd_priv_t *priv = (osd_priv_t*)osd->priv; + + if(!priv->expanded) + x -= osd->widget->allocation.width - OSD_S_W; + else + x -= osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X; + + if(OSD_S_Y > 0) + y -= OSD_S_Y; + else + y -= osd->widget->allocation.height - OSD_S_PH + OSD_S_Y; + + /* within square around puller? */ + if(y > 0 && y < OSD_S_PH && x > 0 && x < OSD_S_PW) { + /* really within puller shape? */ + if(x > Z_RAD || osm_gps_map_in_circle(x, y, Z_RAD, Z_RAD, Z_RAD)) { + /* expand source selector */ + osd_source_toggle(osd); + + /* tell upper layers that user clicked some background element */ + /* of the OSD */ + return OSD_BG; + } + } + + /* check for clicks into data area */ + if(priv->expanded && !priv->handler_id) { + /* re-adjust from puller top to content top */ + if(OSD_S_Y < 0) + y += OSD_S_EXP_H - OSD_S_PH; + + if(x > OSD_S_PW && + x < OSD_S_PW + OSD_S_EXP_W && + y > 0 && + y < OSD_S_EXP_H) { + + int step = (priv->height - 2*OSD_TEXT_BORDER) + / OSM_GPS_MAP_SOURCE_LAST; + + y -= OSD_TEXT_BORDER - OSD_TEXT_SKIP; + y /= step; + y += 1; + + gint old = 0; + g_object_get(osd->widget, "map-source", &old, NULL); + + if(y > OSM_GPS_MAP_SOURCE_NULL && + y <= OSM_GPS_MAP_SOURCE_LAST && + old != y) { + g_object_set(osd->widget, "map-source", y, NULL); + + osd_render_source_sel(osd); + osm_gps_map_repaint(OSM_GPS_MAP(osd->widget)); + } + + /* return "clicked in OSD background" to prevent further */ + /* processing by application */ + return OSD_BG; + } + } + + return OSD_NONE; +} +#endif // OSD_SOURCE_SEL + static osd_button_t -osm_gps_map_osd_check(gint x, gint y) { +osd_check(osm_gps_map_osd_t *osd, gint x, gint y) { osd_button_t but = OSD_NONE; +#ifdef OSD_SOURCE_SEL + /* the source selection area is handles internally */ + but = osd_source_check(osd, x, y); + if(but != OSD_NONE) + return but; +#endif + + x -= OSD_X; + y -= OSD_Y; + + if(OSD_X < 0) + x -= (osd->widget->allocation.width - OSD_W); + + if(OSD_Y < 0) + y -= (osd->widget->allocation.height - OSD_H); + /* first do a rough test for the OSD area. */ /* this is just to avoid an unnecessary detailed test */ - if(x > OSD_X && x < OSD_X + OSD_W && - y > OSD_Y && y < OSD_Y + OSD_H) { - but = osm_gps_map_osd_check_dpad(x, y); + if(x > 0 && x < OSD_W && y > 0 && y < OSD_H) { +#ifndef OSD_NO_DPAD + but = osd_check_dpad(x, y); +#endif if(but == OSD_NONE) - but = osm_gps_map_osd_check_zoom(x, y); + but = osd_check_zoom(x, y); } return but; } -static void -osm_gps_map_osd_shape_shadow(cairo_t *cr) { - cairo_set_source_rgba (cr, 0, 0, 0, 0.2); - cairo_fill (cr); - cairo_stroke (cr); -} - -static void -osm_gps_map_osd_shape(cairo_t *cr) { - cairo_set_source_rgb (cr, 1, 1, 1); - cairo_fill_preserve (cr); - cairo_set_source_rgb (cr, OSD_COLOR); - cairo_set_line_width (cr, 1); - cairo_stroke (cr); -} - +#ifndef OSD_NO_DPAD static void -osm_gps_map_osd_dpad_labels(cairo_t *cr, gint x, gint y) { +osd_dpad_labels(cairo_t *cr, gint x, gint y) { /* move reference to dpad center */ x += D_RAD; y += D_RAD; @@ -256,18 +684,22 @@ cairo_rel_line_to (cr, offset[i][2][0], offset[i][2][1]); } } +#endif -/* draw the sattelite dish icon in the center of the dpad */ -#define GPS_V0 (D_RAD/8) +#ifdef OSD_GPS_BUTTON +/* draw the satellite dish icon in the center of the dpad */ +#define GPS_V0 (D_RAD/7) #define GPS_V1 (D_RAD/10) #define GPS_V2 (D_RAD/5) /* draw a satellite receiver dish */ +/* this is either drawn in the center of the dpad (if present) */ +/* or in the middle of the zoom area */ static void -osm_gps_map_osd_dpad_gps(cairo_t *cr, gint x, gint y) { +osd_dpad_gps(cairo_t *cr, gint x, gint y) { /* move reference to dpad center */ - x += D_RAD; - y += D_RAD + GPS_V0; + x += (1-Z_GPS) * D_RAD + Z_GPS * Z_RAD * 3; + y += (1-Z_GPS) * D_RAD + Z_GPS * Z_RAD + GPS_V0; cairo_move_to (cr, x-GPS_V0, y+GPS_V0); cairo_rel_line_to (cr, +GPS_V0, -GPS_V0); @@ -282,11 +714,12 @@ cairo_move_to (cr, x, y-GPS_V2); cairo_rel_line_to (cr, +GPS_V1, -GPS_V1); } +#endif #define Z_LEN (2*Z_RAD/3) static void -osm_gps_map_osd_zoom_labels(cairo_t *cr, gint x, gint y) { +osd_zoom_labels(cairo_t *cr, gint x, gint y) { cairo_move_to (cr, x + Z_LEFT - Z_LEN, y + Z_MID); cairo_line_to (cr, x + Z_LEFT + Z_LEN, y + Z_MID); @@ -296,130 +729,396 @@ cairo_line_to (cr, x + Z_RIGHT + Z_LEN, y + Z_MID); } +/* various parameters used to create the scale */ +#define OSD_SCALE_H2 (OSD_SCALE_H/2) +#define OSD_SCALE_TICK (2*OSD_SCALE_FONT_SIZE/3) +#define OSD_SCALE_M (OSD_SCALE_H2 - OSD_SCALE_TICK) +#define OSD_SCALE_I (OSD_SCALE_H2 + OSD_SCALE_TICK) +#define OSD_SCALE_FD (OSD_SCALE_FONT_SIZE/4) + static void -osm_gps_map_osd_labels(cairo_t *cr, gint width, gboolean enabled) { - if(enabled) cairo_set_source_rgb (cr, OSD_COLOR); - else cairo_set_source_rgb (cr, OSD_COLOR_DISABLED); - cairo_set_line_width (cr, width); - cairo_stroke (cr); -} +osd_render_scale(osm_gps_map_osd_t *osd) +{ + osd_priv_t *priv = (osd_priv_t*)osd->priv; -static void -osm_gps_map_osd_labels_shadow(cairo_t *cr, gint width, gboolean enabled) { - cairo_set_source_rgba (cr, 0, 0, 0, enabled?0.3:0.15); - cairo_set_line_width (cr, width); + /* this only needs to be rendered if the zoom has changed */ + gint zoom; + g_object_get(OSM_GPS_MAP(osd->widget), "zoom", &zoom, NULL); + if(zoom == priv->scale_zoom) + return; + + priv->scale_zoom = zoom; + + float m_per_pix = osm_gps_map_get_scale(OSM_GPS_MAP(osd->widget)); + + /* first fill with transparency */ + cairo_t *cr = cairo_create(priv->scale); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0); + // pink for testing: cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.2); + cairo_paint(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + + /* determine the size of the scale width in meters */ + float width = (OSD_SCALE_W-OSD_SCALE_FONT_SIZE/6) * m_per_pix; + + /* scale this to useful values */ + int exp = logf(width)*M_LOG10E; + int mant = width/pow(10,exp); + int width_metric = mant * pow(10,exp); + char *dist_str = NULL; + if(width_metric<1000) + dist_str = g_strdup_printf("%u m", width_metric); + else + dist_str = g_strdup_printf("%u km", width_metric/1000); + width_metric /= m_per_pix; + + /* and now the hard part: scale for useful imperial values :-( */ + /* try to convert to feet, 1ft == 0.3048 m */ + width /= 0.3048; + float imp_scale = 0.3048; + char *dist_imp_unit = "ft"; + + if(width >= 100) { + /* 1yd == 3 feet */ + width /= 3.0; + imp_scale *= 3.0; + dist_imp_unit = "yd"; + + if(width >= 1760.0) { + /* 1mi == 1760 yd */ + width /= 1760.0; + imp_scale *= 1760.0; + dist_imp_unit = "mi"; + } + } + + /* also convert this to full tens/hundreds */ + exp = logf(width)*M_LOG10E; + mant = width/pow(10,exp); + int width_imp = mant * pow(10,exp); + char *dist_str_imp = g_strdup_printf("%u %s", width_imp, dist_imp_unit); + + /* convert back to pixels */ + width_imp *= imp_scale; + width_imp /= m_per_pix; + + cairo_select_font_face (cr, "Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_BOLD); + cairo_set_font_size (cr, OSD_SCALE_FONT_SIZE); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + + cairo_text_extents_t extents; + cairo_text_extents (cr, dist_str, &extents); + + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/6); + cairo_move_to (cr, 2*OSD_SCALE_FD, OSD_SCALE_H2-OSD_SCALE_FD); + cairo_text_path (cr, dist_str); + cairo_stroke (cr); + cairo_move_to (cr, 2*OSD_SCALE_FD, + OSD_SCALE_H2+OSD_SCALE_FD + extents.height); + cairo_text_path (cr, dist_str_imp); cairo_stroke (cr); + + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_move_to (cr, 2*OSD_SCALE_FD, OSD_SCALE_H2-OSD_SCALE_FD); + cairo_show_text (cr, dist_str); + cairo_move_to (cr, 2*OSD_SCALE_FD, + OSD_SCALE_H2+OSD_SCALE_FD + extents.height); + cairo_show_text (cr, dist_str_imp); + + g_free(dist_str); + g_free(dist_str_imp); + + /* draw white line */ + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); + cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/3); + cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_M); + cairo_rel_line_to (cr, 0, OSD_SCALE_TICK); + cairo_rel_line_to (cr, width_metric, 0); + cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK); + cairo_stroke(cr); + cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_I); + cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK); + cairo_rel_line_to (cr, width_imp, 0); + cairo_rel_line_to (cr, 0, +OSD_SCALE_TICK); + cairo_stroke(cr); + + /* draw black line */ + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/6); + cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_M); + cairo_rel_line_to (cr, 0, OSD_SCALE_TICK); + cairo_rel_line_to (cr, width_metric, 0); + cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK); + cairo_stroke(cr); + cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_I); + cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK); + cairo_rel_line_to (cr, width_imp, 0); + cairo_rel_line_to (cr, 0, +OSD_SCALE_TICK); + cairo_stroke(cr); + + cairo_destroy(cr); } static void -osm_gps_map_osd_render(OsmGpsMapPrivate *priv) { +osd_render(osm_gps_map_osd_t *osd) +{ + osd_priv_t *priv = (osd_priv_t*)osd->priv; + +#ifndef OSD_COLOR + GdkColor bg = GTK_WIDGET(osd->widget)->style->bg[GTK_STATE_NORMAL]; + GdkColor fg = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_NORMAL]; + GdkColor da = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_INSENSITIVE]; +#endif /* first fill with transparency */ - cairo_t *cr = cairo_create(priv->osd.overlay); + cairo_t *cr = cairo_create(priv->overlay); cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0); cairo_paint(cr); - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); /* --------- draw zoom and dpad shape shadow ----------- */ - gint x = 0, y = 0; - - osm_gps_map_osd_zoom_shape(cr, x + OSD_SHADOW, y + OSD_SHADOW); - osm_gps_map_osd_shape_shadow(cr); - osm_gps_map_osd_dpad_shape(cr, x + OSD_SHADOW, y + OSD_SHADOW); - osm_gps_map_osd_shape_shadow(cr); +#ifdef OSD_SHADOW_ENABLE + osd_zoom_shape(cr, 1+OSD_SHADOW, 1+OSD_SHADOW); + osd_shape_shadow(cr); +#ifndef OSD_NO_DPAD + osd_dpad_shape(cr, 1+OSD_SHADOW, 1+OSD_SHADOW); + osd_shape_shadow(cr); +#endif +#endif /* --------- draw zoom and dpad shape ----------- */ - osm_gps_map_osd_zoom_shape(cr, x, y); - osm_gps_map_osd_shape(cr); - osm_gps_map_osd_dpad_shape(cr, x, y); - osm_gps_map_osd_shape(cr); + osd_zoom_shape(cr, 1, 1); +#ifndef OSD_COLOR + osd_shape(cr, &bg, &fg); +#else + osd_shape(cr); +#endif +#ifndef OSD_NO_DPAD + osd_dpad_shape(cr, 1, 1); +#ifndef OSD_COLOR + osd_shape(cr, &bg, &fg); +#else + osd_shape(cr); +#endif +#endif /* --------- draw zoom and dpad labels --------- */ - osm_gps_map_osd_zoom_labels(cr, x + OSD_LBL_SHADOW, y + OSD_LBL_SHADOW); - osm_gps_map_osd_dpad_labels(cr, x + OSD_LBL_SHADOW, y + OSD_LBL_SHADOW); - osm_gps_map_osd_labels_shadow(cr, Z_RAD/3, TRUE); - osm_gps_map_osd_dpad_gps(cr, x + OSD_LBL_SHADOW, y + OSD_LBL_SHADOW); - osm_gps_map_osd_labels_shadow(cr, Z_RAD/6, priv->osd.cb != NULL); - - osm_gps_map_osd_zoom_labels(cr, x, y); - osm_gps_map_osd_dpad_labels(cr, x, y); - osm_gps_map_osd_labels(cr, Z_RAD/3, TRUE); - osm_gps_map_osd_dpad_gps(cr, x, y); - osm_gps_map_osd_labels(cr, Z_RAD/6, priv->osd.cb != NULL); +#ifdef OSD_SHADOW_ENABLE + osd_labels_shadow(cr, Z_RAD/3, TRUE); + osd_zoom_labels(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW); +#ifndef OSD_NO_DPAD + osd_dpad_labels(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW); +#endif + cairo_stroke(cr); +#ifdef OSD_GPS_BUTTON + osd_labels_shadow(cr, Z_RAD/6, osd->cb != NULL); + osd_dpad_gps(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW); + cairo_stroke(cr); +#endif +#endif + +#ifndef OSD_COLOR + osd_labels(cr, Z_RAD/3, TRUE, &fg, &da); +#else + osd_labels(cr, Z_RAD/3, TRUE); +#endif + osd_zoom_labels(cr, 1, 1); +#ifndef OSD_NO_DPAD + osd_dpad_labels(cr, 1, 1); +#endif + cairo_stroke(cr); + +#ifndef OSD_COLOR + osd_labels(cr, Z_RAD/6, osd->cb != NULL, &fg, &da); +#else + osd_labels(cr, Z_RAD/6, osd->cb != NULL); +#endif +#ifdef OSD_GPS_BUTTON + osd_dpad_gps(cr, 1, 1); +#endif + cairo_stroke(cr); cairo_destroy(cr); + +#ifdef OSD_SOURCE_SEL + osd_render_source_sel(osd); +#endif + +#ifdef OSD_SCALE + osd_render_scale(osd); +#endif } static void -osm_gps_map_osd_draw_controls (OsmGpsMap *map, gint xoffset, gint yoffset) +osd_draw(osm_gps_map_osd_t *osd, GdkDrawable *drawable) { - OsmGpsMapPrivate *priv = map->priv; - - /* backup previous contents */ - if(!priv->osd.backup) - priv->osd.backup = gdk_pixmap_new(priv->pixmap, OSD_W+2, OSD_H+2, -1); - - gint x = OSD_X + EXTRA_BORDER + xoffset; - gint y = OSD_Y + EXTRA_BORDER + yoffset; - - /* create backup of background */ - gdk_draw_drawable(priv->osd.backup, - GTK_WIDGET(map)->style->fg_gc[GTK_WIDGET_STATE(GTK_WIDGET(map))], - priv->pixmap, x-1, y-1, 0, 0, OSD_W+2, OSD_H+2); - - priv->osd.backup_x = x-1; - priv->osd.backup_y = y-1; + osd_priv_t *priv = (osd_priv_t*)osd->priv; /* OSD itself uses some off-screen rendering, so check if the */ /* offscreen buffer is present and create it if not */ - if(!priv->osd.overlay) { + if(!priv->overlay) { /* create overlay ... */ - priv->osd.overlay = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, OSD_W, OSD_H); + priv->overlay = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, OSD_W+2, OSD_H+2); + +#ifdef OSD_SOURCE_SEL + /* the initial OSD state is alway not-expanded */ + priv->map_source = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + OSD_S_W+2, OSD_S_H+2); +#endif + +#ifdef OSD_SCALE + priv->scale = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + OSD_SCALE_W, OSD_SCALE_H); + priv->scale_zoom = -1; +#endif + /* ... and render it */ - osm_gps_map_osd_render(priv); + osd_render(osd); } // now draw this onto the original context - cairo_t *cr = gdk_cairo_create(priv->pixmap); - cairo_set_source_surface(cr, priv->osd.overlay, x, y); + cairo_t *cr = gdk_cairo_create(drawable); + + int x = OSD_X, y = OSD_Y; + if(OSD_X < 0) + x = osd->widget->allocation.width - OSD_W + OSD_X; + + if(OSD_Y < 0) + y = osd->widget->allocation.height - OSD_H + OSD_Y; + + cairo_set_source_surface(cr, priv->overlay, x, y); cairo_paint(cr); + +#ifdef OSD_SOURCE_SEL + if(!priv->handler_id) { + /* the OSD source selection is not being animated */ + if(!priv->expanded) + x = osd->widget->allocation.width - OSD_S_W; + else + x = osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X; + } else + x = priv->shift; + + y = OSD_S_Y; + if(OSD_S_Y < 0) { + if(!priv->expanded) + y = osd->widget->allocation.height - OSD_S_H + OSD_S_Y; + else + y = osd->widget->allocation.height - OSD_S_EXP_H + OSD_S_Y; + } + + cairo_set_source_surface(cr, priv->map_source, x, y); + cairo_paint(cr); +#endif + +#ifdef OSD_SCALE + x = OSD_X; + y = -OSD_Y; + if(x < 0) x += osd->widget->allocation.width - OSD_SCALE_W; + if(y < 0) y += osd->widget->allocation.height - OSD_SCALE_H; + + cairo_set_source_surface(cr, priv->scale, x, y); + cairo_paint(cr); +#endif + cairo_destroy(cr); } static void -osm_gps_map_osd_restore (OsmGpsMap *map) +osd_free(osm_gps_map_osd_t *osd) { - OsmGpsMapPrivate *priv = map->priv; + osd_priv_t *priv = (osd_priv_t *)(osd->priv); - /* restore backup of previous contents */ - if(priv->osd.backup) { - /* create backup of background */ - gdk_draw_drawable(priv->pixmap, - GTK_WIDGET(map)->style->fg_gc[GTK_WIDGET_STATE(GTK_WIDGET(map))], - priv->osd.backup, 0, 0, - priv->osd.backup_x, priv->osd.backup_y, OSD_W+2, OSD_H+2); - } + if (priv->overlay) + cairo_surface_destroy(priv->overlay); + +#ifdef OSD_SOURCE_SEL + if(priv->handler_id) + gtk_timeout_remove(priv->handler_id); + + if (priv->map_source) + cairo_surface_destroy(priv->map_source); +#endif + +#ifdef OSD_SCALE + if (priv->scale) + cairo_surface_destroy(priv->scale); +#endif + + g_free(priv); } -static void -osm_gps_map_osd_enable_gps (OsmGpsMap *map, OsmGpsMapOsdGpsCallback cb, gpointer data) +static gboolean +osd_busy(osm_gps_map_osd_t *osd) { - OsmGpsMapPrivate *priv; +#ifdef OSD_SOURCE_SEL + osd_priv_t *priv = (osd_priv_t *)(osd->priv); + return (priv->handler_id != 0); +#else + return FALSE; +#endif +} + +static osm_gps_map_osd_t osd_classic = { + .widget = NULL, + + .draw = osd_draw, + .check = osd_check, + .render = osd_render, + .free = osd_free, + .busy = osd_busy, + + .cb = NULL, + .data = NULL, + + .priv = NULL +}; + +/* this is the only function that's externally visible */ +void +osm_gps_map_osd_classic_init(OsmGpsMap *map) +{ + osd_priv_t *priv = osd_classic.priv = g_new0(osd_priv_t, 1); + + osd_classic.priv = priv; + + osm_gps_map_register_osd(map, &osd_classic); +} - g_return_if_fail (OSM_IS_GPS_MAP (map)); - priv = map->priv; +#ifdef OSD_GPS_BUTTON +/* below are osd specific functions which aren't used by osm-gps-map */ +/* but instead are to be used by the main application */ +void osm_gps_map_osd_enable_gps (OsmGpsMap *map, OsmGpsMapOsdCallback cb, + gpointer data) { + osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map); + g_return_if_fail (osd); - priv->osd.cb = cb; - priv->osd.data = data; + osd->cb = cb; + osd->data = data; /* this may have changed the state of the gps button */ /* we thus re-render the overlay */ - osm_gps_map_osd_render(priv); + osd->render(osd); - osm_gps_map_map_redraw_idle(map); + osm_gps_map_redraw(map); +} +#endif + +osd_button_t +osm_gps_map_osd_check(OsmGpsMap *map, gint x, gint y) { + osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map); + g_return_val_if_fail (osd, OSD_NONE); + + return osd_check(osd, x, y); }