// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/public/cpp/pagination/pagination_controller.h"
#include "ash/public/cpp/pagination/pagination_model.h"
#include "base/check.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
namespace ash {
namespace {
// Constants for dealing with scroll events.
const int kMinScrollToSwitchPage = 10;
const int kMinHorizVelocityToSwitchPage = 800;
const double kFinishTransitionThreshold = 0.33;
} // namespace
PaginationController::PaginationController(PaginationModel* model,
ScrollAxis scroll_axis,
const RecordMetrics& record_metrics)
: pagination_model_(model),
scroll_axis_(scroll_axis),
record_metrics_(record_metrics) {
DCHECK(pagination_model_);
DCHECK(record_metrics_);
}
PaginationController::~PaginationController() = default;
bool PaginationController::OnScroll(const gfx::Vector2dF& offset,
ui::EventType type) {
float offset_magnitude;
if (scroll_axis_ == SCROLL_AXIS_HORIZONTAL) {
// If the view scrolls horizontally, both horizontal and vertical scroll
// events are valid (since most mouse wheels only have vertical scrolling).
offset_magnitude =
abs(offset.x()) > abs(offset.y()) ? offset.x() : offset.y();
} else {
// If the view scrolls vertically, only vertical scroll events are valid.
offset_magnitude = offset.y();
}
// Do not scroll on very small events.
// TODO(calamity): This should only apply on touchpad scroll but touchpad
// events are coming in as mousewheel events. See https://crbug.com/594264.
if (abs(offset_magnitude) > kMinScrollToSwitchPage &&
!pagination_model_->has_transition()) {
const int delta = offset_magnitude > 0 ? -1 : 1;
SelectPageAndRecordMetric(delta, type);
return true;
}
return false;
}
bool PaginationController::OnGestureEvent(const ui::GestureEvent& event,
const gfx::Rect& bounds) {
const ui::GestureEventDetails& details = event.details();
switch (event.type()) {
case ui::EventType::kGestureScrollBegin: {
float scroll = scroll_axis_ == SCROLL_AXIS_HORIZONTAL
? details.scroll_x_hint()
: details.scroll_y_hint();
return StartDrag(scroll);
}
case ui::EventType::kGestureScrollUpdate: {
float scroll = scroll_axis_ == SCROLL_AXIS_HORIZONTAL
? details.scroll_x()
: details.scroll_y();
return UpdateDrag(scroll, bounds);
}
case ui::EventType::kGestureScrollEnd: {
return EndDrag(event);
}
case ui::EventType::kScrollFlingStart: {
float velocity = scroll_axis_ == SCROLL_AXIS_HORIZONTAL
? details.velocity_x()
: details.velocity_y();
if (fabs(velocity) > kMinHorizVelocityToSwitchPage) {
pagination_model_->EndScroll(true);
const int delta = velocity < 0 ? 1 : -1;
SelectPageAndRecordMetric(delta, event.type());
} else {
// If the gesture ends in a fling below page switch velocity threshold,
// decide whether to switch page depending on the scroll progress (if
// gesture ends with a slow fling after the user has dragged the page
// beyond page switch drag threshold, switch the page).
EndDrag(event);
}
return true;
}
default:
return false;
}
}
void PaginationController::StartMouseDrag(const gfx::Vector2dF& offset) {
float scroll =
scroll_axis_ == SCROLL_AXIS_HORIZONTAL ? offset.x() : offset.y();
StartDrag(scroll);
}
void PaginationController::UpdateMouseDrag(const gfx::Vector2dF& offset,
const gfx::Rect& bounds) {
float scroll =
scroll_axis_ == SCROLL_AXIS_HORIZONTAL ? offset.x() : offset.y();
UpdateDrag(scroll, bounds);
}
void PaginationController::EndMouseDrag(const ui::MouseEvent& event) {
EndDrag(event);
}
bool PaginationController::StartDrag(float scroll) {
if (scroll == 0)
return false;
pagination_model_->StartScroll();
return true;
}
bool PaginationController::UpdateDrag(float scroll, const gfx::Rect& bounds) {
if (!pagination_model_->IsValidPageRelative(scroll < 0 ? 1 : -1) &&
!pagination_model_->has_transition()) {
// scroll > 0 means moving contents right or down. That is,
// transitioning to the previous page. If scrolling to an invalid page,
// ignore the event until movement continues in a valid direction.
return true;
}
int width =
scroll_axis_ == SCROLL_AXIS_HORIZONTAL ? bounds.width() : bounds.height();
pagination_model_->UpdateScroll(scroll / width);
return true;
}
bool PaginationController::EndDrag(const ui::LocatedEvent& event) {
const bool cancel_transition =
pagination_model_->transition().progress < kFinishTransitionThreshold;
pagination_model_->EndScroll(cancel_transition);
if (!cancel_transition)
record_metrics_.Run(event.type());
return true;
}
void PaginationController::SelectPageAndRecordMetric(int delta,
ui::EventType type) {
if (pagination_model_->IsValidPageRelative(delta)) {
record_metrics_.Run(type);
}
pagination_model_->SelectPageRelative(delta, true);
}
} // namespace ash