1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.calendar
17 
18 import android.content.Context
19 import android.graphics.Canvas
20 import android.graphics.drawable.Drawable
21 import android.util.AttributeSet
22 import android.util.Log
23 import android.view.Gravity
24 import android.widget.Button
25 
26 /**
27  * A button with more than two states. When the button is pressed
28  * or clicked, the state transitions automatically.
29  *
30  * **XML attributes**
31  * See [ MultiStateButton Attributes][R.styleable.MultiStateButton],
32  * [Button][android.R.styleable.Button], [TextView Attributes][android.R.styleable.TextView],
33  * [ ][android.R.styleable.View]
34  *
35  */
36 class MultiStateButton(context: Context?, attrs: AttributeSet?, defStyle: Int) :
37                        Button(context, attrs, defStyle) {
38     //The current state for this button, ranging from 0 to maxState-1
39     var mState = 0
40         private set
41 
42     //The maximum number of states allowed for this button.
43     private var mMaxStates = 1
44 
45     //The currently displaying resource ID. This gets set to a default on creation and remains
46     //on the last set if the resources get set to null.
47     private var mButtonResource = 0
48 
49     //A list of all drawable resources used by this button in the order it uses them.
50     private var mButtonResources: IntArray
51     private var mButtonDrawable: Drawable? = null
52 
53     constructor(context: Context?) : this(context, null) {}
54     constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0) {}
55 
performClicknull56     override fun performClick(): Boolean {
57         /* When clicked, toggle the state */
58         transitionState()
59         return super.performClick()
60     }
61 
transitionStatenull62     fun transitionState() {
63         mState = (mState + 1) % mMaxStates
64         setButtonDrawable(mButtonResources[mState])
65     }
66 
67     /**
68      * Allows for a new set of drawable resource ids to be set.
69      *
70      * This sets the maximum states allowed to the length of the resources array. It will also
71      * set the current state to the maximum allowed if it's greater than the new max.
72      */
73     //@Throws(IllegalArgumentException::class)
setButtonResourcesnull74     fun setButtonResources(resources: IntArray?) {
75         if (resources == null) {
76             throw IllegalArgumentException("Button resources cannot be null")
77         }
78         mMaxStates = resources.size
79         if (mState >= mMaxStates) {
80             mState = mMaxStates - 1
81         }
82         mButtonResources = resources
83     }
84 
85     /**
86      * Attempts to set the state. Returns true if successful, false otherwise.
87      */
setStatenull88     fun setState(state: Int): Boolean {
89         if (state >= mMaxStates || state < 0) {
90             //When moved out of Calendar the tag should be changed.
91             Log.w("Cal", "MultiStateButton state set to value greater than maxState or < 0")
92             return false
93         }
94         mState = state
95         setButtonDrawable(mButtonResources[mState])
96         return true
97     }
98 
99     /**
100      * Set the background to a given Drawable, identified by its resource id.
101      *
102      * @param resid the resource id of the drawable to use as the background
103      */
setButtonDrawablenull104     fun setButtonDrawable(resid: Int) {
105         if (resid != 0 && resid == mButtonResource) {
106             return
107         }
108         mButtonResource = resid
109         var d: Drawable? = null
110         if (mButtonResource != 0) {
111             d = getResources().getDrawable(mButtonResource)
112         }
113         setButtonDrawable(d)
114     }
115 
116     /**
117      * Set the background to a given Drawable
118      *
119      * @param d The Drawable to use as the background
120      */
setButtonDrawablenull121     fun setButtonDrawable(d: Drawable?) {
122         if (d != null) {
123             if (mButtonDrawable != null) {
124                 mButtonDrawable?.setCallback(null)
125                 unscheduleDrawable(mButtonDrawable)
126             }
127             d.setCallback(this)
128             d.setState(getDrawableState())
129             d.setVisible(getVisibility() === VISIBLE, false)
130             mButtonDrawable = d
131             mButtonDrawable?.setState(getDrawableState())
132             setMinHeight(mButtonDrawable?.getIntrinsicHeight() ?: 0)
133             setWidth(mButtonDrawable?.getIntrinsicWidth() ?: 0)
134         }
135         refreshDrawableState()
136     }
137 
onDrawnull138     protected override fun onDraw(canvas: Canvas) {
139         super.onDraw(canvas)
140         if (mButtonDrawable != null) {
141             val verticalGravity: Int = getGravity() and Gravity.VERTICAL_GRAVITY_MASK
142             val horizontalGravity: Int = getGravity() and Gravity.HORIZONTAL_GRAVITY_MASK
143             val height: Int = mButtonDrawable?.getIntrinsicHeight() ?: 0
144             val width: Int = mButtonDrawable?.getIntrinsicWidth() ?: 0
145             var y = 0
146             var x = 0
147             when (verticalGravity) {
148                 Gravity.BOTTOM -> y = getHeight() - height
149                 Gravity.CENTER_VERTICAL -> y = (getHeight() - height) / 2
150             }
151             when (horizontalGravity) {
152                 Gravity.RIGHT -> x = getWidth() - width
153                 Gravity.CENTER_HORIZONTAL -> x = (getWidth() - width) / 2
154             }
155             mButtonDrawable?.setBounds(x, y, x + width, y + height)
156             mButtonDrawable?.draw(canvas)
157         }
158     }
159 
160     init {
161         //Currently using the standard buttonStyle, will update when new resources are added.
162         //TODO add a more generic default button
163         mButtonResources = intArrayOf(R.drawable.widget_show)
164         setButtonDrawable(mButtonResources[mState])
165     }
166 }