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 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) {
buttonSymbolPaint.apply {
@ -133,19 +135,35 @@ abstract class OnScreenButton(
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) {
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
relativeY = (y - heightDiff) / adjustedHeight
}
fun endEdit() {
/**
* Ends the current edit session
*/
open fun endEdit() {
config.relativeX = relativeX
config.relativeY = relativeY
isEditing = false
}
open fun resetRelativeValues() {

View File

@ -43,7 +43,6 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
private val ALPHA_RANGE = 55..255
}
private val controls = Controls(this)
private var onButtonStateChangedListener : OnButtonStateChangedListener? = null
private var onStickStateChangedListener : OnStickStateChangedListener? = null
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 }
}
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
lateinit var vibrator : Vibrator
private val effectClick = VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
private val controls = Controls(this)
override fun onDraw(canvas : Canvas) {
super.onDraw(canvas)
@ -218,42 +222,50 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
}
private val editingTouchHandler = OnTouchListener { _, event ->
controls.allButtons.any { button ->
when (event.actionMasked) {
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP,
MotionEvent.ACTION_CANCEL -> {
if (button.isEditing) {
button.endEdit()
return@any true
}
}
var handled = false
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
if (button.config.enabled && button.isTouched(event.x, event.y)) {
button.startEdit()
performClick()
return@any true
}
}
MotionEvent.ACTION_MOVE -> {
if (button.isEditing) {
button.edit(event.x, event.y)
return@any true
when (event.actionMasked) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
// Handle this event only if no other button is being edited
if (editInfo.editButton == null) {
handled = controls.allButtons.any { button ->
if (button.config.enabled && button.isTouched(event.x, event.y)) {
editInfo.editButton = button
button.startEdit(event.x, event.y)
performClick()
true
} else false
}
}
}
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 {
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() {
controls.allButtons.forEach {
@ -301,7 +313,9 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
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) {
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 var fullEditVisible = true
private var editMode = false
@Inject
lateinit var appSettings : AppSettings
private val closeAction : () -> Unit = {
if (editMode) {
if (binding.onScreenControllerView.isEditing) {
toggleFabVisibility(true)
binding.onScreenControllerView.setEditMode(false)
editMode = false
binding.onScreenControllerView.setEditMode(EditMode.None)
} else {
fullEditVisible = !fullEditVisible
toggleFabVisibility(fullEditVisible)
@ -55,28 +53,32 @@ class OnScreenEditActivity : AppCompatActivity() {
}
}
private val editAction = {
editMode = true
binding.onScreenControllerView.setEditMode(true)
private val moveAction = {
binding.onScreenControllerView.setEditMode(EditMode.Move)
toggleFabVisibility(false)
}
private val toggleAction : () -> Unit = {
val buttonProps = binding.onScreenControllerView.getButtonProps()
val checkArray = buttonProps.map { it.second }.toBooleanArray()
val checkedButtonsArray = buttonProps.map { it.enabled }.toBooleanArray()
MaterialAlertDialogBuilder(this)
.setMultiChoiceItems(buttonProps.map {
val longText = getString(it.first.long!!)
if (it.first.short == longText) longText else "$longText: ${it.first.short}"
}.toTypedArray(), checkArray) { _, which, isChecked ->
checkArray[which] = isChecked
}.setPositiveButton(R.string.confirm) { _, _ ->
buttonProps.forEachIndexed { index, pair ->
if (checkArray[index] != pair.second)
binding.onScreenControllerView.setButtonEnabled(pair.first, checkArray[index])
.setMultiChoiceItems(
buttonProps.map { button ->
val longText = getString(button.buttonId.long!!)
if (button.buttonId.short == longText) longText else "$longText: ${button.buttonId.short}"
}.toTypedArray(),
checkedButtonsArray
) { _, which, isChecked ->
checkedButtonsArray[which] = isChecked
}
.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() }
.show()
}
@ -105,7 +107,7 @@ class OnScreenEditActivity : AppCompatActivity() {
Pair(R.drawable.ic_palette, paletteAction),
Pair(R.drawable.ic_restore) { binding.onScreenControllerView.resetControls() },
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_in) { binding.onScreenControllerView.increaseScale() },
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
}