1// Copyright (c) 2012 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/renderer/disambiguation_popup_helper.h"
6
7#include "third_party/WebKit/public/platform/WebRect.h"
8#include "ui/gfx/size_conversions.h"
9
10using blink::WebRect;
11using blink::WebVector;
12
13namespace {
14
15// The amount of padding to add to the disambiguation popup to show
16// content around the possible elements, adding some context.
17const int kDisambiguationPopupPadding = 8;
18
19// Constants used for fitting the disambiguation popup inside the bounds of
20// the view. Note that there are mirror constants in PopupZoomer.java.
21const int kDisambiguationPopupBoundsMargin = 25;
22
23// The smallest allowable touch target used for disambiguation popup.
24// This value is used to determine the minimum amount we need to scale to
25// make all targets touchable.
26const int kDisambiguationPopupMinimumTouchSize = 40;
27const float kDisambiguationPopupMaxScale = 5.0;
28const float kDisambiguationPopupMinScale = 2.0;
29
30// Compute the scaling factor to ensure the smallest touch candidate reaches
31// a certain clickable size after zooming
32float FindOptimalScaleFactor(const WebVector<WebRect>& target_rects,
33                             float total_scale) {
34  using std::min;
35  using std::max;
36  if (!target_rects.size())  // shall never reach
37    return kDisambiguationPopupMinScale;
38  float smallest_target = min(target_rects[0].width * total_scale,
39                              target_rects[0].height * total_scale);
40  for (size_t i = 1; i < target_rects.size(); i++) {
41    smallest_target = min(smallest_target, target_rects[i].width * total_scale);
42    smallest_target = min(smallest_target,
43        target_rects[i].height * total_scale);
44  }
45  smallest_target = max(smallest_target, 1.0f);
46  return min(kDisambiguationPopupMaxScale, max(kDisambiguationPopupMinScale,
47      kDisambiguationPopupMinimumTouchSize / smallest_target)) * total_scale;
48}
49
50void TrimEdges(int *e1, int *e2, int max_combined) {
51  if (*e1 + *e2 <= max_combined)
52    return;
53
54  if (std::min(*e1, *e2) * 2 >= max_combined)
55    *e1 = *e2 = max_combined / 2;
56  else if (*e1 > *e2)
57    *e1 = max_combined - *e2;
58  else
59    *e2 = max_combined - *e1;
60}
61
62// Ensure the disambiguation popup fits inside the screen,
63// clip the edges farthest to the touch point if needed.
64gfx::Rect CropZoomArea(const gfx::Rect& zoom_rect,
65                       const gfx::Size& viewport_size,
66                       const gfx::Point& touch_point,
67                       float scale) {
68  gfx::Size max_size = viewport_size;
69  max_size.Enlarge(-2 * kDisambiguationPopupBoundsMargin,
70                   -2 * kDisambiguationPopupBoundsMargin);
71  max_size = ToCeiledSize(ScaleSize(max_size, 1.0 / scale));
72
73  int left = touch_point.x() - zoom_rect.x();
74  int right = zoom_rect.right() - touch_point.x();
75  int top = touch_point.y() - zoom_rect.y();
76  int bottom = zoom_rect.bottom() - touch_point.y();
77  TrimEdges(&left, &right, max_size.width());
78  TrimEdges(&top, &bottom, max_size.height());
79
80  return gfx::Rect(touch_point.x() - left,
81                   touch_point.y() - top,
82                   left + right,
83                   top + bottom);
84}
85
86}  // namespace
87
88namespace content {
89
90float DisambiguationPopupHelper::ComputeZoomAreaAndScaleFactor(
91    const gfx::Rect& tap_rect,
92    const WebVector<WebRect>& target_rects,
93    const gfx::Size& screen_size,
94    const gfx::Size& visible_content_size,
95    float total_scale,
96    gfx::Rect* zoom_rect) {
97  *zoom_rect = tap_rect;
98  for (size_t i = 0; i < target_rects.size(); i++)
99    zoom_rect->Union(gfx::Rect(target_rects[i]));
100  zoom_rect->Inset(-kDisambiguationPopupPadding, -kDisambiguationPopupPadding);
101
102  zoom_rect->Intersect(gfx::Rect(visible_content_size));
103
104  float new_total_scale =
105      FindOptimalScaleFactor(target_rects, total_scale);
106  *zoom_rect = CropZoomArea(
107      *zoom_rect, screen_size, tap_rect.CenterPoint(), new_total_scale);
108
109  return new_total_scale;
110}
111
112}  // namespace content
113