Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "Axis.h"
8
9
#include <math.h> // for fabsf, pow, powf
10
#include <algorithm> // for max
11
12
#include "APZCTreeManager.h" // for APZCTreeManager
13
#include "AsyncPanZoomController.h" // for AsyncPanZoomController
14
#include "FrameMetrics.h" // for FrameMetrics
15
#include "SimpleVelocityTracker.h" // for FrameMetrics
16
#include "mozilla/Attributes.h" // for final
17
#include "mozilla/Preferences.h" // for Preferences
18
#include "mozilla/gfx/Rect.h" // for RoundedIn
19
#include "mozilla/layers/APZThreadUtils.h" // for AssertOnControllerThread
20
#include "mozilla/mozalloc.h" // for operator new
21
#include "mozilla/FloatingPoint.h" // for FuzzyEqualsAdditive
22
#include "nsMathUtils.h" // for NS_lround
23
#include "nsPrintfCString.h" // for nsPrintfCString
24
#include "nsThreadUtils.h" // for NS_DispatchToMainThread, etc
25
#include "nscore.h" // for NS_IMETHOD
26
27
static mozilla::LazyLogModule sApzAxsLog("apz.axis");
28
#define AXIS_LOG(...) MOZ_LOG(sApzAxsLog, LogLevel::Debug, (__VA_ARGS__))
29
30
namespace mozilla {
31
namespace layers {
32
33
bool FuzzyEqualsCoordinate(float aValue1, float aValue2) {
34
return FuzzyEqualsAdditive(aValue1, aValue2, COORDINATE_EPSILON) ||
35
FuzzyEqualsMultiplicative(aValue1, aValue2);
36
}
37
38
Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController)
39
: mPos(0),
40
mVelocity(0.0f),
41
mAxisLocked(false),
42
mAsyncPanZoomController(aAsyncPanZoomController),
43
mOverscroll(0),
44
mMSDModel(0.0, 0.0, 0.0, 400.0, 1.2),
45
mVelocityTracker(mAsyncPanZoomController->GetPlatformSpecificState()
46
->CreateVelocityTracker(this)) {}
47
48
float Axis::ToLocalVelocity(float aVelocityInchesPerMs) const {
49
ScreenPoint velocity =
50
MakePoint(aVelocityInchesPerMs * mAsyncPanZoomController->GetDPI());
51
// Use ToScreenCoordinates() to convert a point rather than a vector by
52
// treating the point as a vector, and using (0, 0) as the anchor.
53
ScreenPoint panStart = mAsyncPanZoomController->ToScreenCoordinates(
54
mAsyncPanZoomController->PanStart(), ParentLayerPoint());
55
ParentLayerPoint localVelocity =
56
mAsyncPanZoomController->ToParentLayerCoordinates(velocity, panStart);
57
return localVelocity.Length();
58
}
59
60
void Axis::UpdateWithTouchAtDevicePoint(ParentLayerCoord aPos,
61
uint32_t aTimestampMs) {
62
// mVelocityTracker is controller-thread only
63
APZThreadUtils::AssertOnControllerThread();
64
65
mPos = aPos;
66
67
AXIS_LOG("%p|%s got position %f\n", mAsyncPanZoomController, Name(),
68
mPos.value);
69
if (Maybe<float> newVelocity =
70
mVelocityTracker->AddPosition(aPos, aTimestampMs)) {
71
mVelocity = mAxisLocked ? 0 : *newVelocity;
72
AXIS_LOG("%p|%s velocity from tracker is %f\n", mAsyncPanZoomController,
73
Name(), mVelocity);
74
}
75
}
76
77
void Axis::HandleDynamicToolbarMovement(uint32_t aStartTimestampMs,
78
uint32_t aEndTimestampMs,
79
ParentLayerCoord aDelta) {
80
// mVelocityTracker is controller-thread only
81
APZThreadUtils::AssertOnControllerThread();
82
83
mVelocity = mVelocityTracker->HandleDynamicToolbarMovement(
84
aStartTimestampMs, aEndTimestampMs, aDelta);
85
}
86
87
void Axis::StartTouch(ParentLayerCoord aPos, uint32_t aTimestampMs) {
88
mStartPos = aPos;
89
mPos = aPos;
90
mVelocityTracker->StartTracking(aPos, aTimestampMs);
91
mAxisLocked = false;
92
}
93
94
bool Axis::AdjustDisplacement(
95
ParentLayerCoord aDisplacement,
96
/* ParentLayerCoord */ float& aDisplacementOut,
97
/* ParentLayerCoord */ float& aOverscrollAmountOut,
98
bool aForceOverscroll /* = false */) {
99
if (mAxisLocked) {
100
aOverscrollAmountOut = 0;
101
aDisplacementOut = 0;
102
return false;
103
}
104
if (aForceOverscroll) {
105
aOverscrollAmountOut = aDisplacement;
106
aDisplacementOut = 0;
107
return false;
108
}
109
110
EndOverscrollAnimation();
111
112
ParentLayerCoord displacement = aDisplacement;
113
114
// First consume any overscroll in the opposite direction along this axis.
115
ParentLayerCoord consumedOverscroll = 0;
116
if (mOverscroll > 0 && aDisplacement < 0) {
117
consumedOverscroll = std::min(mOverscroll, -aDisplacement);
118
} else if (mOverscroll < 0 && aDisplacement > 0) {
119
consumedOverscroll = 0.f - std::min(-mOverscroll, aDisplacement);
120
}
121
mOverscroll -= consumedOverscroll;
122
displacement += consumedOverscroll;
123
124
// Split the requested displacement into an allowed displacement that does
125
// not overscroll, and an overscroll amount.
126
aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement);
127
if (aOverscrollAmountOut != 0.0f) {
128
// No need to have a velocity along this axis anymore; it won't take us
129
// anywhere, so we're just spinning needlessly.
130
AXIS_LOG("%p|%s has overscrolled, clearing velocity\n",
131
mAsyncPanZoomController, Name());
132
mVelocity = 0.0f;
133
displacement -= aOverscrollAmountOut;
134
}
135
aDisplacementOut = displacement;
136
return fabsf(consumedOverscroll) > EPSILON;
137
}
138
139
ParentLayerCoord Axis::ApplyResistance(
140
ParentLayerCoord aRequestedOverscroll) const {
141
// 'resistanceFactor' is a value between 0 and 1/16, which:
142
// - tends to 1/16 as the existing overscroll tends to 0
143
// - tends to 0 as the existing overscroll tends to the composition length
144
// The actual overscroll is the requested overscroll multiplied by this
145
// factor.
146
float resistanceFactor =
147
(1 - fabsf(GetOverscroll()) / GetCompositionLength()) / 16;
148
float result = resistanceFactor < 0 ? ParentLayerCoord(0)
149
: aRequestedOverscroll * resistanceFactor;
150
result = clamped(result, -8.0f, 8.0f);
151
return result;
152
}
153
154
void Axis::OverscrollBy(ParentLayerCoord aOverscroll) {
155
MOZ_ASSERT(CanScroll());
156
// We can get some spurious calls to OverscrollBy() with near-zero values
157
// due to rounding error. Ignore those (they might trip the asserts below.)
158
if (FuzzyEqualsAdditive(aOverscroll.value, 0.0f, COORDINATE_EPSILON)) {
159
return;
160
}
161
EndOverscrollAnimation();
162
aOverscroll = ApplyResistance(aOverscroll);
163
if (aOverscroll > 0) {
164
#ifdef DEBUG
165
if (!FuzzyEqualsCoordinate(GetCompositionEnd().value, GetPageEnd().value)) {
166
nsPrintfCString message(
167
"composition end (%f) is not equal (within error) to page end (%f)\n",
168
GetCompositionEnd().value, GetPageEnd().value);
169
NS_ASSERTION(false, message.get());
170
MOZ_CRASH("GFX: Overscroll issue > 0");
171
}
172
#endif
173
MOZ_ASSERT(mOverscroll >= 0);
174
} else if (aOverscroll < 0) {
175
#ifdef DEBUG
176
if (!FuzzyEqualsCoordinate(GetOrigin().value, GetPageStart().value)) {
177
nsPrintfCString message(
178
"composition origin (%f) is not equal (within error) to page origin "
179
"(%f)\n",
180
GetOrigin().value, GetPageStart().value);
181
NS_ASSERTION(false, message.get());
182
MOZ_CRASH("GFX: Overscroll issue < 0");
183
}
184
#endif
185
MOZ_ASSERT(mOverscroll <= 0);
186
}
187
mOverscroll += aOverscroll;
188
}
189
190
ParentLayerCoord Axis::GetOverscroll() const { return mOverscroll; }
191
192
void Axis::StartOverscrollAnimation(float aVelocity) {
193
aVelocity = clamped(aVelocity / 2.0f, -20.0f, 20.0f);
194
SetVelocity(aVelocity);
195
mMSDModel.SetPosition(mOverscroll);
196
// Convert velocity from ParentLayerCoords/millisecond to
197
// ParentLayerCoords/second.
198
mMSDModel.SetVelocity(mVelocity * 1000.0);
199
}
200
201
void Axis::EndOverscrollAnimation() {
202
mMSDModel.SetPosition(0.0);
203
mMSDModel.SetVelocity(0.0);
204
}
205
206
bool Axis::SampleOverscrollAnimation(const TimeDuration& aDelta) {
207
mMSDModel.Simulate(aDelta);
208
mOverscroll = mMSDModel.GetPosition();
209
210
if (mMSDModel.IsFinished(1.0)) {
211
// "Jump" to the at-rest state. The jump shouldn't be noticeable as the
212
// velocity and overscroll are already low.
213
AXIS_LOG("%p|%s oscillation dropped below threshold, going to rest\n",
214
mAsyncPanZoomController, Name());
215
ClearOverscroll();
216
mVelocity = 0;
217
return false;
218
}
219
220
// Otherwise, continue the animation.
221
return true;
222
}
223
224
bool Axis::IsOverscrolled() const { return mOverscroll != 0.f; }
225
226
void Axis::ClearOverscroll() {
227
EndOverscrollAnimation();
228
mOverscroll = 0;
229
}
230
231
ParentLayerCoord Axis::PanStart() const { return mStartPos; }
232
233
ParentLayerCoord Axis::PanDistance() const { return fabs(mPos - mStartPos); }
234
235
ParentLayerCoord Axis::PanDistance(ParentLayerCoord aPos) const {
236
return fabs(aPos - mStartPos);
237
}
238
239
void Axis::EndTouch(uint32_t aTimestampMs) {
240
// mVelocityQueue is controller-thread only
241
APZThreadUtils::AssertOnControllerThread();
242
243
// If the velocity tracker wasn't able to compute a velocity, zero out
244
// the velocity to make sure we don't get a fling based on some old and
245
// no-longer-relevant value of mVelocity. Also if the axis is locked then
246
// just reset the velocity to 0 since we don't need any velocity to carry
247
// into the fling.
248
if (mAxisLocked) {
249
mVelocity = 0;
250
} else if (Maybe<float> velocity =
251
mVelocityTracker->ComputeVelocity(aTimestampMs)) {
252
mVelocity = *velocity;
253
} else {
254
mVelocity = 0;
255
}
256
mAxisLocked = false;
257
AXIS_LOG("%p|%s ending touch, computed velocity %f\n",
258
mAsyncPanZoomController, Name(), mVelocity);
259
}
260
261
void Axis::CancelGesture() {
262
// mVelocityQueue is controller-thread only
263
APZThreadUtils::AssertOnControllerThread();
264
265
AXIS_LOG("%p|%s cancelling touch, clearing velocity queue\n",
266
mAsyncPanZoomController, Name());
267
mVelocity = 0.0f;
268
mVelocityTracker->Clear();
269
}
270
271
bool Axis::CanScroll() const {
272
return GetPageLength() - GetCompositionLength() > COORDINATE_EPSILON;
273
}
274
275
bool Axis::CanScroll(ParentLayerCoord aDelta) const {
276
if (!CanScroll() || mAxisLocked) {
277
return false;
278
}
279
280
return fabs(DisplacementWillOverscrollAmount(aDelta) - aDelta) >
281
COORDINATE_EPSILON;
282
}
283
284
CSSCoord Axis::ClampOriginToScrollableRect(CSSCoord aOrigin) const {
285
CSSToParentLayerScale zoom = GetScaleForAxis(GetFrameMetrics().GetZoom());
286
ParentLayerCoord origin = aOrigin * zoom;
287
288
ParentLayerCoord result;
289
if (origin < GetPageStart()) {
290
result = GetPageStart();
291
} else if (origin + GetCompositionLength() > GetPageEnd()) {
292
result = GetPageEnd() - GetCompositionLength();
293
} else {
294
return aOrigin;
295
}
296
297
return result / zoom;
298
}
299
300
bool Axis::CanScrollNow() const { return !mAxisLocked && CanScroll(); }
301
302
ParentLayerCoord Axis::DisplacementWillOverscrollAmount(
303
ParentLayerCoord aDisplacement) const {
304
ParentLayerCoord newOrigin = GetOrigin() + aDisplacement;
305
ParentLayerCoord newCompositionEnd = GetCompositionEnd() + aDisplacement;
306
// If the current pan plus a displacement takes the window to the left of or
307
// above the current page rect.
308
bool minus = newOrigin < GetPageStart();
309
// If the current pan plus a displacement takes the window to the right of or
310
// below the current page rect.
311
bool plus = newCompositionEnd > GetPageEnd();
312
if (minus && plus) {
313
// Don't handle overscrolled in both directions; a displacement can't cause
314
// this, it must have already been zoomed out too far.
315
return 0;
316
}
317
if (minus) {
318
return newOrigin - GetPageStart();
319
}
320
if (plus) {
321
return newCompositionEnd - GetPageEnd();
322
}
323
return 0;
324
}
325
326
CSSCoord Axis::ScaleWillOverscrollAmount(float aScale, CSSCoord aFocus) const {
327
// Internally, do computations in ParentLayer coordinates *before* the scale
328
// is applied.
329
CSSToParentLayerScale zoom = GetFrameMetrics().GetZoom().ToScaleFactor();
330
ParentLayerCoord focus = aFocus * zoom;
331
ParentLayerCoord originAfterScale = (GetOrigin() + focus) - (focus / aScale);
332
333
bool both = ScaleWillOverscrollBothSides(aScale);
334
bool minus = GetPageStart() - originAfterScale > COORDINATE_EPSILON;
335
bool plus =
336
(originAfterScale + (GetCompositionLength() / aScale)) - GetPageEnd() >
337
COORDINATE_EPSILON;
338
339
if ((minus && plus) || both) {
340
// If we ever reach here it's a bug in the client code.
341
MOZ_ASSERT(false,
342
"In an OVERSCROLL_BOTH condition in ScaleWillOverscrollAmount");
343
return 0;
344
}
345
if (minus) {
346
return (originAfterScale - GetPageStart()) / zoom;
347
}
348
if (plus) {
349
return (originAfterScale + (GetCompositionLength() / aScale) -
350
GetPageEnd()) /
351
zoom;
352
}
353
return 0;
354
}
355
356
bool Axis::IsAxisLocked() const { return mAxisLocked; }
357
358
float Axis::GetVelocity() const { return mAxisLocked ? 0 : mVelocity; }
359
360
void Axis::SetVelocity(float aVelocity) {
361
AXIS_LOG("%p|%s direct-setting velocity to %f\n", mAsyncPanZoomController,
362
Name(), aVelocity);
363
mVelocity = aVelocity;
364
}
365
366
ParentLayerCoord Axis::GetCompositionEnd() const {
367
return GetOrigin() + GetCompositionLength();
368
}
369
370
ParentLayerCoord Axis::GetPageEnd() const {
371
return GetPageStart() + GetPageLength();
372
}
373
374
ParentLayerCoord Axis::GetScrollRangeEnd() const {
375
return GetPageEnd() - GetCompositionLength();
376
}
377
378
ParentLayerCoord Axis::GetOrigin() const {
379
ParentLayerPoint origin =
380
GetFrameMetrics().GetScrollOffset() * GetFrameMetrics().GetZoom();
381
return GetPointOffset(origin);
382
}
383
384
ParentLayerCoord Axis::GetCompositionLength() const {
385
return GetRectLength(GetFrameMetrics().GetCompositionBounds());
386
}
387
388
ParentLayerCoord Axis::GetPageStart() const {
389
ParentLayerRect pageRect = GetFrameMetrics().GetExpandedScrollableRect() *
390
GetFrameMetrics().GetZoom();
391
return GetRectOffset(pageRect);
392
}
393
394
ParentLayerCoord Axis::GetPageLength() const {
395
ParentLayerRect pageRect = GetFrameMetrics().GetExpandedScrollableRect() *
396
GetFrameMetrics().GetZoom();
397
return GetRectLength(pageRect);
398
}
399
400
bool Axis::ScaleWillOverscrollBothSides(float aScale) const {
401
const FrameMetrics& metrics = GetFrameMetrics();
402
ParentLayerRect screenCompositionBounds =
403
metrics.GetCompositionBounds() / ParentLayerToParentLayerScale(aScale);
404
return GetRectLength(screenCompositionBounds) - GetPageLength() >
405
COORDINATE_EPSILON;
406
}
407
408
const FrameMetrics& Axis::GetFrameMetrics() const {
409
return mAsyncPanZoomController->GetFrameMetrics();
410
}
411
412
const ScrollMetadata& Axis::GetScrollMetadata() const {
413
return mAsyncPanZoomController->GetScrollMetadata();
414
}
415
416
bool Axis::OverscrollBehaviorAllowsHandoff() const {
417
// Scroll handoff is a "non-local" overscroll behavior, so it's allowed
418
// with "auto" and disallowed with "contain" and "none".
419
return GetOverscrollBehavior() == OverscrollBehavior::Auto;
420
}
421
422
bool Axis::OverscrollBehaviorAllowsOverscrollEffect() const {
423
// An overscroll effect is a "local" overscroll behavior, so it's allowed
424
// with "auto" and "contain" and disallowed with "none".
425
return GetOverscrollBehavior() != OverscrollBehavior::None;
426
}
427
428
AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
429
: Axis(aAsyncPanZoomController) {}
430
431
ParentLayerCoord AxisX::GetPointOffset(const ParentLayerPoint& aPoint) const {
432
return aPoint.x;
433
}
434
435
ParentLayerCoord AxisX::GetRectLength(const ParentLayerRect& aRect) const {
436
return aRect.Width();
437
}
438
439
ParentLayerCoord AxisX::GetRectOffset(const ParentLayerRect& aRect) const {
440
return aRect.X();
441
}
442
443
CSSToParentLayerScale AxisX::GetScaleForAxis(
444
const CSSToParentLayerScale2D& aScale) const {
445
return CSSToParentLayerScale(aScale.xScale);
446
}
447
448
ScreenPoint AxisX::MakePoint(ScreenCoord aCoord) const {
449
return ScreenPoint(aCoord, 0);
450
}
451
452
const char* AxisX::Name() const { return "X"; }
453
454
bool AxisX::CanScrollTo(Side aSide) const {
455
switch (aSide) {
456
case eSideLeft:
457
return CanScroll(-COORDINATE_EPSILON * 2);
458
case eSideRight:
459
return CanScroll(COORDINATE_EPSILON * 2);
460
default:
461
MOZ_ASSERT_UNREACHABLE("aSide is out of valid values");
462
return false;
463
}
464
}
465
466
OverscrollBehavior AxisX::GetOverscrollBehavior() const {
467
return GetScrollMetadata().GetOverscrollBehavior().mBehaviorX;
468
}
469
470
AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController)
471
: Axis(aAsyncPanZoomController) {}
472
473
ParentLayerCoord AxisY::GetPointOffset(const ParentLayerPoint& aPoint) const {
474
return aPoint.y;
475
}
476
477
ParentLayerCoord AxisY::GetRectLength(const ParentLayerRect& aRect) const {
478
return aRect.Height();
479
}
480
481
ParentLayerCoord AxisY::GetRectOffset(const ParentLayerRect& aRect) const {
482
return aRect.Y();
483
}
484
485
CSSToParentLayerScale AxisY::GetScaleForAxis(
486
const CSSToParentLayerScale2D& aScale) const {
487
return CSSToParentLayerScale(aScale.yScale);
488
}
489
490
ScreenPoint AxisY::MakePoint(ScreenCoord aCoord) const {
491
return ScreenPoint(0, aCoord);
492
}
493
494
const char* AxisY::Name() const { return "Y"; }
495
496
bool AxisY::CanScrollTo(Side aSide) const {
497
switch (aSide) {
498
case eSideTop:
499
return CanScroll(-COORDINATE_EPSILON * 2);
500
case eSideBottom:
501
return CanScroll(COORDINATE_EPSILON * 2);
502
default:
503
MOZ_ASSERT_UNREACHABLE("aSide is out of valid values");
504
return false;
505
}
506
}
507
508
OverscrollBehavior AxisY::GetOverscrollBehavior() const {
509
return GetScrollMetadata().GetOverscrollBehavior().mBehaviorY;
510
}
511
512
} // namespace layers
513
} // namespace mozilla