1+ package com.yoimerdr.android.virtualjoystick.drawer.drawable
2+
3+ import android.graphics.Canvas
4+ import android.graphics.drawable.Drawable
5+ import com.yoimerdr.android.virtualjoystick.api.drawable.DrawableBitCache
6+ import com.yoimerdr.android.virtualjoystick.control.Control
7+ import com.yoimerdr.android.virtualjoystick.drawer.core.EmptyDrawer
8+ import com.yoimerdr.android.virtualjoystick.geometry.factory.RectFFactory
9+ import com.yoimerdr.android.virtualjoystick.geometry.size.Size
10+
11+ /* *
12+ * A drawer that uses different drawables based on the control's direction.
13+ * */
14+ open class DirectionalDrawableDrawer (
15+ /* *
16+ * The drawer properties.
17+ * */
18+ override val properties : DirectionalDrawableProperties ,
19+ ) : EmptyDrawer(
20+ properties
21+ ) {
22+
23+ /* *
24+ * @param states A map of drawables for each direction.
25+ * @param mode The sizing mode for the drawables.
26+ * */
27+ @JvmOverloads
28+ constructor (
29+ states: Map <Control .Direction , Drawable >,
30+ mode: Mode = Mode .AUTOSIZE ,
31+ ) : this (
32+ DirectionalDrawableProperties (
33+ states,
34+ mode
35+ )
36+ )
37+
38+ /* *
39+ * @param states A list of pairs of direction and drawable.
40+ * @param mode The sizing mode for the drawables.
41+ * */
42+ @JvmOverloads
43+ constructor (
44+ states: List <Pair <Control .Direction , Drawable >>,
45+ mode: Mode = Mode .AUTOSIZE ,
46+ ) : this (
47+ states.toMap(),
48+ mode
49+ )
50+
51+ /* *
52+ * @param states A map of drawables for each direction.
53+ * @param mode The sizing mode for the drawables.
54+ * */
55+ open class DirectionalDrawableProperties @JvmOverloads constructor(
56+ states : Map <Control .Direction , Drawable >,
57+ mode : Mode = Mode .AUTOSIZE ,
58+ ) : SimpleProperties() {
59+
60+ private val mStates: MutableMap <Control .Direction , Drawable > =
61+ states.toMutableMap()
62+
63+ /* *
64+ * Gets the map of drawables for each direction.
65+ * */
66+ val states: Map <Control .Direction , Drawable >
67+ get() = mStates.toMap()
68+
69+ /* *
70+ * The sizing mode for the drawables.
71+ * */
72+ var mode: Mode = mode
73+ /* *
74+ * Sets the sizing mode for the drawables.
75+ * */
76+ set(value) {
77+ hasChanged = value != field
78+ field = value
79+ }
80+
81+ /* *
82+ * Sets the drawable for a specific direction.
83+ *
84+ * @param direction The target direction.
85+ * @param drawable The drawable to set.
86+ * */
87+ fun setState (
88+ direction : Control .Direction ,
89+ drawable : Drawable ,
90+ ) {
91+ hasChanged = drawable != mStates[direction]
92+ mStates[direction] = drawable
93+ }
94+
95+ /* *
96+ * Removes the drawable for a specific direction.
97+ * @param direction The target direction to remove.
98+ * */
99+ fun removeState (
100+ direction : Control .Direction ,
101+ ) {
102+ hasChanged = mStates.remove(direction) != null
103+ }
104+
105+ /* *
106+ * Gets the drawable for a specific direction.
107+ *
108+ * @param direction The target direction.
109+ * @return The drawable for the specified direction, or null if not set.
110+ * */
111+ fun getState (
112+ direction : Control .Direction ,
113+ ): Drawable ? = mStates[direction]
114+ }
115+
116+ /* *
117+ * The sizing mode for the drawables.
118+ * */
119+ enum class Mode {
120+ /* *
121+ * The drawable size is adjusted to fit the control's radius.
122+ * */
123+ AUTOSIZE ,
124+ /* *
125+ * The drawable size is fixed to its intrinsic size.
126+ * */
127+ FIXED
128+ }
129+
130+ private val mCaches: MutableMap <Control .Direction , DrawableBitCache > = mutableMapOf ()
131+
132+ override fun canDraw (control : Control ): Boolean {
133+ return ! isValid(control)
134+ }
135+
136+ override fun onConfigured () {
137+ properties.states.forEach {
138+ if (mCaches[it.key] == null ) {
139+ mCaches[it.key] = DrawableBitCache (it.value)
140+ }
141+ }
142+ }
143+
144+ override fun onChange () {
145+ properties.states.apply {
146+ forEach {
147+ var cache = mCaches[it.key]
148+ if (cache == null ) {
149+ cache = DrawableBitCache (it.value)
150+ mCaches[it.key] = cache
151+ } else cache.drawable = it.value
152+ }
153+
154+ if (size < mCaches.size) {
155+ val keysToRemove = mCaches.keys - keys
156+ keysToRemove.forEach { key ->
157+ val cache = mCaches.remove(key)
158+ cache?.recycle()
159+ }
160+ }
161+ }
162+
163+ super .onChange()
164+ }
165+
166+ /* *
167+ * Calculates the drawable size to draw.
168+ *
169+ * @param control The [Control] from where the drawer is used.
170+ * @param drawable The drawable to be drawn.
171+ * */
172+ protected open fun getSize (control : Control , drawable : Drawable ): Size {
173+ if (properties.mode == Mode .FIXED )
174+ return Size (
175+ drawable.intrinsicWidth,
176+ drawable.intrinsicHeight
177+ )
178+ val side = (control.radius * 2f ).toInt()
179+ return Size (
180+ side,
181+ side
182+ )
183+ }
184+
185+ /* *
186+ * Adds the bitmap of the drawable for the specified direction.
187+ * @param bitmaps The list to add the bitmap to.
188+ * @param direction The target direction.
189+ * */
190+ protected fun addComponentBitmap (
191+ bitmaps : MutableList <DrawableBitCache >,
192+ direction : Control .Direction ,
193+ ) {
194+ val cache = mCaches[direction]
195+ if (cache != null )
196+ bitmaps.add(cache)
197+ }
198+
199+ override fun onDraw (canvas : Canvas , control : Control ) {
200+ val bitmaps = mutableListOf<DrawableBitCache >()
201+ val direction = lastDirection ? : control.direction
202+
203+ addComponentBitmap(
204+ bitmaps,
205+ direction
206+ )
207+
208+ if (
209+ bitmaps.isEmpty() &&
210+ direction != Control .Direction .NONE &&
211+ lastType == Control .DirectionType .COMPLETE
212+ ) {
213+ // If there is no specific drawable for the direction,
214+ // try to compose it using the available ones.
215+
216+ when (direction) {
217+ Control .Direction .UP_RIGHT -> {
218+ addComponentBitmap(bitmaps, Control .Direction .UP )
219+ addComponentBitmap(bitmaps, Control .Direction .RIGHT )
220+ }
221+
222+ Control .Direction .UP_LEFT -> {
223+ addComponentBitmap(bitmaps, Control .Direction .UP )
224+ addComponentBitmap(bitmaps, Control .Direction .LEFT )
225+ }
226+
227+ Control .Direction .DOWN_RIGHT -> {
228+ addComponentBitmap(bitmaps, Control .Direction .DOWN )
229+ addComponentBitmap(bitmaps, Control .Direction .RIGHT )
230+ }
231+
232+ Control .Direction .DOWN_LEFT -> {
233+ addComponentBitmap(bitmaps, Control .Direction .DOWN )
234+ addComponentBitmap(bitmaps, Control .Direction .LEFT )
235+ }
236+
237+ else -> {}
238+ }
239+ }
240+
241+ val center = control.center
242+ bitmaps.forEach {
243+ val size = getSize(control, it.drawable)
244+ val dest = RectFFactory .withCenterAt(center, size.width / 2f , size.height / 2f )
245+ canvas.drawBitmap(it.bitmap, null , dest, null )
246+ }
247+ }
248+ }
0 commit comments