michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko; michael@0: michael@0: import org.mozilla.gecko.Telemetry; michael@0: import org.mozilla.gecko.TelemetryContract; michael@0: import org.mozilla.gecko.animation.PropertyAnimator; michael@0: import org.mozilla.gecko.animation.ViewHelper; michael@0: import org.mozilla.gecko.widget.IconTabWidget; michael@0: michael@0: import android.content.Context; michael@0: import android.content.res.Resources; michael@0: import android.graphics.Rect; michael@0: import android.os.Build; michael@0: import android.util.AttributeSet; michael@0: import android.view.LayoutInflater; michael@0: import android.view.View; michael@0: import android.view.ViewGroup; michael@0: import android.widget.Button; michael@0: import android.widget.FrameLayout; michael@0: import android.widget.ImageButton; michael@0: import android.widget.LinearLayout; michael@0: import android.widget.RelativeLayout; michael@0: michael@0: public class TabsPanel extends LinearLayout michael@0: implements LightweightTheme.OnChangeListener, michael@0: IconTabWidget.OnTabChangedListener { michael@0: private static final String LOGTAG = "GeckoTabsPanel"; michael@0: michael@0: public static enum Panel { michael@0: NORMAL_TABS, michael@0: PRIVATE_TABS, michael@0: REMOTE_TABS michael@0: } michael@0: michael@0: public static interface PanelView { michael@0: public ViewGroup getLayout(); michael@0: public void setTabsPanel(TabsPanel panel); michael@0: public void show(); michael@0: public void hide(); michael@0: public boolean shouldExpand(); michael@0: } michael@0: michael@0: public static interface TabsLayoutChangeListener { michael@0: public void onTabsLayoutChange(int width, int height); michael@0: } michael@0: michael@0: private Context mContext; michael@0: private final GeckoApp mActivity; michael@0: private final LightweightTheme mTheme; michael@0: private RelativeLayout mHeader; michael@0: private TabsListContainer mTabsContainer; michael@0: private PanelView mPanel; michael@0: private PanelView mPanelNormal; michael@0: private PanelView mPanelPrivate; michael@0: private PanelView mPanelRemote; michael@0: private RelativeLayout mFooter; michael@0: private TabsLayoutChangeListener mLayoutChangeListener; michael@0: michael@0: private IconTabWidget mTabWidget; michael@0: private static ImageButton mAddTab; michael@0: michael@0: private Panel mCurrentPanel; michael@0: private boolean mIsSideBar; michael@0: private boolean mVisible; michael@0: michael@0: public TabsPanel(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: mContext = context; michael@0: mActivity = (GeckoApp) context; michael@0: mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme(); michael@0: michael@0: setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, michael@0: LinearLayout.LayoutParams.FILL_PARENT)); michael@0: setOrientation(LinearLayout.VERTICAL); michael@0: michael@0: mCurrentPanel = Panel.NORMAL_TABS; michael@0: mVisible = false; michael@0: michael@0: mIsSideBar = false; michael@0: michael@0: LayoutInflater.from(context).inflate(R.layout.tabs_panel, this); michael@0: initialize(); michael@0: } michael@0: michael@0: private void initialize() { michael@0: mHeader = (RelativeLayout) findViewById(R.id.tabs_panel_header); michael@0: mTabsContainer = (TabsListContainer) findViewById(R.id.tabs_container); michael@0: michael@0: mPanelNormal = (PanelView) findViewById(R.id.normal_tabs); michael@0: mPanelNormal.setTabsPanel(this); michael@0: michael@0: mPanelPrivate = (PanelView) findViewById(R.id.private_tabs); michael@0: mPanelPrivate.setTabsPanel(this); michael@0: michael@0: mPanelRemote = (PanelView) findViewById(R.id.synced_tabs); michael@0: mPanelRemote.setTabsPanel(this); michael@0: michael@0: mFooter = (RelativeLayout) findViewById(R.id.tabs_panel_footer); michael@0: michael@0: mAddTab = (ImageButton) findViewById(R.id.add_tab); michael@0: mAddTab.setOnClickListener(new Button.OnClickListener() { michael@0: @Override michael@0: public void onClick(View v) { michael@0: TabsPanel.this.addTab(); michael@0: } michael@0: }); michael@0: michael@0: mTabWidget = (IconTabWidget) findViewById(R.id.tab_widget); michael@0: michael@0: mTabWidget.addTab(R.drawable.tabs_normal, R.string.tabs_normal); michael@0: mTabWidget.addTab(R.drawable.tabs_private, R.string.tabs_private); michael@0: michael@0: if (!GeckoProfile.get(mContext).inGuestMode()) { michael@0: mTabWidget.addTab(R.drawable.tabs_synced, R.string.tabs_synced); michael@0: } michael@0: michael@0: mTabWidget.setTabSelectionListener(this); michael@0: } michael@0: michael@0: private void addTab() { michael@0: if (mCurrentPanel == Panel.NORMAL_TABS) { michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_tab"); michael@0: mActivity.addTab(); michael@0: } else { michael@0: Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.ACTIONBAR, "new_private_tab"); michael@0: mActivity.addPrivateTab(); michael@0: } michael@0: michael@0: mActivity.autoHideTabs(); michael@0: } michael@0: michael@0: @Override michael@0: public void onTabChanged(int index) { michael@0: if (index == 0) michael@0: show(Panel.NORMAL_TABS, false); michael@0: else if (index == 1) michael@0: show(Panel.PRIVATE_TABS, false); michael@0: else michael@0: show(Panel.REMOTE_TABS, false); michael@0: } michael@0: michael@0: private static int getTabContainerHeight(TabsListContainer listContainer) { michael@0: Resources resources = listContainer.getContext().getResources(); michael@0: michael@0: PanelView panelView = listContainer.getCurrentPanelView(); michael@0: if (panelView != null && !panelView.shouldExpand()) { michael@0: return resources.getDimensionPixelSize(R.dimen.tabs_tray_horizontal_height); michael@0: } michael@0: michael@0: int actionBarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height); michael@0: int screenHeight = resources.getDisplayMetrics().heightPixels; michael@0: michael@0: Rect windowRect = new Rect(); michael@0: listContainer.getWindowVisibleDisplayFrame(windowRect); michael@0: int windowHeight = windowRect.bottom - windowRect.top; michael@0: michael@0: // The web content area should have at least 1.5x the height of the action bar. michael@0: // The tabs panel shouldn't take less than 50% of the screen height and can take michael@0: // up to 80% of the window height. michael@0: return (int) Math.max(screenHeight * 0.5f, michael@0: Math.min(windowHeight - 2.5f * actionBarHeight, windowHeight * 0.8f) - actionBarHeight); michael@0: } michael@0: michael@0: @Override michael@0: public void onAttachedToWindow() { michael@0: super.onAttachedToWindow(); michael@0: mTheme.addListener(this); michael@0: } michael@0: michael@0: @Override michael@0: public void onDetachedFromWindow() { michael@0: super.onDetachedFromWindow(); michael@0: mTheme.removeListener(this); michael@0: } michael@0: michael@0: @Override michael@0: public void onLightweightThemeChanged() { michael@0: final int background = getResources().getColor(R.color.background_tabs); michael@0: final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background, true); michael@0: if (drawable == null) michael@0: return; michael@0: michael@0: drawable.setAlpha(34, 0); michael@0: setBackgroundDrawable(drawable); michael@0: } michael@0: michael@0: @Override michael@0: public void onLightweightThemeReset() { michael@0: setBackgroundColor(getContext().getResources().getColor(R.color.background_tabs)); michael@0: } michael@0: michael@0: @Override michael@0: protected void onLayout(boolean changed, int left, int top, int right, int bottom) { michael@0: super.onLayout(changed, left, top, right, bottom); michael@0: onLightweightThemeChanged(); michael@0: } michael@0: michael@0: // Tabs List Container holds the ListView michael@0: public static class TabsListContainer extends FrameLayout { michael@0: public TabsListContainer(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: } michael@0: michael@0: public PanelView getCurrentPanelView() { michael@0: final int childCount = getChildCount(); michael@0: for (int i = 0; i < childCount; i++) { michael@0: View child = getChildAt(i); michael@0: if (!(child instanceof PanelView)) michael@0: continue; michael@0: michael@0: if (child.getVisibility() == View.VISIBLE) michael@0: return (PanelView) child; michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: michael@0: @Override michael@0: protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { michael@0: if (!GeckoAppShell.getGeckoInterface().hasTabsSideBar()) { michael@0: int heightSpec = MeasureSpec.makeMeasureSpec(getTabContainerHeight(TabsListContainer.this), MeasureSpec.EXACTLY); michael@0: super.onMeasure(widthMeasureSpec, heightSpec); michael@0: } else { michael@0: super.onMeasure(widthMeasureSpec, heightMeasureSpec); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Tabs Panel Toolbar contains the Buttons michael@0: public static class TabsPanelToolbar extends LinearLayout michael@0: implements LightweightTheme.OnChangeListener { michael@0: private final LightweightTheme mTheme; michael@0: michael@0: public TabsPanelToolbar(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: mTheme = ((GeckoApplication) context.getApplicationContext()).getLightweightTheme(); michael@0: michael@0: setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, michael@0: (int) context.getResources().getDimension(R.dimen.browser_toolbar_height))); michael@0: michael@0: setOrientation(LinearLayout.HORIZONTAL); michael@0: } michael@0: michael@0: @Override michael@0: public void onAttachedToWindow() { michael@0: super.onAttachedToWindow(); michael@0: mTheme.addListener(this); michael@0: } michael@0: michael@0: @Override michael@0: public void onDetachedFromWindow() { michael@0: super.onDetachedFromWindow(); michael@0: mTheme.removeListener(this); michael@0: } michael@0: michael@0: @Override michael@0: public void onLightweightThemeChanged() { michael@0: final int background = getResources().getColor(R.color.background_tabs); michael@0: final LightweightThemeDrawable drawable = mTheme.getColorDrawable(this, background); michael@0: if (drawable == null) michael@0: return; michael@0: michael@0: drawable.setAlpha(34, 34); michael@0: setBackgroundDrawable(drawable); michael@0: } michael@0: michael@0: @Override michael@0: public void onLightweightThemeReset() { michael@0: setBackgroundColor(getContext().getResources().getColor(R.color.background_tabs)); michael@0: } michael@0: michael@0: @Override michael@0: protected void onLayout(boolean changed, int left, int top, int right, int bottom) { michael@0: super.onLayout(changed, left, top, right, bottom); michael@0: onLightweightThemeChanged(); michael@0: } michael@0: } michael@0: michael@0: public void show(Panel panel) { michael@0: show(panel, true); michael@0: } michael@0: michael@0: public void show(Panel panel, boolean shouldResize) { michael@0: if (!isShown()) michael@0: setVisibility(View.VISIBLE); michael@0: michael@0: if (mPanel != null) { michael@0: // Hide the old panel. michael@0: mPanel.hide(); michael@0: } michael@0: michael@0: final boolean showAnimation = !mVisible; michael@0: mVisible = true; michael@0: mCurrentPanel = panel; michael@0: michael@0: int index = panel.ordinal(); michael@0: mTabWidget.setCurrentTab(index); michael@0: michael@0: if (index == 0) { michael@0: mPanel = mPanelNormal; michael@0: } else if (index == 1) { michael@0: mPanel = mPanelPrivate; michael@0: } else { michael@0: mPanel = mPanelRemote; michael@0: } michael@0: michael@0: mPanel.show(); michael@0: michael@0: if (mCurrentPanel == Panel.REMOTE_TABS) { michael@0: if (mFooter != null) michael@0: mFooter.setVisibility(View.GONE); michael@0: michael@0: mAddTab.setVisibility(View.INVISIBLE); michael@0: } else { michael@0: if (mFooter != null) michael@0: mFooter.setVisibility(View.VISIBLE); michael@0: michael@0: mAddTab.setVisibility(View.VISIBLE); michael@0: mAddTab.setImageLevel(index); michael@0: } michael@0: michael@0: if (shouldResize) { michael@0: if (isSideBar()) { michael@0: if (showAnimation) michael@0: dispatchLayoutChange(getWidth(), getHeight()); michael@0: } else { michael@0: int actionBarHeight = mContext.getResources().getDimensionPixelSize(R.dimen.browser_toolbar_height); michael@0: int height = actionBarHeight + getTabContainerHeight(mTabsContainer); michael@0: dispatchLayoutChange(getWidth(), height); michael@0: } michael@0: } michael@0: } michael@0: michael@0: public void hide() { michael@0: if (mVisible) { michael@0: mVisible = false; michael@0: dispatchLayoutChange(0, 0); michael@0: } michael@0: } michael@0: michael@0: public void refresh() { michael@0: removeAllViews(); michael@0: michael@0: LayoutInflater.from(mContext).inflate(R.layout.tabs_panel, this); michael@0: initialize(); michael@0: michael@0: if (mVisible) michael@0: show(mCurrentPanel); michael@0: } michael@0: michael@0: public void autoHidePanel() { michael@0: mActivity.autoHideTabs(); michael@0: } michael@0: michael@0: @Override michael@0: public boolean isShown() { michael@0: return mVisible; michael@0: } michael@0: michael@0: public boolean isSideBar() { michael@0: return mIsSideBar; michael@0: } michael@0: michael@0: public void setIsSideBar(boolean isSideBar) { michael@0: mIsSideBar = isSideBar; michael@0: } michael@0: michael@0: public Panel getCurrentPanel() { michael@0: return mCurrentPanel; michael@0: } michael@0: michael@0: public void prepareTabsAnimation(PropertyAnimator animator) { michael@0: // Not worth doing this on pre-Honeycomb without proper michael@0: // hardware accelerated animations. michael@0: if (Build.VERSION.SDK_INT < 11) { michael@0: return; michael@0: } michael@0: michael@0: final Resources resources = getContext().getResources(); michael@0: final int toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height); michael@0: final int tabsPanelWidth = getWidth(); michael@0: michael@0: if (mVisible) { michael@0: if (mIsSideBar) { michael@0: ViewHelper.setTranslationX(mHeader, -tabsPanelWidth); michael@0: } else { michael@0: ViewHelper.setTranslationY(mHeader, -toolbarHeight); michael@0: } michael@0: michael@0: if (mIsSideBar) { michael@0: ViewHelper.setTranslationX(mTabsContainer, -tabsPanelWidth); michael@0: } else { michael@0: ViewHelper.setTranslationY(mTabsContainer, -toolbarHeight); michael@0: ViewHelper.setAlpha(mTabsContainer, 0); michael@0: } michael@0: michael@0: // The footer view is only present on the sidebar michael@0: if (mIsSideBar) { michael@0: ViewHelper.setTranslationX(mFooter, -tabsPanelWidth); michael@0: } michael@0: } michael@0: michael@0: if (mIsSideBar) { michael@0: final int translationX = (mVisible ? 0 : -tabsPanelWidth); michael@0: michael@0: animator.attach(mTabsContainer, michael@0: PropertyAnimator.Property.TRANSLATION_X, michael@0: translationX); michael@0: animator.attach(mHeader, michael@0: PropertyAnimator.Property.TRANSLATION_X, michael@0: translationX); michael@0: animator.attach(mFooter, michael@0: PropertyAnimator.Property.TRANSLATION_X, michael@0: translationX); michael@0: } else { michael@0: final int translationY = (mVisible ? 0 : -toolbarHeight); michael@0: michael@0: animator.attach(mTabsContainer, michael@0: PropertyAnimator.Property.ALPHA, michael@0: mVisible ? 1.0f : 0.0f); michael@0: animator.attach(mTabsContainer, michael@0: PropertyAnimator.Property.TRANSLATION_Y, michael@0: translationY); michael@0: animator.attach(mHeader, michael@0: PropertyAnimator.Property.TRANSLATION_Y, michael@0: translationY); michael@0: } michael@0: michael@0: mHeader.setLayerType(View.LAYER_TYPE_HARDWARE, null); michael@0: mTabsContainer.setLayerType(View.LAYER_TYPE_HARDWARE, null); michael@0: } michael@0: michael@0: public void finishTabsAnimation() { michael@0: if (Build.VERSION.SDK_INT < 11) { michael@0: return; michael@0: } michael@0: michael@0: mHeader.setLayerType(View.LAYER_TYPE_NONE, null); michael@0: mTabsContainer.setLayerType(View.LAYER_TYPE_NONE, null); michael@0: michael@0: // If the tray is now hidden, call hide() on current panel and unset it as the current panel michael@0: // to avoid hide() being called again when the tray is opened next. michael@0: if (!mVisible && mPanel != null) { michael@0: mPanel.hide(); michael@0: mPanel = null; michael@0: } michael@0: } michael@0: michael@0: public void setTabsLayoutChangeListener(TabsLayoutChangeListener listener) { michael@0: mLayoutChangeListener = listener; michael@0: } michael@0: michael@0: private void dispatchLayoutChange(int width, int height) { michael@0: if (mLayoutChangeListener != null) michael@0: mLayoutChangeListener.onTabsLayoutChange(width, height); michael@0: } michael@0: }