Hi all,
This is an adaptation for JUCE of the “Yet Another Smooth Scrolling” Firefox plugin.
SmoothScrollBar.h
// =============================================================================
#ifndef __JUCE_SMOOTHSCROLLBAR__
#define __JUCE_SMOOTHSCROLLBAR__
// =============================================================================
#include "juce.h"
// =============================================================================
class SmoothTimer;
class SmoothScrollBar : public ScrollBar
{
SmoothTimer *smoothTimer;
public:
SmoothScrollBar (const bool isVertical, const bool buttonsAreVisible);
~SmoothScrollBar ();
// =================================================================
void initGlobals ();
void initSmoothWheel ();
bool modifierHeld (const MouseEvent &e, const int modifier);
bool acquireEvent (const MouseEvent &e, double detail);
void smoothWheelHandler (const MouseEvent &e, double detail);
void mouseWheelLoop ();
// =================================================================
double scrollProfile (double current);
double transition (double percentage, double srcProfile, double dstProfile);
double profileLinear (double percentage);
double profileLinearVelocity (double percentage);
double profilePower2 (double percentage);
double profilePower2Velocity (double percentage);
double profilePower3 (double percentage);
double profilePower3Velocity (double percentage);
double profileCosine (double percentage);
double profileCosineVelocity (double percentage);
double profileConstVelocity (double percentage);
double profileConstVelocityVelocity (double percentage);
// =================================================================
private:
// **************** CONFIGURATION SECTION ****************
// Global enable/disable for this extension. default is true.
// setting it to false will disable this extension completely.
const bool sw_EnableThisExtension;
// =================================================================
// Select one of several preset settings: (default is 7)
// 0 : don't use preset. use actual variables values.
// 1 : turbo: for those who like quickies (duration=150, non-linear, smooth transitions, relative step)
// 2 : fast: slower than turbo, faster than the medium (duration=300, non-linear, smooth transitions, relative step)
// 3 : medium: [was the default in v0.2] (duration=500, non-linear, smooth transitions, relative step)
// 4 : nice and easy: yet even slower and smoother (duration=1000, non-linear, smooth transitions, relative step)
// 5 : fast + adaptive-step
// 6 : fast + adaptive-duration
// 7 : -> [DEFAULT] fast + adaptive-step + adaptive-duration
// 8 : medium + adaptive-step
// 9 : medium + adaptive-step + adattive-duration
// 10 : same as version 0.1 default (duration=250, linear, no smooth transition, no relative step)
// 11 : same as version 0.1 with isLinear=false, duration=150 (no smooth transition, no relative step)
const int sw_PresetSettings;
// =================================================================
// Max milisec to complete scroll after last scroll event
// reasonable values are (fast)100/250/500/1000(slow). default is 300
// -> this value is overriden if a preset is selected.
int sw_ScrollDuration;
// =================================================================
// If enabled, set step size relative to the scrolled area height by a constant factor.
// thus, if enabled and the visible window/frame is short, so will be the scroll steps.
// when enabled, it overrides gecko internal fixed pixels/line constant. default is true.
// -> this value is overriden if a preset is selected.
bool sw_EnableRelativeStepSize;
// =================================================================
// If enabled, the step size will vary according to the delay between consecutive wheel events.
// such that fast wheel scroll will cause step size to increase, and vice versa.
// the desired effect is that when you're reading, and want to scroll, the step is small,
// but it you're just browsing through the document, with faster wheel action,
// you'll gain more 'milage' per wheel event. default is true.
// -> this value is overriden if a preset is selected.
bool sw_EnableAdaptiveStep;
// =================================================================
// If enabled, duration will vary according to the delay between consecutive wheel events,
// such that fast wheel scroll will cause duration to DEcrease, and vice versa.
// the desired effect is to make it slower and smoother while reading, but 'snappy' when browsing.
// default is true.
// -> this value is overriden if a preset is selected.
bool sw_EnableAdaptiveDuration;
// =================================================================
// If enabled, causes the scroll to 'land gracefully' on the top or bottom edges of the document.
// default is true.
bool sw_EnableSoftEdge;
// =================================================================
// Limits the frames/sec of the smooth scroll operation.
// for best visual performance, set this value to your monitor refresh rate. default is 100.
// reasonable values are 20 to 120
// note that setting it to high values will make no difference if your cpu isn't up for the task.
// tips: for faster Gecko rendering, set Desktop color to 16bpp, and turn off font-anti-aliasing
// IMPORTANT TIP: if possible, set your monitor refresh rate to 100, and ScrollMaxFPS to either 100 or 50.
// (other values will cause imprecision due to low resolution internal timers)
const int sw_ScrollMaxFPS;
// =================================================================
// IMPORTANT NOTE ABOUT MODIFIER KEYS:
// by default, mozilla/phoenix/firebird assigns the ctrl key to enlarge/reduce the text size.
// the alt key is assigned to browse history. shift key is not assigned.
// in order for a modifier key to work with this extension, it has to be UNassigned first.
// you can UNassign a modifier key for the mouse wheel as follows:
// mozilla: edit->preferences->advanced->mouse Wheel -> set it to scroll normally.
// phoenix/firebird: about:config in the locationbar bar. then change mousewheel.with<xxx>key.action property to 0
// =================================================================
// Select a modifier key for fine small scroll steps. default is 3 (SHIFT key).
// possible values: 0=Disable, 1=ALT, 2=CTRL, 3=SHIFT
const int sw_ModifierSmallStep;
// =================================================================
// Select a modifier key for full page scroll (like PgUp/PgDn). default is 1 (ALT key).
// possible values: 0=Disable, 1=ALT, 2=CTRL, 3=SHIFT
const int sw_ModifierFullPageStep;
// =================================================================
// Select a modifier key for currently disabling smoothwheel. default is 0 (not assigned).
// possible values: 0=Disable, 1=ALT, 2=CTRL, 3=SHIFT
const int sw_ModifierDisableSmoothWheel;
// =================================================================
// Curtesy of All-In-One-Gestures author, starting with v0.3, smoothwheel uses a new target detection system
// (that's the system that decides what part of the screen to scroll when the wheel is scrolled)
// the new system is able to scroll inside textareas of forms, drop-down menus and more.
// however, on older Gecko builds, it currently performs less that optimal. So, if you feel that you prefere
// previous version's (v0.2) detection mechanism, set sw_compatibilityDetectionMode to true.
// default is false.
const bool sw_compatibilityDetectionMode;
// ********* END OF COMMON CONFIGURATION OPTIONS SECTION ***********
//WHILE THE ABOVE COMMON CONFIGURATIONS ARE MOSTLY ENABLE/DISABLE, THE NEXT
//VALUES CAN BE USED TO FINE-TUNE THE BEHAVIOUR OF EACH FEATURE
// =================================================================
//these 2 values control the step size when EnableAdaptiveStep=true.
//they define the factors for the smallest and largest steps, respectively. defaults are .4 and 1.2
//for extreme control (and variation) (requires a steady hand as well), try 0.3 and 3 respectively.
double sw_AdaptiveStepMinStepFactor;
double sw_AdaptiveStepMaxStepFactor;
// =================================================================
// These 2 values control the duration when EnableAdaptiveDuration=true.
// they define the factors for the smallest and largest durations, respectively. defaults are .8 and 2
double sw_AdaptiveDurationMinStepFactor;
double sw_AdaptiveDurationMaxStepFactor;
// =================================================================
// Factor to use when EnableRelativeStepSize is true. default is 0.15 of viewable page height.
const double sw_RelativeStepFactor;
// =================================================================
// This factor configures how much "full page" is when ModifierFullPageStep is used.
// 1 will result in full page height. 0.5 results in half page, etc.
// default is 0.9 of full page height.
const double sw_FullPageFactor;
// =================================================================
// Factor for how short the scroll steps are if ModifierSmallStep is used.
// default is 0.1 of normal step.
const double sw_ShortStepFactor;
// =================================================================
// #pixels per scroll line. relevant only if EnableRelativeStepSize=false.
// default value is 20 (empirical estimation of internal Gecko value)
const int sw_ScrollPx;
// =================================================================
// the next 4 values control the duration between wheel events that define the
// adaptive behaviour of the adaptiveStep and adaptiveDurations features.
// =================================================================
// defaults are .7 and 1.5
double sw_AdaptiveStepMinDurationFactor;
double sw_AdaptiveStepMaxDurationFactor;
// =================================================================
// defaults are .7 and 1.5
double sw_AdaptiveDurationMinDurationFactor;
double sw_AdaptiveDurationMaxDurationFactor;
// =================================================================
//******* it's recommended not to change the next values since it'll prevent optimizations *******/
// =================================================================
// Tries to make a smooth transition if new scroll event occures while still scrolling. default is true.
// -> this value is overriden if a preset is selected.
// -> changing the default value will prevent code optimizations.
bool sw_EnableSmoothEventTransition;
// =================================================================
// Selects either linear (=0) or various non-linear(=1/2/3) scroll profiles. default is 1.
// possible values:
// 0 : Linear
// 1 : 2nd power (non-linear)
// 2 : 3rd power (non-linear)
// 3 : cosine (non-linear)
// -> this value is overriden if a preset is selected.
// -> changing the default value will prevent code optimizations.
int sw_SmoothScrollProfile;
// =================================================================
// Percentage of when to finish transitional profile (from previous velocity to absolute scroll profile)
// default is 1.
// -> this value is overriden if a preset is selected.
// -> changing the default value will prevent code optimizations.
int sw_TransEnd;
// =================================================================
// THIS VALUE IS IRRELEVANT WITH THE NEW (v0.3+) DETECTION MECHANISM
// Whether or not to enable WHOLE PAGE scroll when mouse wheel is over form textarea. default is false.
// if set to false, mouse wheel events will NOT be intercepted by this extension while
// mouse is over a form textarea. instead, the DEFAULT scroll will be issued.
// if set to true, the WHOLE PAGE will scroll smoothly when mouse is over a form textarea.
const bool sw_EnableWholePageScrollInForms;
// =================================================================
// controls debug mode on/off. default is false.
bool sw_debugMode;
//*************** END OF CONFIGURATION SECTION ********************
//fps converted to ms between scroll events
const int sw_LoopInterval;
// =================================================================
// Global variables (used by more than one function).
double sw_scrollLeft; // in pixels
double sw_scrollLeftLastEvent; // in pixels
double sw_scrollEventTimeStamp; // timestamp
double sw_scrollDeadline; // timestamp
void* sw_clientFrame; // holds the object to scroll
bool sw_intervalID; // holds the interval timer handle. also indicates whether we're currently scrolling.
double sw_velocityLastEvent; //
double sw_velocityCurrent; //
double sw_scrollCurrentStep; // how many pixels to scroll for the current scroll event.
double sw_scrollCurrentDuration; // how many ms to assign for the current scroll event.
bool sw_optimizedProfileCodeExists; // whether optimized code exist according to various variables
bool sw_optimizedHandlerExists; // if all vairables match, there's a pre-set optimized handler for default settings.
double sw_maxTop; // maximum top to scroll to. =sw_clientFrame.scrollHeight-sw_clientFrame.clientHeight
bool sw_isScrollable; //
// =================================================================
// Set some of the adaptive functionality parameters only once in advance.
double sw_ASa;
double sw_ASb;
double sw_ADa;
double sw_ADb;
// =================================================================
double sw_lastWheelLoopEvent;
double sw_dtn_0;
double sw_dtn_1;
double sw_dtn_2;
// =================================================================
public:
// =================================================================
/** override the ScrollBar methods */
bool keyPressed (const KeyPress &key);
void mouseWheelMove (const MouseEvent &e,
float wheelIncrementX,
float wheelIncrementY);
// =================================================================
private:
// =================================================================
friend class SmoothTimer;
void internalCallback ();
// =================================================================
};
// =============================================================================
class SmoothTimer : public Timer
{
public:
SmoothTimer (SmoothScrollBar *p)
: parent (p)
{}
void timerCallback ()
{
parent->internalCallback ();
}
private:
SmoothScrollBar* parent;
};
// =============================================================================
#endif // __JUCE_SMOOTHSCROLLBAR__
// =============================================================================
SmoothScrollBar.cpp
// =============================================================================
#include "SmoothScrollBar.h"
// =============================================================================
#define M_PI (3.14159265358979323846)
// =============================================================================
SmoothScrollBar::SmoothScrollBar (const bool isVertical,
const bool buttonsAreVisible = true)
: ScrollBar (isVertical, buttonsAreVisible),
// SW FPS
sw_LoopInterval (roundDoubleToInt (1000 / sw_ScrollMaxFPS)),
// SW OPTIONS
sw_EnableThisExtension (true),
sw_PresetSettings (7),
sw_ScrollDuration (100),
sw_EnableRelativeStepSize (true),
sw_EnableAdaptiveStep (true),
sw_EnableAdaptiveDuration (true),
sw_EnableSoftEdge (true),
sw_ScrollMaxFPS (120),
sw_ModifierSmallStep (3),
sw_ModifierFullPageStep (2),
sw_ModifierDisableSmoothWheel (0),
sw_compatibilityDetectionMode (false),
// SW GLOBALS
sw_scrollLeft (0),
sw_scrollLeftLastEvent (0),
sw_scrollEventTimeStamp (0),
sw_scrollDeadline (0),
sw_clientFrame (NULL),
sw_intervalID (false),
sw_velocityLastEvent (0),
sw_velocityCurrent (0),
sw_scrollCurrentStep (1),
sw_scrollCurrentDuration (0),
sw_optimizedProfileCodeExists (false),
sw_optimizedHandlerExists (false),
sw_maxTop (0),
sw_isScrollable (false),
// SW ADAPTIVE
sw_AdaptiveStepMinStepFactor (.4),
sw_AdaptiveStepMaxStepFactor (1.2),
sw_AdaptiveDurationMinStepFactor (.8),
sw_AdaptiveDurationMaxStepFactor (2),
sw_RelativeStepFactor (0.15),
sw_FullPageFactor (0.9),
sw_ShortStepFactor (0.1),
sw_ScrollPx (20),
sw_AdaptiveStepMinDurationFactor (.7),
sw_AdaptiveStepMaxDurationFactor (1.5),
sw_AdaptiveDurationMinDurationFactor (.7),
sw_AdaptiveDurationMaxDurationFactor (1.5),
sw_EnableSmoothEventTransition (true),
sw_SmoothScrollProfile (3),
sw_TransEnd (1),
sw_EnableWholePageScrollInForms (false),
sw_debugMode (false),
// SW OTHERS
sw_ASa (0),
sw_ASb (0),
sw_ADa (0),
sw_ADb (0),
// SW OTHERS
sw_lastWheelLoopEvent (0),
sw_dtn_0 (0),
sw_dtn_1 (0),
sw_dtn_2 (0)
{
smoothTimer = new SmoothTimer(this);
initSmoothWheel ();
}
// =============================================================================
SmoothScrollBar::~SmoothScrollBar ()
{
deleteAndZero (smoothTimer);
}
// =============================================================================
bool SmoothScrollBar::keyPressed (const KeyPress &key)
{
if (ScrollBar::isVertical())
{
if (key.upKey)
{
smoothWheelHandler (MouseEvent(0,0,ModifierKeys::getCurrentModifiersRealtime(),NULL,Time::getCurrentTime(),
0,0, Time::getCurrentTime(),
1, false), -1);
return true;
}
else
if (key.downKey)
{
smoothWheelHandler (MouseEvent(0,0,ModifierKeys::getCurrentModifiersRealtime(),NULL,Time::getCurrentTime(),
0,0, Time::getCurrentTime(),
1, false), +1);
return true;
}
}
else
{
if (key.leftKey)
{
smoothWheelHandler (MouseEvent(0,0,ModifierKeys::getCurrentModifiersRealtime(),NULL,Time::getCurrentTime(),
0,0, Time::getCurrentTime(),
1, false), -1);
return true;
}
else
if (key.rightKey)
{
smoothWheelHandler (MouseEvent(0,0,ModifierKeys::getCurrentModifiersRealtime(),NULL,Time::getCurrentTime(),
0,0, Time::getCurrentTime(),
1, false), +1);
return true;
}
}
return false;
}
// =============================================================================
void SmoothScrollBar::mouseWheelMove (const MouseEvent &e,
float wheelIncrementX,
float wheelIncrementY)
{
double increment = ScrollBar::isVertical() ? wheelIncrementY : wheelIncrementX;
smoothWheelHandler (e, -increment);
}
// =============================================================================
//checks whether the specified modifier key was held when the event was intercepted.
bool SmoothScrollBar::modifierHeld (const MouseEvent &e, const int modifier)
{
switch (modifier)
{
case 1:
return e.mods.isAltDown();
case 2:
return e.mods.isCtrlDown();
case 3:
return e.mods.isShiftDown();
}
return false;
}
// =============================================================================
bool SmoothScrollBar::acquireEvent (const MouseEvent &e, double detail)
{
//implicit else: ok. intercepting event. initialize variables, step size etc.
//sw_intervalID = window.setInterval(sw_smoothWheelLoop,sw_LoopInterval);
//sw_clientFrame= tmpClientFrame;
//int viewHeight = getHeight();//clientFrame.innerHeight;
int viewHeight;
if (ScrollBar::isVertical())
{
viewHeight = getParentComponent()->getHeight();
if (viewHeight == 0)
viewHeight = getHeight();
}
else
{
viewHeight = getParentComponent()->getWidth();
if (viewHeight == 0)
viewHeight = getWidth();
}
if (modifierHeld (e, sw_ModifierFullPageStep))
sw_scrollCurrentStep = viewHeight * sw_FullPageFactor;
else
sw_scrollCurrentStep = (sw_EnableRelativeStepSize) ?
viewHeight * sw_RelativeStepFactor :
sw_ScrollPx * (abs(detail));
if (modifierHeld (e, sw_ModifierSmallStep))
sw_scrollCurrentStep = sw_scrollCurrentStep * sw_ShortStepFactor;
sw_maxTop = ScrollBar::getMaximumRangeLimit();
sw_velocityCurrent = 0;
smoothTimer->startTimer (sw_LoopInterval);
sw_intervalID = true;
return true;
}
// =============================================================================
void SmoothScrollBar::smoothWheelHandler (const MouseEvent &e, double detail)
{
bool alreadyScrolling = (sw_velocityCurrent != 0);
double now = Time::getMillisecondCounterHiRes();
if (!alreadyScrolling)
{
//already scrolling, do average 2
sw_dtn_1=
sw_dtn_2=
sw_ScrollDuration * sw_AdaptiveDurationMaxStepFactor;
}
else
{
sw_dtn_2 = sw_dtn_1;
sw_dtn_1 = sw_dtn_0;
}
sw_dtn_0 = (now - sw_scrollEventTimeStamp);
//average last 3 event deltas.
double dtn = (sw_dtn_0 + sw_dtn_1 + sw_dtn_2) / 3;
// if all settings match, use a pre-configured handler.
if (sw_optimizedHandlerExists)
{
if (!sw_intervalID)
if (!acquireEvent(e, detail))
return;
double sf = sw_ASa * dtn + sw_ASb;
double df = sw_ADa * dtn + sw_ADb;
sw_scrollCurrentDuration = 300 * ((df < .8) ? .8 :
((df > 2) ? 2 : df));
sw_scrollLeft += sw_scrollCurrentStep * ((detail > 0) ? 1 : -1) *
((sf > 1.2) ? 1.2 :
((sf < .4) ? .4 : sf));
double st = ScrollBar::getCurrentRangeStart();
double sd = st + sw_scrollLeft;
sw_scrollLeft = (sd < 0) ? -st : ((sd > sw_maxTop) ? sw_maxTop - st : sw_scrollLeft);
sw_scrollEventTimeStamp = sw_velocityCurrent ? sw_lastWheelLoopEvent : now; //now;
sw_scrollDeadline = sw_scrollEventTimeStamp + sw_scrollCurrentDuration;
sw_scrollLeftLastEvent = sw_scrollLeft;
sw_velocityLastEvent = sw_velocityCurrent;
//e.preventDefault ();
//e.stopPropagation ();
return;
}
else
{
//implicit else, consider all variables explicitly
if (!sw_intervalID)
//if not currently scrolling
if (!sw_EnableThisExtension || !acquireEvent(e, detail))
//if event should not be intercepted
return; //let default handler kick in
//implicit else: event acquired and vars initialized
//implicit else: currently scrolling -> modify target and deadline.
//e.preventDefault ();
//e.stopPropagation ();
}
double suggestedStepFactor = jlimit(sw_AdaptiveStepMinStepFactor,
sw_AdaptiveStepMaxStepFactor,
sw_ASa * dtn + sw_ASb);
double suggestedDurationFactor = jlimit(sw_AdaptiveDurationMinStepFactor,
sw_AdaptiveDurationMaxStepFactor,
sw_ADa * dtn + sw_ADb);
sw_scrollCurrentDuration = sw_ScrollDuration *
((sw_EnableAdaptiveDuration) ? suggestedDurationFactor : 1);
sw_scrollLeft += ((detail > 0) ? 1 : -1) * sw_scrollCurrentStep *
((sw_EnableAdaptiveStep) ? suggestedStepFactor : 1);
if (sw_EnableSoftEdge)
{
double destination = ScrollBar::getCurrentRangeStart() + sw_scrollLeft;
if (destination < 0)
sw_scrollLeft = -ScrollBar::getCurrentRangeStart();
else
if (destination > ScrollBar::getMaximumRangeLimit())
sw_scrollLeft = ScrollBar::getMaximumRangeLimit() -
ScrollBar::getCurrentRangeStart();
}
sw_scrollEventTimeStamp = alreadyScrolling ? sw_lastWheelLoopEvent : now;
sw_scrollDeadline = sw_scrollEventTimeStamp + sw_scrollCurrentDuration;
sw_scrollLeftLastEvent = sw_scrollLeft;
sw_velocityLastEvent = sw_velocityCurrent;
}
// =============================================================================
void SmoothScrollBar::internalCallback ()
{
double toScroll, currentTime, currentPercentage, timeLeft;
currentTime = Time::getMillisecondCounterHiRes ();
if (sw_lastWheelLoopEvent == currentTime)
{
//we're back too soon. means previous loop was delayed.
//smoothTimer->stopTimer ();
//sw_intervalID = false;
smoothTimer->startTimer (sw_LoopInterval);
sw_intervalID = true;
return;
}
sw_lastWheelLoopEvent = currentTime;
timeLeft = sw_scrollDeadline - currentTime;
if (timeLeft <= 0)
//we're past the deadline. scroll what's left.
toScroll = sw_scrollLeft;
else
{
//calculate next scroll destination according to scroll profile
currentPercentage = 1 - timeLeft / sw_scrollCurrentDuration;
toScroll= /*roundDoubleToIntAccurate*/ (sw_scrollLeftLastEvent *
(scrollProfile (currentPercentage) - 1) +
sw_scrollLeft);
if ((sw_scrollLeft - toScroll) * sw_scrollLeft < 0)
//if going past destination point
toScroll = sw_scrollLeft;
}
if ((toScroll == 0) && (sw_scrollLeft != 0))
return;
ScrollBar::setCurrentRangeStart (ScrollBar::getCurrentRangeStart () + toScroll);
sw_scrollLeft -= toScroll;
//no more scroll needed ?
if (sw_scrollLeft == 0)
{
//window.clearInterval (sw_intervalID);
smoothTimer->stopTimer ();
sw_intervalID = false;
sw_velocityCurrent = 0;
return;
}
}
// =============================================================================
/**
Transform percentage of time to percentage of location according to a scroll profile
it might consider an implicit parameter of the speed of the scroll at the time
the event was intercepted.
*/
double SmoothScrollBar::scrollProfile (double percentage)
{
double sw_p, sw_p1, sw_p2, sw_p21, sw_p2v;
if (sw_optimizedProfileCodeExists)
{
sw_p = percentage;
sw_p1 = 1 - sw_p;
sw_p2 = sw_p1 * sw_p1;
sw_p21 = 1 - sw_p2;
sw_p2v = sw_p2 * sw_velocityLastEvent;
sw_velocityCurrent = sw_p2v + sw_p21 * 2 * sw_p1;
return sw_p2v * sw_p + sw_p21 * sw_p21; //location
}
//implicit else: non-optimized path, consider all settings explicitly.
double location, velocity;
switch (sw_SmoothScrollProfile)
{
case 0:
location = profileLinear (percentage);
break;
case 1:
location = profilePower2 (percentage);
break;
case 2:
location = profilePower3 (percentage);
break;
case 3:
location = profileCosine (percentage);
break;
default:
location = profileLinear (percentage);
break;
}
if (sw_EnableSmoothEventTransition)
{
switch (sw_SmoothScrollProfile)
{
case 0:
velocity = profileLinearVelocity (percentage);
break;
case 1:
velocity = profilePower2Velocity (percentage);
break;
case 2:
velocity = profilePower3Velocity (percentage);
break;
case 3:
velocity = profileCosineVelocity (percentage);
break;
default:
velocity = profileLinearVelocity (percentage);
break;
}
location = transition (percentage, profileConstVelocity (percentage), location);
sw_velocityCurrent= transition (percentage, profileConstVelocityVelocity (percentage), velocity);
}
return location;
}
// =============================================================================
/**
Returns a weighted average of 2 profiles according (non-linear) to percentage.
*/
double SmoothScrollBar::transition (double percentage, double srcProfile, double dstProfile)
{
double srcPer = ((sw_TransEnd - percentage) / sw_TransEnd) *
((sw_TransEnd - percentage) / sw_TransEnd);
double dstPer = 1 - srcPer;
return (percentage < sw_TransEnd) ? ((srcPer * srcProfile) + (dstPer * dstProfile)) : dstProfile;
}
// =============================================================================
/**
various scroll profiles and their velocities (derivatives) functions.
*/
double SmoothScrollBar::profileLinear (double percentage) { return percentage; }
double SmoothScrollBar::profileLinearVelocity (double percentage) { return 1; }
double SmoothScrollBar::profilePower2 (double percentage) { return 1-(1-percentage)*(1-percentage); }
double SmoothScrollBar::profilePower2Velocity (double percentage) { return 2*(1-percentage); }
double SmoothScrollBar::profilePower3 (double percentage) { return 1-(1-percentage)*(1-percentage)*(1-percentage); }
double SmoothScrollBar::profilePower3Velocity (double percentage) { return 3*(1-percentage)*(1-percentage); }
double SmoothScrollBar::profileCosine (double percentage) { return .5-.5*cos(M_PI*percentage); }
double SmoothScrollBar::profileCosineVelocity (double percentage) { return .5*sin(M_PI*percentage)*M_PI; }
// using implicit parameter velocityLastEvent.
// this profile does NOT achieve destination point on deadline.
// it represents a linear motion at the speed that was recorded when the event was intercepted.
// this profile is used for transition when a scroll event is intercepted while still scrolling.
double SmoothScrollBar::profileConstVelocity (double percentage) { return sw_velocityLastEvent*percentage; }
double SmoothScrollBar::profileConstVelocityVelocity (double percentage) { return sw_velocityLastEvent; }
// =============================================================================
/**
Initialize variable according to the preset, and determine whether optimization is possible.
*/
struct SmoothWheelPreset
{
int ScrollDuration;
bool EnableRelativeStepSize;
bool EnableAdaptiveStep;
bool EnableAdaptiveDuration;
int SmoothScrollProfile;
bool EnableSmoothEventTransition;
int TransEnd;
};
// =============================================================================
const SmoothWheelPreset presets[] =
{
{ 150, true, false, false, 1, true, 1 }, // 1
{ 300, true, false, false, 1, true, 1 }, // 2
{ 500, true, false, false, 1, true, 1 }, // 3 : was default in v0.2
{ 1000, true, false, false, 1, true, 1 }, // 4
{ 300, true, true, false, 1, true, 1 }, // 5
{ 300, true, false, true , 1, true, 1 }, // 6
{ 300, true, true , true , 1, true, 1 }, // 7 : default with v0.3, v0.4
{ 500, true, true , false , 1, true, 1 }, // 8
{ 500, true, true , true , 1, true, 1 }, // 9
{ 250, false, false, false, 0, false, 1 }, // 10
{ 150, false, false, false, 2, false, 1 } // 11
};
// =============================================================================
void SmoothScrollBar::initSmoothWheel ()
{
if (sw_PresetSettings >= 1 && sw_PresetSettings <= 11)
{
sw_ScrollDuration = presets[sw_PresetSettings].ScrollDuration;
sw_EnableRelativeStepSize = presets[sw_PresetSettings].EnableRelativeStepSize;
sw_EnableAdaptiveStep = presets[sw_PresetSettings].EnableAdaptiveStep;
sw_EnableAdaptiveDuration = presets[sw_PresetSettings].EnableAdaptiveDuration;
sw_SmoothScrollProfile = presets[sw_PresetSettings].SmoothScrollProfile;
sw_EnableSmoothEventTransition = presets[sw_PresetSettings].EnableSmoothEventTransition;
sw_TransEnd = presets[sw_PresetSettings].TransEnd;
}
initGlobals();
sw_optimizedProfileCodeExists = (sw_SmoothScrollProfile == 1) &&
(sw_EnableSmoothEventTransition == true) &&
(sw_TransEnd == 1);
sw_optimizedHandlerExists =
(
sw_EnableThisExtension &&
sw_EnableAdaptiveStep &&
sw_EnableAdaptiveDuration &&
sw_EnableSoftEdge &&
(sw_ScrollDuration == 300) &&
(sw_AdaptiveStepMinStepFactor == 0.4) &&
(sw_AdaptiveStepMaxStepFactor == 1.2) &&
(sw_AdaptiveDurationMinStepFactor == .8) &&
(sw_AdaptiveDurationMaxStepFactor == 2)
);
}
// =============================================================================
void SmoothScrollBar::initGlobals ()
{
//set some of the adaptive functionality parameters only once in advance.
sw_ASa = ((sw_AdaptiveStepMinStepFactor - sw_AdaptiveStepMaxStepFactor) /
(sw_AdaptiveStepMaxDurationFactor - sw_AdaptiveStepMinDurationFactor)) /
sw_ScrollDuration; //diagonal
sw_ASb = sw_AdaptiveStepMaxStepFactor - sw_ASa *
sw_AdaptiveStepMinDurationFactor; //offset
sw_ADa = ((sw_AdaptiveDurationMaxStepFactor - sw_AdaptiveDurationMinStepFactor)/
(sw_AdaptiveDurationMaxDurationFactor - sw_AdaptiveDurationMinDurationFactor)) /
sw_ScrollDuration; //diagonal
sw_ADb = sw_AdaptiveDurationMinStepFactor - sw_ADa *
sw_AdaptiveDurationMinDurationFactor; //offset
//if (sw_intervalID)
//window.clearInterval (sw_intervalID);
sw_scrollLeft = 0;
sw_scrollLeftLastEvent = 0;
sw_scrollEventTimeStamp = 0;
sw_scrollDeadline = 0;
sw_clientFrame = NULL;
sw_intervalID = NULL;
sw_velocityLastEvent = 0;
sw_velocityCurrent = 0;
sw_scrollCurrentStep = 1;
sw_scrollCurrentDuration = 0;
sw_maxTop = 0;
sw_isScrollable = false;
return;
}
// =============================================================================
Regards,
Max