OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 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 package org.chromium.chrome.browser.compositor.overlays.strip; |
| 6 |
| 7 import static org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Ani
matableAnimation.createAnimation; |
| 8 |
| 9 import android.content.Context; |
| 10 import android.graphics.RectF; |
| 11 |
| 12 import com.google.android.apps.chrome.R; |
| 13 |
| 14 import org.chromium.base.VisibleForTesting; |
| 15 import org.chromium.chrome.browser.Tab; |
| 16 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation; |
| 17 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animatable
; |
| 18 import org.chromium.chrome.browser.compositor.layouts.ChromeAnimation.Animation; |
| 19 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost; |
| 20 import org.chromium.chrome.browser.compositor.layouts.components.CompositorButto
n; |
| 21 import org.chromium.chrome.browser.compositor.layouts.components.VirtualView; |
| 22 import org.chromium.chrome.browser.compositor.overlays.strip.TabLoadTracker.TabL
oadTrackerCallback; |
| 23 import org.chromium.chrome.browser.util.MathUtils; |
| 24 import org.chromium.ui.base.LocalizationUtils; |
| 25 import org.chromium.ui.resources.AndroidResourceType; |
| 26 import org.chromium.ui.resources.LayoutResource; |
| 27 import org.chromium.ui.resources.ResourceManager; |
| 28 |
| 29 import java.util.List; |
| 30 |
| 31 /** |
| 32 * {@link StripLayoutTab} is used to keep track of the strip position and render
ing information for |
| 33 * a particular tab so it can draw itself onto the GL canvas. |
| 34 */ |
| 35 public class StripLayoutTab |
| 36 implements ChromeAnimation.Animatable<StripLayoutTab.Property>, VirtualV
iew { |
| 37 /** |
| 38 * Animatable properties that can be used with a {@link ChromeAnimation.Anim
atable} on a |
| 39 * {@link StripLayoutTab}. |
| 40 */ |
| 41 enum Property { |
| 42 X_OFFSET, |
| 43 Y_OFFSET, |
| 44 WIDTH, |
| 45 } |
| 46 |
| 47 // Behavior Constants |
| 48 private static final float VISIBILITY_FADE_CLOSE_BUTTON_PERCENTAGE = 0.99f; |
| 49 |
| 50 // Animation/Timer Constants |
| 51 private static final int ANIM_TAB_CLOSE_BUTTON_FADE_MS = 150; |
| 52 |
| 53 // Close button width |
| 54 private static final int CLOSE_BUTTON_WIDTH_DP = 36; |
| 55 |
| 56 private int mId = Tab.INVALID_TAB_ID; |
| 57 |
| 58 private final TabLoadTracker mLoadTracker; |
| 59 private final LayoutRenderHost mRenderHost; |
| 60 |
| 61 private boolean mVisible = true; |
| 62 private boolean mIsDying = false; |
| 63 private boolean mCanShowCloseButton = true; |
| 64 private boolean mIncognito; |
| 65 private float mContentOffsetX; |
| 66 private float mVisiblePercentage = 1.f; |
| 67 private String mAccessibilityDescription; |
| 68 |
| 69 // Ideal intermediate parameters |
| 70 private float mIdealX; |
| 71 private float mTabOffsetX; |
| 72 private float mTabOffsetY; |
| 73 |
| 74 // Actual draw parameters |
| 75 private float mDrawX; |
| 76 private float mDrawY; |
| 77 private float mWidth; |
| 78 private float mHeight; |
| 79 private RectF mTouchTarget = new RectF(); |
| 80 |
| 81 private boolean mShowingCloseButton = true; |
| 82 |
| 83 private final CompositorButton mCloseButton; |
| 84 |
| 85 // Content Animations |
| 86 private ChromeAnimation<Animatable<?>> mContentAnimations; |
| 87 |
| 88 private float mLoadingSpinnerRotationDegrees = 0.0f; |
| 89 |
| 90 // Preallocated |
| 91 private final RectF mClosePlacement = new RectF(); |
| 92 |
| 93 /** |
| 94 * Create a {@link StripLayoutTab} that represents the {@link Tab} with an i
d of |
| 95 * {@code id}. |
| 96 * |
| 97 * @param context An Android context for accessing system resources. |
| 98 * @param id The id of the {@link Tab} to visually represent. |
| 99 * @param loadTrackerCallback The {@link TabLoadTrackerCallback} to be notif
ied of loading state |
| 100 * changes. |
| 101 * @param renderHost The {@link LayoutRenderHost}. |
| 102 * @param incogntio Whether or not this layout tab is icognito. |
| 103 */ |
| 104 public StripLayoutTab(Context context, int id, TabLoadTrackerCallback loadTr
ackerCallback, |
| 105 LayoutRenderHost renderHost, boolean incognito) { |
| 106 mId = id; |
| 107 mLoadTracker = new TabLoadTracker(id, loadTrackerCallback); |
| 108 mRenderHost = renderHost; |
| 109 mIncognito = incognito; |
| 110 mCloseButton = new CompositorButton(context, 0, 0); |
| 111 mCloseButton.setResources(R.drawable.btn_tab_close_normal, R.drawable.bt
n_tab_close_pressed, |
| 112 R.drawable.btn_tab_close_white_normal, R.drawable.btn_tab_close_
white_pressed); |
| 113 mCloseButton.setIncognito(mIncognito); |
| 114 mCloseButton.setBounds(getCloseRect()); |
| 115 mCloseButton.setClickSlop(0.f); |
| 116 String description = |
| 117 context.getResources().getString(R.string.accessibility_tabstrip
_btn_close_tab); |
| 118 mCloseButton.setAccessibilityDescription(description, description); |
| 119 } |
| 120 |
| 121 /** |
| 122 * Get a list of virtual views for accessibility events. |
| 123 * |
| 124 * @param views A List to populate with virtual views. |
| 125 */ |
| 126 public void getVirtualViews(List<VirtualView> views) { |
| 127 if (mShowingCloseButton) views.add(mCloseButton); |
| 128 views.add(this); |
| 129 } |
| 130 |
| 131 /** |
| 132 * @param description A description for accessibility events. |
| 133 */ |
| 134 public void setAccessibilityDescription(String description) { |
| 135 mAccessibilityDescription = description; |
| 136 } |
| 137 |
| 138 @Override |
| 139 public String getAccessibilityDescription() { |
| 140 return mAccessibilityDescription; |
| 141 } |
| 142 |
| 143 @Override |
| 144 public void getTouchTarget(RectF target) { |
| 145 target.set(mTouchTarget); |
| 146 } |
| 147 |
| 148 @Override |
| 149 public boolean checkClicked(float x, float y) { |
| 150 return mTouchTarget.contains(x, y); |
| 151 } |
| 152 |
| 153 /** |
| 154 * @return The id of the {@link Tab} this {@link StripLayoutTab} represents. |
| 155 */ |
| 156 public int getId() { |
| 157 return mId; |
| 158 } |
| 159 |
| 160 /** |
| 161 * @param foreground Whether or not this tab is a foreground tab. |
| 162 * @return The Android resource that represents the tab background. |
| 163 */ |
| 164 public int getResourceId(boolean foreground) { |
| 165 if (foreground) { |
| 166 return mIncognito ? R.drawable.bg_tabstrip_incognito_tab : R.drawabl
e.bg_tabstrip_tab; |
| 167 } |
| 168 return mIncognito ? R.drawable.bg_tabstrip_incognito_background_tab |
| 169 : R.drawable.bg_tabstrip_background_tab; |
| 170 } |
| 171 |
| 172 /** |
| 173 * @param visible Whether or not this {@link StripLayoutTab} should be drawn
. |
| 174 */ |
| 175 public void setVisible(boolean visible) { |
| 176 mVisible = visible; |
| 177 } |
| 178 |
| 179 /** |
| 180 * @return Whether or not this {@link StripLayoutTab} should be drawn. |
| 181 */ |
| 182 public boolean isVisible() { |
| 183 return mVisible; |
| 184 } |
| 185 |
| 186 /** |
| 187 * Mark this tab as in the process of dying. This lets us track which tabs
are dead after |
| 188 * animations. |
| 189 * @param isDying Whether or not the tab is dying. |
| 190 */ |
| 191 public void setIsDying(boolean isDying) { |
| 192 mIsDying = isDying; |
| 193 } |
| 194 |
| 195 /** |
| 196 * @return Whether or not the tab is dying. |
| 197 */ |
| 198 public boolean isDying() { |
| 199 return mIsDying; |
| 200 } |
| 201 |
| 202 /** |
| 203 * @return Whether or not this tab should be visually represented as loading
. |
| 204 */ |
| 205 public boolean isLoading() { |
| 206 return mLoadTracker.isLoading(); |
| 207 } |
| 208 |
| 209 /** |
| 210 * @return The rotation of the loading spinner in degrees. |
| 211 */ |
| 212 public float getLoadingSpinnerRotation() { |
| 213 return mLoadingSpinnerRotationDegrees; |
| 214 } |
| 215 |
| 216 /** |
| 217 * Additive spinner rotation update. |
| 218 * @param rotation The amount to rotate the spinner by in degrees. |
| 219 */ |
| 220 public void addLoadingSpinnerRotation(float rotation) { |
| 221 mLoadingSpinnerRotationDegrees = (mLoadingSpinnerRotationDegrees + rotat
ion) % 1080; |
| 222 } |
| 223 |
| 224 /** |
| 225 * Called when this tab has started loading. |
| 226 */ |
| 227 public void pageLoadingStarted() { |
| 228 mLoadTracker.pageLoadingStarted(); |
| 229 } |
| 230 |
| 231 /** |
| 232 * Called when this tab has finished loading. |
| 233 */ |
| 234 public void pageLoadingFinished() { |
| 235 mLoadTracker.pageLoadingFinished(); |
| 236 } |
| 237 |
| 238 /** |
| 239 * Called when this tab has started loading resources. |
| 240 */ |
| 241 public void loadingStarted() { |
| 242 mLoadTracker.loadingStarted(); |
| 243 } |
| 244 |
| 245 /** |
| 246 * Called when this tab has finished loading resources. |
| 247 */ |
| 248 public void loadingFinished() { |
| 249 mLoadTracker.loadingFinished(); |
| 250 } |
| 251 |
| 252 /** |
| 253 * @param offsetX How far to offset the tab content (favicons and title). |
| 254 */ |
| 255 public void setContentOffsetX(float offsetX) { |
| 256 mContentOffsetX = MathUtils.clamp(offsetX, 0.f, mWidth); |
| 257 } |
| 258 |
| 259 /** |
| 260 * @return How far to offset the tab content (favicons and title). |
| 261 */ |
| 262 public float getContentOffsetX() { |
| 263 return mContentOffsetX; |
| 264 } |
| 265 |
| 266 /** |
| 267 * @param visiblePercentage How much of the tab is visible (not overlapped b
y other tabs). |
| 268 */ |
| 269 public void setVisiblePercentage(float visiblePercentage) { |
| 270 mVisiblePercentage = visiblePercentage; |
| 271 checkCloseButtonVisibility(true); |
| 272 } |
| 273 |
| 274 /** |
| 275 * @return How much of the tab is visible (not overlapped by other tabs). |
| 276 */ |
| 277 @VisibleForTesting |
| 278 public float getVisiblePercentage() { |
| 279 return mVisiblePercentage; |
| 280 } |
| 281 |
| 282 /** |
| 283 * @param show Whether or not the close button is allowed to be shown. |
| 284 */ |
| 285 public void setCanShowCloseButton(boolean show) { |
| 286 mCanShowCloseButton = show; |
| 287 checkCloseButtonVisibility(true); |
| 288 } |
| 289 |
| 290 /** |
| 291 * @param x The actual position in the strip, taking into account stacking,
scrolling, etc. |
| 292 */ |
| 293 public void setDrawX(float x) { |
| 294 mCloseButton.setX(mCloseButton.getX() + (x - mDrawX)); |
| 295 mDrawX = x; |
| 296 mTouchTarget.left = mDrawX; |
| 297 mTouchTarget.right = mDrawX + mWidth; |
| 298 } |
| 299 |
| 300 /** |
| 301 * @return The actual position in the strip, taking into account stacking, s
crolling, etc. |
| 302 */ |
| 303 public float getDrawX() { |
| 304 return mDrawX; |
| 305 } |
| 306 |
| 307 /** |
| 308 * @param y The vertical position for the tab. |
| 309 */ |
| 310 public void setDrawY(float y) { |
| 311 mCloseButton.setY(mCloseButton.getY() + (y - mDrawY)); |
| 312 mDrawY = y; |
| 313 mTouchTarget.top = mDrawY; |
| 314 mTouchTarget.bottom = mDrawY + mHeight; |
| 315 } |
| 316 |
| 317 /** |
| 318 * @return The vertical position for the tab. |
| 319 */ |
| 320 public float getDrawY() { |
| 321 return mDrawY; |
| 322 } |
| 323 |
| 324 /** |
| 325 * @param width The width of the tab. |
| 326 */ |
| 327 public void setWidth(float width) { |
| 328 mWidth = width; |
| 329 resetCloseRect(); |
| 330 mTouchTarget.right = mDrawX + mWidth; |
| 331 } |
| 332 |
| 333 /** |
| 334 * @return The width of the tab. |
| 335 */ |
| 336 public float getWidth() { |
| 337 return mWidth; |
| 338 } |
| 339 |
| 340 /** |
| 341 * @param height The height of the tab. |
| 342 */ |
| 343 public void setHeight(float height) { |
| 344 mHeight = height; |
| 345 resetCloseRect(); |
| 346 mTouchTarget.bottom = mDrawY + mHeight; |
| 347 } |
| 348 |
| 349 /** |
| 350 * @return The height of the tab. |
| 351 */ |
| 352 public float getHeight() { |
| 353 return mHeight; |
| 354 } |
| 355 |
| 356 /** |
| 357 * @param closePressed The current pressed state of the attached button. |
| 358 */ |
| 359 public void setClosePressed(boolean closePressed) { |
| 360 mCloseButton.setPressed(closePressed); |
| 361 } |
| 362 |
| 363 /** |
| 364 * @return The current pressed state of the close button. |
| 365 */ |
| 366 public boolean getClosePressed() { |
| 367 return mCloseButton.isPressed(); |
| 368 } |
| 369 |
| 370 /** |
| 371 * @return The close button for this tab. |
| 372 */ |
| 373 public CompositorButton getCloseButton() { |
| 374 return mCloseButton; |
| 375 } |
| 376 |
| 377 /** |
| 378 * This represents how much this tab's width should be counted when position
ing tabs in the |
| 379 * stack. As tabs close or open, their width weight is increased. They vis
ually take up |
| 380 * the same amount of space but the other tabs will smoothly move out of the
way to make room. |
| 381 * @return The weight from 0 to 1 that the width of this tab should have on
the stack. |
| 382 */ |
| 383 public float getWidthWeight() { |
| 384 return MathUtils.clamp(1.f - mDrawY / mHeight, 0.f, 1.f); |
| 385 } |
| 386 |
| 387 /** |
| 388 * @param x The x position of the position to test. |
| 389 * @param y The y position of the position to test. |
| 390 * @return Whether or not {@code x} and {@code y} is over the close button f
or this tab and |
| 391 * if the button can be clicked. |
| 392 */ |
| 393 public boolean checkCloseHitTest(float x, float y) { |
| 394 return mShowingCloseButton ? mCloseButton.checkClicked(x, y) : false; |
| 395 } |
| 396 |
| 397 /** |
| 398 * This is used to help calculate the tab's position and is not used for ren
dering. |
| 399 * @param offsetX The offset of the tab (used for drag and drop, slide anima
ting, etc). |
| 400 */ |
| 401 public void setOffsetX(float offsetX) { |
| 402 mTabOffsetX = offsetX; |
| 403 } |
| 404 |
| 405 /** |
| 406 * This is used to help calculate the tab's position and is not used for ren
dering. |
| 407 * @return The offset of the tab (used for drag and drop, slide animating, e
tc). |
| 408 */ |
| 409 public float getOffsetX() { |
| 410 return mTabOffsetX; |
| 411 } |
| 412 |
| 413 /** |
| 414 * This is used to help calculate the tab's position and is not used for ren
dering. |
| 415 * @param x The ideal position, in an infinitely long strip, of this tab. |
| 416 */ |
| 417 public void setIdealX(float x) { |
| 418 mIdealX = x; |
| 419 } |
| 420 |
| 421 /** |
| 422 * This is used to help calculate the tab's position and is not used for ren
dering. |
| 423 * @return The ideal position, in an infinitely long strip, of this tab. |
| 424 */ |
| 425 public float getIdealX() { |
| 426 return mIdealX; |
| 427 } |
| 428 |
| 429 /** |
| 430 * This is used to help calculate the tab's position and is not used for ren
dering. |
| 431 * @param offsetY The vertical offset of the tab. |
| 432 */ |
| 433 public void setOffsetY(float offsetY) { |
| 434 mTabOffsetY = offsetY; |
| 435 } |
| 436 |
| 437 /** |
| 438 * This is used to help calculate the tab's position and is not used for ren
dering. |
| 439 * @return The vertical offset of the tab. |
| 440 */ |
| 441 public float getOffsetY() { |
| 442 return mTabOffsetY; |
| 443 } |
| 444 |
| 445 private void startAnimation(Animation<Animatable<?>> animation, boolean fini
shPrevious) { |
| 446 if (finishPrevious) finishAnimation(); |
| 447 |
| 448 if (mContentAnimations == null) { |
| 449 mContentAnimations = new ChromeAnimation<Animatable<?>>(); |
| 450 } |
| 451 |
| 452 mContentAnimations.add(animation); |
| 453 } |
| 454 |
| 455 /** |
| 456 * Finishes any content animations currently owned and running on this Strip
LayoutTab. |
| 457 */ |
| 458 public void finishAnimation() { |
| 459 if (mContentAnimations == null) return; |
| 460 |
| 461 mContentAnimations.updateAndFinish(); |
| 462 mContentAnimations = null; |
| 463 } |
| 464 |
| 465 /** |
| 466 * @return Whether or not there are any content animations running on this S
tripLayoutTab. |
| 467 */ |
| 468 public boolean isAnimating() { |
| 469 return mContentAnimations != null; |
| 470 } |
| 471 |
| 472 /** |
| 473 * Updates any content animations on this StripLayoutTab. |
| 474 * @param time The current time of the app in ms. |
| 475 * @param jumpToEnd Whether or not to force any current animations to end. |
| 476 * @return Whether or not animations are done. |
| 477 */ |
| 478 public boolean onUpdateAnimation(long time, boolean jumpToEnd) { |
| 479 if (mContentAnimations == null) return true; |
| 480 |
| 481 boolean finished = true; |
| 482 if (jumpToEnd) { |
| 483 finished = mContentAnimations.finished(); |
| 484 } else { |
| 485 finished = mContentAnimations.update(time); |
| 486 } |
| 487 |
| 488 if (jumpToEnd || finished) finishAnimation(); |
| 489 |
| 490 return finished; |
| 491 } |
| 492 |
| 493 @Override |
| 494 public void setProperty(Property prop, float val) { |
| 495 switch (prop) { |
| 496 case X_OFFSET: |
| 497 setOffsetX(val); |
| 498 break; |
| 499 case Y_OFFSET: |
| 500 setOffsetY(val); |
| 501 break; |
| 502 case WIDTH: |
| 503 setWidth(val); |
| 504 break; |
| 505 } |
| 506 } |
| 507 |
| 508 private void resetCloseRect() { |
| 509 RectF closeRect = getCloseRect(); |
| 510 mCloseButton.setWidth(closeRect.width()); |
| 511 mCloseButton.setHeight(closeRect.height()); |
| 512 mCloseButton.setX(closeRect.left); |
| 513 mCloseButton.setY(closeRect.top); |
| 514 } |
| 515 |
| 516 private RectF getCloseRect() { |
| 517 if (!LocalizationUtils.isLayoutRtl()) { |
| 518 mClosePlacement.left = getWidth() - CLOSE_BUTTON_WIDTH_DP; |
| 519 mClosePlacement.right = mClosePlacement.left + CLOSE_BUTTON_WIDTH_DP
; |
| 520 } else { |
| 521 mClosePlacement.left = 0; |
| 522 mClosePlacement.right = CLOSE_BUTTON_WIDTH_DP; |
| 523 } |
| 524 |
| 525 mClosePlacement.top = 0; |
| 526 mClosePlacement.bottom = getHeight(); |
| 527 |
| 528 float xOffset = 0; |
| 529 ResourceManager manager = mRenderHost.getResourceManager(); |
| 530 if (manager != null) { |
| 531 LayoutResource resource = |
| 532 manager.getResource(AndroidResourceType.STATIC, getResourceI
d(false)); |
| 533 if (resource != null) { |
| 534 xOffset = LocalizationUtils.isLayoutRtl() |
| 535 ? resource.getPadding().left |
| 536 : -(resource.getBitmapSize().width() - resource.getPaddi
ng().right); |
| 537 } |
| 538 } |
| 539 |
| 540 mClosePlacement.offset(getDrawX() + xOffset, getDrawY()); |
| 541 return mClosePlacement; |
| 542 } |
| 543 |
| 544 // TODO(dtrainor): Don't animate this if we're selecting or deselecting this
tab. |
| 545 private void checkCloseButtonVisibility(boolean animate) { |
| 546 boolean shouldShow = |
| 547 mCanShowCloseButton && mVisiblePercentage > VISIBILITY_FADE_CLOS
E_BUTTON_PERCENTAGE; |
| 548 |
| 549 if (shouldShow != mShowingCloseButton) { |
| 550 float opacity = shouldShow ? 1.f : 0.f; |
| 551 if (animate) { |
| 552 startAnimation(buildCloseButtonOpacityAnimation(opacity), true); |
| 553 } else { |
| 554 mCloseButton.setOpacity(opacity); |
| 555 } |
| 556 mShowingCloseButton = shouldShow; |
| 557 if (!mShowingCloseButton) mCloseButton.setPressed(false); |
| 558 } |
| 559 } |
| 560 |
| 561 private Animation<Animatable<?>> buildCloseButtonOpacityAnimation(float fina
lOpacity) { |
| 562 return createAnimation(mCloseButton, CompositorButton.Property.OPACITY, |
| 563 mCloseButton.getOpacity(), finalOpacity, ANIM_TAB_CLOSE_BUTTON_F
ADE_MS, 0, false, |
| 564 ChromeAnimation.getLinearInterpolator()); |
| 565 } |
| 566 } |
OLD | NEW |