// Copyright (c) 2012 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 "content/renderer/disambiguation_popup_helper.h" #include "third_party/WebKit/public/platform/WebRect.h" #include "ui/gfx/size_conversions.h" using WebKit::WebRect; using WebKit::WebVector; namespace { // The amount of padding to add to the disambiguation popup to show // content around the possible elements, adding some context. const int kDisambiguationPopupPadding = 8; // Constants used for fitting the disambiguation popup inside the bounds of // the view. Note that there are mirror constants in PopupZoomer.java. const int kDisambiguationPopupBoundsMargin = 25; // The smallest allowable touch target used for disambiguation popup. // This value is used to determine the minimum amount we need to scale to // make all targets touchable. const int kDisambiguationPopupMinimumTouchSize = 40; const float kDisambiguationPopupMaxScale = 5.0; const float kDisambiguationPopupMinScale = 2.0; // Compute the scaling factor to ensure the smallest touch candidate reaches // a certain clickable size after zooming float FindOptimalScaleFactor(const WebVector& target_rects, float total_scale) { using std::min; using std::max; if (!target_rects.size()) // shall never reach return kDisambiguationPopupMinScale; float smallest_target = min(target_rects[0].width * total_scale, target_rects[0].height * total_scale); for (size_t i = 1; i < target_rects.size(); i++) { smallest_target = min(smallest_target, target_rects[i].width * total_scale); smallest_target = min(smallest_target, target_rects[i].height * total_scale); } smallest_target = max(smallest_target, 1.0f); return min(kDisambiguationPopupMaxScale, max(kDisambiguationPopupMinScale, kDisambiguationPopupMinimumTouchSize / smallest_target)) * total_scale; } void TrimEdges(int *e1, int *e2, int max_combined) { if (*e1 + *e2 <= max_combined) return; if (std::min(*e1, *e2) * 2 >= max_combined) *e1 = *e2 = max_combined / 2; else if (*e1 > *e2) *e1 = max_combined - *e2; else *e2 = max_combined - *e1; } // Ensure the disambiguation popup fits inside the screen, // clip the edges farthest to the touch point if needed. gfx::Rect CropZoomArea(const gfx::Rect& zoom_rect, const gfx::Size& viewport_size, const gfx::Point& touch_point, float scale) { gfx::Size max_size = viewport_size; max_size.Enlarge(-2 * kDisambiguationPopupBoundsMargin, -2 * kDisambiguationPopupBoundsMargin); max_size = ToCeiledSize(ScaleSize(max_size, 1.0 / scale)); int left = touch_point.x() - zoom_rect.x(); int right = zoom_rect.right() - touch_point.x(); int top = touch_point.y() - zoom_rect.y(); int bottom = zoom_rect.bottom() - touch_point.y(); TrimEdges(&left, &right, max_size.width()); TrimEdges(&top, &bottom, max_size.height()); return gfx::Rect(touch_point.x() - left, touch_point.y() - top, left + right, top + bottom); } } // namespace namespace content { float DisambiguationPopupHelper::ComputeZoomAreaAndScaleFactor( const gfx::Rect& tap_rect, const WebVector& target_rects, const gfx::Size& screen_size, const gfx::Size& visible_content_size, float total_scale, gfx::Rect* zoom_rect) { *zoom_rect = tap_rect; for (size_t i = 0; i < target_rects.size(); i++) zoom_rect->Union(gfx::Rect(target_rects[i])); zoom_rect->Inset(-kDisambiguationPopupPadding, -kDisambiguationPopupPadding); zoom_rect->Intersect(gfx::Rect(visible_content_size)); float new_total_scale = FindOptimalScaleFactor(target_rects, total_scale); *zoom_rect = CropZoomArea( *zoom_rect, screen_size, tap_rect.CenterPoint(), new_total_scale); return new_total_scale; } } // namespace content