// Copyright 2015 The Chromium Authors
// 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.toolbar;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.text.TextPaint;
import androidx.annotation.IntDef;
import org.chromium.chrome.browser.theme.ThemeUtils;
import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.widget.TintedDrawable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Locale;
/** A drawable for the tab switcher icon. */
public class TabSwitcherDrawable extends TintedDrawable {
@IntDef({
TabSwitcherDrawableLocation.TAB_TOOLBAR,
TabSwitcherDrawableLocation.HUB_TOOLBAR,
TabSwitcherDrawableLocation.TAB_SWITCHER_TOOLBAR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface TabSwitcherDrawableLocation {
int TAB_TOOLBAR = 0;
int HUB_TOOLBAR = 1;
int TAB_SWITCHER_TOOLBAR = 2;
}
private static final float LEFT_FRACTION = 3f / 4f;
private final float mSingleDigitTextSize;
private final float mDoubleDigitTextSize;
private final Rect mTextBounds = new Rect();
private final Paint mNotificationPaint;
private final Paint mNotificationBgPaint;
private final TextPaint mTextPaint;
private final Paint mIconPaint;
// Tab Count Label
private int mTabCount;
private boolean mIncognito;
private String mTextRenderedForTesting;
private Canvas mIconCanvas;
private Bitmap mIconBitmap;
private boolean mShouldShowNotificationIcon;
private @TabSwitcherDrawableLocation int mTabSwitcherDrawableLocation;
/**
* Creates a {@link TabSwitcherDrawable}.
*
* @param context A {@link Context} instance.
* @param brandedColorScheme The {@link BrandedColorScheme} used to tint the drawable.
* @return A {@link TabSwitcherDrawable} instance.
*/
public static TabSwitcherDrawable createTabSwitcherDrawable(
Context context,
@BrandedColorScheme int brandedColorScheme,
@TabSwitcherDrawableLocation int tabSwitcherDrawableLocation) {
Bitmap icon =
BitmapFactory.decodeResource(
context.getResources(), R.drawable.btn_tabswitcher_modern);
return new TabSwitcherDrawable(
context, brandedColorScheme, icon, tabSwitcherDrawableLocation);
}
private TabSwitcherDrawable(
Context context,
@BrandedColorScheme int brandedColorScheme,
Bitmap bitmap,
@TabSwitcherDrawableLocation int tabSwitcherDrawableLocation) {
super(context, bitmap);
setTint(ThemeUtils.getThemedToolbarIconTint(context, brandedColorScheme));
mSingleDigitTextSize =
context.getResources().getDimension(R.dimen.toolbar_tab_count_text_size_1_digit);
mDoubleDigitTextSize =
context.getResources().getDimension(R.dimen.toolbar_tab_count_text_size_2_digit);
mTextPaint = new TextPaint();
mTextPaint.setAntiAlias(true);
mTextPaint.setTextAlign(Align.CENTER);
mTextPaint.setTypeface(Typeface.create("google-sans-medium", Typeface.BOLD));
mTextPaint.setColor(getColorForState());
mIconPaint = new Paint();
mIconPaint.setAntiAlias(true);
mNotificationBgPaint = new Paint();
mNotificationBgPaint.setAntiAlias(true);
mNotificationBgPaint.setStyle(Paint.Style.FILL);
mNotificationBgPaint.setColor(Color.TRANSPARENT);
mNotificationBgPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mNotificationPaint = new Paint();
mNotificationPaint.setAntiAlias(true);
mNotificationPaint.setStyle(Paint.Style.FILL);
mNotificationPaint.setColor(SemanticColorUtils.getDefaultIconColorAccent1(context));
// Draw all icon components onto a local canvas before setting on the actual canvas.
mIconBitmap =
Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
mIconCanvas = new Canvas(mIconBitmap);
mTabSwitcherDrawableLocation = tabSwitcherDrawableLocation;
}
@Override
protected boolean onStateChange(int[] state) {
boolean retVal = super.onStateChange(state);
if (retVal) mTextPaint.setColor(getColorForState());
return retVal;
}
@Override
public void draw(Canvas canvas) {
// Clear the canvas on each redraw.
mIconCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
super.draw(mIconCanvas);
Rect drawableBounds = getBounds();
String textString = getTabCountString();
mTextRenderedForTesting = textString;
if (!textString.isEmpty()) {
mTextPaint.getTextBounds(textString, 0, textString.length(), mTextBounds);
int textX = drawableBounds.width() / 2;
int textY =
drawableBounds.height() / 2
+ (mTextBounds.bottom - mTextBounds.top) / 2
- mTextBounds.bottom;
mIconCanvas.drawText(textString, textX, textY, mTextPaint);
}
// Do not show the notification icon in tab view incognito.
if (mShouldShowNotificationIcon && shouldShowNotificationOnIncognito()) {
// Draw the notification bubble.
float ringWidth = drawableBounds.width() / 18f;
float ringHeight = drawableBounds.height() / 18f;
float notifFillLeft = (drawableBounds.width() * LEFT_FRACTION) - ringWidth;
float notifFillBottom = (drawableBounds.height() / 4f) + ringHeight;
float notifBgLeft = (drawableBounds.width() * LEFT_FRACTION) - (ringWidth * 2f);
float notifBgBottom = (drawableBounds.height() / 4f) + (ringHeight * 2f);
mIconCanvas.drawOval(
notifBgLeft,
drawableBounds.top,
drawableBounds.right,
notifBgBottom,
mNotificationBgPaint);
mIconCanvas.drawOval(
notifFillLeft,
drawableBounds.top + ringHeight,
drawableBounds.right - ringWidth,
notifFillBottom,
mNotificationPaint);
}
canvas.drawBitmap(mIconBitmap, 0, 0, mIconPaint);
}
/**
* @return The current tab count this drawable is displaying.
*/
public int getTabCount() {
return mTabCount;
}
/**
* Update the visual state based on the number of tabs present.
* @param tabCount The number of tabs.
*/
public void updateForTabCount(int tabCount, boolean incognito) {
if (tabCount == mTabCount && incognito == mIncognito) return;
mTabCount = tabCount;
mIncognito = incognito;
float textSizePx = mTabCount > 9 ? mDoubleDigitTextSize : mSingleDigitTextSize;
mTextPaint.setTextSize(textSizePx);
invalidateSelf();
}
private String getTabCountString() {
if (mTabCount <= 0) {
return "";
} else if (mTabCount > 99) {
return mIncognito ? ";)" : ":D";
} else {
return String.format(Locale.getDefault(), "%d", mTabCount);
}
}
private int getColorForState() {
return mTint.getColorForState(getState(), 0);
}
private void updatePaint() {
if (mTextPaint != null) mTextPaint.setColor(getColorForState());
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
super.setColorFilter(colorFilter);
updatePaint();
}
/**
* This call is responsible for setting whether the notification bubble shows on the icon or
* not. Any non-test callsite should be guarded by the DATA_SHARING flag.
*/
public void setNotificationIconStatus(boolean shouldShow) {
mShouldShowNotificationIcon = shouldShow;
invalidateSelf();
}
public void setNotificationBackground(@BrandedColorScheme int brandedColorScheme) {
if (brandedColorScheme == BrandedColorScheme.LIGHT_BRANDED_THEME
|| brandedColorScheme == BrandedColorScheme.DARK_BRANDED_THEME) {
mNotificationBgPaint.setColor(Color.WHITE);
mNotificationBgPaint.setXfermode(null);
} else {
mNotificationBgPaint.setColor(Color.TRANSPARENT);
mNotificationBgPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
}
}
public void setIncognitoStatus(boolean isIncognito) {
mIncognito = isIncognito;
}
private boolean shouldShowNotificationOnIncognito() {
return !(mIncognito
&& mTabSwitcherDrawableLocation == TabSwitcherDrawableLocation.TAB_TOOLBAR);
}
public String getTextRenderedForTesting() {
return mTextRenderedForTesting;
}
}