@@ -42,6 +42,7 @@ import kotlinx.coroutines.flow.asStateFlow
4242import kotlinx.coroutines.flow.update
4343import kotlinx.coroutines.launch
4444import javax.inject.Inject
45+ import kotlin.math.roundToInt
4546
4647
4748data class ControlPadBuilderScreenState (
@@ -50,8 +51,10 @@ data class ControlPadBuilderScreenState(
5051 val showItemEditor : Boolean = false ,
5152 val itemToBeEdited : ControlPadItem ? = null ,
5253 val transformableStatesMap : SnapshotStateMap <Long , TransformableState > = SnapshotStateMap (),
53- val isModified : Boolean = false
54- )
54+ val isModified : Boolean = false ,
55+ val useAngleSnap : Boolean = false ,
56+ val angleSnapDivision : Int = 36 ,
57+ )
5558
5659sealed interface ControlPadBuilderScreenEvent {
5760 data object OnAddItemClick : ControlPadBuilderScreenEvent
@@ -65,6 +68,8 @@ sealed interface ControlPadBuilderScreenEvent {
6568 data object OnBackPress : ControlPadBuilderScreenEvent
6669 data class OnResolutionReported (val controlPad : ControlPad , val builderScreenResolution : Resolution , val tempOpen : Boolean = false ) : ControlPadBuilderScreenEvent
6770 data object OnTempOpenCompleted : ControlPadBuilderScreenEvent
71+ data object OnUseAngleSnapChange : ControlPadBuilderScreenEvent
72+ data class OnAngleSnapChange (val newValue : Float ) : ControlPadBuilderScreenEvent
6873
6974}
7075
@@ -91,6 +96,13 @@ class ControlPadBuilderScreenViewModel @Inject constructor(
9196 Log .d(tag," onCleared() ${hashCode()} " )
9297 }
9398
99+ private fun snappedRotation (input : Float ): Float {
100+ return input.div(360 )
101+ .times(_uiState .value.angleSnapDivision)// Scale the input into [-angleSnapDivision,angleSnapDivision]
102+ .roundToInt()// Snap!
103+ .times(360 ).toFloat()
104+ .div(_uiState .value.angleSnapDivision)// Scale back to normal and convert
105+ }
94106 fun loadControlPadItemsFor (controlPad : ControlPad ){
95107 _uiState .update {
96108 it.copy(isModified = false )
@@ -101,6 +113,8 @@ class ControlPadBuilderScreenViewModel @Inject constructor(
101113 Log .d(tag, items.toString())
102114 _uiState .value.controlPadItems.clear()
103115 items.forEach { item ->
116+ var temporalRotation: Float = item.rotation// I have little understanding on kotlin, thanks closure isolation
117+
104118 uiState.value.controlPadItems.add(item)
105119 uiState.value.transformableStatesMap[item.id] =
106120 TransformableState { zoomChange, offsetChange, rotationChange ->
@@ -110,15 +124,29 @@ class ControlPadBuilderScreenViewModel @Inject constructor(
110124 val controlPadItem = uiState.value.controlPadItems[index]
111125
112126 val newScale = controlPadItem.scale * zoomChange
113- val newRotation = controlPadItem.rotation + rotationChange
114- val newOffset = controlPadItem.offset + offsetChange.rotateBy(newRotation) * newScale
127+
128+ val newRotation = if (controlPadItem.itemType == ItemType .JOYSTICK || controlPadItem.itemType == ItemType .STEERING_WHEEL ) 0f
129+ else if (_uiState .value.useAngleSnap){ temporalRotation + rotationChange }
130+ else { controlPadItem.rotation + rotationChange }
131+
132+ temporalRotation = newRotation
133+
134+ val snappedNewRotation = snappedRotation(newRotation)
135+
136+ val newOffset = controlPadItem.offset + offsetChange.rotateBy(
137+ if (rotationChange == 0f ) controlPadItem.rotation
138+ else if (_uiState .value.useAngleSnap) snappedNewRotation
139+ else newRotation
140+ ) * newScale
115141
116142 uiState.value.controlPadItems[index] =
117143 controlPadItem.copy(
118144 offsetX = newOffset.x,
119145 offsetY = newOffset.y,
120146 // Joystick and steering wheel should not be rotatable
121- rotation = if (controlPadItem.itemType == ItemType .JOYSTICK || controlPadItem.itemType == ItemType .STEERING_WHEEL ) 0f else newRotation,
147+ rotation = if (rotationChange == 0f ) controlPadItem.rotation
148+ else if (_uiState .value.useAngleSnap) snappedNewRotation
149+ else newRotation,
122150 scale = newScale.coerceIn(minScale,maxScale)
123151 )
124152
@@ -198,29 +226,41 @@ class ControlPadBuilderScreenViewModel @Inject constructor(
198226 properties = event.properties
199227 )
200228 viewModelScope.launch {
229+ var temporalRotation: Float = newItem.rotation
201230 controlPadItemRepository.save(newItem).also { newId ->
202231 controlPadItemRepository.getById(newId).also { newItem ->
203232
204233 uiState.value.controlPadItems.add(newItem!! )
205234
206-
207235 uiState.value.transformableStatesMap[newItem.id] =
208236 TransformableState { zoomChange, offsetChange, rotationChange ->
209-
210237 val index = uiState.value.controlPadItems.indexOfFirst { it.id == newItem.id }
211-
212238 val controlPadItem = uiState.value.controlPadItems[index]
213239
214240 val newScale = controlPadItem.scale * zoomChange
215- val newRotation = controlPadItem.rotation + rotationChange
216- val newOffset = controlPadItem.offset + offsetChange.rotateBy(newRotation) * newScale
241+
242+ val newRotation = if (controlPadItem.itemType == ItemType .JOYSTICK || controlPadItem.itemType == ItemType .STEERING_WHEEL ) 0f
243+ else if (_uiState .value.useAngleSnap){ temporalRotation + rotationChange }
244+ else { controlPadItem.rotation + rotationChange }
245+
246+ temporalRotation = newRotation
247+
248+ val snappedNewRotation = snappedRotation(newRotation)
249+
250+ val newOffset = controlPadItem.offset + offsetChange.rotateBy(
251+ if (rotationChange == 0f ) controlPadItem.rotation
252+ else if (_uiState .value.useAngleSnap) snappedNewRotation
253+ else newRotation
254+ ) * newScale
217255
218256 uiState.value.controlPadItems[index] =
219257 controlPadItem.copy(
220258 offsetX = newOffset.x,
221259 offsetY = newOffset.y,
222260 // Joystick and steering wheel should not be rotatable
223- rotation = if (controlPadItem.itemType == ItemType .JOYSTICK || controlPadItem.itemType == ItemType .STEERING_WHEEL ) 0f else newRotation,
261+ rotation = if (rotationChange == 0f ) controlPadItem.rotation
262+ else if (_uiState .value.useAngleSnap) snappedNewRotation
263+ else newRotation,
224264 scale = newScale.coerceIn(minScale,maxScale)
225265 )
226266
@@ -260,6 +300,18 @@ class ControlPadBuilderScreenViewModel @Inject constructor(
260300
261301 }
262302
303+ is ControlPadBuilderScreenEvent .OnUseAngleSnapChange -> {
304+ _uiState .update {
305+ it.copy(useAngleSnap = ! it.useAngleSnap)
306+ }
307+ }
308+ is ControlPadBuilderScreenEvent .OnAngleSnapChange -> {
309+ _uiState .update {
310+ it.copy(angleSnapDivision = event.newValue.toInt())
311+ }
312+ }
313+
314+
263315 ControlPadBuilderScreenEvent .OnBackPress -> {}
264316 ControlPadBuilderScreenEvent .OnTempOpenCompleted -> {}
265317 }
0 commit comments