blob: 8b1d7f40d38d7e7f8c1516ba63c2ee28daff9f4b [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/graphics/triple_tap_detector.h"
#include "base/auto_reset.h"
#include "base/logging.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_tree_host.h"
#include "ui/events/event.h"
#include "ui/events/event_processor.h"
#include "ui/events/event_utils.h"
namespace chromecast {
TripleTapDetector::TripleTapDetector(aura::Window* root_window,
TripleTapDetectorDelegate* delegate)
: root_window_(root_window),
delegate_(delegate),
enabled_(false),
triple_tap_state_(TripleTapState::NONE),
tap_count_(0) {
root_window->GetHost()->GetEventSource()->AddEventRewriter(this);
}
TripleTapDetector::~TripleTapDetector() {
root_window_->GetHost()->GetEventSource()->RemoveEventRewriter(this);
}
ui::EventRewriteStatus TripleTapDetector::RewriteEvent(
const ui::Event& event,
std::unique_ptr<ui::Event>* rewritten_event) {
if (!enabled_ || !delegate_ || !event.IsTouchEvent()) {
return ui::EVENT_REWRITE_CONTINUE;
}
const ui::TouchEvent& touch_event = static_cast<const ui::TouchEvent&>(event);
return HandleTripleTapState(touch_event, triple_tap_state_);
}
ui::EventRewriteStatus TripleTapDetector::NextDispatchEvent(
const ui::Event& last_event,
std::unique_ptr<ui::Event>* new_event) {
// Only called if the rewriter has returned EVENT_REWRITE_DISPATCH_ANOTHER,
// and we don't do that.
NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus TripleTapDetector::HandleTripleTapState(
const ui::TouchEvent& event,
TripleTapState state) {
if (event.type() == ui::ET_TOUCH_PRESSED) {
// If a press happened again before the minimum inter-tap interval, cancel
// the triple tap.
if (state == TripleTapState::INTERVAL_WAIT &&
(event.time_stamp() - stashed_events_.back().time_stamp()) <
gesture_detector_config_.double_tap_min_time) {
TripleTapReset();
return ui::EVENT_REWRITE_DISCARD;
}
// Otherwise transition into a touched state.
triple_tap_state_ = TripleTapState::TOUCH;
last_tap_location_ = event.location();
// If this is pressed too long, it should be treated as a long-press, and
// not part of a triple-tap, so set a timer to detect that.
triple_tap_timer_.Start(FROM_HERE,
gesture_detector_config_.longpress_timeout, this,
&TripleTapDetector::OnLongPressIntervalTimerFired);
// Copy the event and then kill the original touch pressed event, we'll
// produce a new one when the timer expires if it turns out this wasn't a
// triple-tap.
stashed_events_.push_back(event);
return ui::EVENT_REWRITE_DISCARD;
}
// Finger was released while we were waiting for one, count it as a tap.
if (event.type() == ui::ET_TOUCH_RELEASED && state == TripleTapState::TOUCH) {
triple_tap_state_ = TripleTapState::INTERVAL_WAIT;
triple_tap_timer_.Start(FROM_HERE,
gesture_detector_config_.double_tap_timeout, this,
&TripleTapDetector::OnTripleTapIntervalTimerFired);
stashed_events_.push_back(event);
tap_count_++;
if (tap_count_ == 3) {
stashed_events_.clear();
TripleTapReset();
delegate_->OnTripleTap(event.location());
}
return ui::EVENT_REWRITE_DISCARD;
}
if (state != TripleTapState::NONE) {
stashed_events_.push_back(event);
return ui::EVENT_REWRITE_DISCARD;
}
return ui::EVENT_REWRITE_CONTINUE;
}
void TripleTapDetector::OnTripleTapIntervalTimerFired() {
TripleTapReset();
}
void TripleTapDetector::OnLongPressIntervalTimerFired() {
TripleTapReset();
}
void TripleTapDetector::TripleTapReset() {
triple_tap_state_ = TripleTapState::NONE;
tap_count_ = 0;
triple_tap_timer_.Stop();
for (auto& event : stashed_events_) {
DispatchEvent(&event);
}
stashed_events_.clear();
}
void TripleTapDetector::DispatchEvent(ui::TouchEvent* event) {
// Turn off triple-tap before re-dispatching to avoid infinite recursion into
// this detector.
base::AutoReset<bool> toggle_enable(&enabled_, false);
DCHECK(!root_window_->GetHost()
->dispatcher()
->OnEventFromSource(event)
.dispatcher_destroyed);
}
} // namespace chromecast