--- trunk/src/osm-gps-map-osd-classic.c 2009/08/24 12:57:15 75 +++ trunk/src/osm-gps-map-osd-classic.c 2009/09/08 19:08:37 102 @@ -19,10 +19,16 @@ #include "config.h" #include // abs -#include // M_PI +#include // M_PI/cos() /* parameters that can be overwritten from the config file: */ -/* OSM_GPS_MAP_OSD_DIAMETER */ +/* OSD_DIAMETER */ +/* OSD_X, OSD_Y */ + +#define OSD_SCALE + +#define OSD_SCALE_W 120 +#define OSD_SCALE_H 20 #ifndef USE_CAIRO #error "OSD control display lacks a non-cairo implementation!" @@ -38,19 +44,35 @@ /* the offscreen representation of the OSD */ cairo_surface_t *overlay; - GdkColor bg, fg, da; +#ifdef OSD_SCALE + cairo_surface_t *scale; +#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 } osd_priv_t; /* position and extent of bounding box */ +#ifndef OSD_X #define OSD_X (10) +#endif + +#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 @@ -67,24 +89,37 @@ #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 (2 * D_RAD + Z_STEP) +#define Z_TOP ((1-Z_GPS) * (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); @@ -93,12 +128,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) @@ -106,20 +202,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; @@ -137,81 +236,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; } -static osd_button_t -osm_gps_map_osd_check(gint x, gint y) { - osd_button_t but = OSD_NONE; +#ifdef OSD_SOURCE_SEL - /* 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); +/* 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) - if(but == OSD_NONE) - but = osm_gps_map_osd_check_zoom(x, y); +/* 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); } +} - return but; +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 -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); -} + 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 -/* if no color has been specified we just use the gdks default colors */ -static void -osm_gps_map_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); + 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 -static void -osm_gps_map_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); + 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 +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 > 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 = osd_check_zoom(x, y); + } + + return but; +} + +#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; @@ -234,18 +682,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); @@ -260,11 +712,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); @@ -274,101 +727,203 @@ cairo_line_to (cr, x + Z_RIGHT + Z_LEN, y + Z_MID); } -#ifndef OSD_COLOR -/* if no color has been specified we just use the gdks default colors */ -static void -osm_gps_map_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); - cairo_stroke (cr); +static char * +dist_str_metric(int dist) +{ + if(dist<1000) + return g_strdup_printf("%u m", dist); + + return g_strdup_printf("%u km", dist/1000); } -#else + +#define OSD_SCALE_FONT_SIZE 12 + 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); -} -#endif +osd_render_scale(osm_gps_map_osd_t *osd) +{ + osd_priv_t *priv = (osd_priv_t*)osd->priv; + float m_per_pix = osm_gps_map_get_scale(OSM_GPS_MAP(osd->widget)); -#ifdef OSD_SHADOW_ENABLE -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); + /* 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); + // 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-2) * m_per_pix; + printf("%d pixels is %f m\n", OSD_SCALE_W-2, width); + + /* 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 = dist_str_metric(width_metric); + 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"; + } + } + + printf("this is %f %s\n", width, dist_imp_unit); + printf("this is %f pixels\n", width * imp_scale / 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_move_to (cr, OSD_SCALE_FONT_SIZE/3, OSD_SCALE_FONT_SIZE); + cairo_text_path (cr, dist_str); + cairo_set_line_width (cr, 2); cairo_stroke (cr); + + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_move_to (cr, OSD_SCALE_FONT_SIZE/3, OSD_SCALE_FONT_SIZE); + cairo_show_text (cr, dist_str); + + /* 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, 4); + cairo_move_to (cr, 2, 2*OSD_SCALE_FONT_SIZE/3); + cairo_rel_line_to (cr, 0, 2*OSD_SCALE_FONT_SIZE/3); + cairo_rel_line_to (cr, width_metric, 0); + cairo_rel_line_to (cr, 0, -2*OSD_SCALE_FONT_SIZE/3); + cairo_stroke(cr); + + /* draw black line */ + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_set_line_width (cr, 2); + cairo_move_to (cr, 2, 2*OSD_SCALE_FONT_SIZE/3); + cairo_rel_line_to (cr, 0, 2*OSD_SCALE_FONT_SIZE/3); + cairo_rel_line_to (cr, width_metric, 0); + cairo_rel_line_to (cr, 0, -2*OSD_SCALE_FONT_SIZE/3); + cairo_stroke(cr); + + /* xyz */ + + cairo_destroy(cr); } -#endif static void -osm_gps_map_osd_render(osm_gps_map_osd_t *osd) { +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->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; - #ifdef OSD_SHADOW_ENABLE - 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); + 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); + osd_zoom_shape(cr, 1, 1); #ifndef OSD_COLOR - osm_gps_map_osd_shape(cr, &priv->bg, &priv->fg); + osd_shape(cr, &bg, &fg); #else - osm_gps_map_osd_shape(cr); + osd_shape(cr); #endif - osm_gps_map_osd_dpad_shape(cr, x, y); +#ifndef OSD_NO_DPAD + osd_dpad_shape(cr, 1, 1); #ifndef OSD_COLOR - osm_gps_map_osd_shape(cr, &priv->bg, &priv->fg); + osd_shape(cr, &bg, &fg); #else - osm_gps_map_osd_shape(cr); + osd_shape(cr); +#endif #endif /* --------- draw zoom and dpad labels --------- */ #ifdef OSD_SHADOW_ENABLE - 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, osd->cb != NULL); + 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 - osm_gps_map_osd_zoom_labels(cr, x, y); - osm_gps_map_osd_dpad_labels(cr, x, y); #ifndef OSD_COLOR - osm_gps_map_osd_labels(cr, Z_RAD/3, TRUE, &priv->fg, &priv->da); + osd_labels(cr, Z_RAD/3, TRUE, &fg, &da); #else - osm_gps_map_osd_labels(cr, Z_RAD/3, TRUE); + 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 - osm_gps_map_osd_dpad_gps(cr, x, y); + cairo_stroke(cr); + #ifndef OSD_COLOR - osm_gps_map_osd_labels(cr, Z_RAD/6, osd->cb != NULL, &priv->fg, &priv->da); + osd_labels(cr, Z_RAD/6, osd->cb != NULL, &fg, &da); #else - osm_gps_map_osd_labels(cr, Z_RAD/6, osd->cb != NULL); + 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(osm_gps_map_osd_t *osd, GdkDrawable *drawable) +osd_draw(osm_gps_map_osd_t *osd, GdkDrawable *drawable) { osd_priv_t *priv = (osd_priv_t*)osd->priv; @@ -377,34 +932,116 @@ if(!priv->overlay) { /* create overlay ... */ priv->overlay = - cairo_image_surface_create(CAIRO_FORMAT_ARGB32, OSD_W, OSD_H); + 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+2, OSD_SCALE_H+2); +#endif + /* ... and render it */ - osm_gps_map_osd_render(osd); + osd_render(osd); } // now draw this onto the original context cairo_t *cr = gdk_cairo_create(drawable); - cairo_set_source_surface(cr, priv->overlay, OSD_X, OSD_Y); + + 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_free(osm_gps_map_osd_t *osd) +osd_free(osm_gps_map_osd_t *osd) { osd_priv_t *priv = (osd_priv_t *)(osd->priv); 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 gboolean +osd_busy(osm_gps_map_osd_t *osd) +{ +#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 = { - .draw = osm_gps_map_osd_draw, - .check = osm_gps_map_osd_check, - .render = osm_gps_map_osd_render, - .free = osm_gps_map_osd_free, + .widget = NULL, + + .draw = osd_draw, + .check = osd_check, + .render = osd_render, + .free = osd_free, + .busy = osd_busy, .cb = NULL, .data = NULL, @@ -420,20 +1057,15 @@ osd_classic.priv = priv; - /* extract style info from the widget */ - priv->bg = GTK_WIDGET(map)->style->bg[GTK_STATE_NORMAL]; - priv->fg = GTK_WIDGET(map)->style->fg[GTK_STATE_NORMAL]; - priv->da = GTK_WIDGET(map)->style->fg[GTK_STATE_INSENSITIVE]; - osm_gps_map_register_osd(map, &osd_classic); } - +#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) { +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); osd->cb = cb; @@ -445,3 +1077,12 @@ 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); +}