touch_selection_controller.cc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711
  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. #include "ui/touch_selection/touch_selection_controller.h"
  5. #include <memory>
  6. #include "base/auto_reset.h"
  7. #include "base/check_op.h"
  8. #include "base/metrics/histogram_macros.h"
  9. #include "base/metrics/user_metrics.h"
  10. #include "base/notreached.h"
  11. namespace ui {
  12. namespace {
  13. gfx::Vector2dF ComputeLineOffsetFromBottom(const gfx::SelectionBound& bound) {
  14. gfx::Vector2dF line_offset =
  15. gfx::ScaleVector2d(bound.edge_start() - bound.edge_end(), 0.5f);
  16. // An offset of 8 DIPs is sufficient for most line sizes. For small lines,
  17. // using half the line height avoids synthesizing a point on a line above
  18. // (or below) the intended line.
  19. const gfx::Vector2dF kMaxLineOffset(8.f, 8.f);
  20. line_offset.SetToMin(kMaxLineOffset);
  21. line_offset.SetToMax(-kMaxLineOffset);
  22. return line_offset;
  23. }
  24. TouchHandleOrientation ToTouchHandleOrientation(
  25. gfx::SelectionBound::Type type) {
  26. switch (type) {
  27. case gfx::SelectionBound::LEFT:
  28. return TouchHandleOrientation::LEFT;
  29. case gfx::SelectionBound::RIGHT:
  30. return TouchHandleOrientation::RIGHT;
  31. case gfx::SelectionBound::CENTER:
  32. return TouchHandleOrientation::CENTER;
  33. case gfx::SelectionBound::EMPTY:
  34. return TouchHandleOrientation::UNDEFINED;
  35. }
  36. NOTREACHED() << "Invalid selection bound type: " << type;
  37. return TouchHandleOrientation::UNDEFINED;
  38. }
  39. } // namespace
  40. TouchSelectionController::Config::Config()
  41. : max_tap_duration(base::Milliseconds(300)),
  42. tap_slop(8),
  43. enable_adaptive_handle_orientation(false),
  44. enable_longpress_drag_selection(false),
  45. hide_active_handle(false) {}
  46. TouchSelectionController::Config::~Config() {
  47. }
  48. TouchSelectionController::TouchSelectionController(
  49. TouchSelectionControllerClient* client,
  50. const Config& config)
  51. : client_(client),
  52. config_(config),
  53. response_pending_input_event_(INPUT_EVENT_TYPE_NONE),
  54. start_orientation_(TouchHandleOrientation::UNDEFINED),
  55. end_orientation_(TouchHandleOrientation::UNDEFINED),
  56. active_status_(INACTIVE),
  57. temporarily_hidden_(false),
  58. anchor_drag_to_selection_start_(false),
  59. longpress_drag_selector_(this),
  60. selection_handle_dragged_(false),
  61. consume_touch_sequence_(false),
  62. show_touch_handles_(false) {
  63. DCHECK(client_);
  64. }
  65. TouchSelectionController::~TouchSelectionController() {
  66. }
  67. void TouchSelectionController::OnSelectionBoundsChanged(
  68. const gfx::SelectionBound& start,
  69. const gfx::SelectionBound& end) {
  70. if (start == start_ && end_ == end)
  71. return;
  72. if (start.type() == gfx::SelectionBound::EMPTY ||
  73. end.type() == gfx::SelectionBound::EMPTY ||
  74. !show_touch_handles_) {
  75. HideHandles();
  76. return;
  77. }
  78. // Swap the Handles when the start and end selection points cross each other.
  79. if (active_status_ == SELECTION_ACTIVE) {
  80. // Bounds have the same orientation.
  81. bool need_swap = (start_selection_handle_->IsActive() &&
  82. end_.edge_end() == start.edge_end()) ||
  83. (end_selection_handle_->IsActive() &&
  84. end.edge_end() == start_.edge_end());
  85. // Bounds have different orientation.
  86. // Specifically, for writing-mode: vertical-*, selection bounds are
  87. // horizontal.
  88. // When vertical-lr:
  89. // - start bound is from right to left,
  90. // - end bound is from left to right.
  91. // When vertical-rl:
  92. // - start bound is from left to right,
  93. // - end bound is from right to left.
  94. // So when previous start/end bound become current end/start bound,
  95. // edge_start() and edge_end() are swapped. Therefore, we are comparing
  96. // edge_end() with edge_start() here.
  97. need_swap |= (start_selection_handle_->IsActive() &&
  98. end_.edge_end() == start.edge_start()) ||
  99. (end_selection_handle_->IsActive() &&
  100. end.edge_end() == start_.edge_start());
  101. if (need_swap)
  102. start_selection_handle_.swap(end_selection_handle_);
  103. }
  104. // Update |anchor_drag_to_selection_start_| for long press drag selector.
  105. // Since selection can be updated with only one end at a time, if one end is
  106. // equal to the previous value, the updated end is the other.
  107. if (start_ == start)
  108. anchor_drag_to_selection_start_ = false;
  109. else if (end_ == end)
  110. anchor_drag_to_selection_start_ = true;
  111. start_ = start;
  112. end_ = end;
  113. start_orientation_ = ToTouchHandleOrientation(start_.type());
  114. end_orientation_ = ToTouchHandleOrientation(end_.type());
  115. // Ensure that |response_pending_input_event_| is cleared after the method
  116. // completes, while also making its current value available for the duration
  117. // of the call.
  118. InputEventType causal_input_event = response_pending_input_event_;
  119. response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
  120. base::AutoReset<InputEventType> auto_reset_response_pending_input_event(
  121. &response_pending_input_event_, causal_input_event);
  122. if ((start_orientation_ == TouchHandleOrientation::LEFT ||
  123. start_orientation_ == TouchHandleOrientation::RIGHT) &&
  124. (end_orientation_ == TouchHandleOrientation::RIGHT ||
  125. end_orientation_ == TouchHandleOrientation::LEFT)) {
  126. OnSelectionChanged();
  127. return;
  128. }
  129. if (start_orientation_ == TouchHandleOrientation::CENTER) {
  130. OnInsertionChanged();
  131. return;
  132. }
  133. HideHandles();
  134. }
  135. void TouchSelectionController::OnViewportChanged(
  136. const gfx::RectF viewport_rect) {
  137. // Trigger a force update if the viewport is changed, so that
  138. // it triggers a call to change the mirror values if required.
  139. if (viewport_rect_ == viewport_rect)
  140. return;
  141. viewport_rect_ = viewport_rect;
  142. if (active_status_ == INACTIVE)
  143. return;
  144. if (active_status_ == INSERTION_ACTIVE) {
  145. DCHECK(insertion_handle_);
  146. insertion_handle_->SetViewportRect(viewport_rect);
  147. } else if (active_status_ == SELECTION_ACTIVE) {
  148. DCHECK(start_selection_handle_);
  149. DCHECK(end_selection_handle_);
  150. start_selection_handle_->SetViewportRect(viewport_rect);
  151. end_selection_handle_->SetViewportRect(viewport_rect);
  152. }
  153. // Update handle layout after setting the new Viewport size.
  154. UpdateHandleLayoutIfNecessary();
  155. }
  156. bool TouchSelectionController::WillHandleTouchEvent(const MotionEvent& event) {
  157. bool handled = WillHandleTouchEventImpl(event);
  158. // If Action::DOWN is consumed, the rest of touch sequence should be consumed,
  159. // too, regardless of value of |handled|.
  160. // TODO(mohsen): This will consume touch events until the next Action::DOWN.
  161. // Ideally we should consume until the final Action::UP/Action::CANCEL.
  162. // But, apparently, we can't reliably determine the final Action::CANCEL in a
  163. // multi-touch scenario. See https://crbug.com/653212.
  164. if (event.GetAction() == MotionEvent::Action::DOWN)
  165. consume_touch_sequence_ = handled;
  166. return handled || consume_touch_sequence_;
  167. }
  168. void TouchSelectionController::HandleTapEvent(const gfx::PointF& location,
  169. int tap_count) {
  170. if (tap_count > 1) {
  171. response_pending_input_event_ = REPEATED_TAP;
  172. } else {
  173. response_pending_input_event_ = TAP;
  174. }
  175. }
  176. void TouchSelectionController::HandleLongPressEvent(
  177. base::TimeTicks event_time,
  178. const gfx::PointF& location) {
  179. longpress_drag_selector_.OnLongPressEvent(event_time, location);
  180. response_pending_input_event_ = LONG_PRESS;
  181. }
  182. void TouchSelectionController::OnScrollBeginEvent() {
  183. // When there is an active selection, if the user performs a long-press that
  184. // does not trigger a new selection (e.g. a long-press on an empty area) and
  185. // then scrolls, the scroll will move the selection. In this case we will
  186. // think incorrectly that the selection change was due to the long-press and
  187. // will activate touch selection and start long-press drag gesture (see
  188. // ActivateInsertionIfNecessary()). To prevent this, we need to reset the
  189. // state of touch selection controller and long-press drag selector.
  190. // TODO(mohsen): Remove this workaround when we have enough information about
  191. // the cause of a selection change (see https://crbug.com/571897).
  192. longpress_drag_selector_.OnScrollBeginEvent();
  193. response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
  194. }
  195. void TouchSelectionController::HideHandles() {
  196. response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
  197. DeactivateInsertion();
  198. DeactivateSelection();
  199. start_ = gfx::SelectionBound();
  200. end_ = gfx::SelectionBound();
  201. start_orientation_ = ToTouchHandleOrientation(start_.type());
  202. end_orientation_ = ToTouchHandleOrientation(end_.type());
  203. }
  204. void TouchSelectionController::HideAndDisallowShowingAutomatically() {
  205. HideHandles();
  206. show_touch_handles_ = false;
  207. }
  208. void TouchSelectionController::SetTemporarilyHidden(bool hidden) {
  209. if (temporarily_hidden_ == hidden)
  210. return;
  211. temporarily_hidden_ = hidden;
  212. RefreshHandleVisibility();
  213. }
  214. bool TouchSelectionController::Animate(base::TimeTicks frame_time) {
  215. if (active_status_ == INSERTION_ACTIVE)
  216. return insertion_handle_->Animate(frame_time);
  217. if (active_status_ == SELECTION_ACTIVE) {
  218. bool needs_animate = start_selection_handle_->Animate(frame_time);
  219. needs_animate |= end_selection_handle_->Animate(frame_time);
  220. return needs_animate;
  221. }
  222. return false;
  223. }
  224. gfx::RectF TouchSelectionController::GetRectBetweenBounds() const {
  225. // Short-circuit for efficiency.
  226. if (active_status_ == INACTIVE)
  227. return gfx::RectF();
  228. if (start_.visible() && !end_.visible()) {
  229. // This BoundingRect is actually a line unless the selection is rotated.
  230. return gfx::BoundingRect(start_.edge_start(), start_.edge_end());
  231. }
  232. if (end_.visible() && !start_.visible()) {
  233. // This BoundingRect is actually a line unless the selection is rotated.
  234. return gfx::BoundingRect(end_.edge_start(), end_.edge_end());
  235. }
  236. // If both handles are visible, or both are invisible, use the entire rect.
  237. // Specifically, if both handles are on the same horizontal line for
  238. // writing-mode: vertical-*, or both are on the same vertical line for
  239. // writing-mode: horizontal, the entire rect is actually a line unless the
  240. // selection is rotated.
  241. return RectFBetweenSelectionBounds(start_, end_);
  242. }
  243. gfx::RectF TouchSelectionController::GetVisibleRectBetweenBounds() const {
  244. // Short-circuit for efficiency.
  245. if (active_status_ == INACTIVE)
  246. return gfx::RectF();
  247. // Returns the rect of the entire visible selection rect.
  248. return RectFBetweenVisibleSelectionBounds(start_, end_);
  249. }
  250. gfx::RectF TouchSelectionController::GetStartHandleRect() const {
  251. if (active_status_ == INSERTION_ACTIVE)
  252. return insertion_handle_->GetVisibleBounds();
  253. if (active_status_ == SELECTION_ACTIVE)
  254. return start_selection_handle_->GetVisibleBounds();
  255. return gfx::RectF();
  256. }
  257. gfx::RectF TouchSelectionController::GetEndHandleRect() const {
  258. if (active_status_ == INSERTION_ACTIVE)
  259. return insertion_handle_->GetVisibleBounds();
  260. if (active_status_ == SELECTION_ACTIVE)
  261. return end_selection_handle_->GetVisibleBounds();
  262. return gfx::RectF();
  263. }
  264. float TouchSelectionController::GetTouchHandleHeight() const {
  265. if (active_status_ == INSERTION_ACTIVE)
  266. return insertion_handle_->GetVisibleBounds().height();
  267. if (active_status_ == SELECTION_ACTIVE) {
  268. if (GetStartVisible())
  269. return start_selection_handle_->GetVisibleBounds().height();
  270. if (GetEndVisible())
  271. return end_selection_handle_->GetVisibleBounds().height();
  272. }
  273. return 0.f;
  274. }
  275. float TouchSelectionController::GetActiveHandleMiddleY() const {
  276. const gfx::SelectionBound* bound = nullptr;
  277. if (active_status_ == INSERTION_ACTIVE && insertion_handle_->IsActive())
  278. bound = &start_;
  279. if (active_status_ == SELECTION_ACTIVE) {
  280. if (start_selection_handle_->IsActive())
  281. bound = &start_;
  282. else if (end_selection_handle_->IsActive())
  283. bound = &end_;
  284. }
  285. if (!bound)
  286. return 0.f;
  287. return (bound->edge_start().y() + bound->edge_end().y()) / 2.f;
  288. }
  289. const gfx::PointF& TouchSelectionController::GetStartPosition() const {
  290. return start_.edge_end();
  291. }
  292. const gfx::PointF& TouchSelectionController::GetEndPosition() const {
  293. return end_.edge_end();
  294. }
  295. bool TouchSelectionController::WillHandleTouchEventImpl(
  296. const MotionEvent& event) {
  297. show_touch_handles_ = true;
  298. if (config_.enable_longpress_drag_selection &&
  299. longpress_drag_selector_.WillHandleTouchEvent(event)) {
  300. return true;
  301. }
  302. if (active_status_ == INSERTION_ACTIVE) {
  303. DCHECK(insertion_handle_);
  304. return insertion_handle_->WillHandleTouchEvent(event);
  305. }
  306. if (active_status_ == SELECTION_ACTIVE) {
  307. DCHECK(start_selection_handle_);
  308. DCHECK(end_selection_handle_);
  309. if (start_selection_handle_->IsActive())
  310. return start_selection_handle_->WillHandleTouchEvent(event);
  311. if (end_selection_handle_->IsActive())
  312. return end_selection_handle_->WillHandleTouchEvent(event);
  313. const gfx::PointF event_pos(event.GetX(), event.GetY());
  314. if ((event_pos - GetStartPosition()).LengthSquared() <=
  315. (event_pos - GetEndPosition()).LengthSquared()) {
  316. return start_selection_handle_->WillHandleTouchEvent(event);
  317. }
  318. return end_selection_handle_->WillHandleTouchEvent(event);
  319. }
  320. return false;
  321. }
  322. void TouchSelectionController::OnSwipeToMoveCursorBegin() {
  323. if (config_.hide_active_handle) {
  324. // Hide the handle when magnifier is showing since it can confuse the user.
  325. SetTemporarilyHidden(true);
  326. // If the user has typed something, the insertion handle might be hidden.
  327. // Prepare to show touch handles on end.
  328. show_touch_handles_ = true;
  329. }
  330. }
  331. void TouchSelectionController::OnSwipeToMoveCursorEnd() {
  332. // Show the handle at the end if magnifier was showing.
  333. if (config_.hide_active_handle)
  334. SetTemporarilyHidden(false);
  335. }
  336. void TouchSelectionController::OnDragBegin(
  337. const TouchSelectionDraggable& draggable,
  338. const gfx::PointF& drag_position) {
  339. if (&draggable == insertion_handle_.get()) {
  340. DCHECK_EQ(active_status_, INSERTION_ACTIVE);
  341. if (config_.hide_active_handle)
  342. insertion_handle_->SetTransparent();
  343. client_->OnSelectionEvent(INSERTION_HANDLE_DRAG_STARTED);
  344. anchor_drag_to_selection_start_ = true;
  345. return;
  346. }
  347. DCHECK_EQ(active_status_, SELECTION_ACTIVE);
  348. if (&draggable == start_selection_handle_.get()) {
  349. anchor_drag_to_selection_start_ = true;
  350. } else if (&draggable == end_selection_handle_.get()) {
  351. anchor_drag_to_selection_start_ = false;
  352. } else {
  353. DCHECK_EQ(&draggable, &longpress_drag_selector_);
  354. anchor_drag_to_selection_start_ =
  355. (drag_position - GetStartPosition()).LengthSquared() <
  356. (drag_position - GetEndPosition()).LengthSquared();
  357. }
  358. if (config_.hide_active_handle) {
  359. if (&draggable == start_selection_handle_.get()) {
  360. start_selection_handle_->SetTransparent();
  361. } else if (&draggable == end_selection_handle_.get()) {
  362. end_selection_handle_->SetTransparent();
  363. }
  364. }
  365. gfx::PointF base = GetStartPosition() + GetStartLineOffset();
  366. gfx::PointF extent = GetEndPosition() + GetEndLineOffset();
  367. if (anchor_drag_to_selection_start_)
  368. std::swap(base, extent);
  369. // If this is the first drag, log an action to allow user action sequencing.
  370. if (!selection_handle_dragged_)
  371. base::RecordAction(base::UserMetricsAction("SelectionChanged"));
  372. selection_handle_dragged_ = true;
  373. // When moving the handle we want to move only the extent point. Before doing
  374. // so we must make sure that the base point is set correctly.
  375. client_->SelectBetweenCoordinates(base, extent);
  376. client_->OnSelectionEvent(SELECTION_HANDLE_DRAG_STARTED);
  377. }
  378. void TouchSelectionController::OnDragUpdate(
  379. const TouchSelectionDraggable& draggable,
  380. const gfx::PointF& drag_position) {
  381. // As the position corresponds to the bottom left point of the selection
  382. // bound, offset it to some reasonable point on the current line of text.
  383. gfx::Vector2dF line_offset = anchor_drag_to_selection_start_
  384. ? GetStartLineOffset()
  385. : GetEndLineOffset();
  386. gfx::PointF line_position = drag_position + line_offset;
  387. if (&draggable == insertion_handle_.get())
  388. client_->MoveCaret(line_position);
  389. else
  390. client_->MoveRangeSelectionExtent(line_position);
  391. // We use the bound middle point to restrict the ability to move up and
  392. // down, but let user move it more freely in horizontal direction.
  393. if (&draggable == &longpress_drag_selector_) {
  394. // Show magnifier at the selection edge.
  395. const gfx::SelectionBound* bound =
  396. anchor_drag_to_selection_start_ ? &start_ : &end_;
  397. const float x = bound->edge_start().x();
  398. const float y = (bound->edge_start().y() + bound->edge_end().y()) / 2.f;
  399. client_->OnDragUpdate(TouchSelectionDraggable::Type::kLongpress,
  400. gfx::PointF(x, y));
  401. } else {
  402. const float y = GetActiveHandleMiddleY();
  403. client_->OnDragUpdate(TouchSelectionDraggable::Type::kTouchHandle,
  404. gfx::PointF(drag_position.x(), y));
  405. }
  406. }
  407. void TouchSelectionController::OnDragEnd(
  408. const TouchSelectionDraggable& draggable) {
  409. if (&draggable == insertion_handle_.get())
  410. client_->OnSelectionEvent(INSERTION_HANDLE_DRAG_STOPPED);
  411. else
  412. client_->OnSelectionEvent(SELECTION_HANDLE_DRAG_STOPPED);
  413. }
  414. bool TouchSelectionController::IsWithinTapSlop(
  415. const gfx::Vector2dF& delta) const {
  416. return delta.LengthSquared() <
  417. (static_cast<double>(config_.tap_slop) * config_.tap_slop);
  418. }
  419. void TouchSelectionController::OnHandleTapped(const TouchHandle& handle) {
  420. if (insertion_handle_ && &handle == insertion_handle_.get())
  421. client_->OnSelectionEvent(INSERTION_HANDLE_TAPPED);
  422. }
  423. void TouchSelectionController::SetNeedsAnimate() {
  424. client_->SetNeedsAnimate();
  425. }
  426. std::unique_ptr<TouchHandleDrawable>
  427. TouchSelectionController::CreateDrawable() {
  428. return client_->CreateDrawable();
  429. }
  430. base::TimeDelta TouchSelectionController::GetMaxTapDuration() const {
  431. return config_.max_tap_duration;
  432. }
  433. bool TouchSelectionController::IsAdaptiveHandleOrientationEnabled() const {
  434. return config_.enable_adaptive_handle_orientation;
  435. }
  436. void TouchSelectionController::OnLongPressDragActiveStateChanged() {
  437. // The handles should remain hidden for the duration of a longpress drag,
  438. // including the time between a longpress and the start of drag motion.
  439. RefreshHandleVisibility();
  440. }
  441. gfx::PointF TouchSelectionController::GetSelectionStart() const {
  442. return GetStartPosition();
  443. }
  444. gfx::PointF TouchSelectionController::GetSelectionEnd() const {
  445. return GetEndPosition();
  446. }
  447. void TouchSelectionController::OnInsertionChanged() {
  448. DeactivateSelection();
  449. const bool activated = ActivateInsertionIfNecessary();
  450. const TouchHandle::AnimationStyle animation = GetAnimationStyle(!activated);
  451. insertion_handle_->SetFocus(start_.edge_start(), start_.edge_end());
  452. insertion_handle_->SetVisible(GetStartVisible(), animation);
  453. UpdateHandleLayoutIfNecessary();
  454. client_->OnSelectionEvent(activated ? INSERTION_HANDLE_SHOWN
  455. : INSERTION_HANDLE_MOVED);
  456. }
  457. void TouchSelectionController::OnSelectionChanged() {
  458. DeactivateInsertion();
  459. const bool activated = ActivateSelectionIfNecessary();
  460. const TouchHandle::AnimationStyle animation = GetAnimationStyle(!activated);
  461. start_selection_handle_->SetFocus(start_.edge_start(), start_.edge_end());
  462. end_selection_handle_->SetFocus(end_.edge_start(), end_.edge_end());
  463. start_selection_handle_->SetOrientation(start_orientation_);
  464. end_selection_handle_->SetOrientation(end_orientation_);
  465. start_selection_handle_->SetVisible(GetStartVisible(), animation);
  466. end_selection_handle_->SetVisible(GetEndVisible(), animation);
  467. UpdateHandleLayoutIfNecessary();
  468. client_->OnSelectionEvent(activated ? SELECTION_HANDLES_SHOWN
  469. : SELECTION_HANDLES_MOVED);
  470. }
  471. bool TouchSelectionController::ActivateInsertionIfNecessary() {
  472. DCHECK_NE(SELECTION_ACTIVE, active_status_);
  473. if (!insertion_handle_) {
  474. insertion_handle_ = std::make_unique<TouchHandle>(
  475. this, TouchHandleOrientation::CENTER, viewport_rect_);
  476. }
  477. if (active_status_ == INACTIVE || response_pending_input_event_ == TAP ||
  478. response_pending_input_event_ == LONG_PRESS) {
  479. active_status_ = INSERTION_ACTIVE;
  480. insertion_handle_->SetEnabled(true);
  481. insertion_handle_->SetViewportRect(viewport_rect_);
  482. response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
  483. return true;
  484. }
  485. return false;
  486. }
  487. void TouchSelectionController::DeactivateInsertion() {
  488. if (active_status_ != INSERTION_ACTIVE)
  489. return;
  490. DCHECK(insertion_handle_);
  491. active_status_ = INACTIVE;
  492. insertion_handle_->SetEnabled(false);
  493. client_->OnSelectionEvent(INSERTION_HANDLE_CLEARED);
  494. }
  495. bool TouchSelectionController::ActivateSelectionIfNecessary() {
  496. DCHECK_NE(INSERTION_ACTIVE, active_status_);
  497. if (!start_selection_handle_) {
  498. start_selection_handle_ =
  499. std::make_unique<TouchHandle>(this, start_orientation_, viewport_rect_);
  500. } else {
  501. start_selection_handle_->SetEnabled(true);
  502. start_selection_handle_->SetViewportRect(viewport_rect_);
  503. }
  504. if (!end_selection_handle_) {
  505. end_selection_handle_ =
  506. std::make_unique<TouchHandle>(this, end_orientation_, viewport_rect_);
  507. } else {
  508. end_selection_handle_->SetEnabled(true);
  509. end_selection_handle_->SetViewportRect(viewport_rect_);
  510. }
  511. // As a long press received while a selection is already active may trigger
  512. // an entirely new selection, notify the client but avoid sending an
  513. // intervening SELECTION_HANDLES_CLEARED update to avoid unnecessary state
  514. // changes.
  515. if (active_status_ == INACTIVE ||
  516. response_pending_input_event_ == LONG_PRESS ||
  517. response_pending_input_event_ == REPEATED_TAP) {
  518. if (active_status_ == SELECTION_ACTIVE) {
  519. // The active selection session finishes with the start of the new one.
  520. LogSelectionEnd();
  521. }
  522. active_status_ = SELECTION_ACTIVE;
  523. selection_handle_dragged_ = false;
  524. selection_start_time_ = base::TimeTicks::Now();
  525. response_pending_input_event_ = INPUT_EVENT_TYPE_NONE;
  526. longpress_drag_selector_.OnSelectionActivated();
  527. return true;
  528. }
  529. return false;
  530. }
  531. void TouchSelectionController::DeactivateSelection() {
  532. if (active_status_ != SELECTION_ACTIVE)
  533. return;
  534. DCHECK(start_selection_handle_);
  535. DCHECK(end_selection_handle_);
  536. LogSelectionEnd();
  537. longpress_drag_selector_.OnSelectionDeactivated();
  538. start_selection_handle_->SetEnabled(false);
  539. end_selection_handle_->SetEnabled(false);
  540. active_status_ = INACTIVE;
  541. client_->OnSelectionEvent(SELECTION_HANDLES_CLEARED);
  542. }
  543. void TouchSelectionController::UpdateHandleLayoutIfNecessary() {
  544. if (active_status_ == INSERTION_ACTIVE) {
  545. DCHECK(insertion_handle_);
  546. insertion_handle_->UpdateHandleLayout();
  547. } else if (active_status_ == SELECTION_ACTIVE) {
  548. DCHECK(start_selection_handle_);
  549. DCHECK(end_selection_handle_);
  550. start_selection_handle_->UpdateHandleLayout();
  551. end_selection_handle_->UpdateHandleLayout();
  552. }
  553. }
  554. void TouchSelectionController::RefreshHandleVisibility() {
  555. TouchHandle::AnimationStyle animation_style = GetAnimationStyle(true);
  556. if (active_status_ == SELECTION_ACTIVE) {
  557. start_selection_handle_->SetVisible(GetStartVisible(), animation_style);
  558. end_selection_handle_->SetVisible(GetEndVisible(), animation_style);
  559. }
  560. if (active_status_ == INSERTION_ACTIVE)
  561. insertion_handle_->SetVisible(GetStartVisible(), animation_style);
  562. // Update handle layout if handle visibility is explicitly changed.
  563. UpdateHandleLayoutIfNecessary();
  564. }
  565. gfx::Vector2dF TouchSelectionController::GetStartLineOffset() const {
  566. return ComputeLineOffsetFromBottom(start_);
  567. }
  568. gfx::Vector2dF TouchSelectionController::GetEndLineOffset() const {
  569. return ComputeLineOffsetFromBottom(end_);
  570. }
  571. bool TouchSelectionController::GetStartVisible() const {
  572. if (!start_.visible())
  573. return false;
  574. return !temporarily_hidden_ && !longpress_drag_selector_.IsActive();
  575. }
  576. bool TouchSelectionController::GetEndVisible() const {
  577. if (!end_.visible())
  578. return false;
  579. return !temporarily_hidden_ && !longpress_drag_selector_.IsActive();
  580. }
  581. TouchHandle::AnimationStyle TouchSelectionController::GetAnimationStyle(
  582. bool was_active) const {
  583. return was_active && client_->SupportsAnimation()
  584. ? TouchHandle::ANIMATION_SMOOTH
  585. : TouchHandle::ANIMATION_NONE;
  586. }
  587. void TouchSelectionController::LogSelectionEnd() {
  588. // TODO(mfomitchev): Once we are able to tell the difference between
  589. // 'successful' and 'unsuccessful' selections - log
  590. // Event.TouchSelection.Duration instead and get rid of
  591. // Event.TouchSelectionD.WasDraggeduration.
  592. if (selection_handle_dragged_) {
  593. base::TimeDelta duration = base::TimeTicks::Now() - selection_start_time_;
  594. UMA_HISTOGRAM_CUSTOM_TIMES("Event.TouchSelection.WasDraggedDuration",
  595. duration, base::Milliseconds(500),
  596. base::Seconds(60), 60);
  597. }
  598. }
  599. } // namespace ui