static void clamp_adjustments (ChamplainKineticScrollView *scroll) { ChamplainKineticScrollViewPrivate *priv = scroll->priv; if (priv->viewport) { ChamplainAdjustment *hadj, *vadj; gdouble d, value, lower, step_increment; champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport), &hadj, &vadj); /* Snap to the nearest step increment on hadjustment */ champlain_adjustment_get_values (hadj, &value, &lower, NULL, &step_increment); d = (rint ((value - lower) / step_increment) * step_increment) + lower; champlain_adjustment_set_value (hadj, d); /* Snap to the nearest step increment on vadjustment */ champlain_adjustment_get_values (vadj, &value, &lower, NULL, &step_increment); d = (rint ((value - lower) / step_increment) * step_increment) + lower; champlain_adjustment_set_value (vadj, d); } }
static void deceleration_new_frame_cb (ClutterTimeline *timeline, gint frame_num, ChamplainKineticScrollView *scroll) { ChamplainKineticScrollViewPrivate *priv = scroll->priv; if (priv->viewport) { gdouble value, lower, upper; ChamplainAdjustment *hadjust, *vadjust; gint i; gboolean stop = TRUE; champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport), &hadjust, &vadjust); for (i = 0; i < clutter_timeline_get_delta (timeline) / 15; i++) { champlain_adjustment_set_value (hadjust, priv->dx + champlain_adjustment_get_value (hadjust)); champlain_adjustment_set_value (vadjust, priv->dy + champlain_adjustment_get_value (vadjust)); priv->dx = (priv->dx / priv->decel_rate); priv->dy = (priv->dy / priv->decel_rate); } /* Check if we've hit the upper or lower bounds and stop the timeline */ champlain_adjustment_get_values (hadjust, &value, &lower, &upper, NULL); if (((priv->dx > 0) && (value < upper)) || ((priv->dx < 0) && (value > lower))) stop = FALSE; if (stop) { champlain_adjustment_get_values (vadjust, &value, &lower, &upper, NULL); if (((priv->dy > 0) && (value < upper)) || ((priv->dy < 0) && (value > lower))) stop = FALSE; } if (stop) { clutter_timeline_stop (timeline); deceleration_completed_cb (timeline, scroll); } } }
static void champlain_viewport_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ChamplainAdjustment *adjustment; ChamplainViewportPrivate *priv = CHAMPLAIN_VIEWPORT (object)->priv; switch (prop_id) { case PROP_X_ORIGIN: g_value_set_int (value, priv->x); break; case PROP_Y_ORIGIN: g_value_set_int (value, priv->y); break; case PROP_HADJUST: champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (object), &adjustment, NULL); g_value_set_object (value, adjustment); break; case PROP_VADJUST: champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (object), NULL, &adjustment); g_value_set_object (value, adjustment); break; case PROP_SYNC_ADJUST: g_value_set_boolean (value, priv->sync_adjustments); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }
static gboolean button_release_event_cb (ClutterActor *stage, ClutterButtonEvent *event, ChamplainKineticScrollView *scroll) { ChamplainKineticScrollViewPrivate *priv = scroll->priv; ClutterActor *actor = CLUTTER_ACTOR (scroll); gboolean decelerating = FALSE; if ((event->type != CLUTTER_MOTION || event->modifier_state & CLUTTER_BUTTON1_MASK) && (event->type != CLUTTER_BUTTON_RELEASE || event->button != 1) && (event->type != CLUTTER_TOUCH_END || priv->sequence != clutter_event_get_event_sequence ((ClutterEvent *) event))) return FALSE; g_signal_handlers_disconnect_by_func (stage, motion_event_cb, scroll); g_signal_handlers_disconnect_by_func (stage, button_release_event_cb, scroll); if (priv->kinetic && priv->viewport) { gfloat x, y; if (clutter_actor_transform_stage_point (actor, event->x, event->y, &x, &y)) { double frac, x_origin, y_origin; GTimeVal release_time, motion_time; ChamplainAdjustment *hadjust, *vadjust; glong time_diff; gint i; /* Get time delta */ g_get_current_time (&release_time); /* Get average position/time of last x mouse events */ priv->last_motion++; x_origin = y_origin = 0; motion_time = (GTimeVal){ 0, 0 }; for (i = 0; i < priv->last_motion; i++) { ChamplainKineticScrollViewMotion *motion = &g_array_index (priv->motion_buffer, ChamplainKineticScrollViewMotion, i); /* FIXME: This doesn't guard against overflows - Should * either fix that, or calculate the correct maximum * value for the buffer size */ x_origin += motion->x; y_origin += motion->y; motion_time.tv_sec += motion->time.tv_sec; motion_time.tv_usec += motion->time.tv_usec; } x_origin /= priv->last_motion; y_origin /= priv->last_motion; motion_time.tv_sec /= priv->last_motion; motion_time.tv_usec /= priv->last_motion; if (motion_time.tv_sec == release_time.tv_sec) time_diff = release_time.tv_usec - motion_time.tv_usec; else time_diff = release_time.tv_usec + (G_USEC_PER_SEC - motion_time.tv_usec); /* On a macbook that's running Ubuntu 9.04 sometimes 'time_diff' is 0 and this causes a division by 0 when computing 'frac'. This check avoids this error. */ if (time_diff != 0) { /* Work out the fraction of 1/60th of a second that has elapsed */ frac = (time_diff / 1000.0) / (1000.0 / 60.0); /* See how many units to move in 1/60th of a second */ priv->dx = (x_origin - x) / frac; priv->dy = (y_origin - y) / frac; /* Get adjustments to do step-increment snapping */ champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport), &hadjust, &vadjust); if (ABS (priv->dx) > 1 || ABS (priv->dy) > 1) { gdouble value, lower, step_increment, d, a, x, y, n; /* TODO: Convert this all to fixed point? */ /* We want n, where x / y n < z, * x = Distance to move per frame * y = Deceleration rate * z = maximum distance from target * * Rearrange to n = log (x / z) / log (y) * To simplify, z = 1, so n = log (x) / log (y) * * As z = 1, this will cause stops to be slightly abrupt - * add a constant 15 frames to compensate. */ x = MAX (ABS (priv->dx), ABS (priv->dy)); y = priv->decel_rate; n = logf (x) / logf (y) + 15.0; /* Now we have n, adjust dx/dy so that we finish on a step * boundary. * * Distance moved, using the above variable names: * * d = x + x/y + x/y 2 + ... + x/y n * * Using geometric series, * * d = (1 - 1/y (n+1))/(1 - 1/y)*x * * Let a = (1 - 1/y (n+1))/(1 - 1/y), * * d = a * x * * Find d and find its nearest page boundary, then solve for x * * x = d / a */ /* Get adjustments, work out y n */ a = (1.0 - 1.0 / pow (y, n + 1)) / (1.0 - 1.0 / y); /* Solving for dx */ d = a * priv->dx; champlain_adjustment_get_values (hadjust, &value, &lower, NULL, &step_increment); d = ((rint (((value + d) - lower) / step_increment) * step_increment) + lower) - value; priv->dx = (d / a); /* Solving for dy */ d = a * (priv->dy); champlain_adjustment_get_values (vadjust, &value, &lower, NULL, &step_increment); d = ((rint (((value + d) - lower) / step_increment) * step_increment) + lower) - value; priv->dy = (d / a); priv->deceleration_timeline = clutter_timeline_new ((n / 60) * 1000.0); } else { gdouble value, lower, step_increment, d, a, y; /* Start a short effects timeline to snap to the nearest step * boundary (see equations above) */ y = priv->decel_rate; a = (1.0 - 1.0 / pow (y, 4 + 1)) / (1.0 - 1.0 / y); champlain_adjustment_get_values (hadjust, &value, &lower, NULL, &step_increment); d = ((rint ((value - lower) / step_increment) * step_increment) + lower) - value; priv->dx = (d / a); champlain_adjustment_get_values (vadjust, &value, &lower, NULL, &step_increment); d = ((rint ((value - lower) / step_increment) * step_increment) + lower) - value; priv->dy = (d / a); priv->deceleration_timeline = clutter_timeline_new (250); } g_signal_connect (priv->deceleration_timeline, "new_frame", G_CALLBACK (deceleration_new_frame_cb), scroll); g_signal_connect (priv->deceleration_timeline, "completed", G_CALLBACK (deceleration_completed_cb), scroll); clutter_timeline_start (priv->deceleration_timeline); decelerating = TRUE; } } } priv->sequence = NULL; /* Reset motion event buffer */ priv->last_motion = 0; if (!decelerating) { clamp_adjustments (scroll); g_signal_emit_by_name (scroll, "panning-completed", NULL); } /* Due to the way gestures in progress connect to the stage in order to * receive events, we must let these events go through, as they could be * essential for the management of the ClutterZoomGesture in the * ChamplainView. */ return FALSE; }
static gboolean motion_event_cb (ClutterActor *stage, ClutterMotionEvent *event, ChamplainKineticScrollView *scroll) { ChamplainKineticScrollViewPrivate *priv = scroll->priv; ClutterActor *actor = CLUTTER_ACTOR (scroll); gfloat x, y; if ((event->type != CLUTTER_MOTION || !(event->modifier_state & CLUTTER_BUTTON1_MASK)) && (event->type != CLUTTER_TOUCH_UPDATE || priv->sequence != clutter_event_get_event_sequence ((ClutterEvent *) event))) return FALSE; if (clutter_actor_transform_stage_point (actor, event->x, event->y, &x, &y)) { ChamplainKineticScrollViewMotion *motion; if (priv->viewport) { gdouble dx, dy; ChamplainAdjustment *hadjust, *vadjust; champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport), &hadjust, &vadjust); motion = &g_array_index (priv->motion_buffer, ChamplainKineticScrollViewMotion, priv->last_motion); if (hadjust) { dx = (motion->x - x) + champlain_adjustment_get_value (hadjust); champlain_adjustment_set_value (hadjust, dx); } if (vadjust) { dy = (motion->y - y) + champlain_adjustment_get_value (vadjust); champlain_adjustment_set_value (vadjust, dy); } } priv->last_motion++; if (priv->last_motion == priv->motion_buffer->len) { priv->motion_buffer = g_array_remove_index (priv->motion_buffer, 0); g_array_set_size (priv->motion_buffer, priv->last_motion); priv->last_motion--; } motion = &g_array_index (priv->motion_buffer, ChamplainKineticScrollViewMotion, priv->last_motion); motion->x = x; motion->y = y; g_get_current_time (&motion->time); } /* Due to the way gestures in progress connect to the stage in order to * receive events, we must let these events go through, as they could be * essential for the management of the ClutterZoomGesture in the * ChamplainView. */ return FALSE; }
static gboolean motion_event_cb (ClutterActor *stage, ClutterMotionEvent *event, ChamplainKineticScrollView *scroll) { ChamplainKineticScrollViewPrivate *priv = scroll->priv; ClutterActor *actor = CLUTTER_ACTOR (scroll); gfloat x, y; if (event->type != CLUTTER_MOTION || !(event->modifier_state & CLUTTER_BUTTON1_MASK)) return FALSE; if (clutter_actor_transform_stage_point (actor, event->x, event->y, &x, &y)) { ChamplainKineticScrollViewMotion *motion; if (priv->viewport) { gdouble dx, dy; ChamplainAdjustment *hadjust, *vadjust; champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport), &hadjust, &vadjust); motion = &g_array_index (priv->motion_buffer, ChamplainKineticScrollViewMotion, priv->last_motion); if (hadjust) { dx = (motion->x - x) + champlain_adjustment_get_value (hadjust); champlain_adjustment_set_value (hadjust, dx); } if (vadjust) { dy = (motion->y - y) + champlain_adjustment_get_value (vadjust); champlain_adjustment_set_value (vadjust, dy); } } priv->last_motion++; if (priv->last_motion == priv->motion_buffer->len) { priv->motion_buffer = g_array_remove_index (priv->motion_buffer, 0); g_array_set_size (priv->motion_buffer, priv->last_motion); priv->last_motion--; } motion = &g_array_index (priv->motion_buffer, ChamplainKineticScrollViewMotion, priv->last_motion); motion->x = x; motion->y = y; g_get_current_time (&motion->time); } return TRUE; }