[libchamplain/wrap: 3/14] view: Implement and add horizontal-wrap property



commit 1224e9a016bd48b3013057a4e757b4950b91aaf2
Author: Jonas Danielsson <jonas threetimestwo org>
Date:   Tue Apr 8 12:47:56 2014 +0200

    view: Implement and add horizontal-wrap property
    
    To give the impression of horizontal wrap clones of the map- and
    user layers will be placed next to the real layers. When we cross over
    to them we will re-position the viewport to give the illusion of wrapping
    the map. The same trick is needed for the zoom actor.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=577597

 champlain/champlain-view.c               |  266 +++++++++++++++++++++++++++---
 champlain/champlain-view.h               |    3 +
 docs/reference/libchamplain-sections.txt |    2 +
 3 files changed, 246 insertions(+), 25 deletions(-)
---
diff --git a/champlain/champlain-view.c b/champlain/champlain-view.c
index 837c9f7..d7761f5 100644
--- a/champlain/champlain-view.c
+++ b/champlain/champlain-view.c
@@ -103,7 +103,9 @@ enum
   PROP_STATE,
   PROP_BACKGROUND_PATTERN,
   PROP_GOTO_ANIMATION_MODE,
-  PROP_GOTO_ANIMATION_DURATION
+  PROP_GOTO_ANIMATION_DURATION,
+  PROP_HORIZONTAL_WRAP
+
 };
 
 #define PADDING 10
@@ -146,16 +148,24 @@ 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 */
+                                                /* map_layer clones left */
+                                                /* map_layer clones right */
   ClutterActor *user_layers;                    /* user_layers */
+                                                /* user_layers clones left */
+                                                /* user_layers clones right */
   ClutterActor *zoom_overlay_actor; /* zoom_overlay_actor */
   ClutterActor *license_actor;      /* license_actor */
 
   ClutterContent *background_content; 
 
+  gboolean hwrap;
+  GList *clones;
+  gint num_clones;
+
   gdouble viewport_x;
   gdouble viewport_y;
   gint viewport_width;
@@ -206,7 +216,7 @@ struct _ChamplainViewPrivate
 
 G_DEFINE_TYPE (ChamplainView, champlain_view, CLUTTER_TYPE_ACTOR);
 
-
+static void update_clones (ChamplainView *view);
 static gboolean scroll_event (ClutterActor *actor,
     ClutterScrollEvent *event,
     ChamplainView *view);
@@ -258,6 +268,17 @@ static ChamplainBoundingBox *get_bounding_box (ChamplainView *view,
     gdouble x,
     gdouble y);
 
+static inline gint
+x_to_wrap_x (gint x, gint width) {
+  while (x < 0)
+    x += width;
+
+  while (x >= width)
+    x -= width;
+
+  return x;
+}
+
 
 static void
 update_coords (ChamplainView *view,
@@ -411,7 +432,12 @@ resize_viewport (ChamplainView *view)
   
   lower_x = MIN (-priv->viewport_width / 2, -priv->viewport_width + map_width / 2);
   lower_y = MIN (-priv->viewport_height / 2, -priv->viewport_height + map_height / 2);
-  upper_x = MAX (map_width - priv->viewport_width / 2, map_width / 2);
+
+  if (priv->hwrap)
+    upper_x = MAX (map_width + priv->viewport_width / 2, priv->viewport_width + map_width / 2);
+  else
+    upper_x = MAX (map_width - priv->viewport_width / 2, map_width / 2);
+
   upper_y = MAX (map_height - priv->viewport_height / 2, map_height / 2);
 
   /* we don't want to get notified about the position change now */
@@ -501,6 +527,10 @@ champlain_view_get_property (GObject *object,
       g_value_set_uint (value, priv->goto_duration);
       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);
     }
@@ -578,6 +608,10 @@ champlain_view_set_property (GObject *object,
       priv->goto_duration = g_value_get_uint (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);
     }
@@ -973,6 +1007,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
@@ -1050,6 +1098,9 @@ _update_idle_cb (ChamplainView *view)
 
   resize_viewport (view);
 
+  if (priv->hwrap)
+    update_clones(view);
+
   if (priv->keep_center_on_resize)
     champlain_view_center_on (view, priv->latitude, priv->longitude);
   else
@@ -1081,6 +1132,56 @@ view_size_changed_cb (ChamplainView *view,
   priv->viewport_height = height;
 }
 
+static void
+update_clones_of (ChamplainView *view, ClutterActor *actor, gint map_size)
+{
+  DEBUG_LOG ()
+
+  ChamplainViewPrivate *priv = view->priv;
+  gint i;
+
+  for (i = 0; i < priv->num_clones; i++) {
+    ClutterActor *clone_right = clutter_clone_new (actor);
+    ClutterActor *clone_left  = clutter_clone_new (actor);
+
+    clutter_actor_set_x (clone_left, -(i + 1) * map_size);
+    clutter_actor_set_x (clone_right, (i + 1) * map_size);
+
+    /* user layers can wrap the map_width, make sure they remain visible */
+    clutter_actor_insert_child_below (priv->viewport_container, clone_left,
+                                      priv->user_layers);
+    clutter_actor_insert_child_below (priv->viewport_container, clone_right,
+                                      priv->user_layers);
+
+    priv->clones = g_list_prepend (priv->clones, clone_right);
+    priv->clones = g_list_prepend (priv->clones, clone_left);
+  }
+}
+
+static void
+update_clones (ChamplainView *view)
+{
+  DEBUG_LOG ()
+
+  ChamplainViewPrivate *priv = view->priv;
+  gint cols, tile_size, map_size;
+  gfloat view_width;
+
+  tile_size = champlain_map_source_get_tile_size (priv->map_source);
+  cols = champlain_map_source_get_column_count (priv->map_source,
+                                                priv->zoom_level);
+  map_size = tile_size * cols;
+  clutter_actor_get_size (CLUTTER_ACTOR (view), &view_width, NULL);
+
+  priv->num_clones = ceil ((view_width / (2 * map_size))) + 1;
+
+  if (priv->clones != NULL) {
+    g_list_free_full (priv->clones, (GDestroyNotify) clutter_actor_destroy);
+    priv->clones = NULL;
+  }
+  update_clones_of (view, priv->map_layer, map_size);
+  update_clones_of (view, priv->user_layers, map_size);
+}
 
 static void 
 slice_free_gint64 (gpointer data)
@@ -1097,7 +1198,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 };
 
@@ -1139,6 +1239,9 @@ champlain_view_init (ChamplainView *view)
   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->num_clones = 0;
+  priv->clones = NULL;
+  priv->hwrap = FALSE;
 
   clutter_actor_set_background_color (CLUTTER_ACTOR (view), &color);
 
@@ -1157,15 +1260,15 @@ 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",
@@ -1243,13 +1346,24 @@ viewport_pos_changed_cb (G_GNUC_UNUSED GObject *gobject,
 
   if (ABS (x - priv->viewport_x) > 100 || ABS (y - priv->viewport_y) > 100)
     {
-      update_coords (view, x, y, FALSE);
+      gint size, cols, map_width;
+
+      size = champlain_map_source_get_tile_size (priv->map_source);
+      cols = champlain_map_source_get_column_count (priv->map_source,
+                                                    priv->zoom_level);
+      map_width = size * cols;
+
+      /* Faux wrapping, by positioning viewport to correct wrap point */
+      if (priv->hwrap && (x < 0 || x >= map_width))
+        position_viewport (view, x_to_wrap_x (x, map_width), y);
+      else
+        update_coords (view, x, y, FALSE);
+
       load_visible_tiles (view, FALSE);
       priv->location_updated = TRUE;
     }
 }
 
-
 static gboolean
 kinetic_scroll_button_press_cb (G_GNUC_UNUSED ClutterActor *actor,
     ClutterButtonEvent *event,
@@ -1723,6 +1837,14 @@ champlain_view_x_to_longitude (ChamplainView *view,
 
   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);
 
+  if (priv->hwrap) {
+    gint cols = champlain_map_source_get_column_count (priv->map_source,
+                                                     priv->zoom_level);
+    gint width = champlain_map_source_get_tile_size (priv->map_source) * cols;
+
+    x = x_to_wrap_x (x, width);
+  }
+
   longitude = champlain_map_source_get_longitude (priv->map_source,
         priv->zoom_level,
         x + priv->viewport_x);
@@ -1998,7 +2120,6 @@ fill_tile_cb (FillTileCallbackData *data)
   return FALSE;
 }
 
-
 static void
 load_visible_tiles (ChamplainView *view,
     gboolean relocate)
@@ -2020,10 +2141,15 @@ load_visible_tiles (ChamplainView *view,
   max_x_end = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level);
   max_y_end = champlain_map_source_get_row_count (priv->map_source, priv->zoom_level);
 
-  x_start = CLAMP (priv->viewport_x / size, 0, max_x_end);
   x_count = ceil ((gfloat) (priv->viewport_width) / size) + 1;
-  x_end = MIN (x_start + x_count, max_x_end);
-  x_count = x_end - x_start;
+  if (priv->hwrap) {
+    x_start = priv->viewport_x / size;
+    x_end = x_start + x_count;
+  } else {
+    x_start = CLAMP (priv->viewport_x / size, 0, max_x_end);
+    x_end = MIN (x_start + x_count, max_x_end);
+    x_count = x_end - x_start;
+  }
 
   y_start = CLAMP (priv->viewport_y / size, 0, max_y_end);
   y_count = ceil ((gfloat) priv->viewport_height / size) + 1;
@@ -2032,8 +2158,14 @@ load_visible_tiles (ChamplainView *view,
 
   g_hash_table_remove_all (priv->visible_tiles);
   for (x = x_start; x < x_end; x++)
-    for (y = y_start; y < y_end; y++)
-      tile_table_set (view, priv->visible_tiles, x, y, TRUE);
+    for (y = y_start; y < y_end; y++) {
+      gint tile_x = x;
+
+      if (priv->hwrap)
+        tile_x = x_to_wrap_x (tile_x, max_x_end);
+
+      tile_table_set (view, priv->visible_tiles, tile_x, y, TRUE);
+    }
 
   /* fill background tiles */
   if (priv->background_content != NULL)
@@ -2068,16 +2200,21 @@ load_visible_tiles (ChamplainView *view,
     {
       for (i = 0; i < arm_size; i++)
         {
-          if (!tile_in_tile_table (view, priv->tile_map, x, y) &&
-              tile_in_tile_table (view, priv->visible_tiles, x, y) &&
+          gint tile_x = x;
+
+          if (priv->hwrap)
+            tile_x = x_to_wrap_x (tile_x, max_x_end);
+
+          if (!tile_in_tile_table (view, priv->tile_map, tile_x, y) &&
+              tile_in_tile_table (view, priv->visible_tiles, tile_x, y) &&
               y >= y_start && y < y_end)
             {
               FillTileCallbackData *data;
 
-              DEBUG ("Loading tile %d, %d, %d", priv->zoom_level, x, y);
+              DEBUG ("Loading tile %d, %d, %d", priv->zoom_level, tile_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;
@@ -2507,6 +2644,67 @@ 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 (view, "notify::zoom-level",
+                      G_CALLBACK (update_clones), NULL);
+    update_clones (view);
+  } else {
+    g_signal_handlers_disconnect_by_func (view,
+                                          G_CALLBACK (update_clones), NULL);
+    g_list_free_full (priv->clones, (GDestroyNotify) clutter_actor_destroy);
+    priv->clones = NULL;
+  }
+  resize_viewport (view);
+  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), NULL);
+
+  ChamplainViewPrivate *priv = view->priv;
+
+  return priv->hwrap;
+}
+
+
 static void
 position_zoom_actor (ChamplainView *view)
 {
@@ -2569,7 +2767,8 @@ show_zoom_actor (ChamplainView *view,
     {
       ClutterActorIter iter;
       ClutterActor *child;
-      gint size;
+      ClutterActor *tile_container;
+      gint size, i;
       gint x_first, y_first;
       gdouble zoom_actor_width, zoom_actor_height;
       gdouble max_x_end, max_y_end;
@@ -2594,6 +2793,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))
         {
@@ -2606,7 +2806,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
@@ -2616,6 +2816,22 @@ 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);
+
+      if (priv->hwrap) {
+        for (i = 0; i < priv->num_clones; i++) {
+          ClutterActor *clone_left = clutter_clone_new (tile_container);
+          ClutterActor *clone_right = clutter_clone_new (tile_container);
+          gfloat tiles_x;
+
+          clutter_actor_get_position (tile_container, &tiles_x, NULL);
+          clutter_actor_set_x (clone_left, tiles_x - (i * max_x_end * size));
+          clutter_actor_set_x (clone_right, tiles_x + (i * max_x_end * size));
+
+          clutter_actor_add_child (zoom_actor, clone_left);
+          clutter_actor_add_child (zoom_actor, clone_right);
+        }
+      }
 
       zoom_actor_width = clutter_actor_get_width (zoom_actor);
       zoom_actor_height = clutter_actor_get_height (zoom_actor);
diff --git a/champlain/champlain-view.h b/champlain/champlain-view.h
index 251dcf1..494f297 100644
--- a/champlain/champlain-view.h
+++ b/champlain/champlain-view.h
@@ -126,6 +126,8 @@ void champlain_view_set_animate_zoom (ChamplainView *view,
     gboolean value);
 void champlain_view_set_background_pattern (ChamplainView *view,
     ClutterContent *background);
+void champlain_view_set_horizontal_wrap (ChamplainView *view,
+    gboolean wrap);
 
 void champlain_view_add_layer (ChamplainView *view,
     ChamplainLayer *layer);
@@ -143,6 +145,7 @@ gboolean champlain_view_get_zoom_on_double_click (ChamplainView *view);
 gboolean champlain_view_get_animate_zoom (ChamplainView *view);
 ChamplainState champlain_view_get_state (ChamplainView *view);
 ClutterContent *champlain_view_get_background_pattern (ChamplainView *view);
+gboolean champlain_view_get_horizontal_wrap (ChamplainView *view);
 
 void champlain_view_reload_tiles (ChamplainView *view);
 
diff --git a/docs/reference/libchamplain-sections.txt b/docs/reference/libchamplain-sections.txt
index 87b473d..097a21a 100644
--- a/docs/reference/libchamplain-sections.txt
+++ b/docs/reference/libchamplain-sections.txt
@@ -141,6 +141,7 @@ champlain_view_set_keep_center_on_resize
 champlain_view_set_zoom_on_double_click
 champlain_view_set_animate_zoom
 champlain_view_set_background_pattern
+champlain_view_set_horizontal_wrap
 champlain_view_add_layer
 champlain_view_remove_layer
 champlain_view_get_zoom_level
@@ -153,6 +154,7 @@ champlain_view_get_keep_center_on_resize
 champlain_view_get_zoom_on_double_click
 champlain_view_get_animate_zoom
 champlain_view_get_background_pattern
+champlain_view_get_horizontal_wrap
 champlain_view_reload_tiles
 champlain_view_x_to_longitude
 champlain_view_y_to_latitude


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]