2014-05-26 16:13:29 +00:00
|
|
|
/* GTK - The GIMP Toolkit
|
|
|
|
* Copyright (C) 2014 Lieven van der Heide
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include "gtkkineticscrolling.h"
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
2014-06-06 06:47:19 +00:00
|
|
|
#include "fallback-c89.c"
|
|
|
|
|
2014-05-26 16:13:29 +00:00
|
|
|
/*
|
|
|
|
* All our curves are second degree linear differential equations, and
|
|
|
|
* so they can always be written as linear combinations of 2 base
|
|
|
|
* solutions. c1 and c2 are the coefficients to these two base solutions,
|
|
|
|
* and are computed from the initial position and velocity.
|
|
|
|
*
|
|
|
|
* In the case of simple deceleration, the differential equation is
|
|
|
|
*
|
|
|
|
* y'' = -my'
|
|
|
|
*
|
|
|
|
* With m the resistence factor. For this we use the following 2
|
|
|
|
* base solutions:
|
|
|
|
*
|
|
|
|
* f1(x) = 1
|
|
|
|
* f2(x) = exp(-mx)
|
|
|
|
*
|
|
|
|
* In the case of overshoot, the differential equation is
|
|
|
|
*
|
|
|
|
* y'' = -my' - ky
|
|
|
|
*
|
|
|
|
* With m the resistance, and k the spring stiffness constant. We let
|
|
|
|
* k = m^2 / 4, so that the system is critically damped (ie, returns to its
|
|
|
|
* equilibrium position as quickly as possible, without oscillating), and offset
|
|
|
|
* the whole thing, such that the equilibrium position is at 0. This gives the
|
|
|
|
* base solutions
|
|
|
|
*
|
|
|
|
* f1(x) = exp(-mx / 2)
|
|
|
|
* f2(x) = t exp(-mx / 2)
|
|
|
|
*/
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
GTK_KINETIC_SCROLLING_PHASE_DECELERATING,
|
|
|
|
GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING,
|
|
|
|
GTK_KINETIC_SCROLLING_PHASE_FINISHED,
|
|
|
|
} GtkKineticScrollingPhase;
|
|
|
|
|
|
|
|
struct _GtkKineticScrolling
|
|
|
|
{
|
|
|
|
GtkKineticScrollingPhase phase;
|
|
|
|
gdouble lower;
|
|
|
|
gdouble upper;
|
|
|
|
gdouble overshoot_width;
|
|
|
|
gdouble decel_friction;
|
|
|
|
gdouble overshoot_friction;
|
|
|
|
|
|
|
|
gdouble c1;
|
|
|
|
gdouble c2;
|
|
|
|
gdouble equilibrium_position;
|
|
|
|
|
|
|
|
gdouble t;
|
|
|
|
gdouble position;
|
|
|
|
gdouble velocity;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void gtk_kinetic_scrolling_init_overshoot (GtkKineticScrolling *data,
|
|
|
|
gdouble equilibrium_position,
|
|
|
|
gdouble initial_position,
|
|
|
|
gdouble initial_velocity);
|
|
|
|
|
|
|
|
GtkKineticScrolling *
|
|
|
|
gtk_kinetic_scrolling_new (gdouble lower,
|
|
|
|
gdouble upper,
|
|
|
|
gdouble overshoot_width,
|
|
|
|
gdouble decel_friction,
|
|
|
|
gdouble overshoot_friction,
|
|
|
|
gdouble initial_position,
|
|
|
|
gdouble initial_velocity)
|
|
|
|
{
|
|
|
|
GtkKineticScrolling *data;
|
|
|
|
|
|
|
|
data = g_slice_new0 (GtkKineticScrolling);
|
|
|
|
data->lower = lower;
|
|
|
|
data->upper = upper;
|
|
|
|
data->decel_friction = decel_friction;
|
|
|
|
data->overshoot_friction = overshoot_friction;
|
|
|
|
if(initial_position < lower)
|
|
|
|
{
|
|
|
|
gtk_kinetic_scrolling_init_overshoot (data,
|
|
|
|
lower,
|
|
|
|
initial_position,
|
|
|
|
initial_velocity);
|
|
|
|
}
|
|
|
|
else if(initial_position > upper)
|
|
|
|
{
|
|
|
|
gtk_kinetic_scrolling_init_overshoot (data,
|
|
|
|
upper,
|
|
|
|
initial_position,
|
|
|
|
initial_velocity);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
data->phase = GTK_KINETIC_SCROLLING_PHASE_DECELERATING;
|
|
|
|
data->c1 = initial_velocity / decel_friction + initial_position;
|
|
|
|
data->c2 = -initial_velocity / decel_friction;
|
|
|
|
data->t = 0;
|
|
|
|
data->position = initial_position;
|
|
|
|
data->velocity = initial_velocity;
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
gtk_kinetic_scrolling_free (GtkKineticScrolling *kinetic)
|
|
|
|
{
|
|
|
|
g_slice_free (GtkKineticScrolling, kinetic);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gtk_kinetic_scrolling_init_overshoot (GtkKineticScrolling *data,
|
|
|
|
gdouble equilibrium_position,
|
|
|
|
gdouble initial_position,
|
|
|
|
gdouble initial_velocity)
|
|
|
|
{
|
|
|
|
data->phase = GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING;
|
|
|
|
data->equilibrium_position = equilibrium_position;
|
|
|
|
data->c1 = initial_position - equilibrium_position;
|
|
|
|
data->c2 = initial_velocity + data->overshoot_friction / 2 * data->c1;
|
|
|
|
data->t = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
gboolean
|
|
|
|
gtk_kinetic_scrolling_tick (GtkKineticScrolling *data,
|
|
|
|
gdouble time_delta,
|
|
|
|
gdouble *position)
|
|
|
|
{
|
|
|
|
switch(data->phase)
|
|
|
|
{
|
|
|
|
case GTK_KINETIC_SCROLLING_PHASE_DECELERATING:
|
|
|
|
{
|
2016-04-24 09:41:26 +00:00
|
|
|
gdouble last_position = data->position;
|
|
|
|
gdouble last_time = data->t;
|
2014-05-26 16:13:29 +00:00
|
|
|
gdouble exp_part;
|
|
|
|
|
|
|
|
data->t += time_delta;
|
|
|
|
|
|
|
|
exp_part = exp (-data->decel_friction * data->t);
|
|
|
|
data->position = data->c1 + data->c2 * exp_part;
|
|
|
|
data->velocity = -data->decel_friction * data->c2 * exp_part;
|
|
|
|
|
|
|
|
if(data->position < data->lower)
|
|
|
|
{
|
|
|
|
gtk_kinetic_scrolling_init_overshoot(data,data->lower,data->position,data->velocity);
|
|
|
|
}
|
2015-05-31 01:27:24 +00:00
|
|
|
else if (data->position > data->upper)
|
2014-05-26 16:13:29 +00:00
|
|
|
{
|
|
|
|
gtk_kinetic_scrolling_init_overshoot(data, data->upper, data->position, data->velocity);
|
|
|
|
}
|
2016-04-24 09:41:26 +00:00
|
|
|
else if (fabs(data->velocity) < 1 ||
|
|
|
|
(last_time != 0.0 && fabs(data->position - last_position) < 1))
|
2014-05-26 16:13:29 +00:00
|
|
|
{
|
|
|
|
data->phase = GTK_KINETIC_SCROLLING_PHASE_FINISHED;
|
|
|
|
data->position = round(data->position);
|
|
|
|
data->velocity = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case GTK_KINETIC_SCROLLING_PHASE_OVERSHOOTING:
|
|
|
|
{
|
2015-05-31 01:27:24 +00:00
|
|
|
gdouble exp_part, pos;
|
2014-05-26 16:13:29 +00:00
|
|
|
|
|
|
|
data->t += time_delta;
|
|
|
|
exp_part = exp(-data->overshoot_friction / 2 * data->t);
|
2015-05-31 01:27:24 +00:00
|
|
|
pos = exp_part * (data->c1 + data->c2 * data->t);
|
2014-05-26 16:13:29 +00:00
|
|
|
|
2015-05-31 01:27:24 +00:00
|
|
|
if (pos < data->lower - 50 || pos > data->upper + 50)
|
2014-05-26 16:13:29 +00:00
|
|
|
{
|
2015-05-31 01:27:24 +00:00
|
|
|
pos = CLAMP (pos, data->lower - 50, data->upper + 50);
|
|
|
|
gtk_kinetic_scrolling_init_overshoot (data, data->equilibrium_position, pos, 0);
|
2014-05-26 16:13:29 +00:00
|
|
|
}
|
|
|
|
else
|
2015-05-31 01:27:24 +00:00
|
|
|
data->velocity = data->c2 * exp_part - data->overshoot_friction / 2 * pos;
|
2014-05-26 16:13:29 +00:00
|
|
|
|
2015-05-31 01:27:24 +00:00
|
|
|
data->position = pos + data->equilibrium_position;
|
2014-05-26 16:13:29 +00:00
|
|
|
|
2015-05-31 01:27:24 +00:00
|
|
|
if(fabs (pos) < 0.1)
|
2014-05-26 16:13:29 +00:00
|
|
|
{
|
|
|
|
data->phase = GTK_KINETIC_SCROLLING_PHASE_FINISHED;
|
|
|
|
data->position = data->equilibrium_position;
|
|
|
|
data->velocity = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case GTK_KINETIC_SCROLLING_PHASE_FINISHED:
|
2017-10-06 19:19:42 +00:00
|
|
|
default:
|
2014-05-26 16:13:29 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (position)
|
|
|
|
*position = data->position;
|
|
|
|
|
|
|
|
return data->phase != GTK_KINETIC_SCROLLING_PHASE_FINISHED;
|
|
|
|
}
|
|
|
|
|