Minor visual improvements to text selection UI
[chromium-blink-merge.git] / ui / views / touchui / touch_selection_controller_impl.cc
blobfa036c70fe02c98c11f3533afcd066aa32a2023d
1 // Copyright (c) 2013 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.
5 #include "ui/views/touchui/touch_selection_controller_impl.h"
7 #include "base/time/time.h"
8 #include "ui/aura/client/cursor_client.h"
9 #include "ui/aura/env.h"
10 #include "ui/aura/window.h"
11 #include "ui/base/resource/resource_bundle.h"
12 #include "ui/gfx/canvas.h"
13 #include "ui/gfx/image/image.h"
14 #include "ui/gfx/path.h"
15 #include "ui/gfx/rect.h"
16 #include "ui/gfx/screen.h"
17 #include "ui/gfx/size.h"
18 #include "ui/resources/grit/ui_resources.h"
19 #include "ui/strings/grit/ui_strings.h"
20 #include "ui/views/widget/widget.h"
21 #include "ui/wm/core/coordinate_conversion.h"
22 #include "ui/wm/core/masked_window_targeter.h"
24 namespace {
26 // Constants defining the visual attributes of selection handles
28 // The distance by which a handle image is offset from the bottom of the
29 // selection/text baseline.
30 const int kSelectionHandleVerticalVisualOffset = 2;
32 // When a handle is dragged, the drag position reported to the client view is
33 // offset vertically to represent the cursor position. This constant specifies
34 // the offset in pixels above the bottom of the selection (see pic below). This
35 // is required because say if this is zero, that means the drag position we
36 // report is right on the text baseline. In that case, a vertical movement of
37 // even one pixel will make the handle jump to the line below it. So when the
38 // user just starts dragging, the handle will jump to the next line if the user
39 // makes any vertical movement. So we have this non-zero offset to prevent this
40 // jumping.
42 // Editing handle widget showing the padding and difference between the position
43 // of the ET_GESTURE_SCROLL_UPDATE event and the drag position reported to the
44 // client:
45 // ___________
46 // Selection Highlight --->_____|__|<-|---- Drag position reported to client
47 // _ | O |
48 // Vertical Padding __| | <-|---- ET_GESTURE_SCROLL_UPDATE position
49 // |_ |_____|<--- Editing handle widget
51 // | |
52 // T
53 // Horizontal Padding
55 const int kSelectionHandleVerticalDragOffset = 5;
57 // Padding around the selection handle defining the area that will be included
58 // in the touch target to make dragging the handle easier (see pic above).
59 const int kSelectionHandleHorizPadding = 10;
60 const int kSelectionHandleVertPadding = 20;
62 const int kContextMenuTimoutMs = 200;
64 const int kSelectionHandleQuickFadeDurationMs = 50;
66 // Minimum height for selection handle bar. If the bar height is going to be
67 // less than this value, handle will not be shown.
68 const int kSelectionHandleBarMinHeight = 5;
69 // Maximum amount that selection handle bar can stick out of client view's
70 // boundaries.
71 const int kSelectionHandleBarBottomAllowance = 3;
73 // Creates a widget to host SelectionHandleView.
74 views::Widget* CreateTouchSelectionPopupWidget(
75 gfx::NativeView context,
76 views::WidgetDelegate* widget_delegate) {
77 views::Widget* widget = new views::Widget;
78 views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP);
79 params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
80 params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE;
81 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
82 params.parent = context;
83 params.delegate = widget_delegate;
84 widget->Init(params);
85 return widget;
88 gfx::Image* GetCenterHandleImage() {
89 static gfx::Image* handle_image = nullptr;
90 if (!handle_image) {
91 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
92 IDR_TEXT_SELECTION_HANDLE_CENTER);
94 return handle_image;
97 gfx::Image* GetLeftHandleImage() {
98 static gfx::Image* handle_image = nullptr;
99 if (!handle_image) {
100 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
101 IDR_TEXT_SELECTION_HANDLE_LEFT);
103 return handle_image;
106 gfx::Image* GetRightHandleImage() {
107 static gfx::Image* handle_image = nullptr;
108 if (!handle_image) {
109 handle_image = &ui::ResourceBundle::GetSharedInstance().GetImageNamed(
110 IDR_TEXT_SELECTION_HANDLE_RIGHT);
112 return handle_image;
115 // Return the appropriate handle image based on the bound's type
116 gfx::Image* GetHandleImage(ui::SelectionBound::Type bound_type) {
117 switch(bound_type) {
118 case ui::SelectionBound::LEFT:
119 return GetLeftHandleImage();
120 case ui::SelectionBound::CENTER:
121 return GetCenterHandleImage();
122 case ui::SelectionBound::RIGHT:
123 return GetRightHandleImage();
124 default:
125 NOTREACHED() << "Invalid touch handle bound type.";
126 return nullptr;
130 // Calculates the bounds of the widget containing the selection handle based
131 // on the SelectionBound's type and location
132 gfx::Rect GetSelectionWidgetBounds(const ui::SelectionBound& bound) {
133 gfx::Size image_size = GetHandleImage(bound.type())->Size();
134 int widget_width = image_size.width() + 2 * kSelectionHandleHorizPadding;
135 int widget_height = bound.GetHeight() + image_size.height() +
136 kSelectionHandleVerticalVisualOffset +
137 kSelectionHandleVertPadding;
138 // Due to the shape of the handle images, the widget is aligned differently to
139 // the selection bound depending on the type of the bound.
140 int widget_left = 0;
141 switch (bound.type()) {
142 case ui::SelectionBound::LEFT:
143 widget_left = bound.edge_top_rounded().x() - image_size.width() -
144 kSelectionHandleHorizPadding;
145 break;
146 case ui::SelectionBound::RIGHT:
147 widget_left = bound.edge_top_rounded().x() - kSelectionHandleHorizPadding;
148 break;
149 case ui::SelectionBound::CENTER:
150 widget_left = bound.edge_top_rounded().x() - widget_width / 2;
151 break;
152 default:
153 NOTREACHED() << "Undefined bound type.";
154 break;
156 return gfx::Rect(
157 widget_left, bound.edge_top_rounded().y(), widget_width, widget_height);
160 gfx::Size GetMaxHandleImageSize() {
161 gfx::Rect center_rect = gfx::Rect(GetCenterHandleImage()->Size());
162 gfx::Rect left_rect = gfx::Rect(GetLeftHandleImage()->Size());
163 gfx::Rect right_rect = gfx::Rect(GetRightHandleImage()->Size());
164 gfx::Rect union_rect = center_rect;
165 union_rect.Union(left_rect);
166 union_rect.Union(right_rect);
167 return union_rect.size();
170 // Convenience methods to convert a |bound| from screen to the |client|'s
171 // coordinate system and vice versa.
172 // Note that this is not quite correct because it does not take into account
173 // transforms such as rotation and scaling. This should be in TouchEditable.
174 // TODO(varunjain): Fix this.
175 ui::SelectionBound ConvertFromScreen(ui::TouchEditable* client,
176 const ui::SelectionBound& bound) {
177 ui::SelectionBound result = bound;
178 gfx::Point edge_bottom = bound.edge_bottom_rounded();
179 gfx::Point edge_top = bound.edge_top_rounded();
180 client->ConvertPointFromScreen(&edge_bottom);
181 client->ConvertPointFromScreen(&edge_top);
182 result.SetEdge(edge_top, edge_bottom);
183 return result;
186 ui::SelectionBound ConvertToScreen(ui::TouchEditable* client,
187 const ui::SelectionBound& bound) {
188 ui::SelectionBound result = bound;
189 gfx::Point edge_bottom = bound.edge_bottom_rounded();
190 gfx::Point edge_top = bound.edge_top_rounded();
191 client->ConvertPointToScreen(&edge_bottom);
192 client->ConvertPointToScreen(&edge_top);
193 result.SetEdge(edge_top, edge_bottom);
194 return result;
197 gfx::Rect BoundToRect(const ui::SelectionBound& bound) {
198 return gfx::BoundingRect(bound.edge_top_rounded(),
199 bound.edge_bottom_rounded());
202 } // namespace
204 namespace views {
206 typedef TouchSelectionControllerImpl::EditingHandleView EditingHandleView;
208 class TouchHandleWindowTargeter : public wm::MaskedWindowTargeter {
209 public:
210 TouchHandleWindowTargeter(aura::Window* window,
211 EditingHandleView* handle_view);
213 ~TouchHandleWindowTargeter() override {}
215 private:
216 // wm::MaskedWindowTargeter:
217 bool GetHitTestMask(aura::Window* window, gfx::Path* mask) const override;
219 EditingHandleView* handle_view_;
221 DISALLOW_COPY_AND_ASSIGN(TouchHandleWindowTargeter);
224 // A View that displays the text selection handle.
225 class TouchSelectionControllerImpl::EditingHandleView
226 : public views::WidgetDelegateView {
227 public:
228 EditingHandleView(TouchSelectionControllerImpl* controller,
229 gfx::NativeView context,
230 bool is_cursor_handle)
231 : controller_(controller),
232 image_(GetCenterHandleImage()),
233 is_cursor_handle_(is_cursor_handle),
234 draw_invisible_(false) {
235 widget_.reset(CreateTouchSelectionPopupWidget(context, this));
236 widget_->SetContentsView(this);
238 aura::Window* window = widget_->GetNativeWindow();
239 window->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
240 new TouchHandleWindowTargeter(window, this)));
242 // We are owned by the TouchSelectionController.
243 set_owned_by_client();
246 ~EditingHandleView() override { SetWidgetVisible(false, false); }
248 // Overridden from views::WidgetDelegateView:
249 bool WidgetHasHitTestMask() const override { return true; }
251 void GetWidgetHitTestMask(gfx::Path* mask) const override {
252 gfx::Size image_size = image_->Size();
253 mask->addRect(
254 SkIntToScalar(0),
255 SkIntToScalar(selection_bound_.GetHeight() +
256 kSelectionHandleVerticalVisualOffset),
257 SkIntToScalar(image_size.width()) + 2 * kSelectionHandleHorizPadding,
258 SkIntToScalar(selection_bound_.GetHeight() +
259 kSelectionHandleVerticalVisualOffset +
260 image_size.height() + kSelectionHandleVertPadding));
263 void DeleteDelegate() override {
264 // We are owned and deleted by TouchSelectionController.
267 // Overridden from views::View:
268 void OnPaint(gfx::Canvas* canvas) override {
269 if (draw_invisible_)
270 return;
272 // Draw the handle image.
273 canvas->DrawImageInt(
274 *image_->ToImageSkia(),
275 kSelectionHandleHorizPadding,
276 selection_bound_.GetHeight() + kSelectionHandleVerticalVisualOffset);
279 void OnGestureEvent(ui::GestureEvent* event) override {
280 event->SetHandled();
281 switch (event->type()) {
282 case ui::ET_GESTURE_SCROLL_BEGIN: {
283 widget_->SetCapture(this);
284 controller_->SetDraggingHandle(this);
285 // Distance from the point which is |kSelectionHandleVerticalDragOffset|
286 // pixels above the bottom of the selection bound edge to the event
287 // location (aka the touch-drag point).
288 drag_offset_ = selection_bound_.edge_bottom_rounded() -
289 gfx::Vector2d(0, kSelectionHandleVerticalDragOffset) -
290 event->location();
291 break;
293 case ui::ET_GESTURE_SCROLL_UPDATE: {
294 controller_->SelectionHandleDragged(event->location() + drag_offset_);
295 break;
297 case ui::ET_GESTURE_SCROLL_END:
298 case ui::ET_SCROLL_FLING_START:
299 widget_->ReleaseCapture();
300 controller_->SetDraggingHandle(nullptr);
301 break;
302 default:
303 break;
307 gfx::Size GetPreferredSize() const override {
308 return GetSelectionWidgetBounds(selection_bound_).size();
311 bool IsWidgetVisible() const {
312 return widget_->IsVisible();
315 void SetWidgetVisible(bool visible, bool quick) {
316 if (widget_->IsVisible() == visible)
317 return;
318 widget_->SetVisibilityAnimationDuration(
319 base::TimeDelta::FromMilliseconds(
320 quick ? kSelectionHandleQuickFadeDurationMs : 0));
321 if (visible)
322 widget_->Show();
323 else
324 widget_->Hide();
327 void SetBoundInScreen(const ui::SelectionBound& bound) {
328 bool update_bound_type = false;
329 // Cursor handle should always have the bound type CENTER
330 DCHECK(!is_cursor_handle_ || bound.type() == ui::SelectionBound::CENTER);
332 if (bound.type() != selection_bound_.type()) {
333 // Unless this is a cursor handle, do not set the type to CENTER -
334 // selection handles corresponding to a selection should always use left
335 // or right handle image. If selection handles are dragged to be located
336 // at the same spot, the |bound|'s type here will be CENTER for both of
337 // them. In this case do not update the type of the |selection_bound_|.
338 if (bound.type() != ui::SelectionBound::CENTER || is_cursor_handle_)
339 update_bound_type = true;
341 if (update_bound_type) {
342 selection_bound_.set_type(bound.type());
343 image_ = GetHandleImage(bound.type());
344 SchedulePaint();
346 selection_bound_.SetEdge(bound.edge_top(), bound.edge_bottom());
348 widget_->SetBounds(GetSelectionWidgetBounds(selection_bound_));
350 aura::Window* window = widget_->GetNativeView();
351 gfx::Point edge_top = selection_bound_.edge_top_rounded();
352 gfx::Point edge_bottom = selection_bound_.edge_bottom_rounded();
353 wm::ConvertPointFromScreen(window, &edge_top);
354 wm::ConvertPointFromScreen(window, &edge_bottom);
355 selection_bound_.SetEdge(edge_top, edge_bottom);
358 void SetDrawInvisible(bool draw_invisible) {
359 if (draw_invisible_ == draw_invisible)
360 return;
361 draw_invisible_ = draw_invisible;
362 SchedulePaint();
365 private:
366 scoped_ptr<Widget> widget_;
367 TouchSelectionControllerImpl* controller_;
369 // In local coordinates
370 ui::SelectionBound selection_bound_;
371 gfx::Image* image_;
373 // If true, this is a handle corresponding to the single cursor, otherwise it
374 // is a handle corresponding to one of the two selection bounds.
375 bool is_cursor_handle_;
377 // Offset applied to the scroll events location when calling
378 // TouchSelectionControllerImpl::SelectionHandleDragged while dragging the
379 // handle.
380 gfx::Vector2d drag_offset_;
382 // If set to true, the handle will not draw anything, hence providing an empty
383 // widget. We need this because we may want to stop showing the handle while
384 // it is being dragged. Since it is being dragged, we cannot destroy the
385 // handle.
386 bool draw_invisible_;
388 DISALLOW_COPY_AND_ASSIGN(EditingHandleView);
391 TouchHandleWindowTargeter::TouchHandleWindowTargeter(
392 aura::Window* window,
393 EditingHandleView* handle_view)
394 : wm::MaskedWindowTargeter(window),
395 handle_view_(handle_view) {
398 bool TouchHandleWindowTargeter::GetHitTestMask(aura::Window* window,
399 gfx::Path* mask) const {
400 handle_view_->GetWidgetHitTestMask(mask);
401 return true;
404 TouchSelectionControllerImpl::TouchSelectionControllerImpl(
405 ui::TouchEditable* client_view)
406 : client_view_(client_view),
407 client_widget_(nullptr),
408 selection_handle_1_(new EditingHandleView(this,
409 client_view->GetNativeView(),
410 false)),
411 selection_handle_2_(new EditingHandleView(this,
412 client_view->GetNativeView(),
413 false)),
414 cursor_handle_(new EditingHandleView(this,
415 client_view->GetNativeView(),
416 true)),
417 context_menu_(nullptr),
418 dragging_handle_(nullptr) {
419 aura::Window* client_window = client_view_->GetNativeView();
420 client_window->AddObserver(this);
421 client_widget_ = Widget::GetTopLevelWidgetForNativeView(client_window);
422 if (client_widget_)
423 client_widget_->AddObserver(this);
424 aura::Env::GetInstance()->AddPreTargetHandler(this);
427 TouchSelectionControllerImpl::~TouchSelectionControllerImpl() {
428 HideContextMenu();
429 aura::Env::GetInstance()->RemovePreTargetHandler(this);
430 if (client_widget_)
431 client_widget_->RemoveObserver(this);
432 client_view_->GetNativeView()->RemoveObserver(this);
435 void TouchSelectionControllerImpl::SelectionChanged() {
436 ui::SelectionBound anchor, focus;
437 client_view_->GetSelectionEndPoints(&anchor, &focus);
438 ui::SelectionBound screen_bound_anchor =
439 ConvertToScreen(client_view_, anchor);
440 ui::SelectionBound screen_bound_focus = ConvertToScreen(client_view_, focus);
441 gfx::Rect client_bounds = client_view_->GetBounds();
442 if (anchor.edge_top().y() < client_bounds.y()) {
443 gfx::Point anchor_edge_top = anchor.edge_top_rounded();
444 anchor_edge_top.set_y(client_bounds.y());
445 anchor.SetEdgeTop(anchor_edge_top);
447 if (focus.edge_top().y() < client_bounds.y()) {
448 gfx::Point focus_edge_top = focus.edge_top_rounded();
449 focus_edge_top.set_y(client_bounds.y());
450 focus.SetEdgeTop(focus_edge_top);
452 ui::SelectionBound screen_bound_anchor_clipped =
453 ConvertToScreen(client_view_, anchor);
454 ui::SelectionBound screen_bound_focus_clipped =
455 ConvertToScreen(client_view_, focus);
456 if (screen_bound_anchor_clipped == selection_bound_1_clipped_ &&
457 screen_bound_focus_clipped == selection_bound_2_clipped_)
458 return;
460 selection_bound_1_ = screen_bound_anchor;
461 selection_bound_2_ = screen_bound_focus;
462 selection_bound_1_clipped_ = screen_bound_anchor_clipped;
463 selection_bound_2_clipped_ = screen_bound_focus_clipped;
465 if (client_view_->DrawsHandles()) {
466 UpdateContextMenu();
467 return;
470 if (dragging_handle_) {
471 // We need to reposition only the selection handle that is being dragged.
472 // The other handle stays the same. Also, the selection handle being dragged
473 // will always be at the end of selection, while the other handle will be at
474 // the start.
475 // If the new location of this handle is out of client view, its widget
476 // should not get hidden, since it should still receive touch events.
477 // Hence, we are not using |SetHandleBound()| method here.
478 dragging_handle_->SetBoundInScreen(screen_bound_focus_clipped);
480 // Temporary fix for selection handle going outside a window. On a webpage,
481 // the page should scroll if the selection handle is dragged outside the
482 // window. That does not happen currently. So we just hide the handle for
483 // now.
484 // TODO(varunjain): Fix this: crbug.com/269003
485 dragging_handle_->SetDrawInvisible(!ShouldShowHandleFor(focus));
487 if (dragging_handle_ != cursor_handle_.get()) {
488 // The non-dragging-handle might have recently become visible.
489 EditingHandleView* non_dragging_handle = selection_handle_1_.get();
490 if (dragging_handle_ == selection_handle_1_) {
491 non_dragging_handle = selection_handle_2_.get();
492 // if handle 1 is being dragged, it is corresponding to the end of
493 // selection and the other handle to the start of selection.
494 selection_bound_1_ = screen_bound_focus;
495 selection_bound_2_ = screen_bound_anchor;
496 selection_bound_1_clipped_ = screen_bound_focus_clipped;
497 selection_bound_2_clipped_ = screen_bound_anchor_clipped;
499 SetHandleBound(non_dragging_handle, anchor, screen_bound_anchor_clipped);
501 } else {
502 UpdateContextMenu();
504 // Check if there is any selection at all.
505 if (screen_bound_anchor.edge_top() == screen_bound_focus.edge_top() &&
506 screen_bound_anchor.edge_bottom() == screen_bound_focus.edge_bottom()) {
507 selection_handle_1_->SetWidgetVisible(false, false);
508 selection_handle_2_->SetWidgetVisible(false, false);
509 SetHandleBound(cursor_handle_.get(), anchor, screen_bound_anchor_clipped);
510 return;
513 cursor_handle_->SetWidgetVisible(false, false);
514 SetHandleBound(
515 selection_handle_1_.get(), anchor, screen_bound_anchor_clipped);
516 SetHandleBound(
517 selection_handle_2_.get(), focus, screen_bound_focus_clipped);
521 bool TouchSelectionControllerImpl::IsHandleDragInProgress() {
522 return !!dragging_handle_;
525 void TouchSelectionControllerImpl::HideHandles(bool quick) {
526 selection_handle_1_->SetWidgetVisible(false, quick);
527 selection_handle_2_->SetWidgetVisible(false, quick);
528 cursor_handle_->SetWidgetVisible(false, quick);
531 void TouchSelectionControllerImpl::SetDraggingHandle(
532 EditingHandleView* handle) {
533 dragging_handle_ = handle;
534 if (dragging_handle_)
535 HideContextMenu();
536 else
537 StartContextMenuTimer();
540 void TouchSelectionControllerImpl::SelectionHandleDragged(
541 const gfx::Point& drag_pos) {
542 DCHECK(dragging_handle_);
543 gfx::Point drag_pos_in_client = drag_pos;
544 ConvertPointToClientView(dragging_handle_, &drag_pos_in_client);
546 if (dragging_handle_ == cursor_handle_.get()) {
547 client_view_->MoveCaretTo(drag_pos_in_client);
548 return;
551 // Find the stationary selection handle.
552 ui::SelectionBound anchor_bound =
553 selection_handle_1_ == dragging_handle_ ? selection_bound_2_
554 : selection_bound_1_;
556 // Find selection end points in client_view's coordinate system.
557 gfx::Point p2 = anchor_bound.edge_top_rounded();
558 p2.Offset(0, anchor_bound.GetHeight() / 2);
559 client_view_->ConvertPointFromScreen(&p2);
561 // Instruct client_view to select the region between p1 and p2. The position
562 // of |fixed_handle| is the start and that of |dragging_handle| is the end
563 // of selection.
564 client_view_->SelectRect(p2, drag_pos_in_client);
567 void TouchSelectionControllerImpl::ConvertPointToClientView(
568 EditingHandleView* source, gfx::Point* point) {
569 View::ConvertPointToScreen(source, point);
570 client_view_->ConvertPointFromScreen(point);
573 void TouchSelectionControllerImpl::SetHandleBound(
574 EditingHandleView* handle,
575 const ui::SelectionBound& bound,
576 const ui::SelectionBound& bound_in_screen) {
577 handle->SetWidgetVisible(ShouldShowHandleFor(bound), false);
578 if (handle->IsWidgetVisible())
579 handle->SetBoundInScreen(bound_in_screen);
582 bool TouchSelectionControllerImpl::ShouldShowHandleFor(
583 const ui::SelectionBound& bound) const {
584 if (bound.GetHeight() < kSelectionHandleBarMinHeight)
585 return false;
586 gfx::Rect client_bounds = client_view_->GetBounds();
587 client_bounds.Inset(0, 0, 0, -kSelectionHandleBarBottomAllowance);
588 return client_bounds.Contains(BoundToRect(bound));
591 bool TouchSelectionControllerImpl::IsCommandIdEnabled(int command_id) const {
592 return client_view_->IsCommandIdEnabled(command_id);
595 void TouchSelectionControllerImpl::ExecuteCommand(int command_id,
596 int event_flags) {
597 HideContextMenu();
598 client_view_->ExecuteCommand(command_id, event_flags);
601 void TouchSelectionControllerImpl::OpenContextMenu() {
602 // Context menu should appear centered on top of the selected region.
603 const gfx::Rect rect = context_menu_->GetAnchorRect();
604 const gfx::Point anchor(rect.CenterPoint().x(), rect.y());
605 HideContextMenu();
606 client_view_->OpenContextMenu(anchor);
609 void TouchSelectionControllerImpl::OnMenuClosed(TouchEditingMenuView* menu) {
610 if (menu == context_menu_)
611 context_menu_ = nullptr;
614 void TouchSelectionControllerImpl::OnAncestorWindowTransformed(
615 aura::Window* window,
616 aura::Window* ancestor) {
617 client_view_->DestroyTouchSelection();
620 void TouchSelectionControllerImpl::OnWidgetClosing(Widget* widget) {
621 DCHECK_EQ(client_widget_, widget);
622 client_widget_ = nullptr;
625 void TouchSelectionControllerImpl::OnWidgetBoundsChanged(
626 Widget* widget,
627 const gfx::Rect& new_bounds) {
628 DCHECK_EQ(client_widget_, widget);
629 SelectionChanged();
632 void TouchSelectionControllerImpl::OnKeyEvent(ui::KeyEvent* event) {
633 client_view_->DestroyTouchSelection();
636 void TouchSelectionControllerImpl::OnMouseEvent(ui::MouseEvent* event) {
637 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
638 client_view_->GetNativeView()->GetRootWindow());
639 if (!cursor_client || cursor_client->IsMouseEventsEnabled())
640 client_view_->DestroyTouchSelection();
643 void TouchSelectionControllerImpl::OnScrollEvent(ui::ScrollEvent* event) {
644 client_view_->DestroyTouchSelection();
647 void TouchSelectionControllerImpl::ContextMenuTimerFired() {
648 // Get selection end points in client_view's space.
649 ui::SelectionBound b1_in_screen = selection_bound_1_clipped_;
650 ui::SelectionBound b2_in_screen =
651 cursor_handle_->IsWidgetVisible() ? b1_in_screen
652 : selection_bound_2_clipped_;
653 // Convert from screen to client.
654 ui::SelectionBound b1 = ConvertFromScreen(client_view_, b1_in_screen);
655 ui::SelectionBound b2 = ConvertFromScreen(client_view_, b2_in_screen);
657 // if selection is completely inside the view, we display the context menu
658 // in the middle of the end points on the top. Else, we show it above the
659 // visible handle. If no handle is visible, we do not show the menu.
660 gfx::Rect menu_anchor;
661 if (ShouldShowHandleFor(b1) && ShouldShowHandleFor(b2))
662 menu_anchor = ui::RectBetweenSelectionBounds(b1_in_screen, b2_in_screen);
663 else if (ShouldShowHandleFor(b1))
664 menu_anchor = BoundToRect(b1_in_screen);
665 else if (ShouldShowHandleFor(b2))
666 menu_anchor = BoundToRect(b2_in_screen);
667 else
668 return;
670 // Enlarge the anchor rect so that the menu is offset from the text at least
671 // by the same distance the handles are offset from the text.
672 menu_anchor.Inset(0, -kSelectionHandleVerticalVisualOffset);
674 DCHECK(!context_menu_);
675 context_menu_ = TouchEditingMenuView::Create(this, menu_anchor,
676 GetMaxHandleImageSize(),
677 client_view_->GetNativeView());
680 void TouchSelectionControllerImpl::StartContextMenuTimer() {
681 if (context_menu_timer_.IsRunning())
682 return;
683 context_menu_timer_.Start(
684 FROM_HERE,
685 base::TimeDelta::FromMilliseconds(kContextMenuTimoutMs),
686 this,
687 &TouchSelectionControllerImpl::ContextMenuTimerFired);
690 void TouchSelectionControllerImpl::UpdateContextMenu() {
691 // Hide context menu to be shown when the timer fires.
692 HideContextMenu();
693 StartContextMenuTimer();
696 void TouchSelectionControllerImpl::HideContextMenu() {
697 if (context_menu_)
698 context_menu_->Close();
699 context_menu_ = nullptr;
700 context_menu_timer_.Stop();
703 gfx::NativeView TouchSelectionControllerImpl::GetCursorHandleNativeView() {
704 return cursor_handle_->GetWidget()->GetNativeView();
707 gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle1Bounds() {
708 return selection_handle_1_->GetBoundsInScreen();
711 gfx::Rect TouchSelectionControllerImpl::GetSelectionHandle2Bounds() {
712 return selection_handle_2_->GetBoundsInScreen();
715 gfx::Rect TouchSelectionControllerImpl::GetCursorHandleBounds() {
716 return cursor_handle_->GetBoundsInScreen();
719 bool TouchSelectionControllerImpl::IsSelectionHandle1Visible() {
720 return selection_handle_1_->IsWidgetVisible();
723 bool TouchSelectionControllerImpl::IsSelectionHandle2Visible() {
724 return selection_handle_2_->IsWidgetVisible();
727 bool TouchSelectionControllerImpl::IsCursorHandleVisible() {
728 return cursor_handle_->IsWidgetVisible();
731 gfx::Rect TouchSelectionControllerImpl::GetExpectedHandleBounds(
732 const ui::SelectionBound& bound) {
733 return GetSelectionWidgetBounds(bound);
736 views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle1View() {
737 return selection_handle_1_.get();
740 views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle2View() {
741 return selection_handle_2_.get();
744 } // namespace views