Zoom: Use std::chrono for the timestamping

The reduces a little bit of WX dependency, and makes
the timing code a bit more type-safe.

Also adds a more testable interface for the accelerated
zoom controller.
This commit is contained in:
John Beard 2018-11-22 17:01:59 +00:00 committed by Wayne Stambaugh
parent d5248cced2
commit 1eb0f70de5
4 changed files with 157 additions and 32 deletions

View File

@ -45,7 +45,7 @@ static std::unique_ptr<ZOOM_CONTROLLER> GetZoomControllerForPlatform()
// based on the rotation amount rather than the time difference. // based on the rotation amount rather than the time difference.
return std::make_unique<CONSTANT_ZOOM_CONTROLLER>( CONSTANT_ZOOM_CONTROLLER::MAC_SCALE ); return std::make_unique<CONSTANT_ZOOM_CONTROLLER>( CONSTANT_ZOOM_CONTROLLER::MAC_SCALE );
#else #else
return std::make_unique<ACCELERATING_ZOOM_CONTROLLER>( 500 ); return std::make_unique<ACCELERATING_ZOOM_CONTROLLER>();
#endif #endif
} }

View File

@ -28,19 +28,45 @@
#include <view/zoom_controller.h> #include <view/zoom_controller.h>
#include <make_unique.h>
#include <trace_helpers.h> #include <trace_helpers.h>
#include <wx/log.h> #include <wx/log.h>
#include <wx/time.h> // For timestamping events
#include <algorithm> #include <algorithm>
using namespace KIGFX; using namespace KIGFX;
ACCELERATING_ZOOM_CONTROLLER::ACCELERATING_ZOOM_CONTROLLER( unsigned aAccTimeout ) /**
: m_lastTimeStamp( getTimeStamp() ), m_accTimeout( aAccTimeout ) * A very simple timestamper that uses the #KIGFX::ACCELERATING_ZOOM_CONTROLLER::CLOCK
* to provide a timestamp. Since that's a steady_clock, it's monotonic.
*/
class SIMPLE_TIMESTAMPER : public ACCELERATING_ZOOM_CONTROLLER::TIMESTAMP_PROVIDER
{ {
public:
ACCELERATING_ZOOM_CONTROLLER::TIME_PT GetTimestamp() override
{
return ACCELERATING_ZOOM_CONTROLLER::CLOCK::now();
}
};
ACCELERATING_ZOOM_CONTROLLER::ACCELERATING_ZOOM_CONTROLLER(
const TIMEOUT& aAccTimeout, TIMESTAMP_PROVIDER* aTimestampProv )
: m_accTimeout( aAccTimeout )
{
if( aTimestampProv )
{
m_timestampProv = aTimestampProv;
}
else
{
m_ownTimestampProv = std::make_unique<SIMPLE_TIMESTAMPER>();
m_timestampProv = m_ownTimestampProv.get();
}
m_lastTimestamp = m_timestampProv->GetTimestamp();
} }
@ -49,18 +75,18 @@ double ACCELERATING_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
// The minimal step value when changing the current zoom level // The minimal step value when changing the current zoom level
const double zoomLevelScale = 1.2; const double zoomLevelScale = 1.2;
const auto timeStamp = getTimeStamp(); const auto timestamp = m_timestampProv->GetTimestamp();
auto timeDiff = timeStamp - m_lastTimeStamp; auto timeDiff = std::chrono::duration_cast<TIMEOUT>( timestamp - m_lastTimestamp );
m_lastTimeStamp = timeStamp; m_lastTimestamp = timestamp;
wxLogTrace( traceZoomScroll, wxLogTrace( traceZoomScroll,
wxString::Format( "Rot %d, time diff: %ldms", aRotation, timeDiff ) ); wxString::Format( "Rot %d, time diff: %ldms", aRotation, timeDiff.count() ) );
double zoomScale; double zoomScale;
// Set scaling speed depending on scroll wheel event interval // Set scaling speed depending on scroll wheel event interval
if( timeDiff < m_accTimeout && timeDiff > 0 ) if( timeDiff < m_accTimeout )
{ {
zoomScale = 2.05 - timeDiff / m_accTimeout; zoomScale = 2.05 - timeDiff / m_accTimeout;
@ -81,12 +107,6 @@ double ACCELERATING_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
} }
double ACCELERATING_ZOOM_CONTROLLER::getTimeStamp() const
{
return wxGetLocalTimeMillis().ToDouble();
}
CONSTANT_ZOOM_CONTROLLER::CONSTANT_ZOOM_CONTROLLER( double aScale ) : m_scale( aScale ) CONSTANT_ZOOM_CONTROLLER::CONSTANT_ZOOM_CONTROLLER( double aScale ) : m_scale( aScale )
{ {
} }
@ -108,5 +128,7 @@ double CONSTANT_ZOOM_CONTROLLER::GetScaleForRotation( int aRotation )
} }
// need these until C++17 // need these until C++17
constexpr ACCELERATING_ZOOM_CONTROLLER::TIMEOUT ACCELERATING_ZOOM_CONTROLLER::DEFAULT_TIMEOUT;
constexpr double CONSTANT_ZOOM_CONTROLLER::MAC_SCALE; constexpr double CONSTANT_ZOOM_CONTROLLER::MAC_SCALE;
constexpr double CONSTANT_ZOOM_CONTROLLER::GTK3_SCALE; constexpr double CONSTANT_ZOOM_CONTROLLER::GTK3_SCALE;

View File

@ -30,6 +30,8 @@
#ifndef __ZOOM_CONTROLLER_H #ifndef __ZOOM_CONTROLLER_H
#define __ZOOM_CONTROLLER_H #define __ZOOM_CONTROLLER_H
#include <chrono>
#include <memory>
namespace KIGFX namespace KIGFX
{ {
@ -58,24 +60,65 @@ public:
class ACCELERATING_ZOOM_CONTROLLER : public ZOOM_CONTROLLER class ACCELERATING_ZOOM_CONTROLLER : public ZOOM_CONTROLLER
{ {
public: public:
/** /// The type of the acceleration timeout
* @param aAccTimeout the timeout - if a scoll happens within this timeframe, using TIMEOUT = std::chrono::milliseconds;
* the zoom will be faster
/// The clock used for the timestamp (guaranteed to be monotonic)
using CLOCK = std::chrono::steady_clock;
/// The type of the time stamps
using TIME_PT = std::chrono::time_point<CLOCK>;
/// The default timeout, after which a another scroll will not be accelerated
static constexpr TIMEOUT DEFAULT_TIMEOUT = std::chrono::milliseconds( 500 );
/*
* A class interface that provides timestamps for events
*/ */
ACCELERATING_ZOOM_CONTROLLER( unsigned aAccTimeout ); class TIMESTAMP_PROVIDER
{
public:
virtual ~TIMESTAMP_PROVIDER() = default;
/*
* @return the timestamp at the current time
*/
virtual TIME_PT GetTimestamp() = 0;
};
/**
* @param aAccTimeout the timeout - if a scroll happens within this timeframe,
* the zoom will be faster
* @param aTimestampProv a provider for timestamps. If null, a default will
* be provided, which is the main steady_clock (this is probably what you
* want for real usage)
*/
ACCELERATING_ZOOM_CONTROLLER( const TIMEOUT& aAccTimeout = DEFAULT_TIMEOUT,
TIMESTAMP_PROVIDER* aTimestampProv = nullptr );
double GetScaleForRotation( int aRotation ) override; double GetScaleForRotation( int aRotation ) override;
TIMEOUT GetTimeout() const
{
return m_accTimeout;
}
void SetTimeout( const TIMEOUT& aNewTimeout )
{
m_accTimeout = aNewTimeout;
}
private: private:
/** /// The timestamp provider to use (might be provided externally)
* @return the timestamp of an event at the current time. Monotonic. TIMESTAMP_PROVIDER* m_timestampProv;
*/
double getTimeStamp() const; /// Any provider owned by this class (the default one, if used)
std::unique_ptr<TIMESTAMP_PROVIDER> m_ownTimestampProv;
/// The timestamp of the last event /// The timestamp of the last event
double m_lastTimeStamp; TIME_PT m_lastTimestamp;
/// The timeout value /// The timeout value
unsigned m_accTimeout; TIMEOUT m_accTimeout;
}; };

View File

@ -31,9 +31,6 @@
using namespace KIGFX; using namespace KIGFX;
/**
* Declares a struct as the Boost test fixture.
*/
BOOST_AUTO_TEST_SUITE( ZoomController ) BOOST_AUTO_TEST_SUITE( ZoomController )
@ -81,10 +78,73 @@ BOOST_AUTO_TEST_CASE( ConstController )
} }
} }
/* /**
* Testing the accelerated version without making a very slow test is a little * Timestamper that returns predefined values from a vector
* tricky and would need a mock timestamping interface, which complicates the
* real interface a bit and does not really seem worth the effort.
*/ */
class PREDEF_TIMESTAMPER : public ACCELERATING_ZOOM_CONTROLLER::TIMESTAMP_PROVIDER
{
public:
using STAMP_LIST = std::vector<int>;
PREDEF_TIMESTAMPER( const STAMP_LIST& aStamps )
: m_stamps( aStamps ), m_iter( m_stamps.begin() )
{
}
/**
* @return the next time point in the predefined sequence
*/
ACCELERATING_ZOOM_CONTROLLER::TIME_PT GetTimestamp() override
{
// Don't ask for more samples than given
BOOST_REQUIRE( m_iter != m_stamps.end() );
return ACCELERATING_ZOOM_CONTROLLER::TIME_PT( std::chrono::milliseconds( *m_iter++ ) );
}
const STAMP_LIST m_stamps;
STAMP_LIST::const_iterator m_iter;
};
struct ACCEL_ZOOM_CASE
{
int timeout;
std::vector<int> stamps; // NB includes the initial stamp!
std::vector<int> scrolls;
std::vector<double> zooms;
};
static const std::vector<ACCEL_ZOOM_CASE> accel_cases = {
// Scrolls widely spaced, just go up and down by a constant factor
{ 500, { 0, 1000, 2000, 3000 }, { 120, 120, -120 }, { 1.2, 1.2, 1 / 1.2 } },
// Close scrolls - acceleration on the latter
{ 500, { 0, 1000, 1100 }, { 120, 120 }, { 1.2, 2.05 } },
};
/**
* Check basic setting and getting of values
*/
BOOST_AUTO_TEST_CASE( AccelController )
{
const double tol_percent = 10.0;
for( const auto& c : accel_cases )
{
PREDEF_TIMESTAMPER timestamper( c.stamps );
ACCELERATING_ZOOM_CONTROLLER zoom_ctrl(
std::chrono::milliseconds( c.timeout ), &timestamper );
for( unsigned i = 0; i < c.scrolls.size(); i++ )
{
const auto zoom_scale = zoom_ctrl.GetScaleForRotation( c.scrolls[i] );
BOOST_CHECK_CLOSE( zoom_scale, c.zooms[i], tol_percent );
}
}
}
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()