Rework OSC edit mode handling for better extensibility

Edit mode configuration parameters are now shared between the view and the buttons in a small `OnScreenEditInfo` object, avoiding variable duplication about edit state. The `editingTouchHandler` has also been simplified to only lookup the button if one wasn't being edited already.
This commit is contained in:
lynxnb 2023-03-17 17:54:31 +01:00 committed by Niccolò Betto
parent c4d0d02509
commit 81eb8fd231
4 changed files with 116 additions and 53 deletions

View File

@ -82,8 +82,10 @@ abstract class OnScreenButton(
var hapticFeedback = false var hapticFeedback = false
var isEditing = false /**
private set * The edit session information, populated by the view
*/
protected var editInfo = onScreenControllerView.editInfo
protected open fun renderCenteredText(canvas : Canvas, text : String, size : Float, x : Float, y : Float, alpha : Int) { protected open fun renderCenteredText(canvas : Canvas, text : String, size : Float, x : Float, y : Float, alpha : Int) {
buttonSymbolPaint.apply { buttonSymbolPaint.apply {
@ -133,19 +135,35 @@ abstract class OnScreenButton(
relativeY = config.relativeY relativeY = config.relativeY
} }
fun startEdit() { /**
isEditing = true * Starts an edit session
* @param x The x coordinate of the initial touch
* @param y The y coordinate of the initial touch
*/
open fun startEdit(x : Float, y : Float) {
} }
open fun edit(x : Float, y : Float) { open fun edit(x : Float, y : Float) {
when (editInfo.editMode) {
EditMode.Move -> move(x, y)
else -> return
}
}
/**
* Moves this button to the given coordinates
*/
open fun move(x : Float, y : Float) {
relativeX = x / width relativeX = x / width
relativeY = (y - heightDiff) / adjustedHeight relativeY = (y - heightDiff) / adjustedHeight
} }
fun endEdit() { /**
* Ends the current edit session
*/
open fun endEdit() {
config.relativeX = relativeX config.relativeX = relativeX
config.relativeY = relativeY config.relativeY = relativeY
isEditing = false
} }
open fun resetRelativeValues() { open fun resetRelativeValues() {

View File

@ -43,7 +43,6 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
private val ALPHA_RANGE = 55..255 private val ALPHA_RANGE = 55..255
} }
private val controls = Controls(this)
private var onButtonStateChangedListener : OnButtonStateChangedListener? = null private var onButtonStateChangedListener : OnButtonStateChangedListener? = null
private var onStickStateChangedListener : OnStickStateChangedListener? = null private var onStickStateChangedListener : OnStickStateChangedListener? = null
private val joystickAnimators = mutableMapOf<JoystickButton, Animator?>() private val joystickAnimators = mutableMapOf<JoystickButton, Animator?>()
@ -63,10 +62,15 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
(controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { it.hapticFeedback = hapticFeedback } (controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { it.hapticFeedback = hapticFeedback }
} }
val editInfo = OnScreenEditInfo()
val isEditing get() = editInfo.isEditing
// Populated externally by the activity, as retrieving the vibrator service inside the view crashes the layout editor // Populated externally by the activity, as retrieving the vibrator service inside the view crashes the layout editor
lateinit var vibrator : Vibrator lateinit var vibrator : Vibrator
private val effectClick = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK) private val effectClick = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
private val controls = Controls(this)
override fun onDraw(canvas : Canvas) { override fun onDraw(canvas : Canvas) {
super.onDraw(canvas) super.onDraw(canvas)
@ -218,42 +222,50 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
} }
private val editingTouchHandler = OnTouchListener { _, event -> private val editingTouchHandler = OnTouchListener { _, event ->
controls.allButtons.any { button -> var handled = false
when (event.actionMasked) {
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP,
MotionEvent.ACTION_CANCEL -> {
if (button.isEditing) {
button.endEdit()
return@any true
}
}
MotionEvent.ACTION_DOWN, when (event.actionMasked) {
MotionEvent.ACTION_POINTER_DOWN -> { MotionEvent.ACTION_DOWN,
if (button.config.enabled && button.isTouched(event.x, event.y)) { MotionEvent.ACTION_POINTER_DOWN -> {
button.startEdit() // Handle this event only if no other button is being edited
performClick() if (editInfo.editButton == null) {
return@any true handled = controls.allButtons.any { button ->
} if (button.config.enabled && button.isTouched(event.x, event.y)) {
} editInfo.editButton = button
button.startEdit(event.x, event.y)
MotionEvent.ACTION_MOVE -> { performClick()
if (button.isEditing) { true
button.edit(event.x, event.y) } else false
return@any true
} }
} }
} }
false
}.also { handled -> if (handled) invalidate() } MotionEvent.ACTION_MOVE -> {
editInfo.editButton?.edit(event.x, event.y)
handled = true
}
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP,
MotionEvent.ACTION_CANCEL -> {
editInfo.editButton?.endEdit()
editInfo.editButton = null
handled = true
}
}
handled.also { if (it) invalidate() }
} }
init { init {
setOnTouchListener(playingTouchHandler) setOnTouchListener(playingTouchHandler)
} }
fun setEditMode(editMode : Boolean) = setOnTouchListener(if (editMode) editingTouchHandler else playingTouchHandler) fun setEditMode(editMode : EditMode) {
editInfo.editMode = editMode
setOnTouchListener(if (editMode == EditMode.None) playingTouchHandler else editingTouchHandler)
invalidate()
}
fun resetControls() { fun resetControls() {
controls.allButtons.forEach { controls.allButtons.forEach {
@ -301,7 +313,9 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
onStickStateChangedListener = listener onStickStateChangedListener = listener
} }
fun getButtonProps() = controls.allButtons.map { Pair(it.buttonId, it.config.enabled) } data class ButtonProp(val buttonId : ButtonId, val enabled : Boolean)
fun getButtonProps() = controls.allButtons.map { ButtonProp(it.buttonId, it.config.enabled) }
fun setButtonEnabled(buttonId : ButtonId, enabled : Boolean) { fun setButtonEnabled(buttonId : ButtonId, enabled : Boolean) {
controls.allButtons.first { it.buttonId == buttonId }.config.enabled = enabled controls.allButtons.first { it.buttonId == buttonId }.config.enabled = enabled

View File

@ -29,16 +29,14 @@ class OnScreenEditActivity : AppCompatActivity() {
private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) } private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) }
private var fullEditVisible = true private var fullEditVisible = true
private var editMode = false
@Inject @Inject
lateinit var appSettings : AppSettings lateinit var appSettings : AppSettings
private val closeAction : () -> Unit = { private val closeAction : () -> Unit = {
if (editMode) { if (binding.onScreenControllerView.isEditing) {
toggleFabVisibility(true) toggleFabVisibility(true)
binding.onScreenControllerView.setEditMode(false) binding.onScreenControllerView.setEditMode(EditMode.None)
editMode = false
} else { } else {
fullEditVisible = !fullEditVisible fullEditVisible = !fullEditVisible
toggleFabVisibility(fullEditVisible) toggleFabVisibility(fullEditVisible)
@ -55,28 +53,32 @@ class OnScreenEditActivity : AppCompatActivity() {
} }
} }
private val editAction = { private val moveAction = {
editMode = true binding.onScreenControllerView.setEditMode(EditMode.Move)
binding.onScreenControllerView.setEditMode(true)
toggleFabVisibility(false) toggleFabVisibility(false)
} }
private val toggleAction : () -> Unit = { private val toggleAction : () -> Unit = {
val buttonProps = binding.onScreenControllerView.getButtonProps() val buttonProps = binding.onScreenControllerView.getButtonProps()
val checkArray = buttonProps.map { it.second }.toBooleanArray() val checkedButtonsArray = buttonProps.map { it.enabled }.toBooleanArray()
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setMultiChoiceItems(buttonProps.map { .setMultiChoiceItems(
val longText = getString(it.first.long!!) buttonProps.map { button ->
if (it.first.short == longText) longText else "$longText: ${it.first.short}" val longText = getString(button.buttonId.long!!)
}.toTypedArray(), checkArray) { _, which, isChecked -> if (button.buttonId.short == longText) longText else "$longText: ${button.buttonId.short}"
checkArray[which] = isChecked }.toTypedArray(),
}.setPositiveButton(R.string.confirm) { _, _ -> checkedButtonsArray
buttonProps.forEachIndexed { index, pair -> ) { _, which, isChecked ->
if (checkArray[index] != pair.second) checkedButtonsArray[which] = isChecked
binding.onScreenControllerView.setButtonEnabled(pair.first, checkArray[index]) }
.setPositiveButton(R.string.confirm) { _, _ ->
buttonProps.forEachIndexed { index, button ->
if (checkedButtonsArray[index] != button.enabled)
binding.onScreenControllerView.setButtonEnabled(button.buttonId, checkedButtonsArray[index])
} }
}.setNegativeButton(R.string.cancel, null) }
.setNegativeButton(R.string.cancel, null)
.setOnDismissListener { fullScreen() } .setOnDismissListener { fullScreen() }
.show() .show()
} }
@ -105,7 +107,7 @@ class OnScreenEditActivity : AppCompatActivity() {
Pair(R.drawable.ic_palette, paletteAction), Pair(R.drawable.ic_palette, paletteAction),
Pair(R.drawable.ic_restore) { binding.onScreenControllerView.resetControls() }, Pair(R.drawable.ic_restore) { binding.onScreenControllerView.resetControls() },
Pair(R.drawable.ic_toggle, toggleAction), Pair(R.drawable.ic_toggle, toggleAction),
Pair(R.drawable.ic_edit, editAction), Pair(R.drawable.ic_edit, moveAction),
Pair(R.drawable.ic_zoom_out) { binding.onScreenControllerView.decreaseScale() }, Pair(R.drawable.ic_zoom_out) { binding.onScreenControllerView.decreaseScale() },
Pair(R.drawable.ic_zoom_in) { binding.onScreenControllerView.increaseScale() }, Pair(R.drawable.ic_zoom_in) { binding.onScreenControllerView.increaseScale() },
Pair(R.drawable.ic_opacity_minus) { binding.onScreenControllerView.decreaseOpacity() }, Pair(R.drawable.ic_opacity_minus) { binding.onScreenControllerView.decreaseOpacity() },

View File

@ -0,0 +1,29 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.input.onscreen
enum class EditMode {
None,
Move,
}
/**
* A small class that holds information about the current edit session
* This is used to share information between the [OnScreenControllerView] and the individual [OnScreenButton]s
*/
class OnScreenEditInfo {
/**
* The current edit mode
*/
var editMode : EditMode = EditMode.None
/**
* The button that is currently being edited
*/
var editButton : OnScreenButton? = null
val isEditing get() = editMode != EditMode.None
}