Skip to content

Commit c70feb2

Browse files
committed
feat: add drawer for draw dynamic drawable based on direction
1 parent 30b7c5a commit c70feb2

File tree

1 file changed

+248
-0
lines changed

1 file changed

+248
-0
lines changed
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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

Comments
 (0)