[drape] Fixed inertial kinetic scroll. #6237

Merged
vng merged 2 commits from vng-fix into master 2023-10-08 02:15:48 +00:00
8 changed files with 107 additions and 100 deletions

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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())

View file

@ -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

View file

@ -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)

View file

@ -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);