From 81eb8fd23110683a8689223aa78057e040cdcafb Mon Sep 17 00:00:00 2001 From: lynxnb Date: Fri, 17 Mar 2023 17:54:31 +0100 Subject: [PATCH] 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. --- .../skyline/input/onscreen/OnScreenButton.kt | 30 ++++++-- .../input/onscreen/OnScreenControllerView.kt | 70 +++++++++++-------- .../input/onscreen/OnScreenEditActivity.kt | 40 ++++++----- .../input/onscreen/OnScreenEditInfo.kt | 29 ++++++++ 4 files changed, 116 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt index eaf63719..19681cc3 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt @@ -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() { diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt index c7d2b617..dc657c74 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt @@ -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() @@ -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 diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt index d1dfecfe..8c500504 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt @@ -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() }, diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt new file mode 100644 index 00000000..27727427 --- /dev/null +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt @@ -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 +}