| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380 |
- // ================================================================================
- // == This file is a part of Turbo Badger. (C) 2011-2014, Emil Segerås ==
- // == See tb_core.h for more information. ==
- // ================================================================================
- #include "tb_scroller.h"
- #include "tb_widgets.h"
- #include "tb_system.h"
- #include <math.h>
- namespace tb {
- // == Misc constants ====================================================================
- #define PAN_TARGET_FPS 60
- #define PAN_MSG_DELAY_MS ((double)(1000.0 / PAN_TARGET_FPS))
- #define PAN_START_THRESHOLD_MS 50
- #define PAN_POWER_ACC_THRESHOLD_MS 600
- #define PAN_POWER_MULTIPLIER 1.3f
- #define SCROLL_DECAY 200.0f
- #define SF_GATE_THRESHOLD 0.01f
- // == TBScrollerFunction ================================================================
- // Lab: http://www.madtealab.com/?V=1&C=6&F=5&G=1&O=1&W=774&GW=720&GH=252&GX=13.389616776278201&GY=4.790704772336853&GS=0.13102127484993598&EH=189&a=3.6666666666666665&aMa=20&aN=OrgSpeed&bMa=3&bN=CurPos&c=8&cMa=60&cI=1&cN=FrameRate&d=16&dMa=16&dI=1&dN=numSimulatedSeconds&l=2.388888888888889&lMa=5&lN=Decay&m=0.1&mMa=0.1&mN=GateThreshold&f1=OrgSpeed+%2A+exp%28-x+%2F+Decay%29&f1N=Speed&f2=CurPos+%2B+OrgSpeed+%2A+%281-exp%28-x+%2F+Decay%29%29%2A+Decay&f2N=Pos&f3=marker%28x%2C+predictGatedPoint%29&f3N=GatePoint&f4=aToF%28simulatedPoints%2Cnearest%2C0%2CnumSimulatedSeconds%29%28x%29&f4N=Iterated&f5=OrgSpeed+%2A+x&f5N=Linear1&Expr=%0ApredictGatedPoint+%3D+-log%28GateThreshold+%2F+%28OrgSpeed%29%29+%2A+Decay%0A%0Avar+cur+%3D+OrgSpeed%0AsimulatedPoints+%3D+sample%28function%28%29+%7B%0A+++cur+%3D+cur+%2A+%281+-+0.05%29%3B%0A+++return+cur%0A+%7D%2C+%5BnumSimulatedSeconds+%2A+FrameRate%5D%29%3B%0A%0ApredictGatedPoint
- float TBScrollerFunction::GetDurationFromSpeed(float start_speed)
- {
- float abs_start_speed = ABS(start_speed);
- if (abs_start_speed <= SF_GATE_THRESHOLD)
- return 0;
- return -log(SF_GATE_THRESHOLD / abs_start_speed) * m_decay;
- }
- float TBScrollerFunction::GetSpeedFromDistance(float distance)
- {
- float speed = distance / m_decay;
- if (distance > SF_GATE_THRESHOLD)
- return speed + SF_GATE_THRESHOLD;
- else if (distance < -SF_GATE_THRESHOLD)
- return speed - SF_GATE_THRESHOLD;
- return speed;
- }
- float TBScrollerFunction::GetDistanceAtTime(float start_speed, float elapsed_time_ms)
- {
- assert(elapsed_time_ms >= 0);
- return start_speed * (1 - exp(-elapsed_time_ms / m_decay)) * m_decay;
- }
- int TBScrollerFunction::GetDistanceAtTimeInt(float start_speed, float elapsed_time_ms)
- {
- float distance = GetDistanceAtTime(start_speed, elapsed_time_ms);
- return (int)(distance < 0 ? distance - 0.5f : distance + 0.5f);
- }
- // == TBScroller ========================================================================
- TBScroller::TBScroller(TBWidget *target)
- : m_target(target)
- , m_snap_listener(nullptr)
- , m_func(SCROLL_DECAY)
- , m_previous_pan_dx(0)
- , m_previous_pan_dy(0)
- , m_scroll_start_ms(0)
- , m_scroll_duration_x_ms(0)
- , m_scroll_duration_y_ms(0)
- , m_pan_power_multiplier_x(1)
- , m_pan_power_multiplier_y(1)
- {
- Reset();
- }
- TBScroller::~TBScroller()
- {
- }
- void TBScroller::Reset()
- {
- m_is_started = false;
- m_pan_dx = m_pan_dy = 0;
- m_pan_time_ms = 0;
- m_pan_delta_time_ms = 0;
- m_scroll_start_speed_ppms_x = m_scroll_start_speed_ppms_y = 0;
- m_scroll_start_scroll_x = m_scroll_start_scroll_y = 0;
- // don't reset m_previous_pan_dx and m_previous_pan_dy here.
- // don't reset m_pan_power here. It's done on start since it's needed for next pan!
- m_expected_scroll_x = m_expected_scroll_y = 0;
- }
- void TBScroller::OnScrollBy(int dx, int dy, bool accumulative)
- {
- if (!IsStarted())
- Start();
- float ppms_x = m_func.GetSpeedFromDistance((float)dx);
- float ppms_y = m_func.GetSpeedFromDistance((float)dy);
- if (accumulative && IsScrolling())
- {
- TBWidget::ScrollInfo info = m_target->GetScrollInfo();
- // If new direction is the same as the current direction,
- // calculate the speed needed for the remaining part and
- // add that to the new scroll speed.
- if ((ppms_x < 0) == (m_scroll_start_speed_ppms_x < 0))
- {
- int distance_x = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_x,
- m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_x));
- int distance_remaining_x = m_scroll_start_scroll_x + distance_x - info.x;
- distance_remaining_x += m_func.GetDistanceAtTimeInt(ppms_x, m_func.GetDurationFromSpeed(ppms_x));
- ppms_x = m_func.GetSpeedFromDistance((float)distance_remaining_x);
- }
- if ((ppms_y < 0) == (m_scroll_start_speed_ppms_y < 0))
- {
- int distance_y = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_y,
- m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_y));
- int distance_remaining_y = m_scroll_start_scroll_y + distance_y - info.y;
- distance_remaining_y += m_func.GetDistanceAtTimeInt(ppms_y, m_func.GetDurationFromSpeed(ppms_y));
- ppms_y = m_func.GetSpeedFromDistance((float)distance_remaining_y);
- }
- }
- AdjustToSnappingAndScroll(ppms_x, ppms_y);
- }
- bool TBScroller::OnPan(int dx, int dy)
- {
- if (!IsStarted())
- Start();
- // Pan the target
- const int in_dx = dx, in_dy = dy;
- m_target->ScrollByRecursive(dx, dy);
- // Calculate the pan speed. Smooth it out with the
- // previous pan speed to reduce fluctuation a little.
- double now_ms = TBSystem::GetTimeMS();
- if (m_pan_time_ms)
- {
- if (m_pan_delta_time_ms)
- m_pan_delta_time_ms = (now_ms - m_pan_time_ms + m_pan_delta_time_ms) / 2.0f;
- else
- m_pan_delta_time_ms = now_ms - m_pan_time_ms;
- }
- m_pan_time_ms = now_ms;
- m_pan_dx = (m_pan_dx + in_dx) / 2.0f;
- m_pan_dy = (m_pan_dy + in_dy) / 2.0f;
- // If we change direction, reset the pan power multiplier in that axis.
- if (m_pan_dx != 0 && (m_previous_pan_dx < 0) != (m_pan_dx < 0))
- m_pan_power_multiplier_x = 1;
- if (m_pan_dy != 0 && (m_previous_pan_dy < 0) != (m_pan_dy < 0))
- m_pan_power_multiplier_y = 1;
- m_previous_pan_dx = m_pan_dx;
- m_previous_pan_dy = m_pan_dy;
- return in_dx != dx || in_dy != dy;
- }
- void TBScroller::OnPanReleased()
- {
- if (TBSystem::GetTimeMS() < m_pan_time_ms + PAN_START_THRESHOLD_MS)
- {
- // Don't start scroll if we have too little speed.
- // This will prevent us from scrolling accidently.
- float pan_start_distance_threshold_px = 2 * TBSystem::GetDPI() / 100.0f;
- if (ABS(m_pan_dx) < pan_start_distance_threshold_px && ABS(m_pan_dy) < pan_start_distance_threshold_px)
- {
- StopOrSnapScroll();
- return;
- }
- if (m_pan_delta_time_ms == 0)
- {
- StopOrSnapScroll();
- return;
- }
- float ppms_x = (float)m_pan_dx / (float)m_pan_delta_time_ms;
- float ppms_y = (float)m_pan_dy / (float)m_pan_delta_time_ms;
- ppms_x *= m_pan_power_multiplier_x;
- ppms_y *= m_pan_power_multiplier_y;
- AdjustToSnappingAndScroll(ppms_x, ppms_y);
- }
- else
- StopOrSnapScroll();
- }
- void TBScroller::Start()
- {
- if (IsStarted())
- return;
- m_is_started = true;
- double now_ms = TBSystem::GetTimeMS();
- if (now_ms < m_scroll_start_ms + PAN_POWER_ACC_THRESHOLD_MS)
- {
- m_pan_power_multiplier_x *= PAN_POWER_MULTIPLIER;
- m_pan_power_multiplier_y *= PAN_POWER_MULTIPLIER;
- }
- else
- {
- m_pan_power_multiplier_x = m_pan_power_multiplier_y = 1;
- }
- }
- void TBScroller::Stop()
- {
- DeleteAllMessages();
- Reset();
- }
- bool TBScroller::StopIfAlmostStill()
- {
- double now_ms = TBSystem::GetTimeMS();
- if (now_ms > m_scroll_start_ms + (double)m_scroll_duration_x_ms &&
- now_ms > m_scroll_start_ms + (double)m_scroll_duration_y_ms)
- {
- Stop();
- return true;
- }
- return false;
- }
- void TBScroller::StopOrSnapScroll()
- {
- AdjustToSnappingAndScroll(0, 0);
- if (!IsScrolling())
- Stop();
- }
- void TBScroller::AdjustToSnappingAndScroll(float ppms_x, float ppms_y)
- {
- if (m_snap_listener)
- {
- // Calculate the distance
- int distance_x = m_func.GetDistanceAtTimeInt(ppms_x, m_func.GetDurationFromSpeed(ppms_x));
- int distance_y = m_func.GetDistanceAtTimeInt(ppms_y, m_func.GetDurationFromSpeed(ppms_y));
- // Let the snap listener modify the distance
- TBWidget::ScrollInfo info = m_target->GetScrollInfo();
- int target_x = distance_x + info.x;
- int target_y = distance_y + info.y;
- m_snap_listener->OnScrollSnap(m_target, target_x, target_y);
- distance_x = target_x - info.x;
- distance_y = target_y - info.y;
- // Get the start speed from the new distance
- ppms_x = m_func.GetSpeedFromDistance((float)distance_x);
- ppms_y = m_func.GetSpeedFromDistance((float)distance_y);
- }
- Scroll(ppms_x, ppms_y);
- }
- void TBScroller::Scroll(float start_speed_ppms_x, float start_speed_ppms_y)
- {
- // Set start values
- m_scroll_start_ms = TBSystem::GetTimeMS();
- GetTargetScrollXY(m_scroll_start_scroll_x, m_scroll_start_scroll_y);
- m_scroll_start_speed_ppms_x = start_speed_ppms_x;
- m_scroll_start_speed_ppms_y = start_speed_ppms_y;
- // Calculate duration for the scroll (each axis independently)
- m_scroll_duration_x_ms = m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_x);
- m_scroll_duration_y_ms = m_func.GetDurationFromSpeed(m_scroll_start_speed_ppms_y);
- if (StopIfAlmostStill())
- return;
- // Post the pan message if we don't already have one
- if (!GetMessageByID(TBIDC("scroll")))
- {
- // Update expected translation
- GetTargetChildTranslation(m_expected_scroll_x, m_expected_scroll_y);
- PostMessageDelayed(TBIDC("scroll"), nullptr, (uint32)PAN_MSG_DELAY_MS);
- }
- }
- bool TBScroller::IsScrolling()
- {
- return GetMessageByID(TBIDC("scroll")) ? true : false;
- }
- void TBScroller::GetTargetChildTranslation(int &x, int &y) const
- {
- int root_x = 0, root_y = 0;
- int child_translation_x = 0, child_translation_y = 0;
- TBWidget *scroll_root = m_target->GetScrollRoot();
- scroll_root->ConvertToRoot(root_x, root_y);
- scroll_root->GetChildTranslation(child_translation_x, child_translation_y);
- x = root_x + child_translation_x;
- y = root_y + child_translation_y;
- }
- void TBScroller::GetTargetScrollXY(int &x, int &y) const
- {
- x = 0;
- y = 0;
- TBWidget *tmp = m_target->GetScrollRoot();
- while (tmp)
- {
- TBWidget::ScrollInfo info = tmp->GetScrollInfo();
- x += info.x;
- y += info.y;
- tmp = tmp->GetParent();
- }
- }
- void TBScroller::OnMessageReceived(TBMessage *msg)
- {
- if (msg->message == TBIDC("scroll"))
- {
- int actual_scroll_x = 0, actual_scroll_y = 0;
- GetTargetChildTranslation(actual_scroll_x, actual_scroll_y);
- if (actual_scroll_x != m_expected_scroll_x ||
- actual_scroll_y != m_expected_scroll_y)
- {
- // Something else has affected the target child translation.
- // This should abort the scroll.
- // This could happen f.ex if something shrunk the scroll limits,
- // some other action changed scroll position, or if another
- // scroller started operating on a sub child that when reacing
- // its scroll limit, started scrolling its chain of parents.
- Stop();
- return;
- }
- // Calculate the time elapsed from scroll start. Clip within the
- // duration for each axis.
- double now_ms = TBSystem::GetTimeMS();
- float elapsed_time_x = (float)(now_ms - m_scroll_start_ms);
- float elapsed_time_y = elapsed_time_x;
- elapsed_time_x = MIN(elapsed_time_x, m_scroll_duration_x_ms);
- elapsed_time_y = MIN(elapsed_time_y, m_scroll_duration_y_ms);
- // Get the new scroll position from the current distance in each axis.
- int scroll_x = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_x, elapsed_time_x);
- int scroll_y = m_func.GetDistanceAtTimeInt(m_scroll_start_speed_ppms_y, elapsed_time_y);
- scroll_x += m_scroll_start_scroll_x;
- scroll_y += m_scroll_start_scroll_y;
- // Get the scroll delta and invoke ScrollByRecursive.
- int curr_scroll_x, curr_scroll_y;
- GetTargetScrollXY(curr_scroll_x, curr_scroll_y);
- const int dx = scroll_x - curr_scroll_x;
- const int dy = scroll_y - curr_scroll_y;
- int idx = dx, idy = dy;
- m_target->ScrollByRecursive(idx, idy);
- // Update expected translation
- GetTargetChildTranslation(m_expected_scroll_x, m_expected_scroll_y);
- if ((dx && actual_scroll_x == m_expected_scroll_x) &&
- (dy && actual_scroll_y == m_expected_scroll_y))
- {
- // We didn't get anywhere despite we tried,
- // so we're done (reached the end).
- Stop();
- return;
- }
- if (!StopIfAlmostStill())
- {
- double next_fire_time = msg->GetFireTime() + PAN_MSG_DELAY_MS;
- // avoid timer catch-up if program went sleeping for a while.
- next_fire_time = MAX(next_fire_time, now_ms);
- PostMessageOnTime(TBIDC("scroll"), nullptr, next_fire_time);
- }
- }
- }
- }; // namespace tb
|