Smooth ScrollBar


#1

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


#2

That’s an impressive looking class!

…though I wonder if it’s the right approach to take? I’ve never tried doing a smooth-scroller, but I think I’d have approached it by trying to write an adapter class that “chases” the position of the scrollbar, rather than actually changing the way the scrollbar works. There might be practical reasons why your approach is better, but it feels like it’s the component being scrolled that needs to perform the smoothing, and not the component that’s controlling it?


#3

You’re right. In all case, this class is not exploitable easily. For example, the Viewport have no accessor to determine a personnal ScrollBar, so for a easy use, I need to modify some of your major UI classes before compilation, and i don’t like that.

Maybe, I can see to write a abstract “Smoothing” mecanism ? I think that can be a very good point for the futur of Juce to have this type of class.

Regards,
Max


#4

It would be an interesting problem to try to solve - it’s certainly not obvious what would be the best way to structure such a design.


#5

Sure. For my own work, I use some interpolations here and there. In the UI screenshot (OpenGL Coverflow post) and in the right zone (contextual parameters panels), I use a sigmoid interpolation for the open/close animation.

These static function can be a good starting point if you want to investigate this question, all of that can easily convert to template for type abstraction.

Interpolation.cpp

//=============================================================================
#include "Interpolation.h"
//=============================================================================
#define sq(x) (x*x)
//=============================================================================
// Polynomial Shaping Functions
//=============================================================================
/*
	Blinn-Wyvill Approximation to the Raised Inverted Cosine

	Trigonometric functions like cos() and sin() are ubiquitous in natural sciences, 
	engineering and animation, but they can be expensive to compute. If a situation 
	calls for millions of trigonometric operations per second, substantial speed 
	optimizations can be obtained by using an approximation constructed from simple 
	arithmetic functions. An example is the Blinn-Wyvill polynomial approximation 
	to the Raised Inverted Cosine, which diverges from the authentic (scaled) 
	trigonometric function by less than 0.1% within the range [0...1]. It also 
	shares some of the Raised Inverted Cosine's key properties, having flat 
	derivatives at 0 and 1, and the value 0.5 at x=0.5. It has the strong advantage 
	that it is relatively efficient to compute, since it is comprised exclusively 
	from simple arithmetic operations and cacheable fractions. 
	Unlike the Raised Inverted Cosine, it does not have infinite derivatives, but 
	since it is a sixth-order function, this limitation is unlikely to be noticed 
	in practice.

	This would be a useful approximation for the cos() and sin() trigonometric 
	functions for a small microprocessor (such as an Arduino) which has limited 
	speed and math capabilities.
*/
float Interpolation::blinnWyvillCosineApproximation (float x)
{
  float x2 = x*x;
  float x4 = x2*x2;
  float x6 = x4*x2;
  
  float fa = ( 4.0f/9.0f);
  float fb = (17.0f/9.0f);
  float fc = (22.0f/9.0f);
  
  float y = fa*x6 - fb*x4 + fc*x2;
  return y;
}
//=============================================================================
/*
	Double-Cubic Seat 

	This seat-shaped function is formed by joining two 3rd-order polynomial (cubic) curves. 
	The curves meet with a horizontal inflection point at the control coordinate (a,b) 
	in the unit square.
*/
float Interpolation::doubleCubicSeat (float x, float a, float b)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f;
	float max_param_b = 1.0f;
	a = jmin(max_param_a, jmax(min_param_a, a));  
	b = jmin(max_param_b, jmax(min_param_b, b)); 

	float y = 0.f;
	if (x <= a) {
		y = b - b*pow(1-x/a, 3.0f);
	} else {
		y = b + (1-b)*pow((x-a)/(1-a), 3.0f);
	}
	return y;
}
//=============================================================================
/*
	Double-Cubic Seat with Linear Blend

	This modified version of the Double-Cubic Seat function uses a single variable 
	to control the location of its inflection point along the diagonal of the unit square. 
	A second parameter is used to blend this curve with the Identity Function (y=x). 
	Here, we use the variable b to control the amount of this blend, which has the effect 
	of tilting the slope of the curve's plateau in the vicinity of its inflection point. 
	The adjustable flattening around the inflection point makes this a useful shaping function 
	for lensing or magnifying evenly-spaced data.
*/
float Interpolation::doubleCubicSeatWithLinearBlend (float x, float a, float b)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f;
	float max_param_b = 1.0f;
	a = jmin(max_param_a, jmax(min_param_a, a));  
	b = jmin(max_param_b, jmax(min_param_b, b)); 
	b = 1.0f - b; //reverse for intelligibility.

	float y = 0.f;
	if (x<=a){
		y = b*x + (1-b)*a*(1-pow(1-x/a, 3.0f));
	} else {
		y = b*x + (1-b)*(a + (1-a)*pow((x-a)/(1-a), 3.0f));
	}
	return y;
}
//=============================================================================
/*
	Double-Odd-Polynomial Seat

	The previous Double-Cubic Seat function can be generalized to a form 
	which uses any odd integer exponent. In the code below, the parameter n 
	controls the flatness or breadth of the plateau region in the vicinity 
	of the point (a,b). A good working range for n is the set of whole numbers 
	from 1 to about 20.
*/
float Interpolation::doubleOddPolynomialSeat (float x, float a, float b, int n)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f;
	float max_param_b = 1.0f;
	a = jmin(max_param_a, jmax(min_param_a, a));  
	b = jmin(max_param_b, jmax(min_param_b, b)); 

	int p = 2*n + 1;
	float y = 0.f;
	if (x <= a){
		y = b - b*pow(1-x/a, p);
	} else {
		y = b + (1-b)*pow((x-a)/(1-a), p);
	}
	return y;
}
//=============================================================================
/*
	Symmetric Double-Polynomial Sigmoids

	It is possible to generate sigmoid patterns by joining a symmetric pair of 
	polynomials at the center of the unit square. The exponents in these equations 
	(controlled by the integer parameter n) control the steepness of the wall 
	separating the squelched values from the boosted ones; a suggested range for 
	the whole number n is from 1 to about 10. Of these, the sigmoid created with 
	a 2nd-order (quadratic) exponent comes closest to the Raised Inverted Cosine, 
	approximating it to within 2.8%.
*/
float Interpolation::doublePolynomialSigmoid (float x, float a, float b, int n)
{
	float y = 0.f;
	if (n % 2 == 0)
	{ 
		// even polynomial
		if (x<=0.5f){
			y = pow(2.0f*x, n)/2.0f;
		} else {
			y = 1.0f - pow(2*(x-1), n)/2.0f;
		}
	} else { 
		// odd polynomial
		if (x<=0.5){
			y = pow(2.0f*x, n)/2.0f;
		} else {
			y = 1.0f + pow(2.0f*(x-1), n)/2.0f;
		}
	}
	return y;
}
//=============================================================================
/*
	Quadratic Through a Given Point

	This function defines an axis-aligned quadratic (parabola) which passes 
	through a user-supplied point (a,b) in the unit square. 
	Caution: 
	Not all points in the unit square will produce curves which pass through 
	the locations (0,0) and (1,1).
*/
float Interpolation::quadraticThroughAGivenPoint (float x, float a, float b)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f;
	float max_param_b = 1.0f;
	a = jmin(max_param_a, jmax(min_param_a, a));  
	b = jmin(max_param_b, jmax(min_param_b, b)); 

	float A = (1.f-b)/(1.f-a) - (b/a);
	float B = (A*(a*a)-b)/a;
	float y = A*(x*x) - B*(x);
	y = jmin(1.f,jmax(0.f,y)); 

	return y;
}
//=============================================================================
// Exponential Shaping Functions
//=============================================================================
/*
	Exponential Ease-In and Ease-Out

	In this implementation of an exponential shaping function, the control 
	parameter a permits the designer to vary the function from an ease-out 
	form to an ease-in form.
*/
float Interpolation::exponentialEasing (float x, float a)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	a = jmax(min_param_a, jmin(max_param_a, a));

	if (a < 0.5f){
		// emphasis
		a = 2.0f * (a);
		float y = pow(x, a);
		return y;
	} else {
		// de-emphasis
		a = 2.0f * (a - 0.5f);
		float y = pow(x, 1.0f/(1.f-a));
		return y;
	}
}
//=============================================================================
/*
	Double-Exponential Seat

	A seat-shaped function can be created with a coupling of two exponential functions. 
	This has nicer derivatives than the cubic function, and more continuous control 
	in some respects, at the expense of greater CPU cycles. The recommended range 
	for the control parameter a is from 0 to 1. Note that these equations are 
	very similar to the Double-Exponential Sigmoid described below. 
*/
float Interpolation::doubleExponentialSeat (float x, float a)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	a = jmin(max_param_a, jmax(min_param_a, a)); 

	float y = 0.f;
	if (x <= 0.5f)
	{
		y = (pow(2.0f * x, 1.0f - a)) / 2.0f;
	} 
	else 
	{
		y = 1.0f - (pow(2.0f * (1.0f - x), 1.0f - a)) / 2.0f;
	}
	return y;
}
//=============================================================================
/*
	Double-Exponential Sigmoid

	The same doubling-and-flipping scheme can be used to create sigmoids from pairs 
	of exponential functions. These have the advantage that the control parameter a 
	can be continously varied between 0 and 1, and are therefore very useful as 
	adjustable-contrast functions. However, they are more expensive to compute than 
	the polynomial sigmoid flavors. The Double-Exponential Sigmoid approximates the 
	Raised Inverted Cosine to within 1% when the parameter a is approximately 0.426.
*/
float Interpolation::doubleExponentialSigmoid (float x, float a)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	a = jmin(max_param_a, jmax(min_param_a, a));
	a = 1.0f - a; // for sensible results

	float y = 0.0f;
	if (x <= 0.5f)
	{
		y = (pow(2.0f * x, 1.0f / a)) / 2.0f;
	} else {
		y = 1.0f - (pow(2.0f * (1.0f - x), 1.0f / a)) / 2.0f;
	}
	return y;
}

//=============================================================================
/*
	The Logistic Sigmoid

	The so-called "Logistic Curve" is an elegant sigmoidal function which is believed 
	by many scientists to best represent the growth of organic populations and many 
	other natural phenomena. In software engineering, it is often used for weighting 
	signal-response functions in neural networks. In this implementation, the parameter a 
	regulates the slope or "growth rate" of the sigmoid during its rising portion. 
	When a=0, this version of the Logistic function collapses to the Identity Function (y=x). 
	The Logistic Sigmoid has very natural rates of change, but is expensive to calculate 
	due to the use of many exponential functions. 
*/
float Interpolation::logisticSigmoid (float x, float a)
{
	// n.b.: this Logistic Sigmoid has been normalized.

	float epsilon = 0.0001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	a = jmax(min_param_a, jmin(max_param_a, a));
	a = (1.0f / (1.0f - a) - 1.0f);

	float A = 1.0f / (1.0f + exp(0.0f - ((x - 0.5f) * a * 2.0f)));
	float B = 1.0f / (1.0f + exp(a));
	float C = 1.0f / (1.0f + exp(0.0f - a)); 
	float y = (A-B)/(C-B);
	return y;
}
//=============================================================================
// Circular & Elliptical Shaping Functions
//=============================================================================
/*
	Circular Interpolation: Ease-In and Ease-Out

	A circular arc offers a quick and easy-to-code method for easing in or out 
	of the unit square. The computational efficiency of the function is diminished 
	by its use of a square root, however.
*/
//------------------------------
float Interpolation::circularEaseIn (float x)
{
	float y = 1.0f - sqrt(1.0f - x*x);
	return y;
}
//------------------------------
float Interpolation::circularEaseOut (float x)
{
	float y = sqrt(1.0f - sq(1.0f - x));
	return y;
}
//=============================================================================
/*
	Double-Circle Seat

	This shaping function is formed by the meeting of two circular arcs, 
	which join with a horizontal tangent. The parameter a, in the range [0...1], 
	governs the location of the curve's inflection point along the diagonal 
	of the unit square.
*/
float Interpolation::doubleCircleSeat (float x, float a)
{
	float min_param_a = 0.0f;
	float max_param_a = 1.0f;
	a = jmax(min_param_a, jmin(max_param_a, a)); 

	float y = 0.0f;
	if (x <= a)
	{
		y = sqrt(sq(a) - sq(x - a));
	} 
	else 
	{
		y = 1.0f - sqrt(sq(1-a) - sq(x - a));
	}
	return y;
}
//=============================================================================
/*
	Double-Circle Sigmoid

	This sigmoidal shaping function is formed by the meeting of two circular arcs, 
	which join with a vertical tangent. The parameter a, in the range [0...1], governs 
	the location of the curve's inflection point along the diagonal of the unit square.
*/
float Interpolation::doubleCircleSigmoid (float x, float a)
{
	float min_param_a = 0.0f;
	float max_param_a = 1.0f;
	a = max(min_param_a, min(max_param_a, a)); 

	float y = 0.0f;
	if (x <= a)
	{
		y = a - sqrt(a*a - x*x);
	} else {
		y = a + sqrt(sq(1.0f - a) - sq(x - 1.0f));
	}
	return y;
}
//=============================================================================
/*
	Double-Elliptic Seat

	This seat-shaped function is created by the joining of two elliptical arcs, 
	and is a generalization of the Double-Circle Seat. The two arcs meet at the 
	coordinate (a,b) with a horizontal tangent.
*/
float Interpolation::doubleEllipticSeat (float x, float a, float b)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f;
	float max_param_b = 1.0f;
	a = jmax(min_param_a, jmin(max_param_a, a)); 
	b = jmax(min_param_b, jmin(max_param_b, b)); 

	float y = 0.0f;
	if (x <= a)
	{
		y = (b/a) * sqrt(sq(a) - sq(x-a));
	} 
	else 
	{
		y = 1.0f - ((1.0f - b) / (1.0f -a )) * sqrt(sq(1.0f - a) - sq(x - a));
	}
	return y;
}
//=============================================================================
/*
	Double-Elliptic Sigmoid

	This sigmoid-shaped function is created by the joining of two elliptical arcs, 
	and is a generalization of the Double-Circle Sigmoid. The arcs meet at the 
	coordinate (a, b) in the unit square with a vertical tangent. 
*/
float Interpolation::doubleEllipticSigmoid (float x, float a, float b)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f;
	float max_param_b = 1.0f;
	a = jmax(min_param_a, jmin(max_param_a, a)); 
	b = jmax(min_param_b, jmin(max_param_b, b));

	float y = 0.0f;
	if (x <= a)
	{
		y = b * (1 - (sqrt(sq(a) - sq(x))/a));
	} 
	else 
	{
		y = b + ((1.0f - b) / (1.0f - a)) * sqrt(sq(1.0f - a) - sq(x - 1.0f));
	}
	return y;
}
//=============================================================================
/*
	Double-Linear with Circular Fillet

	This pattern joins two straight lines with a circular arc whose radius is 
	adjustable. The user specifies the fillet's radius (with parameter c) and 
	the coordinate in the unit square where the lines would otherwise intersect 
	(with parameters a and b). This pattern is adapted from Robert D. Miller's 
	"Joining Two Lines with a Circular Arc Fillet", which appears in Graphics Gems III.
*/
//--------------------------------------------------------
// Joining Two Lines with a Circular Arc Fillet
// Adapted from Robert D. Miller / Graphics Gems III.
static float arcStartAngle;
static float arcEndAngle;
static float arcStartX,  arcStartY;
static float arcEndX,    arcEndY;
static float arcCenterX, arcCenterY;
static float arcRadius;
//--------------------------------------------------------
float Interpolation::circularFillet (float x, float a, float b, float R)
{
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f + epsilon;
	float max_param_b = 1.0f - epsilon;
	a = jmax(min_param_a, jmin(max_param_a, a)); 
	b = jmax(min_param_b, jmin(max_param_b, b)); 

	computeFilletParameters (0.0f,0.0f, a,b, a,b, 1.0f,1.0f,  R);
	float t = 0.0f;
	float y = 0.0f;
	x = jmax(0.0f, jmin(1.0f, x));

	if (x <= arcStartX)
	{
		t = x / arcStartX;
		y = t * arcStartY;
	} 
	else 
	if (x >= arcEndX)
	{
		t = (x - arcEndX)/(1 - arcEndX);
		y = arcEndY + t*(1 - arcEndY);
	} 
	else 
	{
		if (x >= arcCenterX)
		{
			y = arcCenterY - sqrt(sq(arcRadius) - sq(x-arcCenterX)); 
		} 
		else
		{
			y = arcCenterY + sqrt(sq(arcRadius) - sq(x-arcCenterX)); 
		}
	}
	return y;
}
//------------------------------------------
// Return signed distance from line Ax + By + C = 0 to point P.
float Interpolation::linetopoint (float a, float b, float c, float ptx, float pty)
{
	float lp = 0.0f;
	float d = sqrt((a*a)+(b*b));
	if (d != 0.0f)
	{
		lp = (a*ptx + b*pty + c)/d;
	}
	return lp;
}
//------------------------------------------
// Compute the parameters of a circular arc 
// fillet between lines L1 (p1 to p2) and
// L2 (p3 to p4) with radius R.  
// 
void Interpolation::computeFilletParameters (float p1x, float p1y, 
												    float p2x, float p2y, 
												    float p3x, float p3y, 
												    float p4x, float p4y,
												    float r)
{
	float c1   = p2x*p1y - p1x*p2y;
	float a1   = p2y-p1y;
	float b1   = p1x-p2x;
	float c2   = p4x*p3y - p3x*p4y;
	float a2   = p4y-p3y;
	float b2   = p3x-p4x;

	if ((a1*b2) == (a2*b1)){  /* Parallel or coincident lines */
		return;
	}

	float d1, d2;
	float mPx, mPy;
	mPx = (p3x + p4x)/2.0f;
	mPy = (p3y + p4y)/2.0f;
	d1 = linetopoint(a1,b1,c1,mPx,mPy);  /* Find distance p1p2 to p3 */
	if (d1 == 0.0f) {
		return; 
	}
	mPx = (p1x + p2x)/2.0f;
	mPy = (p1y + p2y)/2.0f;
	d2 = linetopoint(a2,b2,c2,mPx,mPy);  /* Find distance p3p4 to p2 */
	if (d2 == 0.0f) {
		return; 
	}

	float c1p, c2p, d;
	float rr = r;
	if (d1 <= 0.0f) {
		rr= -rr;
	}
	c1p = c1 - rr*sqrt((a1*a1)+(b1*b1));  /* Line parallel l1 at d */
	rr = r;
	if (d2 <= 0.0f){
		rr = -rr;
	}
	c2p = c2 - rr*sqrt((a2*a2)+(b2*b2));  /* Line parallel l2 at d */
	d = (a1*b2)-(a2*b1);

	float pCx = (c2p*b1-c1p*b2)/d; /* Intersect constructed lines */
	float pCy = (c1p*a2-c2p*a1)/d; /* to find center of arc */
	float pAx = 0;
	float pAy = 0;
	float pBx = 0;
	float pBy = 0;
	float dP,cP;

	dP = (a1*a1) + (b1*b1);        /* Clip or extend lines as required */
	if (dP != 0.0f)
	{
		cP = a1*pCy - b1*pCx;
		pAx = (-a1*c1 - b1*cP)/dP;
		pAy = ( a1*cP - b1*c1)/dP;
	}
	dP = (a2*a2) + (b2*b2);
	if (dP != 0.0f)
	{
		cP = a2*pCy - b2*pCx;
		pBx = (-a2*c2 - b2*cP)/dP;
		pBy = ( a2*cP - b2*c2)/dP;
	}

	float gv1x = pAx-pCx; 
	float gv1y = pAy-pCy;
	float gv2x = pBx-pCx; 
	float gv2y = pBy-pCy;

	float arcStart = (float) atan2(gv1y,gv1x); 
	float arcAngle = 0.0f;
	float dd = (float) sqrt(((gv1x*gv1x)+(gv1y*gv1y)) * ((gv2x*gv2x)+(gv2y*gv2y)));
	if (dd != (float) 0.0f)
	{
		arcAngle = (acos((gv1x*gv2x + gv1y*gv2y)/dd));
	} 
	float crossProduct = (gv1x*gv2y - gv2x*gv1y);
	if (crossProduct < 0.0f)
	{ 
		arcStart -= arcAngle;
	}

	float arc1 = arcStart;
	float arc2 = arcStart + arcAngle;
	if (crossProduct < 0.0f)
	{
		arc1 = arcStart + arcAngle;
		arc2 = arcStart;
	}

	arcCenterX    = pCx;
	arcCenterY    = pCy;
	arcStartAngle = arc1;
	arcEndAngle   = arc2;
	arcRadius     = r;
	arcStartX     = arcCenterX + arcRadius*cos(arcStartAngle);
	arcStartY     = arcCenterY + arcRadius*sin(arcStartAngle);
	arcEndX       = arcCenterX + arcRadius*cos(arcEndAngle);
	arcEndY       = arcCenterY + arcRadius*sin(arcEndAngle);
}
//=============================================================================
/*
	Circular Arc Through a Given Point

	This function defines a circular arc which passes through a user-specified 
	point in the unit square. Unfortunately, not every location in the unit square 
	lends itself to defining a circle which also is confined to the unit square; 
	the user-supplied point must inhabit a zone close to the main (Identity) diagonal. 
	This pattern is adapted from Paul Bourke's Equation of a Circle From 3 Points. 
*/
//---------------------------------------------------------
// Adapted from Paul Bourke 
static float m_Centerx;
static float m_Centery;
static float m_dRadius;
//----------------------
float Interpolation::circularArcThroughAPoint (float x, float a, float b)
{  
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f + epsilon;
	float max_param_b = 1.0f - epsilon;
	a = jmin(max_param_a, jmax(min_param_a, a));
	b = jmin(max_param_b, jmax(min_param_b, b));
	x = jmin(1.0f-epsilon, jmax(0.0f+epsilon, x));

	float pt1x = 0.0f;
	float pt1y = 0.0f;
	float pt2x = a;
	float pt2y = b;
	float pt3x = 1.0f;
	float pt3y = 1.0f;

	if      (!isPerpendicular(pt1x,pt1y, pt2x,pt2y, pt3x,pt3y))		
	   calcCircleFrom3Points (pt1x,pt1y, pt2x,pt2y, pt3x,pt3y);	
	else if (!isPerpendicular(pt1x,pt1y, pt3x,pt3y, pt2x,pt2y))		
	   calcCircleFrom3Points (pt1x,pt1y, pt3x,pt3y, pt2x,pt2y);	
	else if (!isPerpendicular(pt2x,pt2y, pt1x,pt1y, pt3x,pt3y))		
	   calcCircleFrom3Points (pt2x,pt2y, pt1x,pt1y, pt3x,pt3y);	
	else if (!isPerpendicular(pt2x,pt2y, pt3x,pt3y, pt1x,pt1y))		
	   calcCircleFrom3Points (pt2x,pt2y, pt3x,pt3y, pt1x,pt1y);	
	else if (!isPerpendicular(pt3x,pt3y, pt2x,pt2y, pt1x,pt1y))		
	   calcCircleFrom3Points (pt3x,pt3y, pt2x,pt2y, pt1x,pt1y);	
	else if (!isPerpendicular(pt3x,pt3y, pt1x,pt1y, pt2x,pt2y))		
	   calcCircleFrom3Points (pt3x,pt3y, pt1x,pt1y, pt2x,pt2y);	
	else { 
		return 0.0f;
	}

	// constrain
	if ((m_Centerx > 0.0f) && (m_Centerx < 1.0f))
	{
		if (a < m_Centerx)
		{
			m_Centerx = 1.0f;
			m_Centery = 0.0f;
			m_dRadius = 1.0f;
		} 
		else 
		{
			m_Centerx = 0.0f;
			m_Centery = 1.0f;
			m_dRadius = 1.0f;
		}
	}

	float y = 0.0f;
	if (x >= m_Centerx)
	{
		y = m_Centery - sqrt(sq(m_dRadius) - sq(x-m_Centerx)); 
	} 
	else 
	{
		y = m_Centery + sqrt(sq(m_dRadius) - sq(x-m_Centerx)); 
	}
	return y;
}
//----------------------
bool Interpolation::isPerpendicular(float pt1x, float pt1y,
										   float pt2x, float pt2y,
										   float pt3x, float pt3y)
{
	// Check the given point are perpendicular to x or y axis 
	float yDelta_a = pt2y - pt1y;
	float xDelta_a = pt2x - pt1x;
	float yDelta_b = pt3y - pt2y;
	float xDelta_b = pt3x - pt2x;
	float epsilon = 0.000001f;

	// checking whether the line of the two pts are vertical
	if (abs(xDelta_a) <= epsilon && abs(yDelta_b) <= epsilon)
	{
		return false;
	}
	if (abs(yDelta_a) <= epsilon)
	{
		return true;
	}
	else 
	if (abs(yDelta_b) <= epsilon)
	{
		return true;
	}
	else 
	if (abs(xDelta_a)<= epsilon)
	{
		return true;
	}
	else 
	if (abs(xDelta_b)<= epsilon)
	{
		return true;
	}
	else 
		return false;
}
//--------------------------
void Interpolation::calcCircleFrom3Points (float pt1x, float pt1y,
												  float pt2x, float pt2y,
												  float pt3x, float pt3y)
{
	float yDelta_a = pt2y - pt1y;
	float xDelta_a = pt2x - pt1x;
	float yDelta_b = pt3y - pt2y;
	float xDelta_b = pt3x - pt2x;
	float epsilon = 0.000001f;

	if (abs(xDelta_a) <= epsilon && abs(yDelta_b) <= epsilon)
	{
		m_Centerx = 0.5f * (pt2x + pt3x);
		m_Centery = 0.5f * (pt1y + pt2y);
		m_dRadius = sqrt(sq(m_Centerx-pt1x) + sq(m_Centery-pt1y));
		return;
	}

	// IsPerpendicular() assure that xDelta(s) are not zero
	float aSlope = yDelta_a / xDelta_a; 
	float bSlope = yDelta_b / xDelta_b;
	if (abs(aSlope-bSlope) <= epsilon){	
		// checking whether the given points are colinear. 	
		return;
	}

	// calc center
	m_Centerx = (
	 aSlope*bSlope*(pt1y - pt3y) + 
	 bSlope*(pt1x + pt2x) - 
	 aSlope*(pt2x+pt3x) )
	 /(2.0f* (bSlope-aSlope) );
	m_Centery = -1.0f*(m_Centerx - (pt1x+pt2x)/2.0f)/aSlope +  (pt1y+pt2y)/2.0f;
	m_dRadius = sqrt(sq(m_Centerx-pt1x) + sq(m_Centery-pt1y));
}
//=============================================================================
// Bezier and Other Parametric Shaping Functions
//=============================================================================
/*
	Quadratic Bezier

	This function defines a 2nd-order (quadratic) Bezier curve with a single u
	ser-specified spline control point (at the coordinate a,b) in the unit square. 
	This function is guaranteed to have the same entering and exiting slopes as 
	the Double-Linear Interpolator. Put another way, this curve allows the user 
	to precisely specify its rate of change at its endpoints in the unit square. 
*/
float Interpolation::quadraticBezier (float x, float a, float b)
{
	float epsilon = 0.00001f;
	a = jmax(0.0f, jmin(1.0f, a)); 
	b = jmax(0.0f, jmin(1.0f, b)); 
	if (a == 0.5f)
	{
		a += epsilon;
	}
	// solve t from x (an inverse operation)
	float om2a = 1.0f - 2.0f*a;
	float t = (sqrt(a*a + om2a*x) - a)/om2a;
	float y = (1.0f-2.0f*b)*(t*t) + (2.0f*b)*t;
	return y;
}
//=============================================================================
/*
	Cubic Bezier

	The Cubic Bezier is a workhorse of computer graphics; most designers will recognize it 
	from Adobe Illustrator and other popular vector-based drawing programs. 
	Here, this extremely flexible curve is used in as a signal-shaping function, 
	which requires the user to specify two locations in the unit square 
	(at the coordinates a,b and c,d) as its control points. 
	By setting the two control points (a,b) and (c,d) to various locations, 
	the Bezier curve can be used to produce sigmoids, seat-shaped functions, 
	ease-ins and ease-outs.

	Bezier curves are customarily defined in such a way that y and x are both functions 
	of another variable t. In order to obtain y as a function of x, it is necessary to 
	first solve for t using successive approximation, making the code longer than 
	one might first expect. The implementation here is adapted from the 
	Bezmath Postscript library by Don Lancaster. 
*/
float Interpolation::cubicBezier (float x, float a, float b, float c, float d)
{
	float y0a = 0.00f; // initial y
	float x0a = 0.00f; // initial x 
	float y1a = b;    // 1st influence y   
	float x1a = a;    // 1st influence x 
	float y2a = d;    // 2nd influence y
	float x2a = c;    // 2nd influence x
	float y3a = 1.00f; // final y 
	float x3a = 1.00f; // final x 

	float A =	   x3a - 3.0f*x2a + 3.0f*x1a - x0a;
	float B = 3.0f*x2a - 6.0f*x1a + 3.0f*x0a;
	float C = 3.0f*x1a - 3.0f*x0a;   
	float D =      x0a;

	float E =	   y3a - 3.0f*y2a + 3.0f*y1a - y0a;    
	float F = 3.0f*y2a - 6.0f*y1a + 3.0f*y0a;             
	float G = 3.0f*y1a - 3.0f*y0a;             
	float H =	   y0a;

	// Solve for t given x (using Newton-Raphelson), then solve for y given t.
	// Assume for the first guess that t = x.
	float currentt = x;
	int nRefinementIterations = 5;
	for (int i=0; i < nRefinementIterations; i++)
	{
		float currentx = xFromT (currentt, A,B,C,D); 
		float currentslope = slopeFromT (currentt, A,B,C);
		currentt -= (currentx - x)*(currentslope);
		currentt = jlimit(0.0f, 1.0f, currentt);
	} 

	float y = yFromT (currentt,  E,F,G,H);
	return y;
}
//--------------------------
// Helper functions:
float Interpolation::slopeFromT (float t, float A, float B, float C)
{
  float dtdx = 1.0f/(3.0f*A*t*t + 2.0f*B*t + C); 
  return dtdx;
}
//--------------------------
float Interpolation::xFromT (float t, float A, float B, float C, float D)
{
  float x = A*(t*t*t) + B*(t*t) + C*t + D;
  return x;
}
//--------------------------
float Interpolation::yFromT (float t, float E, float F, float G, float H)
{
  float y = E*(t*t*t) + F*(t*t) + G*t + H;
  return y;
}
//=============================================================================
/*
	Cubic Bezier (Nearly) Through Two Given Points

	This shaping function asks the user to specify two points in the unit square. 
	The algorithm then attempts to generate a curve which passes through these points 
	as closely as possible. The curves are not guaranteed to pass through the two points, 
	but come quite close in most instances. The method is adapted from Don Lancaster. 
*/
float Interpolation::cubicBezierNearlyThroughTwoPoints (float x, float a, float b, float c, float d)
{
	float y = 0.0f;
	float epsilon = 0.00001f;
	float min_param_a = 0.0f + epsilon;
	float max_param_a = 1.0f - epsilon;
	float min_param_b = 0.0f + epsilon;
	float max_param_b = 1.0f - epsilon;
	a = jmax(min_param_a, jmin(max_param_a, a));
	b = jmax(min_param_b, jmin(max_param_b, b));

	float x0 = 0.0f;  
	float y0 = 0.0f;
	float x4 = a;  
	float y4 = b;
	float x5 = c;  
	float y5 = d;
	float x3 = 1.0f;  
	float y3 = 1.0f;
	float x1,y1,x2,y2; // to be solved.

	// arbitrary but reasonable 
	// t-values for interior control points
	float t1 = 0.3f;
	float t2 = 0.7f;

	float B0t1 = B0(t1);
	float B1t1 = B1(t1);
	float B2t1 = B2(t1);
	float B3t1 = B3(t1);
	float B0t2 = B0(t2);
	float B1t2 = B1(t2);
	float B2t2 = B2(t2);
	float B3t2 = B3(t2);

	float ccx = x4 - x0*B0t1 - x3*B3t1;
	float ccy = y4 - y0*B0t1 - y3*B3t1;
	float ffx = x5 - x0*B0t2 - x3*B3t2;
	float ffy = y5 - y0*B0t2 - y3*B3t2;

	x2 = (ccx - (ffx*B1t1)/B1t2) / (B2t1 - (B1t1*B2t2)/B1t2);
	y2 = (ccy - (ffy*B1t1)/B1t2) / (B2t1 - (B1t1*B2t2)/B1t2);
	x1 = (ccx - x2*B2t1) / B1t1;
	y1 = (ccy - y2*B2t1) / B1t1;

	x1 = jmax(0.0f+epsilon, jmin(1.0f-epsilon, x1));
	x2 = jmax(0.0f+epsilon, jmin(1.0f-epsilon, x2));

	// Note that this function also requires cubicBezier()!
	y = cubicBezier (x, x1,y1, x2,y2);
	y = jmax(0.0f, jmin(1.0f, y));
	return y;
}
//--------------------------
// Helper functions. 
float Interpolation::B0 (float t)
{
	return (1.0f-t)*(1.0f-t)*(1.0f-t);
}
//--------------------------
float Interpolation::B1 (float t)
{
	return  3.0f*t* (1.0f-t)*(1.0f-t);
}
//--------------------------
float Interpolation::B2 (float t)
{
	return 3.0f*t*t* (1.0f-t);
}
//--------------------------
float Interpolation::B3 (float t)
{
	return t*t*t;
}
//--------------------------
float  Interpolation::findx (float t, float x0, float x1, float x2, float x3)
{
	return x0*B0(t) + x1*B1(t) + x2*B2(t) + x3*B3(t);
}
//--------------------------
float  Interpolation::findy (float t, float y0, float y1, float y2, float y3)
{
	return y0*B0(t) + y1*B1(t) + y2*B2(t) + y3*B3(t);
}
//=============================================================================

Interpolation.h

//=============================================================================
#ifndef __INTERPOLATION_H__
#define __INTERPOLATION_H__
//=============================================================================
#include "juce.h"
//=============================================================================
namespace Interpolation
{
/*
	all the interpolation codes are from the
	http://www.flong.com/texts/code/
	website.

	input: 
			[x] = a horizontal value [0.0...1.0]

			[a] = governs the location of the curve's inflection point along the diagonal 
				  of the unit square. [0.0...1.0]

			[a,b] = a point with [0.0...1.0] coordinates of the unit square.

			[c,d] = a point with [0.0...1.0] coordinates of the unit square.
					Used by cubicBezier and cubicBezierNearlyThroughTwoPoints Shapes.

			[n]	  = controls the flatness or breadth of the plateau region [1..20]
					Used by doubleOddPolynomialSeat and doublePolynomialSigmoid Shapes.

			[R]   = Fillet's radius. 
					Used by circularFillet shape.

	ouput:
			return a float that represent the [y] vertical value from [0.0...1.0]

*/
/* Polynomial Shaping Functions */
	static float blinnWyvillCosineApproximation (float x);
	static float doubleCubicSeat (float x, float a, float b);
	static float doubleCubicSeatWithLinearBlend (float x, float a, float b);
	static float doubleOddPolynomialSeat (float x, float a, float b, int n);
	static float doublePolynomialSigmoid (float x, float a, float b, int n);
	static float quadraticThroughAGivenPoint (float x, float a, float b);
/* Exponential Shaping Functions */
	static float exponentialEasing (float x, float a);
	static float doubleExponentialSeat (float x, float a);
	static float doubleExponentialSigmoid (float x, float a);
	static float logisticSigmoid (float x, float a);
/* Circular & Elliptical Shaping Functions */
	static float circularEaseIn (float x);
	static float circularEaseOut (float x);
	static float doubleCircleSeat (float x, float a);
	static float doubleCircleSigmoid (float x, float a);
	static float doubleEllipticSeat (float x, float a, float b);
	static float doubleEllipticSigmoid (float x, float a, float b);
	// circularFillet
	static float circularFillet (float x, float a, float b, float R);
	// circularFillet helpers
	static float linetopoint (float a, float b, float c, float ptx, float pty);
	static void computeFilletParameters (float p1x, float p1y, 
										 float p2x, float p2y, 
										 float p3x, float p3y, 
										 float p4x, float p4y,
										 float r);
	// circularArcThroughAPoint
	static float circularArcThroughAPoint (float x, float a, float b);
	// circularArcThroughAPoint helpers
	static bool isPerpendicular(float pt1x, float pt1y,
								float pt2x, float pt2y,
								float pt3x, float pt3y);
	static void calcCircleFrom3Points (float pt1x, float pt1y,
									   float pt2x, float pt2y,
									   float pt3x, float pt3y);
/* Bezier and Other Parametric Shaping Functions */
	static float quadraticBezier (float x, float a, float b);
	// cubicBezier
	static float cubicBezier (float x, float a, float b, float c, float d);
	// cubicBezier helpers
	static float slopeFromT (float t, float A, float B, float C);
	static float xFromT (float t, float A, float B, float C, float D);
	static float yFromT (float t, float E, float F, float G, float H);
	// cubicBezierNearlyThroughTwoPoints
	static float cubicBezierNearlyThroughTwoPoints (float x, float a, float b, float c, float d);
	// cubicBezierNearlyThroughTwoPoints helpers
	static float B0 (float t);
	static float B1 (float t);
	static float B2 (float t);
	static float B3 (float t);
	static float findx (float t, float x0, float x1, float x2, float x3);
	static float findy (float t, float y0, float y1, float y2, float y3);
}
//=============================================================================
#endif // __INTERPOLATION_H__
//=============================================================================

I’ve do a chart based from the website informations :

I really hope that can help you.

Regards,
Max


#6

Max, I’m loving finding your posts. Can I use that interpolation code in commercial projects? I rolled my own bezier class, but yours seems to be a lot more sophisticated. Still, if you wanted to add:

[code]class Bezier2DInterpolation
{
private:
float startPoint;
float startControlPoint;

float endPoint;
float endPointControl;

public:
Bezier2DInterpolation(float startPoint_, float startControlPoint_, float endPoint_, float endPointControl_)
: startPoint(startPoint_), startControlPoint(startControlPoint_), endPoint(endPoint_), endPointControl(endPointControl_)
{
}

Bezier2DInterpolation()
	: startPoint(0.0f), startControlPoint(0.0f), endPoint(1.0f), endPointControl(0.5f)
{
}

float getValueFor (float t, bool invert)
{
	float fW = 1.0f - t;
    float fA = fW * fW * fW;
    float fB = 3.0f * t * fW * fW;
    float fC = 3.0f * t * t * fW;
    float fD = t * t * t;

	//float fX = fA * ptB0.X + fB * ptB1.X + fC * ptB2.X + fD * ptB3.X;
    //float fY = fA * ptB0.Y + fB * ptB1.Y + fC * ptB2.Y + fD * ptB3.Y;

	float tPos = fA * startPoint + fB * startControlPoint + fC * endPointControl + fD * endPoint;

	if (invert)
		return 1.0f - tPos;

	return tPos;
}

};
[/code]

Bruce


#7

Thanks for congratulation.

About the interpolation code, I think yes. The URL of the original website i’ve found the code is on the bottom part of the chart. Take a look to the original paper and maybe contact the author if you’ve a doubt. If you’ve a answer, write a little reply on the topic for the others that want to use that in commercial apps :wink:

About your code, thanks for sharing !!! I hope all of us will share is own work, a least the code that is not part of commercial projects.

See ya!