// Copyright 2016 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.compositor.overlays.strip; import org.chromium.chrome.browser.util.MathUtils; import org.chromium.ui.base.LocalizationUtils; /** * A stacker that tells the {@link StripLayoutHelper} how to layer the tabs for the * {@link StaticLayout} when the available window width is >= 600dp. Tabs will be stacked with the * focused tab in front with tabs cascading back to each side. */ public class CascadingStripStacker extends StripStacker { @Override public void setTabOffsets(int selectedIndex, StripLayoutTab[] indexOrderedTabs, float tabStackWidth, int maxTabsToStack, float tabOverlapWidth, float stripLeftMargin, float stripRightMargin, float stripWidth, boolean inReorderMode) { // 1. Calculate the size of the selected tab. This is used later to figure out how // occluded the tabs are. final StripLayoutTab selTab = selectedIndex >= 0 ? indexOrderedTabs[selectedIndex] : null; final float selTabWidth = selTab != null ? selTab.getWidth() : 0; final float selTabVisibleSize = selTabWidth - tabStackWidth - tabOverlapWidth; for (int i = 0; i < indexOrderedTabs.length; i++) { StripLayoutTab tab = indexOrderedTabs[i]; float posX = tab.getIdealX(); // 2. Calculate how many tabs are stacked on the left or the right, giving us an idea // of where we can stack this current tab. int leftStackCount = (i < selectedIndex) ? Math.min(i, maxTabsToStack) : Math.min(maxTabsToStack, selectedIndex) + Math.min(maxTabsToStack, i - selectedIndex); int rightStackCount = (i >= selectedIndex) ? Math.min(indexOrderedTabs.length - 1 - i, maxTabsToStack) : Math.min(indexOrderedTabs.length - 1 - selectedIndex, maxTabsToStack) + Math.min(selectedIndex - i, maxTabsToStack); if (LocalizationUtils.isLayoutRtl()) { int oldLeft = leftStackCount; leftStackCount = rightStackCount; rightStackCount = oldLeft; } // 3. Calculate the proper draw position for the tab. Clamp based on stacking // rules. float minDrawX = tabStackWidth * leftStackCount + stripLeftMargin; float maxDrawX = stripWidth - tabStackWidth * rightStackCount - stripRightMargin; float drawX = MathUtils.clamp(posX + tab.getOffsetX(), minDrawX, maxDrawX - tab.getWidth()); // TODO(dtrainor): Don't set drawX if the tab is closing? tab.setDrawX(drawX); tab.setDrawY(tab.getOffsetY()); // 4. Calculate how visible this tab is. float visiblePercentage = 1.f; if (i != selectedIndex) { final float effectiveTabWidth = Math.max(tab.getWidth(), 1.f); final boolean leftStack = LocalizationUtils.isLayoutRtl() ? i > selectedIndex : i < selectedIndex; final float minVisible = !leftStack ? minDrawX + selTabVisibleSize : minDrawX; final float maxVisible = leftStack ? maxDrawX - selTabVisibleSize : maxDrawX; final float clippedTabWidth = Math.min(posX + effectiveTabWidth, maxVisible) - Math.max(posX, minVisible); visiblePercentage = MathUtils.clamp(clippedTabWidth / effectiveTabWidth, 0.f, 1.f); } tab.setVisiblePercentage(visiblePercentage); // 5. Calculate which index we start sliding content for. // When reordering, we don't want to slide the content of the adjacent tabs. int contentOffsetIndex = inReorderMode ? selectedIndex + 1 : selectedIndex; // 6. Calculate how much the tab is overlapped on the left side or right for RTL. float hiddenAmount = 0.f; if (i > contentOffsetIndex && i > 0) { // 6.a. Get the effective right edge of the previous tab. final StripLayoutTab prevTab = indexOrderedTabs[i - 1]; final float prevLayoutWidth = (prevTab.getWidth() - tabOverlapWidth) * prevTab.getWidthWeight(); float prevTabRight = prevTab.getDrawX(); if (!LocalizationUtils.isLayoutRtl()) prevTabRight += prevLayoutWidth; // 6.b. Subtract our current draw X from the previous tab's right edge and // get the percentage covered. hiddenAmount = Math.max(prevTabRight - drawX, 0); if (LocalizationUtils.isLayoutRtl()) { // Invert The amount because we're RTL. hiddenAmount = prevLayoutWidth - hiddenAmount; } } tab.setContentOffsetX(hiddenAmount); } } @Override public float computeNewTabButtonOffset(StripLayoutTab[] indexOrderedTabs, float tabOverlapWidth, float stripLeftMargin, float stripRightMargin, float stripWidth, float newTabButtonWidth) { return LocalizationUtils.isLayoutRtl() ? computeNewTabButtonOffsetRtl(indexOrderedTabs, tabOverlapWidth, stripLeftMargin, stripRightMargin, stripWidth, newTabButtonWidth) : computeNewTabButtonOffsetLtr(indexOrderedTabs, tabOverlapWidth, stripLeftMargin, stripRightMargin, stripWidth); } @Override public void performOcclusionPass(int selectedIndex, StripLayoutTab[] indexOrderedTabs, float stripWidth) { for (int i = 1; i < indexOrderedTabs.length; i++) { StripLayoutTab prevTab = indexOrderedTabs[i - 1]; StripLayoutTab currTab = indexOrderedTabs[i]; if ((int) prevTab.getDrawY() == (int) currTab.getDrawY() && (int) prevTab.getDrawX() == (int) currTab.getDrawX()) { if (i <= selectedIndex) { prevTab.setVisible(false); } else if (i > selectedIndex) { currTab.setVisible(false); } } else if ((int) prevTab.getDrawX() != (int) currTab.getDrawX()) { if (i <= selectedIndex) { prevTab.setVisible(true); } else if (i > selectedIndex) { currTab.setVisible(true); } } if (i == selectedIndex) currTab.setVisible(true); // If index 0 is selected, this line is required to set its visibility correctly. if (i - 1 == selectedIndex) prevTab.setVisible(true); } } private float computeNewTabButtonOffsetLtr(StripLayoutTab[] indexOrderedTabs, float tabOverlapWidth, float stripLeftMargin, float stripRightMargin, float stripWidth) { float rightEdge = stripLeftMargin; for (int i = 0; i < indexOrderedTabs.length; i++) { StripLayoutTab tab = indexOrderedTabs[i]; float layoutWidth = (tab.getWidth() - tabOverlapWidth) * tab.getWidthWeight(); rightEdge = Math.max(tab.getDrawX() + layoutWidth, rightEdge); } rightEdge = Math.min(rightEdge + tabOverlapWidth, stripWidth - stripRightMargin); // Adjust the right edge by the tab overlap width so that the new tab button is nestled // closer to the tab. rightEdge -= tabOverlapWidth / 2; // The draw X position for the new tab button is the rightEdge of the tab strip. return rightEdge; } private float computeNewTabButtonOffsetRtl(StripLayoutTab[] indexOrderedTabs, float tabOverlapWidth, float stripLeftMargin, float stripRightMargin, float stripWidth, float newTabButtonWidth) { float leftEdge = stripWidth - stripRightMargin; for (int i = 0; i < indexOrderedTabs.length; i++) { StripLayoutTab tab = indexOrderedTabs[i]; leftEdge = Math.min(tab.getDrawX(), leftEdge); } leftEdge = Math.max(leftEdge, stripLeftMargin); // Adjust the left edge by the tab overlap width so that the new tab button is nestled // closer to the tab. leftEdge += tabOverlapWidth / 2; // The draw X position for the new tab button is the left edge of the tab strip minus // the new tab button width. return leftEdge - newTabButtonWidth; } }