OLD | NEW |
(Empty) | |
| 1 #include "content/renderer/input/input_scroll_elasticity_controller.h" |
| 2 |
| 3 namespace content { |
| 4 |
| 5 static const float scrollVelocityZeroingTimeout = 0.10f; |
| 6 static const float rubberbandDirectionLockStretchRatio = 1; |
| 7 static const float rubberbandMinimumRequiredDeltaBeforeStretch = 10; |
| 8 |
| 9 static const float rubberbandStiffness = 20; |
| 10 static const float rubberbandAmplitude = 0.31f; |
| 11 static const float rubberbandPeriod = 1.6f; |
| 12 |
| 13 static float elasticDeltaForTimeDelta(float initialPosition, float initialVeloci
ty, float elapsedTime) |
| 14 { |
| 15 float amplitude = rubberbandAmplitude; |
| 16 float period = rubberbandPeriod; |
| 17 float criticalDampeningFactor = expf((-elapsedTime * rubberbandStiffness) /
period); |
| 18 |
| 19 return (initialPosition + (-initialVelocity * elapsedTime * amplitude)) * cr
iticalDampeningFactor; |
| 20 } |
| 21 |
| 22 static float elasticDeltaForReboundDelta(float delta) |
| 23 { |
| 24 float stiffness = std::max(rubberbandStiffness, 1.0f); |
| 25 return delta / stiffness; |
| 26 } |
| 27 |
| 28 static float reboundDeltaForElasticDelta(float delta) |
| 29 { |
| 30 return delta * rubberbandStiffness; |
| 31 } |
| 32 |
| 33 static float scrollWheelMultiplier() |
| 34 { |
| 35 static float multiplier = -1; |
| 36 if (multiplier < 0) { |
| 37 // XXX |
| 38 // multiplier = [[NSUserDefaults standardUserDefaults] floatForKey:@"NSS
crollWheelMultiplier"]; |
| 39 if (multiplier <= 0) |
| 40 multiplier = 1; |
| 41 } |
| 42 return multiplier; |
| 43 } |
| 44 |
| 45 InputScrollElasticityController::InputScrollElasticityController(cc::ScrollElast
icityControllerClient* client) |
| 46 : m_client(client) |
| 47 , m_inScrollGesture(false) |
| 48 , m_hasScrolled(false) |
| 49 , m_momentumScrollInProgress(false) |
| 50 , m_ignoreMomentumScrolls(false) |
| 51 , m_snapRubberbandTimerIsActive(false) { } |
| 52 |
| 53 InputScrollElasticityController::~InputScrollElasticityController() { } |
| 54 |
| 55 void InputScrollElasticityController::WillShutdown() { |
| 56 m_client = NULL; |
| 57 } |
| 58 |
| 59 void InputScrollElasticityController::Animate(base::TimeTicks time) { |
| 60 snapRubberBandTimerFired(); |
| 61 } |
| 62 |
| 63 bool InputScrollElasticityController::handleWheelEvent(const blink::WebMouseWhee
lEvent& wheelEvent) |
| 64 { |
| 65 if (wheelEvent.phase == blink::WebMouseWheelEvent::PhaseMayBegin) |
| 66 return false; |
| 67 |
| 68 if (wheelEvent.phase == blink::WebMouseWheelEvent::PhaseBegan) { |
| 69 m_inScrollGesture = true; |
| 70 m_hasScrolled = false; |
| 71 m_momentumScrollInProgress = false; |
| 72 m_ignoreMomentumScrolls = false; |
| 73 m_lastMomentumScrollTimestamp = base::Time(); |
| 74 m_momentumVelocity = gfx::Vector2dF(); |
| 75 |
| 76 gfx::Vector2dF stretchAmount = m_client->stretchAmount(); |
| 77 m_stretchScrollForce.set_x(reboundDeltaForElasticDelta(stretchAmount.x()
)); |
| 78 m_stretchScrollForce.set_y(reboundDeltaForElasticDelta(stretchAmount.y()
)); |
| 79 m_overflowScrollDelta = gfx::Vector2dF(); |
| 80 |
| 81 stopSnapRubberbandTimer(); |
| 82 |
| 83 // TODO(erikchen): Use the commented out line once Chromium uses the ret
urn value correctly. |
| 84 // crbug.com/375512 |
| 85 // return shouldHandleEvent(wheelEvent); |
| 86 |
| 87 // This logic is incorrect, since diagonal wheel events are not consumed
. |
| 88 if (m_client->pinnedInDirection(gfx::Vector2dF(-wheelEvent.deltaX, 0)))
{ |
| 89 if (wheelEvent.deltaX > 0 && !wheelEvent.canRubberbandLeft) |
| 90 return false; |
| 91 if (wheelEvent.deltaX < 0 && !wheelEvent.canRubberbandRight) |
| 92 return false; |
| 93 } |
| 94 |
| 95 return true; |
| 96 } |
| 97 |
| 98 if (wheelEvent.phase == blink::WebMouseWheelEvent::PhaseEnded || wheelEvent.
phase == blink::WebMouseWheelEvent::PhaseCancelled) { |
| 99 snapRubberBand(); |
| 100 return m_hasScrolled; |
| 101 } |
| 102 |
| 103 bool isMomentumScrollEvent = (wheelEvent.momentumPhase != blink::WebMouseWhe
elEvent::PhaseNone); |
| 104 if (m_ignoreMomentumScrolls && (isMomentumScrollEvent || m_snapRubberbandTim
erIsActive)) { |
| 105 if (wheelEvent.momentumPhase == blink::WebMouseWheelEvent::PhaseEnded) { |
| 106 m_ignoreMomentumScrolls = false; |
| 107 return true; |
| 108 } |
| 109 return false; |
| 110 } |
| 111 |
| 112 if (!shouldHandleEvent(wheelEvent)) |
| 113 return false; |
| 114 |
| 115 float deltaX = m_overflowScrollDelta.x() - wheelEvent.deltaX; |
| 116 float deltaY = m_overflowScrollDelta.y() - wheelEvent.deltaY; |
| 117 float eventCoalescedDeltaX = -wheelEvent.deltaX; |
| 118 float eventCoalescedDeltaY = -wheelEvent.deltaY; |
| 119 |
| 120 // Reset overflow values because we may decide to remove delta at various po
ints and put it into overflow. |
| 121 m_overflowScrollDelta = gfx::Vector2dF(); |
| 122 |
| 123 gfx::Vector2dF stretchAmount = m_client->stretchAmount(); |
| 124 bool isVerticallyStretched = stretchAmount.y(); |
| 125 bool isHorizontallyStretched = stretchAmount.x(); |
| 126 |
| 127 // Slightly prefer scrolling vertically by applying the = case to deltaY |
| 128 if (fabsf(deltaY) >= fabsf(deltaX)) |
| 129 deltaX = 0; |
| 130 else |
| 131 deltaY = 0; |
| 132 |
| 133 bool shouldStretch = false; |
| 134 |
| 135 blink::WebMouseWheelEvent::Phase momentumPhase = wheelEvent.momentumPhase; |
| 136 |
| 137 // If we are starting momentum scrolling then do some setup. |
| 138 if (!m_momentumScrollInProgress && (momentumPhase == blink::WebMouseWheelEve
nt::PhaseBegan || momentumPhase == blink::WebMouseWheelEvent::PhaseChanged)) { |
| 139 m_momentumScrollInProgress = true; |
| 140 // Start the snap rubber band timer if it's not running. This is needed
to |
| 141 // snap back from the over scroll caused by momentum events. |
| 142 if (!m_snapRubberbandTimerIsActive && m_startTime == base::Time()) |
| 143 snapRubberBand(); |
| 144 } |
| 145 |
| 146 // XXX |
| 147 base::Time wheelEventTimestamp = base::Time::Now(); |
| 148 float timeDelta = (wheelEventTimestamp - m_lastMomentumScrollTimestamp).InSe
condsF(); |
| 149 if (m_inScrollGesture || m_momentumScrollInProgress) { |
| 150 if (m_lastMomentumScrollTimestamp != base::Time() && timeDelta > 0 && ti
meDelta < scrollVelocityZeroingTimeout) { |
| 151 m_momentumVelocity.set_x(eventCoalescedDeltaX / (float)timeDelta); |
| 152 m_momentumVelocity.set_y(eventCoalescedDeltaY / (float)timeDelta); |
| 153 m_lastMomentumScrollTimestamp = wheelEventTimestamp; |
| 154 } else { |
| 155 m_lastMomentumScrollTimestamp = wheelEventTimestamp; |
| 156 m_momentumVelocity = gfx::Vector2dF(); |
| 157 } |
| 158 |
| 159 if (isVerticallyStretched) { |
| 160 if (!isHorizontallyStretched && m_client->pinnedInDirection(gfx::Vec
tor2dF(deltaX, 0))) { |
| 161 // Stretching only in the vertical. |
| 162 if (deltaY != 0 && (fabsf(deltaX / deltaY) < rubberbandDirection
LockStretchRatio)) |
| 163 deltaX = 0; |
| 164 else if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStr
etch) { |
| 165 m_overflowScrollDelta.set_x(m_overflowScrollDelta.x() + delt
aX); |
| 166 deltaX = 0; |
| 167 } else |
| 168 m_overflowScrollDelta.set_x(m_overflowScrollDelta.x() + delt
aX); |
| 169 } |
| 170 } else if (isHorizontallyStretched) { |
| 171 // Stretching only in the horizontal. |
| 172 if (m_client->pinnedInDirection(gfx::Vector2dF(0, deltaY))) { |
| 173 if (deltaX != 0 && (fabsf(deltaY / deltaX) < rubberbandDirection
LockStretchRatio)) |
| 174 deltaY = 0; |
| 175 else if (fabsf(deltaY) < rubberbandMinimumRequiredDeltaBeforeStr
etch) { |
| 176 m_overflowScrollDelta.set_y(m_overflowScrollDelta.y() + delt
aY); |
| 177 deltaY = 0; |
| 178 } else |
| 179 m_overflowScrollDelta.set_y(m_overflowScrollDelta.y() + delt
aY); |
| 180 } |
| 181 } else { |
| 182 // Not stretching at all yet. |
| 183 if (m_client->pinnedInDirection(gfx::Vector2dF(deltaX, deltaY))) { |
| 184 if (fabsf(deltaY) >= fabsf(deltaX)) { |
| 185 if (fabsf(deltaX) < rubberbandMinimumRequiredDeltaBeforeStre
tch) { |
| 186 m_overflowScrollDelta.set_x(m_overflowScrollDelta.x() +
deltaX); |
| 187 deltaX = 0; |
| 188 } else |
| 189 m_overflowScrollDelta.set_x(m_overflowScrollDelta.x() +
deltaX); |
| 190 } |
| 191 shouldStretch = true; |
| 192 } |
| 193 } |
| 194 } |
| 195 |
| 196 if (deltaX != 0 || deltaY != 0) { |
| 197 m_hasScrolled = true; |
| 198 if (!(shouldStretch || isVerticallyStretched || isHorizontallyStretched)
) { |
| 199 if (deltaY != 0) { |
| 200 deltaY *= scrollWheelMultiplier(); |
| 201 m_client->immediateScrollBy(gfx::Vector2dF(0, deltaY)); |
| 202 } |
| 203 if (deltaX != 0) { |
| 204 deltaX *= scrollWheelMultiplier(); |
| 205 m_client->immediateScrollBy(gfx::Vector2dF(deltaX, 0)); |
| 206 } |
| 207 } else { |
| 208 if (!m_client->allowsHorizontalStretching()) { |
| 209 deltaX = 0; |
| 210 eventCoalescedDeltaX = 0; |
| 211 } else if ((deltaX != 0) && !isHorizontallyStretched && !m_client->p
innedInDirection(gfx::Vector2dF(deltaX, 0))) { |
| 212 deltaX *= scrollWheelMultiplier(); |
| 213 |
| 214 m_client->immediateScrollByWithoutContentEdgeConstraints(gfx::Ve
ctor2dF(deltaX, 0)); |
| 215 deltaX = 0; |
| 216 } |
| 217 |
| 218 if (!m_client->allowsVerticalStretching()) { |
| 219 deltaY = 0; |
| 220 eventCoalescedDeltaY = 0; |
| 221 } else if ((deltaY != 0) && !isVerticallyStretched && !m_client->pin
nedInDirection(gfx::Vector2dF(0, deltaY))) { |
| 222 deltaY *= scrollWheelMultiplier(); |
| 223 |
| 224 m_client->immediateScrollByWithoutContentEdgeConstraints(gfx::Ve
ctor2dF(0, deltaY)); |
| 225 deltaY = 0; |
| 226 } |
| 227 |
| 228 gfx::Vector2dF stretchAmount = m_client->stretchAmount(); |
| 229 |
| 230 if (m_momentumScrollInProgress) { |
| 231 if ((m_client->pinnedInDirection(gfx::Vector2dF(eventCoalescedDe
ltaX, eventCoalescedDeltaY)) || (fabsf(eventCoalescedDeltaX) + fabsf(eventCoales
cedDeltaY) <= 0)) && m_lastMomentumScrollTimestamp != base::Time()) { |
| 232 m_ignoreMomentumScrolls = true; |
| 233 m_momentumScrollInProgress = false; |
| 234 snapRubberBand(); |
| 235 } |
| 236 } |
| 237 |
| 238 m_stretchScrollForce.set_x(m_stretchScrollForce.x() + deltaX); |
| 239 m_stretchScrollForce.set_y(m_stretchScrollForce.y() + deltaY); |
| 240 |
| 241 gfx::Vector2dF dampedDelta(ceilf(elasticDeltaForReboundDelta(m_stret
chScrollForce.x())), ceilf(elasticDeltaForReboundDelta(m_stretchScrollForce.y())
)); |
| 242 |
| 243 m_client->immediateScrollByWithoutContentEdgeConstraints(dampedDelta
- stretchAmount); |
| 244 } |
| 245 } |
| 246 |
| 247 if (m_momentumScrollInProgress && momentumPhase == blink::WebMouseWheelEvent
::PhaseEnded) { |
| 248 m_momentumScrollInProgress = false; |
| 249 m_ignoreMomentumScrolls = false; |
| 250 m_lastMomentumScrollTimestamp = base::Time(); |
| 251 } |
| 252 |
| 253 return true; |
| 254 } |
| 255 |
| 256 static inline float roundTowardZero(float num) |
| 257 { |
| 258 return num > 0 ? ceilf(num - 0.5f) : floorf(num + 0.5f); |
| 259 } |
| 260 |
| 261 static inline float roundToDevicePixelTowardZero(float num) |
| 262 { |
| 263 float roundedNum = roundf(num); |
| 264 if (fabs(num - roundedNum) < 0.125) |
| 265 num = roundedNum; |
| 266 |
| 267 return roundTowardZero(num); |
| 268 } |
| 269 |
| 270 void InputScrollElasticityController::snapRubberBandTimerFired() |
| 271 { |
| 272 if (!m_momentumScrollInProgress || m_ignoreMomentumScrolls) { |
| 273 float timeDelta = (base::Time::Now() - m_startTime).InSecondsF(); |
| 274 |
| 275 if (m_startStretch == gfx::Vector2dF()) { |
| 276 m_startStretch = m_client->stretchAmount(); |
| 277 if (m_startStretch == gfx::Vector2dF()) { |
| 278 stopSnapRubberbandTimer(); |
| 279 |
| 280 m_stretchScrollForce = gfx::Vector2dF(); |
| 281 m_startTime = base::Time(); |
| 282 m_origOrigin = gfx::Vector2dF(); |
| 283 m_origVelocity = gfx::Vector2dF(); |
| 284 return; |
| 285 } |
| 286 |
| 287 m_origOrigin = m_client->absoluteScrollPosition() - m_startStretch; |
| 288 m_origVelocity = m_momentumVelocity; |
| 289 |
| 290 // Just like normal scrolling, prefer vertical rubberbanding |
| 291 if (fabsf(m_origVelocity.y()) >= fabsf(m_origVelocity.x())) |
| 292 m_origVelocity.set_x(0); |
| 293 |
| 294 // Don't rubber-band horizontally if it's not possible to scroll hor
izontally |
| 295 if (!m_client->canScrollHorizontally()) |
| 296 m_origVelocity.set_x(0); |
| 297 |
| 298 // Don't rubber-band vertically if it's not possible to scroll verti
cally |
| 299 if (!m_client->canScrollVertically()) |
| 300 m_origVelocity.set_y(0); |
| 301 } |
| 302 |
| 303 gfx::Vector2dF delta(roundToDevicePixelTowardZero(elasticDeltaForTimeDel
ta(m_startStretch.x(), -m_origVelocity.x(), (float)timeDelta)), |
| 304 roundToDevicePixelTowardZero(elasticDeltaForTimeDelta(m
_startStretch.y(), -m_origVelocity.y(), (float)timeDelta))); |
| 305 |
| 306 if (fabs(delta.x()) >= 1 || fabs(delta.y()) >= 1) { |
| 307 m_client->immediateScrollByWithoutContentEdgeConstraints(gfx::Vector
2dF(delta.x(), delta.y()) - m_client->stretchAmount()); |
| 308 |
| 309 gfx::Vector2dF newStretch = m_client->stretchAmount(); |
| 310 |
| 311 m_stretchScrollForce.set_x(reboundDeltaForElasticDelta(newStretch.x(
))); |
| 312 m_stretchScrollForce.set_y(reboundDeltaForElasticDelta(newStretch.y(
))); |
| 313 } else { |
| 314 m_client->adjustScrollPositionToBoundsIfNecessary(); |
| 315 |
| 316 stopSnapRubberbandTimer(); |
| 317 m_stretchScrollForce = gfx::Vector2dF(); |
| 318 m_startTime = base::Time(); |
| 319 m_startStretch = gfx::Vector2dF(); |
| 320 m_origOrigin = gfx::Vector2dF(); |
| 321 m_origVelocity = gfx::Vector2dF(); |
| 322 } |
| 323 } else { |
| 324 m_startTime = base::Time::Now(); |
| 325 m_startStretch = gfx::Vector2dF(); |
| 326 } |
| 327 } |
| 328 |
| 329 bool InputScrollElasticityController::isRubberBandInProgress() const |
| 330 { |
| 331 if (!m_inScrollGesture && !m_momentumScrollInProgress && !m_snapRubberbandTi
merIsActive) |
| 332 return false; |
| 333 |
| 334 return !m_client->stretchAmount().IsZero(); |
| 335 } |
| 336 |
| 337 void InputScrollElasticityController::stopSnapRubberbandTimer() |
| 338 { |
| 339 m_client->stopSnapRubberbandTimer(); |
| 340 m_snapRubberbandTimerIsActive = false; |
| 341 } |
| 342 |
| 343 void InputScrollElasticityController::snapRubberBand() |
| 344 { |
| 345 float timeDelta = (base::Time::Now() - m_lastMomentumScrollTimestamp).InSeco
ndsF(); |
| 346 if (m_lastMomentumScrollTimestamp != base::Time() && timeDelta >= scrollVelo
cityZeroingTimeout) |
| 347 m_momentumVelocity = gfx::Vector2dF(); |
| 348 |
| 349 m_inScrollGesture = false; |
| 350 |
| 351 if (m_snapRubberbandTimerIsActive) |
| 352 return; |
| 353 |
| 354 m_startStretch = gfx::Vector2dF(); |
| 355 m_origOrigin = gfx::Vector2dF(); |
| 356 m_origVelocity = gfx::Vector2dF(); |
| 357 |
| 358 // If there's no momentum scroll or stretch amount, no need to start the tim
er. |
| 359 if (!m_momentumScrollInProgress && m_client->stretchAmount() == gfx::Vector2
dF()) { |
| 360 m_startTime = base::Time(); |
| 361 m_stretchScrollForce = gfx::Vector2dF(); |
| 362 return; |
| 363 } |
| 364 |
| 365 m_startTime = base::Time::Now(); |
| 366 m_client->startSnapRubberbandTimer(); |
| 367 m_snapRubberbandTimerIsActive = true; |
| 368 } |
| 369 |
| 370 bool InputScrollElasticityController::shouldHandleEvent(const blink::WebMouseWhe
elEvent& wheelEvent) |
| 371 { |
| 372 // Once any scrolling has happened, all future events should be handled. |
| 373 if (m_hasScrolled) |
| 374 return true; |
| 375 |
| 376 // The event can't cause scrolling to start if its delta is 0. |
| 377 if (wheelEvent.deltaX == 0 && wheelEvent.deltaY == 0) |
| 378 return false; |
| 379 |
| 380 // If the client isn't pinned, then the event is guaranteed to cause scrolli
ng. |
| 381 if (!m_client->pinnedInDirection(gfx::Vector2dF(-wheelEvent.deltaX, 0))) |
| 382 return true; |
| 383 |
| 384 // If the event is pinned, then the client can't scroll, but it might rubber
band. |
| 385 // Check if the event allows rubber banding. |
| 386 if (wheelEvent.deltaY == 0) { |
| 387 if (wheelEvent.deltaX > 0 && !wheelEvent.canRubberbandLeft) |
| 388 return false; |
| 389 if (wheelEvent.deltaX < 0 && !wheelEvent.canRubberbandRight) |
| 390 return false; |
| 391 } |
| 392 |
| 393 // The event is going to either cause scrolling or rubber banding. |
| 394 return true; |
| 395 } |
| 396 |
| 397 } // namespace content |
OLD | NEW |