diff options
author | Jiří Techet <techet@gmail.com> | 2016-08-14 10:45:33 +0100 |
---|---|---|
committer | Jiří Techet <techet@gmail.com> | 2016-08-14 10:45:33 +0100 |
commit | 4d492a31bf845dc34cb27c19c1fab6971ce151e3 (patch) | |
tree | d7ec5375c26084823cc64d59e6cff60bf60daa01 /champlain | |
parent | e60d0fd405a93f517fd7478b862dfae5bfb87d4f (diff) | |
parent | 08624901af6b45004eb7d3ce94e8e2a196876c44 (diff) | |
download | libchamplain-4d492a31bf845dc34cb27c19c1fab6971ce151e3.tar.gz |
Merge branch 'wrap2'
Diffstat (limited to 'champlain')
-rw-r--r-- | champlain/champlain-path-layer.c | 234 | ||||
-rw-r--r-- | champlain/champlain-view.c | 619 | ||||
-rw-r--r-- | champlain/champlain-view.h | 7 |
3 files changed, 787 insertions, 73 deletions
diff --git a/champlain/champlain-path-layer.c b/champlain/champlain-path-layer.c index 4a8c0fc..47956ad 100644 --- a/champlain/champlain-path-layer.c +++ b/champlain/champlain-path-layer.c @@ -87,9 +87,35 @@ struct _ChamplainPathLayerPrivate gdouble *dash; guint num_dashes; - ClutterContent *canvas; cairo_surface_t *surface; + + /* In order to correctly render paths in the horizontal wrap, + * the path_actor (a map-wide actor) contains two children that + * split the visible paths. + * + * The right_actor renders paths visible on the original map layer. + * (from viewport's x coordinate to the rightmost point on the map) + + * The left_actor renders paths visible on the first cloned map layer. + * (a fixed size, from the leftmost point on the map) + * + * If horizontal wrap is disabled, the left_actor won't render + * anything. + */ ClutterActor *path_actor; + + ClutterActor *right_actor; + ClutterActor *left_actor; + + ClutterContent *right_canvas; + ClutterContent *left_canvas; + + cairo_surface_t *right_surface; + cairo_surface_t *left_surface; + + gboolean right_surface_updated; + gboolean left_surface_updated; + GList *nodes; gboolean redraw_scheduled; }; @@ -225,13 +251,17 @@ champlain_path_layer_dispose (GObject *object) if (priv->view != NULL) set_view (CHAMPLAIN_LAYER (self), NULL); - if (priv->canvas) + if (priv->right_canvas) { - g_object_unref (priv->canvas); - priv->canvas = NULL; + g_object_unref (priv->right_canvas); + g_object_unref (priv->left_canvas); + priv->right_canvas = NULL; + priv->left_canvas = NULL; } g_clear_pointer (&priv->surface, cairo_surface_destroy); + g_clear_pointer (&priv->right_surface, cairo_surface_destroy); + g_clear_pointer (&priv->left_surface, cairo_surface_destroy); G_OBJECT_CLASS (champlain_path_layer_parent_class)->dispose (object); } @@ -379,6 +409,18 @@ champlain_path_layer_class_init (ChamplainPathLayerClass *klass) "surface"); } +static void +initialize_child_actor (ChamplainPathLayer *self, + ClutterActor *child_actor, + ClutterContent *canvas) +{ + ChamplainPathLayerPrivate *priv = self->priv;; + + clutter_actor_set_content (child_actor, canvas); + g_signal_connect (canvas, "draw", G_CALLBACK (redraw_path), self); + clutter_actor_set_size (child_actor, 255, 255); + clutter_actor_add_child (priv->path_actor, child_actor); +} static void champlain_path_layer_init (ChamplainPathLayer *self) @@ -401,14 +443,28 @@ champlain_path_layer_init (ChamplainPathLayer *self) priv->fill_color = clutter_color_copy (&DEFAULT_FILL_COLOR); priv->stroke_color = clutter_color_copy (&DEFAULT_STROKE_COLOR); - priv->canvas = clutter_canvas_new (); - clutter_canvas_set_size (CLUTTER_CANVAS (priv->canvas), 255, 255); - g_signal_connect (priv->canvas, "draw", G_CALLBACK (redraw_path), self); - priv->path_actor = clutter_actor_new (); - clutter_actor_set_size (priv->path_actor, 255, 255); - clutter_actor_set_content (priv->path_actor, priv->canvas); clutter_actor_add_child (CLUTTER_ACTOR (self), priv->path_actor); + clutter_actor_set_size (priv->path_actor, 255, 255); + + priv->right_actor = clutter_actor_new (); + priv->left_actor = clutter_actor_new (); + + priv->right_canvas = clutter_canvas_new (); + priv->left_canvas = clutter_canvas_new (); + + priv->surface = NULL; + priv->right_surface = NULL; + priv->left_surface = NULL; + + priv->right_surface_updated = FALSE; + priv->left_surface_updated = FALSE; + + clutter_canvas_set_size (CLUTTER_CANVAS (priv->right_canvas), 255, 255); + clutter_canvas_set_size (CLUTTER_CANVAS (priv->left_canvas), 0, 0); + + initialize_child_actor (self, priv->right_actor, priv->right_canvas); + initialize_child_actor (self, priv->left_actor, priv->left_canvas); } @@ -468,20 +524,89 @@ champlain_path_layer_new () } +static void +get_map_size (ChamplainView *view, gint *width, gint *height) +{ + gint size, rows, cols; + ChamplainMapSource *map_source = champlain_view_get_map_source (view); + gint zoom_level = champlain_view_get_zoom_level (view); + size = champlain_map_source_get_tile_size (map_source); + rows = champlain_map_source_get_row_count (map_source, + zoom_level); + cols = champlain_map_source_get_column_count (map_source, + zoom_level); + if (width) + *width = size * rows; + + if (height) + *height = size * cols; +} + + static gboolean invalidate_canvas (ChamplainPathLayer *layer) { ChamplainPathLayerPrivate *priv = layer->priv; - gfloat width, height; - - width = 256; - height = 256; + gfloat view_width, view_height; + gint map_width, map_height; + gint viewport_x, viewport_y; + gint anchor_x, anchor_y; + gfloat right_actor_width, right_actor_height; + gfloat left_actor_width, left_actor_height; + + right_actor_width = 256; + right_actor_height = 256; + left_actor_width = 0; + left_actor_height = 0; + map_width = 256; + map_height = 256; + if (priv->view != NULL) - clutter_actor_get_size (CLUTTER_ACTOR (priv->view), &width, &height); + { + get_map_size (priv->view, &map_width, &map_height); + clutter_actor_get_size (CLUTTER_ACTOR (priv->view), &view_width, &view_height); + champlain_view_get_viewport_origin (priv->view, &viewport_x, &viewport_y); + champlain_view_get_viewport_anchor (priv->view, &anchor_x, &anchor_y); + + /* For efficiency in terms of clipping, the path actors must have a minimal size. + * The right_actor renders the paths on the visible side of the original map layer + * (from viewport offset to end of the map). + * The left_actor renders the paths on the visible side of the first cloned map layer + * (from the leftmost point on the map, clamped by the viewport width). + */ + right_actor_width = MIN (map_width - (viewport_x + anchor_x), (gint)view_width); + right_actor_height = MIN (map_height - (viewport_y + anchor_y), (gint)view_height); + left_actor_width = MIN (view_width - right_actor_width, map_width - right_actor_width); + left_actor_height = right_actor_height; + + /* Ensure sizes are positive */ + right_actor_width = MAX (0, right_actor_width); + right_actor_height = MAX (0, right_actor_height); + left_actor_width = MAX (0, left_actor_width); + left_actor_height = MAX (0, left_actor_height); + } + + clutter_actor_set_size (priv->path_actor, map_width, map_height); + + clutter_actor_set_size (priv->right_actor, right_actor_width, right_actor_height); + clutter_canvas_set_size (CLUTTER_CANVAS (priv->right_canvas), right_actor_width, right_actor_height); + priv->right_surface_updated = FALSE; + clutter_content_invalidate (priv->right_canvas); + + /* Since the left actor only renders paths visible on the clone, it should be hidden + * when no clone is visible. + */ + if (left_actor_width != 0) + { + clutter_actor_set_size (priv->left_actor, left_actor_width, left_actor_height); + clutter_canvas_set_size (CLUTTER_CANVAS (priv->left_canvas), left_actor_width, left_actor_height); + priv->left_surface_updated = FALSE; + clutter_content_invalidate (priv->left_canvas); + clutter_actor_show (priv->left_actor); + } + else + clutter_actor_hide (priv->left_actor); - clutter_canvas_set_size (CLUTTER_CANVAS (priv->canvas), width, height); - clutter_actor_set_size (priv->path_actor, width, height); - clutter_content_invalidate (priv->canvas); priv->redraw_scheduled = FALSE; return FALSE; @@ -664,6 +789,60 @@ relocate_cb (G_GNUC_UNUSED GObject *gobject, schedule_redraw (layer); } +static void +update_surface (ChamplainPathLayer *layer, + ClutterCanvas *canvas, + cairo_surface_t *surface) +{ + ChamplainPathLayerPrivate *priv = layer->priv; + + if (canvas == CLUTTER_CANVAS (priv->right_canvas)) + { + cairo_surface_destroy (priv->right_surface); + priv->right_surface = cairo_surface_reference (surface); + priv->right_surface_updated = TRUE; + } + else if (canvas == CLUTTER_CANVAS (priv->left_canvas)) + { + cairo_surface_destroy (priv->left_surface); + priv->left_surface = cairo_surface_reference (surface); + priv->left_surface_updated = TRUE; + } + + /* Updating the exportable surface. Path layer has two surfaces (one for each canvas) + * which have to be merged into a single new one. + */ + if (priv->left_surface_updated && priv->right_surface_updated) + { + gfloat view_width, view_height; + gint map_width, viewport_x, anchor_x; + cairo_surface_t *new_surface; + cairo_t *cr; + + get_map_size (priv->view, &map_width, NULL); + clutter_actor_get_size (CLUTTER_ACTOR (priv->view), &view_width, &view_height); + champlain_view_get_viewport_origin (priv->view, &viewport_x, NULL); + champlain_view_get_viewport_anchor (priv->view, &anchor_x, NULL); + + new_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, view_width, view_height); + cr = cairo_create (new_surface); + + cairo_set_source_surface (cr, + priv->right_surface, + 0, 0); + cairo_paint (cr); + + cairo_set_source_surface (cr, + priv->left_surface, + map_width - viewport_x - anchor_x, 0); + cairo_paint (cr); + + set_surface (CHAMPLAIN_EXPORTABLE (layer), new_surface); + + cairo_surface_destroy (new_surface); + cairo_destroy (cr); + } +} static gboolean redraw_path (ClutterCanvas *canvas, @@ -675,7 +854,8 @@ redraw_path (ClutterCanvas *canvas, ChamplainPathLayerPrivate *priv = layer->priv; GList *elem; ChamplainView *view = priv->view; - gint x, y; + gint viewport_x, viewport_y; + gint anchor_x, anchor_y; /* layer not yet added to the view */ if (view == NULL) @@ -684,8 +864,13 @@ redraw_path (ClutterCanvas *canvas, if (!priv->visible || width == 0.0 || height == 0.0) return FALSE; - champlain_view_get_viewport_origin (priv->view, &x, &y); - clutter_actor_set_position (priv->path_actor, x, y); + champlain_view_get_viewport_origin (priv->view, &viewport_x, &viewport_y); + champlain_view_get_viewport_anchor (priv->view, &anchor_x, &anchor_y); + + if (canvas == CLUTTER_CANVAS (priv->right_canvas)) + clutter_actor_set_position (priv->right_actor, viewport_x, viewport_y); + else + clutter_actor_set_position (priv->left_actor, -anchor_x, viewport_y); /* Clear the drawing area */ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); @@ -702,7 +887,10 @@ redraw_path (ClutterCanvas *canvas, x = champlain_view_longitude_to_x (view, champlain_location_get_longitude (location)); y = champlain_view_latitude_to_y (view, champlain_location_get_latitude (location)); - cairo_line_to (cr, x, y); + if (canvas == CLUTTER_CANVAS (priv->right_canvas)) + cairo_line_to (cr, x, y); + else + cairo_line_to (cr, x + (viewport_x + anchor_x), y); } if (priv->closed_path) @@ -729,7 +917,7 @@ redraw_path (ClutterCanvas *canvas, if (priv->stroke) cairo_stroke (cr); - set_surface (CHAMPLAIN_EXPORTABLE (layer), cairo_get_target (cr)); + update_surface (layer, canvas, cairo_get_target (cr)); return FALSE; } diff --git a/champlain/champlain-view.c b/champlain/champlain-view.c index 49bb22b..63ac2bb 100644 --- a/champlain/champlain-view.c +++ b/champlain/champlain-view.c @@ -104,7 +104,8 @@ enum PROP_BACKGROUND_PATTERN, PROP_GOTO_ANIMATION_MODE, PROP_GOTO_ANIMATION_DURATION, - PROP_WORLD + PROP_WORLD, + PROP_HORIZONTAL_WRAP }; #define PADDING 10 @@ -147,16 +148,27 @@ struct _ChamplainViewPrivate /* ChamplainView */ ClutterActor *kinetic_scroll; /* kinetic_scroll */ ClutterActor *viewport; /* viewport */ - /* viewport_container */ + ClutterActor *viewport_container; /* viewport_container */ ClutterActor *background_layer; /* background_layer */ ClutterActor *zoom_layer; /* zoom_layer */ ClutterActor *map_layer; /* map_layer */ - ClutterActor *user_layers; /* user_layers */ + /* map_layer clones */ + ClutterActor *user_layers; /* user_layers and clones */ ClutterActor *zoom_overlay_actor; /* zoom_overlay_actor */ ClutterActor *license_actor; /* license_actor */ ClutterContent *background_content; + gboolean hwrap; + gint num_clones; + GList *map_clones; + /* There are num_clones user layer slots, overlayed on the map clones. + * The first slot initially contains the real user_layers actor, and the + * rest contain clones. Whenever the cursor enters a clone slot, its content + * is swapped with the real one so as to ensure reactiveness to events. + */ + GList *user_layer_slots; + gdouble viewport_x; gdouble viewport_y; gint viewport_width; @@ -218,11 +230,14 @@ struct _ChamplainViewPrivate gdouble accumulated_scroll_dy; ChamplainBoundingBox *world_bbox; + + GHashTable *visible_tiles; }; G_DEFINE_TYPE (ChamplainView, champlain_view, CLUTTER_TYPE_ACTOR); - +static void exclusive_destroy_clone (ClutterActor *clone); +static void update_clones (ChamplainView *view); static gboolean scroll_event (ClutterActor *actor, ClutterScrollEvent *event, ChamplainView *view); @@ -244,6 +259,18 @@ static void viewport_pos_changed_cb (GObject *gobject, static gboolean kinetic_scroll_button_press_cb (ClutterActor *actor, ClutterButtonEvent *event, ChamplainView *view); +static ClutterActor *sample_user_layer_at_pos (ChamplainView *view, + gfloat x, + gfloat y); +static void swap_user_layer_slots (ChamplainView *view, + gint original_index, + gint clone_index); +static gboolean viewport_motion_cb (ClutterActor *actor, + ClutterMotionEvent *event, + ChamplainView *view); +static gboolean viewport_press_cb (ClutterActor *actor, + ClutterButtonEvent *event, + ChamplainView *view); static void load_visible_tiles (ChamplainView *view, gboolean relocate); static gboolean view_set_zoom_level_at (ChamplainView *view, @@ -277,10 +304,34 @@ static void get_tile_bounds (ChamplainView *view, guint *min_y, guint *max_x, guint *max_y); -static gboolean tile_in_tile_map (ChamplainView *view, +static gboolean tile_in_tile_table (ChamplainView *view, + GHashTable *table, gint tile_x, gint tile_y); +static gdouble +x_to_wrap_x (gdouble x, gdouble width) +{ + if (x < 0) + x += ((gint)-x / (gint)width + 1) * width; + + return fmod (x, width); +} + + +static gint +get_map_width (ChamplainView *view) +{ + gint size, cols; + ChamplainViewPrivate *priv = view->priv; + + size = champlain_map_source_get_tile_size (priv->map_source); + cols = champlain_map_source_get_column_count (priv->map_source, + priv->zoom_level); + return size * cols; +} + + static void update_coords (ChamplainView *view, gdouble x, @@ -361,10 +412,27 @@ view_relocated_cb (G_GNUC_UNUSED ChamplainViewport *viewport, ChamplainView *view) { ChamplainViewPrivate *priv = view->priv; - + gint anchor_x, anchor_y, new_width, new_height; + gint tile_size, column_count, row_count; + clutter_actor_destroy_all_children (priv->zoom_layer); load_visible_tiles (view, TRUE); g_signal_emit_by_name (view, "layer-relocated", NULL); + + /* Clutter clones need their source actor to have an explicitly set size to display properly */ + tile_size = champlain_map_source_get_tile_size (priv->map_source); + column_count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level); + row_count = champlain_map_source_get_row_count (priv->map_source, priv->zoom_level); + champlain_viewport_get_anchor (CHAMPLAIN_VIEWPORT (priv->viewport), &anchor_x, &anchor_y); + + /* The area containing tiles in the map layer is actually column_count * tile_size wide (same + * for height), but the viewport anchor acts as an offset for the tile actors, causing the map + * layer to contain some empty space as well. + */ + new_width = column_count * tile_size + anchor_x; + new_height = row_count * tile_size + anchor_y; + + clutter_actor_set_size (priv->map_layer, new_width, new_height); } @@ -473,8 +541,11 @@ resize_viewport (ChamplainView *view) lower_y = MIN (y_first - priv->viewport_height / 2, (y_first - priv->viewport_height) + (y_last - y_first) / 2); - - upper_x = MAX (x_last - priv->viewport_width / 2, (x_last - x_first) / 2); + + if (priv->hwrap) + upper_x = MAX (x_last - x_first + priv->viewport_width / 2, priv->viewport_width + (x_last - x_first) / 2); + else + upper_x = MAX (x_last - priv->viewport_width / 2, (x_last - x_first) / 2); upper_y = MAX (y_last - priv->viewport_height / 2, (y_last - y_first)/ 2); /* we don't want to get notified about the position change now */ @@ -567,6 +638,10 @@ champlain_view_get_property (GObject *object, g_value_set_boxed (value, priv->world_bbox); break; + case PROP_HORIZONTAL_WRAP: + g_value_set_boolean (value, champlain_view_get_horizontal_wrap (view)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -648,6 +723,10 @@ champlain_view_set_property (GObject *object, champlain_view_set_world (view, g_value_get_boxed (value)); break; + case PROP_HORIZONTAL_WRAP: + champlain_view_set_horizontal_wrap (view, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -723,6 +802,11 @@ champlain_view_dispose (GObject *object) priv->zoom_gesture = NULL; } + if (priv->visible_tiles != NULL) + { + g_hash_table_destroy (priv->visible_tiles); + priv->visible_tiles = NULL; + } priv->map_layer = NULL; priv->license_actor = NULL; @@ -1080,6 +1164,20 @@ champlain_view_class_init (ChamplainViewClass *champlainViewClass) G_PARAM_READWRITE)); /** + * ChamplainView:horizontal-wrap: + * + * Determines whether the view should wrap horizontally. + * + */ + g_object_class_install_property (object_class, + PROP_HORIZONTAL_WRAP, + g_param_spec_boolean ("horizontal-wrap", + "Horizontal wrap", + "Determines whether the view should wrap horizontally.", + FALSE, + CHAMPLAIN_PARAM_READWRITE)); + + /** * ChamplainView::animation-completed: * * The #ChamplainView::animation-completed signal is emitted when any animation in the view @@ -1162,6 +1260,12 @@ _update_idle_cb (ChamplainView *view) else load_visible_tiles (view, FALSE); + if (priv->hwrap) + { + update_clones (view); + position_viewport (view, x_to_wrap_x (priv->viewport_x, get_map_width (view)), priv->viewport_y); + } + return FALSE; } @@ -1188,6 +1292,67 @@ view_size_changed_cb (ChamplainView *view, priv->viewport_height = height; } +static void +exclusive_destroy_clone (ClutterActor *clone) +{ + if (!CLUTTER_IS_CLONE (clone)) + return; + + clutter_actor_destroy (clone); +} + +static void +update_clones (ChamplainView *view) +{ + DEBUG_LOG () + + ChamplainViewPrivate *priv = view->priv; + gint map_size; + gfloat view_width; + gint i; + + map_size = get_map_width (view); + clutter_actor_get_size (CLUTTER_ACTOR (view), &view_width, NULL); + + priv->num_clones = ceil (view_width / map_size) + 1; + + if (priv->map_clones != NULL) + { + /* Only destroy clones, skip the real user_layers actor */ + g_list_free_full (priv->user_layer_slots, (GDestroyNotify) exclusive_destroy_clone); + g_list_free_full (priv->map_clones, (GDestroyNotify) clutter_actor_destroy); + + priv->map_clones = NULL; + priv->user_layer_slots = NULL; + } + + /* Inserting the real user layer in the first slot */ + priv->user_layer_slots = g_list_append (priv->user_layer_slots, priv->user_layers); + clutter_actor_set_x (priv->user_layers, 0); + + for (i = 0; i < priv->num_clones; i++) + { + ClutterActor *map_clone, *user_clone; + + /* Map layer clones */ + map_clone = clutter_clone_new (priv->map_layer); + clutter_actor_set_x (map_clone, (i + 1) * map_size); + clutter_actor_insert_child_below (priv->viewport_container, map_clone, + NULL); + + priv->map_clones = g_list_prepend (priv->map_clones, map_clone); + + /* User layer clones */ + user_clone = clutter_clone_new (priv->user_layers); + clutter_actor_set_x (user_clone, (i + 1) * map_size); + clutter_actor_insert_child_below (priv->viewport_container, user_clone, + priv->user_layers); + + /* Inserting the user layer clones in the following slots */ + priv->user_layer_slots = g_list_append (priv->user_layer_slots, user_clone); + } +} + static void slice_free_gint64 (gpointer data) @@ -1306,7 +1471,6 @@ champlain_view_init (ChamplainView *view) ChamplainViewPrivate *priv = GET_PRIVATE (view); ChamplainMapSourceFactory *factory; ChamplainMapSource *source; - ClutterActor *viewport_container; ClutterLayoutManager *layout; ClutterColor color = { 0xf1, 0xee, 0xe8, 0xff }; @@ -1345,6 +1509,7 @@ champlain_view_init (ChamplainView *view) priv->redraw_timeout = 0; priv->zoom_actor_timeout = 0; priv->tile_map = g_hash_table_new_full (g_int64_hash, g_int64_equal, slice_free_gint64, NULL); + priv->visible_tiles = g_hash_table_new_full (g_int64_hash, g_int64_equal, slice_free_gint64, NULL); priv->goto_duration = 0; priv->goto_mode = CLUTTER_EASE_IN_OUT_CIRC; priv->world_bbox = champlain_bounding_box_new (); @@ -1352,6 +1517,10 @@ champlain_view_init (ChamplainView *view) priv->world_bbox->bottom = CHAMPLAIN_MIN_LATITUDE; priv->world_bbox->right = CHAMPLAIN_MAX_LONGITUDE; priv->world_bbox->top = CHAMPLAIN_MAX_LATITUDE; + priv->num_clones = 0; + priv->map_clones = NULL; + priv->user_layer_slots = NULL; + priv->hwrap = FALSE; clutter_actor_set_background_color (CLUTTER_ACTOR (view), &color); @@ -1370,21 +1539,22 @@ champlain_view_init (ChamplainView *view) priv->map_layer = clutter_actor_new (); priv->user_layers = clutter_actor_new (); - viewport_container = clutter_actor_new (); - clutter_actor_add_child (viewport_container, priv->background_layer); - clutter_actor_add_child (viewport_container, priv->zoom_layer); - clutter_actor_add_child (viewport_container, priv->map_layer); - clutter_actor_add_child (viewport_container, priv->user_layers); + priv->viewport_container = clutter_actor_new (); + clutter_actor_add_child (priv->viewport_container, priv->background_layer); + clutter_actor_add_child (priv->viewport_container, priv->zoom_layer); + clutter_actor_add_child (priv->viewport_container, priv->map_layer); + clutter_actor_add_child (priv->viewport_container, priv->user_layers); /* Setup viewport */ priv->viewport = champlain_viewport_new (); - champlain_viewport_set_child (CHAMPLAIN_VIEWPORT (priv->viewport), viewport_container); + champlain_viewport_set_child (CHAMPLAIN_VIEWPORT (priv->viewport), priv->viewport_container); g_signal_connect (priv->viewport, "relocated", G_CALLBACK (view_relocated_cb), view); g_signal_connect (priv->viewport, "notify::x-origin", G_CALLBACK (viewport_pos_changed_cb), view); g_signal_connect (priv->viewport, "notify::y-origin", G_CALLBACK (viewport_pos_changed_cb), view); + clutter_actor_set_reactive (priv->viewport, TRUE); /* Setup kinetic scroll */ priv->kinetic_scroll = champlain_kinetic_scroll_view_new (FALSE, CHAMPLAIN_VIEWPORT (priv->viewport)); @@ -1464,6 +1634,18 @@ viewport_pos_changed_cb (G_GNUC_UNUSED GObject *gobject, champlain_viewport_get_origin (CHAMPLAIN_VIEWPORT (priv->viewport), &x, &y); + if (priv->hwrap) + { + gint map_width; + map_width = get_map_width (view); + + /* Faux wrapping, by positioning viewport to correct wrap point + * so the master map view is on the left edge of ChamplainView + * (possibly partially invisible) */ + if (x < 0 || x >= map_width) + position_viewport (view, x_to_wrap_x (x, map_width), y); + } + if (ABS (x - priv->viewport_x) > 100 || ABS (y - priv->viewport_y) > 100) { update_coords (view, x, y, FALSE); @@ -1473,6 +1655,126 @@ viewport_pos_changed_cb (G_GNUC_UNUSED GObject *gobject, } +static void +swap_user_layer_slots (ChamplainView *view, + gint original_index, + gint clone_index) +{ + ChamplainViewPrivate *priv = view->priv; + gint map_width = get_map_width (view); + + GList *original_slot = g_list_nth (priv->user_layer_slots, original_index); + GList *clone_slot = g_list_nth (priv->user_layer_slots, clone_index); + + ClutterActor *clone = clone_slot->data; + + original_slot->data = clone; + clone_slot->data = priv->user_layers; + + clutter_actor_set_x (clone, original_index * map_width); + clutter_actor_set_x (priv->user_layers, clone_index * map_width); +} + + +static gboolean +viewport_motion_cb (G_GNUC_UNUSED ClutterActor *actor, + ClutterMotionEvent *event, + ChamplainView *view) +{ + ChamplainViewPrivate *priv = view->priv; + + gint map_width = get_map_width (view); + + gint original_index = g_list_index (priv->user_layer_slots, priv->user_layers); + gint clone_index = (event->x + priv->viewport_x) / map_width; + + if (clone_index != original_index && clone_index < priv->num_clones + 1) + swap_user_layer_slots (view, original_index, clone_index); + + return TRUE; + } + + +static ClutterActor * +sample_user_layer_at_pos (ChamplainView *view, + gfloat x, + gfloat y) +{ + ChamplainViewPrivate *priv = view->priv; + + ClutterStage *stage = CLUTTER_STAGE (clutter_actor_get_stage (CLUTTER_ACTOR (view))); + ClutterActor *retval = clutter_stage_get_actor_at_pos (stage, + CLUTTER_PICK_REACTIVE, x, y); + + /* If no reactive actor is found on top of the clone, return NULL */ + if (!clutter_actor_contains (priv->user_layers, retval)) + return NULL; + + return retval; +} + + +static gboolean +viewport_press_cb (G_GNUC_UNUSED ClutterActor *actor, + ClutterButtonEvent *event, + ChamplainView *view) +{ + DEBUG_LOG () + + ChamplainViewPrivate *priv = view->priv; + + if (!priv->hwrap) + return FALSE; + + gint original_index = g_list_index (priv->user_layer_slots, priv->user_layers); + gint initial_original_index = original_index; + ClutterActor *sampled_actor = NULL; + + /* Sampling neighbouring slots for children that are split by the slot border. + * (e.g. a marker that has one half in a slot #n and the other half in #n-1) + * Whenever a user clicks on the real user layer, it is swapped succesively with + * the right and left neighbors (if they exist) and the area at the event + * coordinates is inspected for a reactive child actor. If a child is found, + * a button press is synthesized over it. + */ + gint right_neighbor_index = original_index + 1; + gint left_neighbor_index = original_index - 1; + + /* Swapping and testing right neighbor */ + if (right_neighbor_index < priv->num_clones) + { + swap_user_layer_slots (view, original_index, right_neighbor_index); + original_index = right_neighbor_index; + sampled_actor = sample_user_layer_at_pos (view, event->x, event->y); + } + + /* Swapping and testing left neighbor */ + if (left_neighbor_index >= 0 && sampled_actor == NULL) + { + swap_user_layer_slots (view, original_index, left_neighbor_index); + original_index = left_neighbor_index; + sampled_actor = sample_user_layer_at_pos (view, event->x, event->y); + } + + /* If found, redirecting event to the sampled actor */ + if (sampled_actor != NULL) + { + ClutterEvent *cloned_event = (ClutterEvent *)event; + clutter_event_set_source (cloned_event, sampled_actor); + clutter_event_put (cloned_event); + } + else + { + /* Swapping the real layer back to its initial slot */ + if (original_index != initial_original_index) + swap_user_layer_slots (view, original_index, initial_original_index); + + return FALSE; + } + + return TRUE; +} + static gboolean kinetic_scroll_button_press_cb (G_GNUC_UNUSED ClutterActor *actor, ClutterButtonEvent *event, @@ -1789,6 +2091,39 @@ champlain_view_zoom_out (ChamplainView *view) champlain_view_set_zoom_level (view, view->priv->zoom_level - 1); } +static void +paint_surface (ChamplainView *view, + cairo_t *cr, + cairo_surface_t *surface, + double x, + double y, + double opacity) +{ + ChamplainViewPrivate *priv = view->priv; + + gint map_width = get_map_width (view); + + cairo_set_source_surface (cr, + surface, + x, y); + cairo_paint_with_alpha (cr, opacity); + + /* Paint each surface num_clones - 1 extra times (last clone is not + * actually visible) in order to horizontally wrap. + */ + if (priv->hwrap) + { + gint i; + + for (i = 1; i <= priv->num_clones - 1; i++) + { + cairo_set_source_surface (cr, + surface, + x + i * map_width, y); + cairo_paint_with_alpha (cr, opacity); + } + } +} static void layers_to_surface (ChamplainView *view, @@ -1809,8 +2144,8 @@ layers_to_surface (ChamplainView *view, surface = champlain_exportable_get_surface (CHAMPLAIN_EXPORTABLE (layer)); if (!surface) continue; - cairo_set_source_surface (cr, surface, 0, 0); - cairo_paint(cr); + + paint_surface (view, cr, surface, 0, 0, 255); } } @@ -1863,7 +2198,7 @@ champlain_view_to_surface (ChamplainView *view, guint tile_y = champlain_tile_get_y (tile); guint tile_size = champlain_tile_get_size (tile); - if (tile_in_tile_map (view, tile_x, tile_y)) + if (tile_in_tile_table (view, priv->tile_map, tile_x, tile_y)) { cairo_surface_t *tile_surface; double x, y, opacity; @@ -1878,10 +2213,8 @@ champlain_view_to_surface (ChamplainView *view, opacity = ((double) clutter_actor_get_opacity (CLUTTER_ACTOR (tile))) / 255.0; x = ((double) tile_x * tile_size) - priv->viewport_x; y = ((double) tile_y * tile_size) - priv->viewport_y; - cairo_set_source_surface (cr, - tile_surface, - x, y); - cairo_paint_with_alpha(cr, opacity); + + paint_surface (view, cr, tile_surface, x, y, opacity); } } @@ -2109,6 +2442,15 @@ champlain_view_x_to_longitude (ChamplainView *view, g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0); + if (priv->hwrap) + { + gdouble width = get_map_width (view); + x = x_to_wrap_x (x, width); + + if (x >= width - priv->viewport_x) + x -= width; + } + longitude = champlain_map_source_get_longitude (priv->map_source, priv->zoom_level, x + priv->viewport_x); @@ -2202,6 +2544,28 @@ champlain_view_latitude_to_y (ChamplainView *view, return y - priv->viewport_y; } +/** + * champlain_view_get_viewport_anchor: + * @view: a #ChamplainView + * @anchor_x: (out): the x coordinate of the viewport anchor + * @anchor_y: (out): the y coordinate of the viewport anchor + * + * Gets the x and y coordinate of the viewport anchor in respect to the layer origin. + * + * Since: 0.12.14 + */ +void +champlain_view_get_viewport_anchor (ChamplainView *view, + gint *anchor_x, + gint *anchor_y) +{ + DEBUG_LOG () + + g_return_if_fail (CHAMPLAIN_IS_VIEW (view)); + ChamplainViewPrivate *priv = view->priv; + + champlain_viewport_get_anchor (CHAMPLAIN_VIEWPORT (priv->viewport), anchor_x, anchor_y); +} /** * champlain_view_get_viewport_origin: @@ -2285,29 +2649,36 @@ fill_background_tiles (ChamplainView *view) } static void -tile_map_set (ChamplainView *view, gint tile_x, gint tile_y, gboolean value) +tile_table_set (ChamplainView *view, + GHashTable *table, + gint tile_x, + gint tile_y, + gboolean value) { ChamplainViewPrivate *priv = view->priv; gint64 count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level); gint64 *key = g_slice_alloc (sizeof(gint64)); *key = (gint64)tile_y * count + tile_x; if (value) - g_hash_table_insert (priv->tile_map, key, GINT_TO_POINTER (TRUE)); + g_hash_table_insert (table, key, GINT_TO_POINTER (TRUE)); else { - g_hash_table_remove (priv->tile_map, key); + g_hash_table_remove (table, key); g_slice_free (gint64, key); } } static gboolean -tile_in_tile_map (ChamplainView *view, gint tile_x, gint tile_y) +tile_in_tile_table (ChamplainView *view, + GHashTable *table, + gint tile_x, + gint tile_y) { ChamplainViewPrivate *priv = view->priv; gint64 count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level); gint64 key = (gint64)tile_y * count + tile_x; - return GPOINTER_TO_INT (g_hash_table_lookup (priv->tile_map, &key)); + return GPOINTER_TO_INT (g_hash_table_lookup (table, &key)); } @@ -2357,8 +2728,10 @@ fill_tile_cb (FillTileCallbackData *data) gint size = data->size; gint zoom_level = data->zoom_level; - if (!tile_in_tile_map (view, x, y) && zoom_level == priv->zoom_level && data->map_source == priv->map_source && - y >= priv->tile_y_first && y < priv->tile_y_last && x >= priv->tile_x_first && x < priv->tile_x_last) + if (!tile_in_tile_table (view, priv->tile_map, x, y) && + zoom_level == priv->zoom_level && + data->map_source == priv->map_source && + tile_in_tile_table (view, priv->visible_tiles, x, y)) { GList *iter; @@ -2369,7 +2742,7 @@ fill_tile_cb (FillTileCallbackData *data) load_tile_for_source (view, iter->data, opacity, size, x, y); } - tile_map_set (view, x, y, TRUE); + tile_table_set (view, priv->tile_map, x, y, TRUE); } g_object_unref (view); @@ -2378,7 +2751,6 @@ fill_tile_cb (FillTileCallbackData *data) return FALSE; } - static void load_visible_tiles (ChamplainView *view, gboolean relocate) @@ -2389,7 +2761,7 @@ load_visible_tiles (ChamplainView *view, ClutterActorIter iter; gint size; ClutterActor *child; - gint x_count, y_count; + gint x_count, y_count, column_count; guint min_x, min_y, max_x, max_y; gint arm_size, arm_max, turn; gint dirs[5] = { 0, 1, 0, -1, 0 }; @@ -2399,22 +2771,41 @@ load_visible_tiles (ChamplainView *view, get_tile_bounds (view, &min_x, &min_y, &max_x, &max_y); x_count = ceil ((gfloat) priv->viewport_width / size) + 1; - y_count = ceil ((gfloat) priv->viewport_height / size) + 1; + column_count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level); - priv->tile_x_first = CLAMP (priv->viewport_x / size, min_x, max_x); - priv->tile_y_first = CLAMP (priv->viewport_y / size, min_y, max_y); + if (priv->hwrap) + { + priv->tile_x_first = priv->viewport_x / size; + priv->tile_x_last = priv->tile_x_first + x_count; + } + else + { + priv->tile_x_first = CLAMP (priv->viewport_x / size, min_x, max_x); + priv->tile_x_last = priv->tile_x_first + x_count; + priv->tile_x_last = CLAMP (priv->tile_x_last, priv->tile_x_first, max_x); + x_count = priv->tile_x_last - priv->tile_x_first; + } - priv->tile_x_last = priv->tile_x_first + x_count; + y_count = ceil ((gfloat) priv->viewport_height / size) + 1; + priv->tile_y_first = CLAMP (priv->viewport_y / size, min_y, max_y); priv->tile_y_last = priv->tile_y_first + y_count; - - priv->tile_x_last = CLAMP (priv->tile_x_last, priv->tile_x_first, max_x); priv->tile_y_last = CLAMP (priv->tile_y_last, priv->tile_y_first, max_y); - - x_count = priv->tile_x_last - priv->tile_x_first; y_count = priv->tile_y_last - priv->tile_y_first; DEBUG ("Range %d, %d to %d, %d", priv->tile_x_first, priv->tile_y_first, priv->tile_x_last, priv->tile_y_last); + g_hash_table_remove_all (priv->visible_tiles); + for (x = priv->tile_x_first; x < priv->tile_x_last; x++) + for (y = priv->tile_y_first; y < priv->tile_y_last; y++) + { + gint tile_x = x; + + if (priv->hwrap) + tile_x = x_to_wrap_x (tile_x, column_count); + + tile_table_set (view, priv->visible_tiles, tile_x, y, TRUE); + } + /* fill background tiles */ if (priv->background_content != NULL) fill_background_tiles (view); @@ -2428,12 +2819,11 @@ load_visible_tiles (ChamplainView *view, gint tile_x = champlain_tile_get_x (tile); gint tile_y = champlain_tile_get_y (tile); - if (tile_x < priv->tile_x_first || tile_x >= priv->tile_x_last || - tile_y < priv->tile_y_first || tile_y >= priv->tile_y_last) + if (!tile_in_tile_table (view, priv->visible_tiles, tile_x, tile_y)) { champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE); clutter_actor_iter_destroy (&iter); - tile_map_set (view, tile_x, tile_y, FALSE); + tile_table_set (view, priv->tile_map, tile_x, tile_y, FALSE); } else if (relocate) champlain_viewport_set_actor_position (CHAMPLAIN_VIEWPORT (priv->viewport), CLUTTER_ACTOR (tile), tile_x * size, tile_y * size); @@ -2449,14 +2839,20 @@ load_visible_tiles (ChamplainView *view, { for (i = 0; i < arm_size; i++) { - if (!tile_in_tile_map (view, x, y) && y >= priv->tile_y_first && y < priv->tile_y_last && x >= priv->tile_x_first && x < priv->tile_x_last) + gint tile_x = x; + + if (priv->hwrap) + tile_x = x_to_wrap_x (tile_x, column_count); + + if (!tile_in_tile_table (view, priv->tile_map, tile_x, y) && + tile_in_tile_table (view, priv->visible_tiles, tile_x, y)) { FillTileCallbackData *data; DEBUG ("Loading tile %d, %d, %d", priv->zoom_level, x, y); data = g_slice_new (FillTileCallbackData); - data->x = x; + data->x = tile_x; data->y = y; data->size = size; data->zoom_level = priv->zoom_level; @@ -2886,6 +3282,80 @@ champlain_view_get_background_pattern (ChamplainView *view) } +/** + * champlain_view_set_horizontal_wrap: + * @view: a #ChamplainView + * @wrap: %TRUE to enable horizontal wrapping + * + * Sets the value of the #ChamplainView:horizontal-wrap property. + */ +void +champlain_view_set_horizontal_wrap (ChamplainView *view, + gboolean wrap) +{ + DEBUG_LOG () + + g_return_if_fail (CHAMPLAIN_IS_VIEW (view)); + + ChamplainViewPrivate *priv = view->priv; + + if (priv->hwrap == wrap) + return; + + priv->hwrap = wrap; + + if (priv->hwrap) + { + g_signal_connect (priv->viewport, "motion-event", + G_CALLBACK (viewport_motion_cb), view); + g_signal_connect (priv->viewport, "button-press-event", + G_CALLBACK (viewport_press_cb), view); + update_clones (view); + } + else + { + g_list_free_full (priv->map_clones, (GDestroyNotify) clutter_actor_destroy); + g_list_free_full (priv->user_layer_slots, (GDestroyNotify) exclusive_destroy_clone); + priv->map_clones = NULL; + priv->user_layer_slots = NULL; + g_signal_handlers_disconnect_by_func (priv->viewport, viewport_motion_cb, view); + g_signal_handlers_disconnect_by_func (priv->viewport, viewport_press_cb, view); + clutter_actor_set_x (priv->user_layers, 0); + } + resize_viewport (view); + + gint map_width = get_map_width (view); + if (priv->hwrap) + position_viewport (view, x_to_wrap_x (priv->viewport_x, map_width), priv->viewport_y); + else + position_viewport (view, priv->viewport_x - ((gint)priv->viewport_width / map_width / 2) * map_width, priv->viewport_y); + + load_visible_tiles (view, FALSE); +} + + +/** + * champlain_view_get_horizontal_wrap: + * @view: a #ChamplainView + * + * Returns the value of the #ChamplainView:horizontal-wrap property. + * + * Returns: (transfer none): %TRUE if #ChamplainView:horizontal-wrap is set. + * + */ +gboolean +champlain_view_get_horizontal_wrap (ChamplainView *view) +{ + DEBUG_LOG () + + g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), FALSE); + + ChamplainViewPrivate *priv = view->priv; + + return priv->hwrap; +} + + static void position_zoom_actor (ChamplainView *view) { @@ -2927,6 +3397,11 @@ zoom_animation_completed (ClutterActor *actor, priv->animating_zoom = FALSE; position_zoom_actor (view); clutter_actor_show (priv->user_layers); + if (priv->hwrap) + update_clones (view); + + if (priv->tiles_loading == 0) + clutter_actor_destroy_all_children (priv->zoom_layer); g_signal_handlers_disconnect_by_func (actor, zoom_animation_completed, view); g_signal_emit_by_name (view, "animation-completed::zoom", NULL); @@ -2949,15 +3424,19 @@ show_zoom_actor (ChamplainView *view, { ClutterActorIter iter; ClutterActor *child; + ClutterActor *tile_container; gint size; gint x_first, y_first; gdouble zoom_actor_width, zoom_actor_height; + gint column_count; gdouble deltax, deltay; guint min_x, min_y, max_x, max_y; get_tile_bounds (view, &min_x, &min_y, &max_x, &max_y); size = champlain_map_source_get_tile_size (priv->map_source); + column_count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level); + x_first = CLAMP (priv->viewport_x / size, min_x, max_x); y_first = CLAMP (priv->viewport_y / size, min_y, max_y); @@ -2972,6 +3451,7 @@ show_zoom_actor (ChamplainView *view, priv->zoom_actor_viewport_x = priv->viewport_x - deltax; priv->zoom_actor_viewport_y = priv->viewport_y - deltay; + tile_container = clutter_actor_new (); clutter_actor_iter_init (&iter, priv->map_layer); while (clutter_actor_iter_next (&iter, &child)) { @@ -2984,7 +3464,7 @@ show_zoom_actor (ChamplainView *view, g_object_ref (CLUTTER_ACTOR (tile)); clutter_actor_iter_remove (&iter); - clutter_actor_add_child (zoom_actor, CLUTTER_ACTOR (tile)); + clutter_actor_add_child (tile_container, CLUTTER_ACTOR (tile)); g_object_unref (CLUTTER_ACTOR (tile)); /* We move overlay tiles to the zoom actor so they get properly reparented @@ -2994,6 +3474,32 @@ show_zoom_actor (ChamplainView *view, clutter_actor_set_position (CLUTTER_ACTOR (tile), (tile_x - x_first) * size, (tile_y - y_first) * size); } + clutter_actor_add_child (zoom_actor, tile_container); + + /* The tile_container is cloned and its clones are also added to the zoom_actor + * in order to horizontally wrap. Moreover, the old clones are hidden while the zooming + * animation is runnning. + */ + if (priv->hwrap) + { + GList *old_clone = priv->map_clones; + gint i; + + for (i = 0; i < priv->num_clones; i++) + { + gfloat tiles_x; + ClutterActor *clone_right = clutter_clone_new (tile_container); + + clutter_actor_hide (CLUTTER_ACTOR (old_clone->data)); + + clutter_actor_get_position (tile_container, &tiles_x, NULL); + clutter_actor_set_x (clone_right, tiles_x + (i * column_count * size)); + + clutter_actor_add_child (zoom_actor, clone_right); + + old_clone = old_clone->next; + } + } zoom_actor_width = clutter_actor_get_width (zoom_actor); zoom_actor_height = clutter_actor_get_height (zoom_actor); @@ -3026,14 +3532,26 @@ show_zoom_actor (ChamplainView *view, if (!priv->animating_zoom) { - clutter_actor_hide (priv->user_layers); + if (priv->hwrap) + { + GList *slot; + for (slot = priv->user_layer_slots; slot != NULL; slot = slot->next) + clutter_actor_hide (CLUTTER_ACTOR (slot->data)); + } + else + clutter_actor_hide (priv->user_layers); + g_signal_connect (zoom_actor, "transition-stopped::scale-x", G_CALLBACK (zoom_animation_completed), view); } priv->animating_zoom = TRUE; } else - clutter_actor_set_scale (zoom_actor, deltazoom, deltazoom); + { + clutter_actor_set_scale (zoom_actor, deltazoom, deltazoom); + if (priv->hwrap) + update_clones (view); + } } static void @@ -3091,7 +3609,10 @@ view_set_zoom_level_at (ChamplainView *view, { resize_viewport (view); remove_all_tiles (view); - position_viewport (view, new_x, new_y); + if (priv->hwrap) + position_viewport (view, x_to_wrap_x (new_x, get_map_width (view)), new_y); + else + position_viewport (view, new_x, new_y); load_visible_tiles (view, FALSE); if (!priv->animate_zoom) diff --git a/champlain/champlain-view.h b/champlain/champlain-view.h index 43eca16..675606d 100644 --- a/champlain/champlain-view.h +++ b/champlain/champlain-view.h @@ -128,7 +128,8 @@ void champlain_view_set_background_pattern (ChamplainView *view, ClutterContent *background); void champlain_view_set_world (ChamplainView *view, ChamplainBoundingBox *bbox); - +void champlain_view_set_horizontal_wrap (ChamplainView *view, + gboolean wrap); void champlain_view_add_layer (ChamplainView *view, ChamplainLayer *layer); void champlain_view_remove_layer (ChamplainView *view, @@ -148,6 +149,7 @@ gboolean champlain_view_get_animate_zoom (ChamplainView *view); ChamplainState champlain_view_get_state (ChamplainView *view); ClutterContent *champlain_view_get_background_pattern (ChamplainView *view); ChamplainBoundingBox *champlain_view_get_world (ChamplainView *view); +gboolean champlain_view_get_horizontal_wrap (ChamplainView *view); void champlain_view_reload_tiles (ChamplainView *view); @@ -160,6 +162,9 @@ gdouble champlain_view_longitude_to_x (ChamplainView *view, gdouble champlain_view_latitude_to_y (ChamplainView *view, gdouble latitude); +void champlain_view_get_viewport_anchor (ChamplainView *view, + gint *anchor_x, + gint *anchor_y); void champlain_view_get_viewport_origin (ChamplainView *view, gint *x, gint *y); |