[drape] Fixed inertial kinetic scroll. #6237
8 changed files with 107 additions and 100 deletions
|
@ -1,17 +1,17 @@
|
|||
#pragma once
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
namespace dp
|
||||
{
|
||||
/// This fuction is called in iOS/Android native code.
|
||||
inline double VisualScale(double exactDensityDPI)
|
||||
{
|
||||
double constexpr kMdpiDensityDPI = 160.;
|
||||
double const tabletFactor = 1.2;
|
||||
// In case of tablets and iPads increased DPI is used to make visual scale bigger.
|
||||
if (GetPlatform().IsTablet())
|
||||
exactDensityDPI *= tabletFactor;
|
||||
exactDensityDPI *= 1.2;
|
||||
|
||||
// For some old devices (for example iPad 2) the density could be less than 160 DPI.
|
||||
// For some old devices (for example iPad 2) the density could be less than 160 DPI (mdpi).
|
||||
// Returns one in that case to keep readable text on the map.
|
||||
return std::max(1.35, exactDensityDPI / kMdpiDensityDPI);
|
||||
return std::max(1.35, exactDensityDPI / 160.0);
|
||||
}
|
||||
} // namespace dp
|
||||
|
|
|
@ -31,7 +31,7 @@ DrapeEngine::DrapeEngine(Params && params)
|
|||
|
||||
VisualParams::Init(params.m_vs, df::CalculateTileSize(m_viewport.GetWidth(), m_viewport.GetHeight()));
|
||||
|
||||
df::VisualParams::Instance().SetFontScale(params.m_fontsScaleFactor);
|
||||
SetFontScaleFactor(params.m_fontsScaleFactor);
|
||||
|
||||
gui::DrapeGui::Instance().SetSurfaceSize(m2::PointF(m_viewport.GetWidth(), m_viewport.GetHeight()));
|
||||
|
||||
|
@ -839,11 +839,6 @@ void DrapeEngine::EnableIsolines(bool enable)
|
|||
|
||||
void DrapeEngine::SetFontScaleFactor(double scaleFactor)
|
||||
{
|
||||
double const kMinScaleFactor = 0.5;
|
||||
double const kMaxScaleFactor = 2.0;
|
||||
|
||||
scaleFactor = base::Clamp(scaleFactor, kMinScaleFactor, kMaxScaleFactor);
|
||||
|
||||
VisualParams::Instance().SetFontScale(scaleFactor);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,27 +1,25 @@
|
|||
#include "kinetic_scroller.hpp"
|
||||
#include "visual_params.hpp"
|
||||
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace df
|
||||
{
|
||||
double const kKineticDuration = 1.5;
|
||||
double const kKineticFadeoff = 4.0;
|
||||
double const kKineticThreshold = 50.0;
|
||||
double const kKineticAcceleration = 0.4;
|
||||
double const kKineticMaxSpeedStart = 1000.0; // pixels per second
|
||||
double const kKineticMaxSpeedEnd = 10000.0; // pixels per second
|
||||
double const kInstantVelocityThresholdUnscaled = 200.0; // pixels per second
|
||||
double constexpr kKineticDuration = 1.5;
|
||||
double constexpr kKineticFadeoff = 4.0;
|
||||
double constexpr kKineticAcceleration = 0.4;
|
||||
|
||||
/// @name Generic pixels per second. Should multiply on visual scale.
|
||||
/// @{
|
||||
double constexpr kKineticMaxSpeedStart = 1000.0;
|
||||
double constexpr kKineticMaxSpeedEnd = 5000.0;
|
||||
double constexpr kInstantVelocityThreshold = 200.0;
|
||||
/// @}
|
||||
|
||||
double CalculateKineticMaxSpeed(ScreenBase const & modelView)
|
||||
{
|
||||
double const lerpCoef = 1.0 - GetNormalizedZoomLevel(modelView.GetScale());
|
||||
return (kKineticMaxSpeedStart * lerpCoef + kKineticMaxSpeedEnd * (1.0 - lerpCoef)) *
|
||||
VisualParams::Instance().GetVisualScale();
|
||||
return kKineticMaxSpeedStart * lerpCoef + kKineticMaxSpeedEnd * (1.0 - lerpCoef);
|
||||
}
|
||||
|
||||
class KineticScrollAnimation : public Animation
|
||||
|
@ -122,25 +120,32 @@ void KineticScroller::Init(ScreenBase const & modelView)
|
|||
{
|
||||
ASSERT(!m_isActive, ());
|
||||
m_isActive = true;
|
||||
m_lastRect = modelView.GlobalRect();
|
||||
m_lastTimestamp = std::chrono::steady_clock::now();
|
||||
m_updatePosition = modelView.GlobalRect().GlobalCenter();
|
||||
m_updateTimestamp = m_lastTimestamp;
|
||||
|
||||
m_points.clear();
|
||||
m_points.emplace_back(modelView.GlobalRect().Center(), ClockT::now());
|
||||
}
|
||||
|
||||
void KineticScroller::Update(ScreenBase const & modelView)
|
||||
{
|
||||
ASSERT(m_isActive, ());
|
||||
using namespace std::chrono;
|
||||
auto const nowTimestamp = std::chrono::steady_clock::now();
|
||||
auto const curPos = modelView.GlobalRect().GlobalCenter();
|
||||
|
||||
double const instantPixelLen = (modelView.GtoP(curPos) - modelView.GtoP(m_updatePosition)).Length();
|
||||
auto const updateElapsed = duration_cast<duration<double>>(nowTimestamp - m_updateTimestamp).count();
|
||||
m_instantVelocity = (updateElapsed >= 1e-5) ? instantPixelLen / updateElapsed : 0.0;
|
||||
auto const nowTime = ClockT::now();
|
||||
if (m_points.size() > 1)
|
||||
{
|
||||
// Time window to store move points for better (smooth) _instant_ velocity calculation.
|
||||
double constexpr kTimeWindowSec = 0.03;
|
||||
auto it = std::find_if(m_points.begin(), m_points.end(), [&nowTime](auto const & e)
|
||||
{
|
||||
return GetDurationSeconds(nowTime, e.second) <= kTimeWindowSec;
|
||||
});
|
||||
|
||||
m_updateTimestamp = nowTimestamp;
|
||||
m_updatePosition = curPos;
|
||||
// Keep last point always.
|
||||
if (it == m_points.end())
|
||||
--it;
|
||||
m_points.erase(m_points.begin(), it);
|
||||
}
|
||||
|
||||
m_points.emplace_back(modelView.GlobalRect().Center(), nowTime);
|
||||
}
|
||||
|
||||
bool KineticScroller::IsActive() const
|
||||
|
@ -148,31 +153,33 @@ bool KineticScroller::IsActive() const
|
|||
return m_isActive;
|
||||
}
|
||||
|
||||
m2::PointD KineticScroller::GetDirection(ScreenBase const & modelView) const
|
||||
// Calculate direction in mercator space, and velocity in pixel space.
|
||||
// We need the same reaction on different zoom levels, and should calculate velocity on pixel space.
|
||||
std::pair<m2::PointD, double> KineticScroller::GetDirectionAndVelocity(ScreenBase const & modelView) const
|
||||
{
|
||||
// In KineticScroller we store m_direction in mixed state.
|
||||
// Direction in mercator space, and length(m_direction) in pixel space.
|
||||
// We need same reaction on different zoom levels, and should calculate velocity on pixel space.
|
||||
ASSERT(m_isActive, ());
|
||||
using namespace std::chrono;
|
||||
auto const nowTimestamp = steady_clock::now();
|
||||
auto const elapsed = duration_cast<duration<double>>(nowTimestamp - m_lastTimestamp).count();
|
||||
ASSERT(!m_points.empty(), ());
|
||||
|
||||
// Or take m_points.back() ?
|
||||
m2::PointD const currentCenter = modelView.GlobalRect().GlobalCenter();
|
||||
|
||||
m2::PointD const lastCenter = m_lastRect.GlobalCenter();
|
||||
double const pxDeltaLength = (modelView.GtoP(currentCenter) - modelView.GtoP(lastCenter)).Length();
|
||||
m2::PointD delta = currentCenter - lastCenter;
|
||||
if (!delta.IsAlmostZero())
|
||||
{
|
||||
delta = delta.Normalize();
|
||||
double const lengthPixel = (modelView.GtoP(currentCenter) - modelView.GtoP(m_points.front().first)).Length();
|
||||
double const elapsedSec = GetDurationSeconds(ClockT::now(), m_points.front().second);
|
||||
if (elapsedSec < 1E-6)
|
||||
return {{}, 0};
|
||||
|
||||
// Velocity on pixels.
|
||||
double const v = std::min(pxDeltaLength / elapsed, CalculateKineticMaxSpeed(modelView));
|
||||
double const vs = VisualParams::Instance().GetVisualScale();
|
||||
|
||||
// At this point length(m_direction) already in pixel space, and delta normalized.
|
||||
return delta * v;
|
||||
}
|
||||
return m2::PointD::Zero();
|
||||
// Most touch filtrations happen here.
|
||||
double const velocity = lengthPixel / elapsedSec;
|
||||
if (velocity < kInstantVelocityThreshold * vs)
|
||||
return {{}, 0};
|
||||
|
||||
m2::PointD const delta = currentCenter - m_points.front().first;
|
||||
if (delta.IsAlmostZero())
|
||||
return {{}, 0};
|
||||
|
||||
return {delta.Normalize(), std::min(velocity, CalculateKineticMaxSpeed(modelView) * vs)};
|
||||
}
|
||||
|
||||
void KineticScroller::Cancel()
|
||||
|
@ -182,30 +189,20 @@ void KineticScroller::Cancel()
|
|||
|
||||
drape_ptr<Animation> KineticScroller::CreateKineticAnimation(ScreenBase const & modelView)
|
||||
{
|
||||
static double vs = VisualParams::Instance().GetVisualScale();
|
||||
static double kVelocityThreshold = kKineticThreshold * vs;
|
||||
static double kInstantVelocityThreshold = kInstantVelocityThresholdUnscaled * vs;
|
||||
|
||||
if (m_instantVelocity < kInstantVelocityThreshold)
|
||||
{
|
||||
Cancel();
|
||||
return drape_ptr<Animation>();
|
||||
}
|
||||
|
||||
auto const direction = GetDirection(modelView);
|
||||
auto const [dir, velocity] = GetDirectionAndVelocity(modelView);
|
||||
// Cancel current animation in any case.
|
||||
Cancel();
|
||||
if (velocity < 1E-6)
|
||||
return {};
|
||||
|
||||
if (direction.Length() < kVelocityThreshold)
|
||||
return drape_ptr<Animation>();
|
||||
|
||||
// Before we start animation we have to convert length(m_direction) from pixel space to mercator space.
|
||||
// Before we start animation we have to convert velocity vector from pixel space to mercator space.
|
||||
m2::PointD const center = modelView.GlobalRect().GlobalCenter();
|
||||
double const offset = (modelView.PtoG(modelView.GtoP(center) + direction) - center).Length();
|
||||
double const offset = (modelView.PtoG(modelView.GtoP(center) + dir * velocity) - center).Length();
|
||||
double const glbLength = kKineticAcceleration * offset;
|
||||
m2::PointD const glbDirection = direction.Normalize() * glbLength;
|
||||
m2::PointD const glbDirection = dir * glbLength;
|
||||
m2::PointD const targetCenter = center + glbDirection;
|
||||
if (!df::GetWorldRect().IsPointInside(targetCenter))
|
||||
return drape_ptr<Animation>();
|
||||
return {};
|
||||
|
||||
return make_unique_dp<KineticScrollAnimation>(center, glbDirection, kKineticDuration);
|
||||
}
|
||||
|
|
|
@ -4,9 +4,8 @@
|
|||
|
||||
#include "drape/pointers.hpp"
|
||||
|
||||
#include "geometry/any_rect2d.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
|
||||
namespace df
|
||||
{
|
||||
|
@ -20,13 +19,15 @@ public:
|
|||
drape_ptr<Animation> CreateKineticAnimation(ScreenBase const & modelView);
|
||||
|
||||
private:
|
||||
m2::PointD GetDirection(ScreenBase const & modelView) const;
|
||||
std::pair<m2::PointD, double> GetDirectionAndVelocity(ScreenBase const & modelView) const;
|
||||
|
||||
std::chrono::steady_clock::time_point m_lastTimestamp;
|
||||
std::chrono::steady_clock::time_point m_updateTimestamp;
|
||||
using ClockT = std::chrono::steady_clock;
|
||||
static double GetDurationSeconds(ClockT::time_point const & t2, ClockT::time_point const & t1)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1).count();
|
||||
}
|
||||
|
||||
std::deque<std::pair<m2::PointD, ClockT::time_point>> m_points;
|
||||
bool m_isActive = false;
|
||||
m2::AnyRectD m_lastRect;
|
||||
m2::PointD m_updatePosition;
|
||||
double m_instantVelocity = 0.0;
|
||||
};
|
||||
} // namespace df
|
||||
|
|
|
@ -66,7 +66,6 @@ char const * UserEventStream::DOUBLE_TAP_AND_HOLD = "DoubleTapAndHold";
|
|||
char const * UserEventStream::END_DOUBLE_TAP_AND_HOLD = "EndDoubleTapAndHold";
|
||||
#endif
|
||||
|
||||
uint8_t constexpr TouchEvent::INVALID_MASKED_POINTER = 0xFF;
|
||||
|
||||
void TouchEvent::SetFirstTouch(const Touch & touch)
|
||||
{
|
||||
|
@ -129,6 +128,17 @@ void TouchEvent::Swap()
|
|||
SetSecondMaskedPointer(swapIndex(GetSecondMaskedPointer()));
|
||||
}
|
||||
|
||||
std::string DebugPrint(Touch const & t)
|
||||
{
|
||||
return DebugPrint(t.m_location) + "; " + std::to_string(t.m_id) + "; " + std::to_string(t.m_force);
|
||||
}
|
||||
|
||||
std::string DebugPrint(TouchEvent const & e)
|
||||
{
|
||||
return std::to_string(e.m_type) + "; { " + DebugPrint(e.m_touches[0]) + " }";
|
||||
}
|
||||
|
||||
|
||||
UserEventStream::UserEventStream()
|
||||
: m_state(STATE_EMPTY)
|
||||
, m_animationSystem(AnimationSystem::Instance())
|
||||
|
|
|
@ -54,6 +54,8 @@ struct Touch
|
|||
m2::PointF m_location = m2::PointF::Zero();
|
||||
int64_t m_id = -1; // if id == -1 then touch is invalid
|
||||
float m_force = 0.0; // relative force of touch [0.0 - 1.0]
|
||||
|
||||
friend std::string DebugPrint(Touch const & t);
|
||||
};
|
||||
|
||||
class TouchEvent : public UserEvent
|
||||
|
@ -73,7 +75,7 @@ public:
|
|||
TOUCH_CANCEL
|
||||
};
|
||||
|
||||
static uint8_t const INVALID_MASKED_POINTER;
|
||||
static uint8_t constexpr INVALID_MASKED_POINTER = 0xFF;
|
||||
|
||||
EventType GetType() const override { return UserEvent::EventType::Touch; }
|
||||
|
||||
|
@ -106,6 +108,8 @@ public:
|
|||
uint8_t GetSecondMaskedPointer() const;
|
||||
size_t GetMaskedCount();
|
||||
|
||||
friend std::string DebugPrint(TouchEvent const & e);
|
||||
|
||||
private:
|
||||
void Swap();
|
||||
|
||||
|
@ -330,7 +334,7 @@ private:
|
|||
class RotateEvent : public UserEvent
|
||||
{
|
||||
public:
|
||||
explicit RotateEvent(double targetAzimuth, bool isAnim, TAnimationCreator const & parallelAnimCreator)
|
||||
RotateEvent(double targetAzimuth, bool isAnim, TAnimationCreator const & parallelAnimCreator)
|
||||
: m_targetAzimuth(targetAzimuth)
|
||||
, m_isAnim(isAnim)
|
||||
, m_parallelAnimCreator(parallelAnimCreator)
|
||||
|
@ -381,19 +385,18 @@ private:
|
|||
class ScrollEvent : public UserEvent
|
||||
{
|
||||
public:
|
||||
ScrollEvent(double distanceX, double distanceY)
|
||||
: m_distanceX(distanceX)
|
||||
, m_distanceY(distanceY)
|
||||
{}
|
||||
ScrollEvent(double distanceX, double distanceY)
|
||||
: m_distanceX(distanceX), m_distanceY(distanceY)
|
||||
{}
|
||||
|
||||
EventType GetType() const override { return UserEvent::EventType::Scroll; }
|
||||
EventType GetType() const override { return UserEvent::EventType::Scroll; }
|
||||
|
||||
double GetDistanceX() const { return m_distanceX; }
|
||||
double GetDistanceY() const { return m_distanceY; }
|
||||
double GetDistanceX() const { return m_distanceX; }
|
||||
double GetDistanceY() const { return m_distanceY; }
|
||||
|
||||
private:
|
||||
double m_distanceX;
|
||||
double m_distanceY;
|
||||
double m_distanceX;
|
||||
double m_distanceY;
|
||||
};
|
||||
|
||||
class UserEventStream
|
||||
|
|
|
@ -55,8 +55,6 @@ void VisualParams::Init(double vs, uint32_t tileSize)
|
|||
vizParams.m_tileSize = tileSize;
|
||||
vizParams.m_visualScale = vs;
|
||||
|
||||
LOG(LINFO, ("Visual scale =", vs, "; Tile size =", tileSize));
|
||||
|
||||
// Here we set up glyphs rendering parameters separately for high-res and low-res screens.
|
||||
if (vs <= 1.0)
|
||||
vizParams.m_glyphVisualParams = { 0.48f, 0.08f, 0.2f, 0.01f, 0.49f, 0.04f };
|
||||
|
@ -64,6 +62,8 @@ void VisualParams::Init(double vs, uint32_t tileSize)
|
|||
vizParams.m_glyphVisualParams = { 0.5f, 0.06f, 0.2f, 0.01f, 0.49f, 0.04f };
|
||||
|
||||
RISE_INITED;
|
||||
|
||||
LOG(LINFO, ("Visual scale =", vs, "; Tile size =", tileSize, "; Resources =", GetResourcePostfix(vs)));
|
||||
}
|
||||
|
||||
uint32_t VisualParams::GetGlyphSdfScale() const
|
||||
|
@ -93,7 +93,7 @@ double VisualParams::GetFontScale() const
|
|||
void VisualParams::SetFontScale(double fontScale)
|
||||
{
|
||||
ASSERT_INITED;
|
||||
m_fontScale = fontScale;
|
||||
m_fontScale = base::Clamp(fontScale, 0.5, 2.0);
|
||||
}
|
||||
|
||||
void VisualParams::SetVisualScale(double visualScale)
|
||||
|
@ -110,7 +110,9 @@ std::string const & VisualParams::GetResourcePostfix(double visualScale)
|
|||
ASSERT_INITED;
|
||||
static VisualScale postfixes[] =
|
||||
{
|
||||
/// @todo Not used in mobile because of minimal visual scale (@see visual_scale.hpp)
|
||||
{"mdpi", kMdpiScale},
|
||||
|
||||
{"hdpi", kHdpiScale},
|
||||
{"xhdpi", kXhdpiScale},
|
||||
{"6plus", k6plusScale},
|
||||
|
@ -186,10 +188,9 @@ VisualParams::GlyphVisualParams const & VisualParams::GetGlyphVisualParams() con
|
|||
return m_glyphVisualParams;
|
||||
}
|
||||
|
||||
m2::RectD const & GetWorldRect()
|
||||
m2::RectD GetWorldRect()
|
||||
{
|
||||
static m2::RectD const worldRect = mercator::Bounds::FullRect();
|
||||
return worldRect;
|
||||
return mercator::Bounds::FullRect();
|
||||
}
|
||||
|
||||
int GetTileScaleBase(ScreenBase const & s, uint32_t tileSize)
|
||||
|
|
|
@ -75,7 +75,7 @@ private:
|
|||
DISALLOW_COPY_AND_MOVE(VisualParams);
|
||||
};
|
||||
|
||||
m2::RectD const & GetWorldRect();
|
||||
m2::RectD GetWorldRect();
|
||||
|
||||
int GetTileScaleBase(ScreenBase const & s, uint32_t tileSize);
|
||||
int GetTileScaleBase(ScreenBase const & s);
|
||||
|
|
Reference in a new issue