Home | History | Annotate | Download | only in input
      1 // Copyright 2014 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/browser/renderer_host/input/touch_handle.h"
      6 
      7 #include <cmath>
      8 
      9 namespace content {
     10 
     11 namespace {
     12 
     13 // Maximum duration of a fade sequence.
     14 const double kFadeDurationMs = 200;
     15 
     16 // Maximum amount of travel for a fade sequence. This avoids handle "ghosting"
     17 // when the handle is moving rapidly while the fade is active.
     18 const double kFadeDistanceSquared = 20.f * 20.f;
     19 
     20 // Avoid using an empty touch rect, as it may fail the intersection test event
     21 // if it lies within the other rect's bounds.
     22 const float kMinTouchMajorForHitTesting = 1.f;
     23 
     24 // The maximum touch size to use when computing whether a touch point is
     25 // targetting a touch handle. This is necessary for devices that misreport
     26 // touch radii, preventing inappropriately largely touch sizes from completely
     27 // breaking handle dragging behavior.
     28 const float kMaxTouchMajorForHitTesting = 36.f;
     29 
     30 }  // namespace
     31 
     32 // Responsible for rendering a selection or insertion handle for text editing.
     33 TouchHandle::TouchHandle(TouchHandleClient* client,
     34                          TouchHandleOrientation orientation)
     35     : drawable_(client->CreateDrawable()),
     36       client_(client),
     37       orientation_(orientation),
     38       deferred_orientation_(TOUCH_HANDLE_ORIENTATION_UNDEFINED),
     39       alpha_(0.f),
     40       animate_deferred_fade_(false),
     41       enabled_(true),
     42       is_visible_(false),
     43       is_dragging_(false),
     44       is_drag_within_tap_region_(false) {
     45   DCHECK_NE(orientation, TOUCH_HANDLE_ORIENTATION_UNDEFINED);
     46   drawable_->SetEnabled(enabled_);
     47   drawable_->SetOrientation(orientation_);
     48   drawable_->SetAlpha(alpha_);
     49   drawable_->SetVisible(is_visible_);
     50   drawable_->SetFocus(position_);
     51 }
     52 
     53 TouchHandle::~TouchHandle() {
     54 }
     55 
     56 void TouchHandle::SetEnabled(bool enabled) {
     57   if (enabled_ == enabled)
     58     return;
     59   if (!enabled) {
     60     EndDrag();
     61     EndFade();
     62   }
     63   enabled_ = enabled;
     64   drawable_->SetEnabled(enabled);
     65 }
     66 
     67 void TouchHandle::SetVisible(bool visible, AnimationStyle animation_style) {
     68   DCHECK(enabled_);
     69   if (is_visible_ == visible)
     70     return;
     71 
     72   is_visible_ = visible;
     73 
     74   // Handle repositioning may have been deferred while previously invisible.
     75   if (visible)
     76     drawable_->SetFocus(position_);
     77 
     78   bool animate = animation_style != ANIMATION_NONE;
     79   if (is_dragging_) {
     80     animate_deferred_fade_ = animate;
     81     return;
     82   }
     83 
     84   if (animate)
     85     BeginFade();
     86   else
     87     EndFade();
     88 }
     89 
     90 void TouchHandle::SetPosition(const gfx::PointF& position) {
     91   DCHECK(enabled_);
     92   if (position_ == position)
     93     return;
     94   position_ = position;
     95   // Suppress repositioning a handle while invisible or fading out to prevent it
     96   // from "ghosting" outside the visible bounds. The position will be pushed to
     97   // the drawable when the handle regains visibility (see |SetVisible()|).
     98   if (is_visible_)
     99     drawable_->SetFocus(position_);
    100 }
    101 
    102 void TouchHandle::SetOrientation(TouchHandleOrientation orientation) {
    103   DCHECK(enabled_);
    104   DCHECK_NE(orientation, TOUCH_HANDLE_ORIENTATION_UNDEFINED);
    105   if (is_dragging_) {
    106     deferred_orientation_ = orientation;
    107     return;
    108   }
    109   DCHECK_EQ(deferred_orientation_, TOUCH_HANDLE_ORIENTATION_UNDEFINED);
    110   if (orientation_ == orientation)
    111     return;
    112 
    113   orientation_ = orientation;
    114   drawable_->SetOrientation(orientation);
    115 }
    116 
    117 bool TouchHandle::WillHandleTouchEvent(const ui::MotionEvent& event) {
    118   if (!enabled_)
    119     return false;
    120 
    121   if (!is_dragging_ && event.GetAction() != ui::MotionEvent::ACTION_DOWN)
    122     return false;
    123 
    124   switch (event.GetAction()) {
    125     case ui::MotionEvent::ACTION_DOWN: {
    126       if (!is_visible_)
    127         return false;
    128       const float touch_size = std::max(
    129           kMinTouchMajorForHitTesting,
    130           std::min(kMaxTouchMajorForHitTesting, event.GetTouchMajor()));
    131       const gfx::RectF touch_rect(event.GetX() - touch_size * .5f,
    132                                   event.GetY() - touch_size * .5f,
    133                                   touch_size,
    134                                   touch_size);
    135       if (!drawable_->IntersectsWith(touch_rect))
    136         return false;
    137       touch_down_position_ = gfx::PointF(event.GetX(), event.GetY());
    138       touch_to_focus_offset_ = position_ - touch_down_position_;
    139       touch_down_time_ = event.GetEventTime();
    140       BeginDrag();
    141     } break;
    142 
    143     case ui::MotionEvent::ACTION_MOVE: {
    144       gfx::PointF touch_move_position(event.GetX(), event.GetY());
    145       if (is_drag_within_tap_region_) {
    146         const float tap_slop = client_->GetTapSlop();
    147         is_drag_within_tap_region_ =
    148             (touch_move_position - touch_down_position_).LengthSquared() <
    149             tap_slop * tap_slop;
    150       }
    151 
    152       // Note that we signal drag update even if we're inside the tap region,
    153       // as there are cases where characters are narrower than the slop length.
    154       client_->OnHandleDragUpdate(*this,
    155                                   touch_move_position + touch_to_focus_offset_);
    156     } break;
    157 
    158     case ui::MotionEvent::ACTION_UP: {
    159       if (is_drag_within_tap_region_ &&
    160           (event.GetEventTime() - touch_down_time_) <
    161               client_->GetTapTimeout()) {
    162         client_->OnHandleTapped(*this);
    163       }
    164 
    165       EndDrag();
    166     } break;
    167 
    168     case ui::MotionEvent::ACTION_CANCEL:
    169       EndDrag();
    170       break;
    171 
    172     default:
    173       break;
    174   };
    175   return true;
    176 }
    177 
    178 bool TouchHandle::Animate(base::TimeTicks frame_time) {
    179   if (fade_end_time_ == base::TimeTicks())
    180     return false;
    181 
    182   DCHECK(enabled_);
    183 
    184   float time_u =
    185       1.f - (fade_end_time_ - frame_time).InMillisecondsF() / kFadeDurationMs;
    186   float position_u =
    187       (position_ - fade_start_position_).LengthSquared() / kFadeDistanceSquared;
    188   float u = std::max(time_u, position_u);
    189   SetAlpha(is_visible_ ? u : 1.f - u);
    190 
    191   if (u >= 1.f) {
    192     EndFade();
    193     return false;
    194   }
    195 
    196   return true;
    197 }
    198 
    199 void TouchHandle::BeginDrag() {
    200   DCHECK(enabled_);
    201   if (is_dragging_)
    202     return;
    203   EndFade();
    204   is_dragging_ = true;
    205   is_drag_within_tap_region_ = true;
    206   client_->OnHandleDragBegin(*this);
    207 }
    208 
    209 void TouchHandle::EndDrag() {
    210   DCHECK(enabled_);
    211   if (!is_dragging_)
    212     return;
    213 
    214   is_dragging_ = false;
    215   is_drag_within_tap_region_ = false;
    216   client_->OnHandleDragEnd(*this);
    217 
    218   if (deferred_orientation_ != TOUCH_HANDLE_ORIENTATION_UNDEFINED) {
    219     TouchHandleOrientation deferred_orientation = deferred_orientation_;
    220     deferred_orientation_ = TOUCH_HANDLE_ORIENTATION_UNDEFINED;
    221     SetOrientation(deferred_orientation);
    222   }
    223 
    224   if (animate_deferred_fade_) {
    225     BeginFade();
    226   } else {
    227     // As drawable visibility assignment is deferred while dragging, push the
    228     // change by forcing fade completion.
    229     EndFade();
    230   }
    231 }
    232 
    233 void TouchHandle::BeginFade() {
    234   DCHECK(enabled_);
    235   DCHECK(!is_dragging_);
    236   animate_deferred_fade_ = false;
    237   const float target_alpha = is_visible_ ? 1.f : 0.f;
    238   if (target_alpha == alpha_) {
    239     EndFade();
    240     return;
    241   }
    242 
    243   drawable_->SetVisible(true);
    244   fade_end_time_ = base::TimeTicks::Now() +
    245                    base::TimeDelta::FromMillisecondsD(
    246                        kFadeDurationMs * std::abs(target_alpha - alpha_));
    247   fade_start_position_ = position_;
    248   client_->SetNeedsAnimate();
    249 }
    250 
    251 void TouchHandle::EndFade() {
    252   DCHECK(enabled_);
    253   animate_deferred_fade_ = false;
    254   fade_end_time_ = base::TimeTicks();
    255   SetAlpha(is_visible_ ? 1.f : 0.f);
    256   drawable_->SetVisible(is_visible_);
    257 }
    258 
    259 void TouchHandle::SetAlpha(float alpha) {
    260   alpha = std::max(0.f, std::min(1.f, alpha));
    261   if (alpha_ == alpha)
    262     return;
    263   alpha_ = alpha;
    264   drawable_->SetAlpha(alpha);
    265 }
    266 
    267 }  // namespace content
    268