Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(3)

Unified Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/widget/findinpage/FindResultBar.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/android/java_staging/src/org/chromium/chrome/browser/widget/findinpage/FindResultBar.java
diff --git a/chrome/android/java_staging/src/org/chromium/chrome/browser/widget/findinpage/FindResultBar.java b/chrome/android/java_staging/src/org/chromium/chrome/browser/widget/findinpage/FindResultBar.java
new file mode 100644
index 0000000000000000000000000000000000000000..1645cf45233bf18c359e62429d99167ef8ebe8b9
--- /dev/null
+++ b/chrome/android/java_staging/src/org/chromium/chrome/browser/widget/findinpage/FindResultBar.java
@@ -0,0 +1,402 @@
+// Copyright 2015 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.
+
+package org.chromium.chrome.browser.widget.findinpage;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import com.google.android.apps.chrome.R;
+
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.chrome.browser.Tab;
+import org.chromium.chrome.browser.findinpage.FindInPageBridge;
+import org.chromium.chrome.browser.util.MathUtils;
+import org.chromium.ui.UiUtils;
+import org.chromium.ui.base.LocalizationUtils;
+import org.chromium.ui.interpolators.BakedBezierInterpolator;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * The view that shows the positions of the find in page matches and allows scrubbing
+ * between the entries.
+ */
+class FindResultBar extends View {
+ private static final int VISIBILTY_ANIMATION_DURATION_MS = 200;
+
+ private final int mBackgroundColor;
+ private final int mBackgroundBorderColor;
+ private final int mResultColor;
+ private final int mResultBorderColor;
+ private final int mActiveColor;
+ private final int mActiveBorderColor;
+
+ private final int mBarTouchWidth;
+ private final int mBarDrawWidth;
+ private final int mResultMinHeight;
+ private final int mActiveMinHeight;
+ private final int mBarVerticalPadding;
+ private final int mMinGapBetweenStacks;
+ private final int mStackedResultHeight;
+
+ private final Tab mTab;
+ private FindInPageBridge mFindInPageBridge;
+
+ int mRectsVersion = -1;
+ private RectF[] mMatches = new RectF[0];
+ private RectF mActiveMatch;
+
+ private ArrayList<Tickmark> mTickmarks = new ArrayList<Tickmark>(0);
+ private int mBarHeightForWhichTickmarksWereCached = -1;
+
+ private Animator mVisibilityAnimation;
+ private boolean mDismissing;
+
+ private final Paint mFillPaint;
+ private final Paint mStrokePaint;
+
+ boolean mWaitingForActivateAck = false;
+
+ private static Comparator<RectF> sComparator = new Comparator<RectF>() {
+ @Override
+ public int compare(RectF a, RectF b) {
+ if (a.top != b.top) return a.top > b.top ? 1 : -1;
+ if (a.top != b.top) return a.left > b.left ? 1 : -1;
+ return 0;
+ }
+ };
+
+ /**
+ * Creates an instance of a {@link FindResultBar}.
+ * @param context The Context to create this {@link FindResultBar} under.
+ * @param tab The Tab containing the ContentView this {@link FindResultBar} will be drawn in.
+ */
+ public FindResultBar(Context context, Tab tab, FindInPageBridge findInPageBridge) {
+ super(context);
+
+ Resources res = context.getResources();
+ mBackgroundColor = res.getColor(
+ R.color.find_result_bar_background_color);
+ mBackgroundBorderColor = res.getColor(
+ R.color.find_result_bar_background_border_color);
+ mResultColor = res.getColor(
+ R.color.find_result_bar_result_color);
+ mResultBorderColor = res.getColor(
+ R.color.find_result_bar_result_border_color);
+ mActiveColor = res.getColor(
+ R.color.find_result_bar_active_color);
+ mActiveBorderColor = res.getColor(
+ R.color.find_result_bar_active_border_color);
+ mBarTouchWidth = res.getDimensionPixelSize(
+ R.dimen.find_result_bar_touch_width);
+ mBarDrawWidth = res.getDimensionPixelSize(R.dimen.find_result_bar_draw_width)
+ + res.getDimensionPixelSize(R.dimen.find_in_page_separator_width);
+ mResultMinHeight = res.getDimensionPixelSize(R.dimen.find_result_bar_result_min_height);
+ mActiveMinHeight = res.getDimensionPixelSize(
+ R.dimen.find_result_bar_active_min_height);
+ mBarVerticalPadding = res.getDimensionPixelSize(
+ R.dimen.find_result_bar_vertical_padding);
+ mMinGapBetweenStacks = res.getDimensionPixelSize(
+ R.dimen.find_result_bar_min_gap_between_stacks);
+ mStackedResultHeight = res.getDimensionPixelSize(
+ R.dimen.find_result_bar_stacked_result_height);
+
+ mFillPaint = new Paint();
+ mStrokePaint = new Paint();
+ mFillPaint.setAntiAlias(true);
+ mStrokePaint.setAntiAlias(true);
+ mFillPaint.setStyle(Paint.Style.FILL);
+ mStrokePaint.setStyle(Paint.Style.STROKE);
+ mStrokePaint.setStrokeWidth(1.0f);
+
+ mFindInPageBridge = findInPageBridge;
+ mTab = tab;
+ mTab.getContentViewCore().getContainerView().addView(
+ this, new FrameLayout.LayoutParams(mBarTouchWidth,
+ ViewGroup.LayoutParams.MATCH_PARENT, Gravity.END));
+ setTranslationX(
+ MathUtils.flipSignIf(mBarTouchWidth, LocalizationUtils.isLayoutRtl()));
+
+ mVisibilityAnimation = ObjectAnimator.ofFloat(this, TRANSLATION_X, 0);
+ mVisibilityAnimation.setDuration(VISIBILTY_ANIMATION_DURATION_MS);
+ mVisibilityAnimation.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE);
+ mTab.getWindowAndroid().startAnimationOverContent(mVisibilityAnimation);
+ }
+
+ /** Dismisses this results bar by removing it from the view hierarchy. */
+ public void dismiss() {
+ mDismissing = true;
+ if (mVisibilityAnimation != null && mVisibilityAnimation.isRunning()) {
+ mVisibilityAnimation.cancel();
+ }
+
+ mVisibilityAnimation = ObjectAnimator.ofFloat(this, TRANSLATION_X,
+ MathUtils.flipSignIf(mBarTouchWidth, LocalizationUtils.isLayoutRtl()));
+ mVisibilityAnimation.setDuration(VISIBILTY_ANIMATION_DURATION_MS);
+ mVisibilityAnimation.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
+ mTab.getWindowAndroid().startAnimationOverContent(mVisibilityAnimation);
+ mVisibilityAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+
+ if (getParent() != null) ((ViewGroup) getParent()).removeView(FindResultBar.this);
+ }
+ });
+ }
+
+ /** Setup the tickmarks to draw using the rects of the find results. */
+ public void setMatchRects(int version, RectF[] rects, RectF activeRect) {
+ if (mRectsVersion != version) {
+ mRectsVersion = version;
+ assert rects != null;
+ mMatches = rects;
+ mTickmarks.clear();
+ Arrays.sort(mMatches, sComparator);
+ mBarHeightForWhichTickmarksWereCached = -1;
+ }
+ mActiveMatch = activeRect; // Can be null.
+ invalidate();
+ }
+
+ /** Clears the tickmarks. */
+ public void clearMatchRects() {
+ setMatchRects(-1, new RectF[0], null);
+ }
+
+ @Override
+ @SuppressLint("ClickableViewAccessibility")
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!mDismissing && mTickmarks.size() > 0 && mTickmarks.size() == mMatches.length
+ && !mWaitingForActivateAck && event.getAction() != MotionEvent.ACTION_CANCEL) {
+ // We decided it's more important to get the keyboard out of the
+ // way asap; the user can compensate if their next MotionEvent
+ // scrolls somewhere unintended.
+ UiUtils.hideKeyboard(this);
+
+ // Identify which drawn tickmark is closest to the user's finger.
+ int closest = Collections.binarySearch(mTickmarks,
+ new Tickmark(event.getY(), event.getY()));
+ if (closest < 0) {
+ // No exact match, so must determine nearest.
+ int insertionPoint = -1 - closest;
+ if (insertionPoint == 0) {
+ closest = 0;
+ } else if (insertionPoint == mTickmarks.size()) {
+ closest = mTickmarks.size() - 1;
+ } else {
+ float distanceA = Math.abs(event.getY()
+ - mTickmarks.get(insertionPoint - 1).centerY());
+ float distanceB = Math.abs(event.getY()
+ - mTickmarks.get(insertionPoint).centerY());
+ closest = insertionPoint - (distanceA <= distanceB ? 1 : 0);
+ }
+ }
+
+ // Now activate the find match corresponding to that tickmark.
+ // Since mTickmarks may be outdated, we can't just pass the index.
+ // Instead we send the renderer the coordinates of the center of the
+ // find match's rect (as originally received in setMatchRects), and
+ // it will activate whatever find result is currently closest to
+ // that point (which will usually be the same one).
+ mWaitingForActivateAck = true;
+ mFindInPageBridge.activateNearestFindResult(
+ mMatches[closest].centerX(),
+ mMatches[closest].centerY());
+ }
+ return true; // Consume the event, whether or not we acted upon it.
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ // Check for new rects, as they may move if the document size changes.
+ if (!mDismissing && mMatches.length > 0) {
+ mFindInPageBridge.requestFindMatchRects(mRectsVersion);
+ }
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int leftMargin = getLeftMargin();
+ mFillPaint.setColor(mBackgroundColor);
+ mStrokePaint.setColor(mBackgroundBorderColor);
+ canvas.drawRect(leftMargin, 0,
+ leftMargin + mBarDrawWidth, getHeight(), mFillPaint);
+ float lineX = LocalizationUtils.isLayoutRtl()
+ ? leftMargin + mBarDrawWidth - 0.5f
+ : leftMargin + 0.5f;
+ canvas.drawLine(lineX, 0, lineX, getHeight(), mStrokePaint);
+
+ if (mMatches.length == 0) {
+ return;
+ }
+
+ if (mBarHeightForWhichTickmarksWereCached != getHeight()) {
+ calculateTickmarks();
+ }
+
+ // Draw all matches (since they're sorted by increasing y-position
+ // overlapping tickmarks will form nice stacks).
+ mFillPaint.setColor(mResultColor);
+ mStrokePaint.setColor(mResultBorderColor);
+ for (Tickmark tickmark : mTickmarks) {
+ RectF rect = tickmark.toRectF();
+ canvas.drawRoundRect(rect, 2, 2, mFillPaint);
+ canvas.drawRoundRect(rect, 2, 2, mStrokePaint);
+ }
+
+ // Draw the active tickmark on top (covering up the inactive tickmark
+ // we probably already drew for it).
+ if (mActiveMatch != null) {
+ Tickmark tickmark;
+ int i = Arrays.binarySearch(mMatches, mActiveMatch, sComparator);
+ if (i >= 0) {
+ // We've already generated a tickmark for all rects in mMatches,
+ // so use the corresponding one. However it was generated
+ // assuming the match would be inactive. Keep the position, but
+ // re-expand it using mActiveMinHeight.
+ tickmark = expandTickmarkToMinHeight(mTickmarks.get(i), true);
+ } else {
+ // How strange - mActiveMatch isn't in mMatches. Do our best to
+ // draw it anyway (though it might not line up exactly).
+ tickmark = tickmarkForRect(mActiveMatch, true);
+ }
+ RectF rect = tickmark.toRectF();
+ mFillPaint.setColor(mActiveColor);
+ mStrokePaint.setColor(mActiveBorderColor);
+ canvas.drawRoundRect(rect, 2, 2, mFillPaint);
+ canvas.drawRoundRect(rect, 2, 2, mStrokePaint);
+ }
+ }
+
+ private int getLeftMargin() {
+ return LocalizationUtils.isLayoutRtl() ? 0 : getWidth() - mBarDrawWidth;
+ }
+
+ private void calculateTickmarks() {
+ // TODO(johnme): Simplify calculation, and switch to integer arithmetic
+ // where possible (tickmarks within groups will still need fractional
+ // y-positions for anti-aliasing, but the start and end positions of
+ // groups can and should be integer-aligned [will give crisp borders],
+ // and the intermediary logic uses more floats than necessary).
+ // TODO(johnme): Consider adding unit tests for this.
+
+ mBarHeightForWhichTickmarksWereCached = getHeight();
+
+ // Generate tickmarks, neatly clustering any overlapping matches.
+ mTickmarks = new ArrayList<Tickmark>(mMatches.length);
+ int i = 0;
+ Tickmark nextTickmark = tickmarkForRect(mMatches[i], false);
+ float lastGroupEnd = -mMinGapBetweenStacks;
+ while (i < mMatches.length) {
+ // Find next cluster of overlapping tickmarks.
+ List<Tickmark> cluster = new ArrayList<Tickmark>();
+ cluster.add(nextTickmark);
+ i++;
+ while (i < mMatches.length) {
+ nextTickmark = tickmarkForRect(mMatches[i], false);
+ if (nextTickmark.mTop <= cluster.get(cluster.size() - 1).mBottom
+ + mMinGapBetweenStacks) {
+ cluster.add(nextTickmark);
+ i++;
+ } else {
+ break;
+ }
+ }
+
+ // Draw cluster.
+ int cn = cluster.size();
+ float minStart = lastGroupEnd + mMinGapBetweenStacks;
+ lastGroupEnd = cluster.get(cn - 1).mBottom;
+ float preferredStart = lastGroupEnd
+ - (cn - 1) * mStackedResultHeight
+ - mResultMinHeight;
+ float maxStart = cluster.get(0).mTop;
+ float start = Math.round(MathUtils.clamp(preferredStart, minStart, maxStart));
+ float scale = start >= preferredStart ? 1.0f :
+ (lastGroupEnd - start) / (lastGroupEnd - preferredStart);
+ float spacing = cn == 1 ? 0 : (lastGroupEnd - start
+ - scale * mResultMinHeight) / (cn - 1);
+ for (int j = 0; j < cn; j++) {
+ Tickmark tickmark = cluster.get(j);
+ tickmark.mTop = start + j * spacing;
+ if (j != cn - 1) {
+ tickmark.mBottom = tickmark.mTop + scale * mResultMinHeight;
+ }
+ mTickmarks.add(tickmark);
+ }
+ }
+ }
+
+ private Tickmark tickmarkForRect(RectF r, boolean active) {
+ // Ratio of results bar height to page height
+ float vScale = mBarHeightForWhichTickmarksWereCached - 2 * mBarVerticalPadding;
+ Tickmark tickmark = new Tickmark(
+ r.top * vScale + mBarVerticalPadding,
+ r.bottom * vScale + mBarVerticalPadding);
+ return expandTickmarkToMinHeight(tickmark, active);
+ }
+
+ private Tickmark expandTickmarkToMinHeight(Tickmark tickmark,
+ boolean active) {
+ int minHeight = active ? mActiveMinHeight : mResultMinHeight;
+ float missingHeight = minHeight - tickmark.height();
+ if (missingHeight > 0) {
+ return new Tickmark(tickmark.mTop - missingHeight / 2.0f,
+ tickmark.mBottom + missingHeight / 2.0f);
+ }
+ return tickmark;
+ }
+
+ /** Like android.graphics.RectF, but without a left or right. */
+ private class Tickmark implements Comparable<Tickmark> {
+ float mTop;
+ float mBottom;
+ Tickmark(float top, float bottom) {
+ this.mTop = top;
+ this.mBottom = bottom;
+ }
+ float height() {
+ return mBottom - mTop;
+ }
+ float centerY() {
+ return (mTop + mBottom) * 0.5f;
+ }
+ RectF toRectF() {
+ int leftMargin = getLeftMargin();
+ RectF rect = new RectF(leftMargin, mTop, leftMargin + mBarDrawWidth, mBottom);
+ rect.inset(2.0f, 0.5f);
+ rect.offset(LocalizationUtils.isLayoutRtl() ? -0.5f : 0.5f, 0);
+ return rect;
+ }
+ @SuppressFBWarnings("EQ_COMPARETO_USE_OBJECT_EQUAL")
+ @Override
+ public int compareTo(Tickmark other) {
+ float center = centerY();
+ float otherCenter = other.centerY();
+ if (center == otherCenter) return 0;
+ return center > otherCenter ? 1 : -1;
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698