chromium/chrome/android/java/src/org/chromium/chrome/browser/compositor/layouts/components/CompositorButton.java

// 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.compositor.layouts.components;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.util.FloatProperty;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutView;
import org.chromium.ui.MotionEventUtils;

/**
 * {@link CompositorButton} keeps track of state for buttons that are rendered in the compositor.
 */
public class CompositorButton extends StripLayoutView {
    /**
     * A property that can be used with a {@link
     * org.chromium.chrome.browser.layouts.animation.CompositorAnimator}.
     */
    public static final FloatProperty<CompositorButton> OPACITY =
            new FloatProperty<CompositorButton>("opacity") {
                @Override
                public void setValue(CompositorButton object, float value) {
                    object.setOpacity(value);
                }

                @Override
                public Float get(CompositorButton object) {
                    return object.getOpacity();
                }
            };

    /** Handler for click actions on VirtualViews. */
    public interface CompositorOnClickHandler {
        /**
         * Handles the click action.
         *
         * @param time The time of the click action.
         */
        void onClick(long time);
    }

    private final CompositorOnClickHandler mClickHandler;

    protected int mResource;
    protected int mBackgroundResource;

    private int mPressedResource;
    private int mIncognitoResource;
    private int mIncognitoPressedResource;

    private float mOpacity;
    private boolean mIsPressed;
    private boolean mIsPressedFromMouse;
    private boolean mIsHovered;
    private String mAccessibilityDescriptionIncognito = "";

    /**
     * Default constructor for {@link CompositorButton}
     *
     * @param context An Android context for fetching dimens.
     * @param width The button width.
     * @param height The button height.
     * @param clickHandler The action to be performed on click.
     */
    public CompositorButton(
            Context context, float width, float height, CompositorOnClickHandler clickHandler) {
        super(false);
        mDrawBounds.set(0, 0, width, height);

        mOpacity = 1.f;
        mIsPressed = false;
        setVisible(true);

        Resources res = context.getResources();
        float sPxToDp = 1.0f / res.getDisplayMetrics().density;
        float clickSlop = res.getDimension(R.dimen.compositor_button_slop) * sPxToDp;
        setTouchTargetInsets(-clickSlop, -clickSlop, -clickSlop, -clickSlop);

        mClickHandler = clickHandler;
    }

    /**
     * A set of Android resources to supply to the compositor.
     * @param resource                  The default Android resource.
     * @param pressedResource           The pressed Android resource.
     * @param incognitoResource         The incognito Android resource.
     * @param incognitoPressedResource  The incognito pressed resource.
     */
    public void setResources(
            int resource,
            int pressedResource,
            int incognitoResource,
            int incognitoPressedResource) {
        mResource = resource;
        mPressedResource = pressedResource;
        mIncognitoResource = incognitoResource;
        mIncognitoPressedResource = incognitoPressedResource;
    }

    /**
     * @param description A string describing the resource.
     */
    public void setAccessibilityDescription(String description, String incognitoDescription) {
        super.setAccessibilityDescription(description);
        mAccessibilityDescriptionIncognito = incognitoDescription;
    }

    /** {@link org.chromium.chrome.browser.layouts.components.VirtualView} Implementation */
    @Override
    public String getAccessibilityDescription() {
        return isIncognito()
                ? mAccessibilityDescriptionIncognito
                : super.getAccessibilityDescription();
    }

    @Override
    public boolean checkClickedOrHovered(float x, float y) {
        if (mOpacity < 1.f || !isVisible()) return false;
        return super.checkClickedOrHovered(x, y);
    }

    @Override
    public void handleClick(long time) {
        mClickHandler.onClick(time);
    }

    /**
     * @param bounds A {@link RectF} representing the location of the button.
     */
    public void setBounds(RectF bounds) {
        mDrawBounds.set(bounds);
    }

    /**
     * @return The opacity of the button.
     */
    public float getOpacity() {
        return mOpacity;
    }

    /**
     * @param opacity The opacity of the button.
     */
    public void setOpacity(float opacity) {
        mOpacity = opacity;
    }

    /**
     * @return The pressed state of the button.
     */
    public boolean isPressed() {
        return mIsPressed;
    }

    /**
     * @param state The pressed state of the button.
     */
    public void setPressed(boolean state) {
        mIsPressed = state;

        // clear isPressedFromMouse state.
        if (!state) {
            setPressedFromMouse(false);
        }
    }

    /**
     * @param state The pressed state of the button.
     * @param fromMouse Whether the event originates from a mouse.
     */
    public void setPressed(boolean state, boolean fromMouse) {
        mIsPressed = state;
        mIsPressedFromMouse = fromMouse;
    }

    /**
     * @param slop The additional area outside of the button to be considered when checking click
     *     target bounds.
     */
    public void setClickSlop(float slop) {
        setTouchTargetInsets(-slop, -slop, -slop, -slop);
    }

    /**
     * @return The Android resource id for this button based on it's state.
     */
    public int getResourceId() {
        if (isPressed()) {
            return isIncognito() ? mIncognitoPressedResource : mPressedResource;
        }
        return isIncognito() ? mIncognitoResource : mResource;
    }

    /**
     * Set state for a drag event.
     * @param x     The x offset of the event.
     * @param y     The y offset of the event.
     * @return      Whether or not the button is selected after the event.
     */
    public boolean drag(float x, float y) {
        if (!checkClickedOrHovered(x, y)) {
            setPressed(false);
            return false;
        }
        return isPressed();
    }

    /**
     * Set state for an onDown event.
     *
     * @param x The x offset of the event.
     * @param y The y offset of the event.
     * @param fromMouse Whether the event originates from a mouse.
     * @param buttons State of all buttons that were pressed when onDown was invoked.
     * @return Whether or not the button was hit.
     */
    public boolean onDown(float x, float y, boolean fromMouse, int buttons) {
        if (checkClickedOrHovered(x, y)
                && MotionEventUtils.isTouchOrPrimaryButton(fromMouse, buttons)) {
            setPressed(true, fromMouse);
            return true;
        }
        return false;
    }

    /**
     * @param x The x offset of the event.
     * @param y The y offset of the event.
     * @param fromMouse Whether the event originates from a mouse.
     * @param buttons State of all buttons that were pressed when onDown was invoked.
     * @return Whether or not the button was clicked.
     */
    public boolean click(float x, float y, boolean fromMouse, int buttons) {
        if (checkClickedOrHovered(x, y)
                && MotionEventUtils.isTouchOrPrimaryButton(fromMouse, buttons)) {
            setPressed(false, false);
            return true;
        }
        return false;
    }

    /**
     * Set state for an onUpOrCancel event.
     * @return Whether or not the button was selected.
     */
    public boolean onUpOrCancel() {
        boolean state = isPressed();
        setPressed(false, false);
        return state;
    }

    /**
     * Set whether button is hovered on.
     *
     * @param isHovered Whether the button is hovered on.
     */
    public void setHovered(boolean isHovered) {
        mIsHovered = isHovered;
    }

    /**
     * @return Whether the button is hovered on.
     */
    public boolean isHovered() {
        return mIsHovered;
    }

    /**
     * Set whether the button is pressed from mouse.
     *
     * @param isPressedFromMouse Whether the button is pressed from mouse.
     */
    private void setPressedFromMouse(boolean isPressedFromMouse) {
        mIsPressedFromMouse = isPressedFromMouse;
    }

    /**
     * @return Whether the button is pressed from mouse.
     */
    public boolean isPressedFromMouse() {
        return mIsPressed && mIsPressedFromMouse;
    }

    /**
     * @return Whether hover background should be applied to the button.
     */
    public boolean getShouldApplyHoverBackground() {
        return isHovered() || isPressedFromMouse();
    }
}