diff --git a/app/src/main/java/emu/skyline/input/GuestEvent.kt b/app/src/main/java/emu/skyline/input/GuestEvent.kt index 2f13557b..2fb47069 100644 --- a/app/src/main/java/emu/skyline/input/GuestEvent.kt +++ b/app/src/main/java/emu/skyline/input/GuestEvent.kt @@ -44,7 +44,8 @@ enum class ButtonId(val value : Long, val short : String? = null, val long : Int LeftSR(1 shl 25, "SR", string.right_shoulder), RightSL(1 shl 26, "SL", string.left_shoulder), RightSR(1 shl 27, "SR", string.right_shoulder), - Menu(1 shl 28, "⌂︎", string.emu_menu_button); + Menu(1 shl 28, "⌂︎", string.emu_menu_button), + All(0x1FFFFFFF, "All"); } /** diff --git a/app/src/main/java/emu/skyline/input/onscreen/ConfigurableButton.kt b/app/src/main/java/emu/skyline/input/onscreen/ConfigurableButton.kt new file mode 100644 index 00000000..3f4fd0eb --- /dev/null +++ b/app/src/main/java/emu/skyline/input/onscreen/ConfigurableButton.kt @@ -0,0 +1,43 @@ +/* + * SPDX-License-Identifier: MPL-2.0 + * Copyright © 2023 Skyline Team and Contributors (https://github.com/skyline-emu/) + */ + +package emu.skyline.input.onscreen + +import emu.skyline.input.ButtonId + +/** + * This interface is used to allow proxying of [OnScreenButton]s + */ +interface ConfigurableButton { + val buttonId : ButtonId + val config : OnScreenConfiguration + + /** + * Starts a button move session + * @param x The x coordinate of the initial touch + * @param y The y coordinate of the initial touch + */ + fun startMove(x : Float, y : Float) + + /** + * Moves this button to the given coordinates + */ + fun move(x : Float, y : Float) + + /** + * Ends the current move session + */ + fun endMove() + + /** + * Resets the button to its default configuration + */ + fun resetConfig() + + fun moveUp() + fun moveDown() + fun moveLeft() + fun moveRight() +} 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 0b8d14f9..ac0c5b91 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenButton.kt @@ -22,14 +22,14 @@ import kotlin.math.roundToInt */ abstract class OnScreenButton( onScreenControllerView : OnScreenControllerView, - val buttonId : ButtonId, + final override val buttonId : ButtonId, private val defaultRelativeX : Float, private val defaultRelativeY : Float, private val defaultRelativeWidth : Float, private val defaultRelativeHeight : Float, drawableId : Int, private val defaultEnabled : Boolean -) { +) : ConfigurableButton { companion object { /** * Aspect ratio the default values were based on @@ -37,7 +37,7 @@ abstract class OnScreenButton( const val CONFIGURED_ASPECT_RATIO = 2074f / 874f } - val config = OnScreenConfiguration(onScreenControllerView.context, buttonId, defaultRelativeX, defaultRelativeY, defaultEnabled) + final override val config = OnScreenConfigurationImpl(onScreenControllerView.context, buttonId, defaultRelativeX, defaultRelativeY, defaultEnabled) protected val drawable = ContextCompat.getDrawable(onScreenControllerView.context, drawableId)!! @@ -50,8 +50,8 @@ abstract class OnScreenButton( var relativeX = config.relativeX var relativeY = config.relativeY - private val relativeWidth get() = defaultRelativeWidth * (config.globalScale + config.scale) - private val relativeHeight get() = defaultRelativeHeight * (config.globalScale + config.scale) + private val relativeWidth get() = defaultRelativeWidth * config.scale + private val relativeHeight get() = defaultRelativeHeight * config.scale /** * The width of the view this button is in, populated by the view during draw @@ -158,28 +158,17 @@ abstract class OnScreenButton( relativeY = config.relativeY } - /** - * 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) { + fun saveConfigValues() { + config.relativeX = relativeX + config.relativeY = relativeY + } + + override fun startMove(x : Float, y : Float) { editInitialTouchPoint.set(x, y) editInitialScale = config.scale } - open fun edit(x : Float, y : Float) { - when (editInfo.editMode) { - EditMode.Move -> move(x, y) - EditMode.Resize -> resize(x, y) - else -> return - } - } - - /** - * Moves this button to the given coordinates - */ - open fun move(x : Float, y : Float) { + override fun move(x : Float, y : Float) { var adjustedX = x var adjustedY = y @@ -216,34 +205,38 @@ abstract class OnScreenButton( relativeY = (adjustedY - heightDiff) / adjustedHeight } - /** - * Resizes this button based on the distance of the given Y coordinate from the initial Y coordinate - */ - open fun resize(x : Float, y : Float) { - // Invert the distance because the Y coordinate increases as you move down the screen - val verticalDistance = editInitialTouchPoint.y - y - config.scale = editInitialScale + verticalDistance / 200f + override fun endMove() { + saveConfigValues() } - /** - * Ends the current edit session - */ - open fun endEdit() { - config.relativeX = relativeX - config.relativeY = relativeY + override fun moveUp() { + move(currentX, currentY - editInfo.arrowKeyMoveAmount) + saveConfigValues() } - open fun resetRelativeValues() { + override fun moveDown() { + move(currentX, currentY + editInfo.arrowKeyMoveAmount) + saveConfigValues() + } + + override fun moveLeft() { + move(currentX - editInfo.arrowKeyMoveAmount, currentY) + saveConfigValues() + } + + override fun moveRight() { + move(currentX + editInfo.arrowKeyMoveAmount, currentY) + saveConfigValues() + } + + override fun resetConfig() { + config.enabled = defaultEnabled + config.alpha = OnScreenConfiguration.DefaultAlpha + config.scale = OnScreenConfiguration.DefaultScale config.relativeX = defaultRelativeX config.relativeY = defaultRelativeY relativeX = defaultRelativeX relativeY = defaultRelativeY } - - open fun resetConfig() { - resetRelativeValues() - config.enabled = defaultEnabled - config.scale = OnScreenConfiguration.DefaultScale - } } diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenConfiguration.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenConfiguration.kt index 96bdfd62..66381456 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenConfiguration.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenConfiguration.kt @@ -10,32 +10,50 @@ import emu.skyline.input.ButtonId import emu.skyline.utils.SwitchColors import emu.skyline.utils.sharedPreferences -class OnScreenConfiguration(private val context : Context, private val buttonId : ButtonId, defaultRelativeX : Float, defaultRelativeY : Float, defaultEnabled : Boolean) { +interface OnScreenConfiguration { companion object { - const val DefaultAlpha = 130 - const val DefaultGlobalScale = 1.15f - const val DefaultScale = 0.0f + const val GroupDisabled = 0 + const val GroupEnabled = 1 + const val GroupIndeterminate = 2 + + const val MinAlpha = 0 + const val MaxAlpha = 255 + const val DefaultAlpha = 128 + + const val MinScale = 0.5f + const val MaxScale = 2.5f + const val DefaultScale = 1.15f + + val DefaultTextColor = SwitchColors.BLACK.color + val DefaultBackgroundColor = SwitchColors.WHITE.color } + var enabled : Boolean + + /** + * The state of a group of buttons, returns an integer that can be used to set the state of a MaterialCheckBox + */ + val groupEnabled get() = if (enabled) GroupEnabled else GroupDisabled + + var alpha : Int + var textColor : Int + var backgroundColor : Int + + var scale : Float + var relativeX : Float + var relativeY : Float +} + +class OnScreenConfigurationImpl(private val context : Context, private val buttonId : ButtonId, defaultRelativeX : Float, defaultRelativeY : Float, defaultEnabled : Boolean) : OnScreenConfiguration { private inline fun config(default : T, prefix : String = "${buttonId.name}_") = sharedPreferences(context, default, prefix, "controller_config") - var enabled by config(defaultEnabled) + override var enabled by config(defaultEnabled) - var alpha by config(DefaultAlpha, "") - var textColor by config(SwitchColors.BLACK.color) - var backgroundColor by config(SwitchColors.WHITE.color) + override var alpha by config(OnScreenConfiguration.DefaultAlpha) + override var textColor by config(OnScreenConfiguration.DefaultTextColor) + override var backgroundColor by config(OnScreenConfiguration.DefaultBackgroundColor) - /** - * The global scale applied to all buttons - */ - var globalScale by config(DefaultGlobalScale, "") - - /** - * The scale of each button, this is added to the global scale - * Allows buttons to have their own size, while still be controlled by the global scale - */ - var scale by config(DefaultScale) - - var relativeX by config(defaultRelativeX) - var relativeY by config(defaultRelativeY) + override var scale by config(OnScreenConfiguration.DefaultScale) + override var relativeX by config(defaultRelativeX) + override var relativeY by config(defaultRelativeY) } 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 52a9ba43..f1e28c02 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenControllerView.kt @@ -17,6 +17,7 @@ import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.View.OnTouchListener +import androidx.annotation.IntRange import emu.skyline.input.ButtonId import emu.skyline.input.ButtonState import emu.skyline.input.ControllerType @@ -37,14 +38,18 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs private val controllerTypeMappings = mapOf(*ControllerType.values().map { it to (setOf(*it.buttons) + setOf(*it.optionalButtons) to setOf(*it.sticks)) }.toTypedArray()) - - private const val SCALE_STEP = 0.05f - private const val ALPHA_STEP = 25 - private val ALPHA_RANGE = 55..255 } private var onButtonStateChangedListener : OnButtonStateChangedListener? = null + fun setOnButtonStateChangedListener(listener : OnButtonStateChangedListener) { + onButtonStateChangedListener = listener + } + private var onStickStateChangedListener : OnStickStateChangedListener? = null + fun setOnStickStateChangedListener(listener : OnStickStateChangedListener) { + onStickStateChangedListener = listener + } + private val joystickAnimators = mutableMapOf() var controllerType : ControllerType? = null set(value) { @@ -62,8 +67,12 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs (controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { it.hapticFeedback = hapticFeedback } } - val editInfo = OnScreenEditInfo() + internal val editInfo = OnScreenEditInfo() val isEditing get() = editInfo.isEditing + val editButton get() = editInfo.editButton + fun setOnEditButtonChangedListener(listener : OnEditButtonChangedListener?) { + editInfo.onEditButtonChangedListener = listener + } // Populated externally by the activity, as retrieving the vibrator service inside the view crashes the layout editor lateinit var vibrator : Vibrator @@ -222,40 +231,52 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs handled.also { if (it) invalidate() } } - private val editingTouchHandler = OnTouchListener { _, event -> - var handled = false + /** + * Tracks whether the last pointer down event changed the active edit button + * Avoids moving the button when the user just wants to select it + */ + private var activeEditButtonChanged = false - 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 + private val editingTouchHandler = OnTouchListener { _, event -> + run { + when (event.actionMasked) { + MotionEvent.ACTION_DOWN, + MotionEvent.ACTION_POINTER_DOWN -> { + val touchedButton = controls.allButtons.firstOrNull { it.isTouched(event.x, event.y) } ?: return@OnTouchListener false + + // Update the selection if the user touched a button other than the selected one + if (touchedButton != editInfo.editButton) { + activeEditButtonChanged = true + editInfo.editButton = touchedButton + performClick() + return@run } + + editInfo.editButton.startMove(event.x, event.y) + } + + MotionEvent.ACTION_MOVE -> { + // If the user just selected another button, don't move it yet + if (activeEditButtonChanged) + return@run + + editInfo.editButton.move(event.x, event.y) + invalidate() + } + + MotionEvent.ACTION_UP, + MotionEvent.ACTION_POINTER_UP, + MotionEvent.ACTION_CANCEL -> { + if (activeEditButtonChanged) { + activeEditButtonChanged = false + return@run + } + + editInfo.editButton.endMove() } } - - 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() } + true } init { @@ -264,39 +285,49 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs fun setEditMode(editMode : EditMode) { editInfo.editMode = editMode - setOnTouchListener(if (isEditing) editingTouchHandler else playingTouchHandler ) + setOnTouchListener(if (isEditing) editingTouchHandler else playingTouchHandler) } - fun resetControls() { - controls.allButtons.forEach { - it.resetConfig() - } - controls.globalScale = OnScreenConfiguration.DefaultGlobalScale - controls.alpha = OnScreenConfiguration.DefaultAlpha + fun selectAllButtons() { + editInfo.editButton = allButtonsProxy + } + + fun setButtonEnabled(enabled : Boolean) { + editInfo.editButton.config.enabled = enabled invalidate() } - fun increaseScale() { - controls.globalScale += SCALE_STEP + fun setButtonScale(@IntRange(from = 0, to = 100) scale : Int) { + fun toScaleRange(value : Int) : Float = (value / 100f) * (OnScreenConfiguration.MaxScale - OnScreenConfiguration.MinScale) + OnScreenConfiguration.MinScale + + editInfo.editButton.config.scale = toScaleRange(scale) invalidate() } - fun decreaseScale() { - controls.globalScale -= SCALE_STEP + fun setButtonOpacity(@IntRange(from = 0, to = 100) opacity : Int) { + fun toAlphaRange(value : Int) : Int = ((value / 100f) * (OnScreenConfiguration.MaxAlpha - OnScreenConfiguration.MinAlpha)).toInt() + OnScreenConfiguration.MinAlpha + + editInfo.editButton.config.alpha = toAlphaRange(opacity) invalidate() } - fun setSnapToGrid(snap : Boolean) { - editInfo.snapToGrid = snap - } - - fun increaseOpacity() { - controls.alpha = (controls.alpha + ALPHA_STEP).coerceIn(ALPHA_RANGE) + fun moveButtonUp() { + editInfo.editButton.moveUp() invalidate() } - fun decreaseOpacity() { - controls.alpha = (controls.alpha - ALPHA_STEP).coerceIn(ALPHA_RANGE) + fun moveButtonDown() { + editInfo.editButton.moveDown() + invalidate() + } + + fun moveButtonLeft() { + editInfo.editButton.moveLeft() + invalidate() + } + + fun moveButtonRight() { + editInfo.editButton.moveRight() invalidate() } @@ -308,23 +339,6 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs return controls.globalBackgroundColor } - fun setOnButtonStateChangedListener(listener : OnButtonStateChangedListener) { - onButtonStateChangedListener = listener - } - - fun setOnStickStateChangedListener(listener : OnStickStateChangedListener) { - onStickStateChangedListener = listener - } - - 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 - invalidate() - } - fun setTextColor(color : Int) { for (button in controls.allButtons) { button.config.textColor = color @@ -338,4 +352,90 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs } invalidate() } + + fun setSnapToGrid(snap : Boolean) { + editInfo.snapToGrid = snap + editInfo.arrowKeyMoveAmount = if (snap) editInfo.gridSize else OnScreenEditInfo.ArrowKeyMoveAmount + } + + fun resetButton() { + editInfo.editButton.resetConfig() + editInfo.onEditButtonChangedListener?.invoke(editInfo.editButton) + invalidate() + } + + /** + * A proxy button that is used to apply changes to all buttons + */ + private val allButtonsProxy = object : ConfigurableButton { + override val buttonId : ButtonId = ButtonId.All + + override val config = object : OnScreenConfiguration { + override var enabled : Boolean + get() = controls.allButtons.all { it.config.enabled } + set(value) { + controls.allButtons.forEach { it.config.enabled = value } + } + + override val groupEnabled : Int + get() { + if (controls.allButtons.all { it.config.enabled }) + return OnScreenConfiguration.GroupEnabled + if (controls.allButtons.all { !it.config.enabled }) + return OnScreenConfiguration.GroupDisabled + return OnScreenConfiguration.GroupIndeterminate + } + + override var alpha : Int + get() = controls.allButtons.sumOf { it.config.alpha } / controls.allButtons.size + set(value) { + controls.allButtons.forEach { it.config.alpha = value } + } + + override var textColor : Int + get() = controls.globalTextColor + set(value) { + setTextColor(value) + } + + override var backgroundColor : Int + get() = controls.globalBackgroundColor + set(value) { + setBackGroundColor(value) + } + + override var scale : Float + get() = (controls.allButtons.sumOf { it.config.scale.toDouble() } / controls.allButtons.size).toFloat() + set(value) { + controls.allButtons.forEach { it.config.scale = value } + } + + override var relativeX = 0f + override var relativeY = 0f + } + + override fun startMove(x : Float, y : Float) {} + override fun move(x : Float, y : Float) {} + override fun endMove() {} + + override fun moveUp() { + controls.allButtons.forEach { it.moveUp() } + } + + override fun moveDown() { + controls.allButtons.forEach { it.moveDown() } + } + + override fun moveLeft() { + controls.allButtons.forEach { it.moveLeft() } + } + + override fun moveRight() { + controls.allButtons.forEach { it.moveRight() } + } + + override fun resetConfig() { + controls.allButtons.forEach { it.resetConfig() } + } + } } 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 f242f92b..3ef97d79 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditActivity.kt @@ -5,109 +5,35 @@ package emu.skyline.input.onscreen +import android.annotation.SuppressLint import android.os.Build import android.os.Bundle import android.os.Vibrator import android.os.VibratorManager import android.view.* -import androidx.annotation.DrawableRes import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat import androidx.core.view.isGone import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.floatingactionbutton.FloatingActionButton import dagger.hilt.android.AndroidEntryPoint import emu.skyline.R import emu.skyline.databinding.OnScreenEditActivityBinding +import emu.skyline.databinding.OscSliderBinding import emu.skyline.settings.AppSettings import emu.skyline.utils.SwitchColors import emu.skyline.utils.SwitchColors.* import petrov.kristiyan.colorpicker.DoubleColorPicker import petrov.kristiyan.colorpicker.DoubleColorPicker.OnChooseDoubleColorListener import javax.inject.Inject +import kotlin.math.roundToInt @AndroidEntryPoint class OnScreenEditActivity : AppCompatActivity() { - private enum class Action(@DrawableRes private val icon : Int, @DrawableRes private val activeIcon : Int = 0) { - Restore(R.drawable.ic_restore), - Toggle(R.drawable.ic_toggle_on), - Move(R.drawable.ic_move), - Resize(R.drawable.ic_resize), - Grid(R.drawable.ic_grid_off, R.drawable.ic_grid_on), - Palette(R.drawable.ic_palette), - ZoomOut(R.drawable.ic_zoom_out), - ZoomIn(R.drawable.ic_zoom_in), - OpacityMinus(R.drawable.ic_opacity_minus), - OpacityPlus(R.drawable.ic_opacity_plus), - Close(R.drawable.ic_close), - ; - - fun getIcon(active : Boolean) = if (activeIcon != 0 && active) activeIcon else icon - } - private val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) } - private var fullEditVisible = true - @Inject lateinit var appSettings : AppSettings - private val closeAction : () -> Unit = { - if (binding.onScreenControllerView.isEditing) { - toggleFabVisibility(true) - binding.onScreenControllerView.setEditMode(EditMode.None) - } else { - fullEditVisible = !fullEditVisible - toggleFabVisibility(fullEditVisible) - fabMapping[Action.Close]!!.animate().rotation(if (fullEditVisible) 0f else 45f) - } - } - - private fun toggleFabVisibility(visible : Boolean) { - fabMapping.forEach { (action, fab) -> - if (action != Action.Close) { - if (visible) fab.show() - else fab.hide() - } - } - } - - private val moveAction = { - binding.onScreenControllerView.setEditMode(EditMode.Move) - toggleFabVisibility(false) - } - - private val resizeAction = { - binding.onScreenControllerView.setEditMode(EditMode.Resize) - toggleFabVisibility(false) - } - - private val toggleAction : () -> Unit = { - val buttonProps = binding.onScreenControllerView.getButtonProps() - val checkedButtonsArray = buttonProps.map { it.enabled }.toBooleanArray() - - MaterialAlertDialogBuilder(this) - .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) - .setOnDismissListener { fullScreen() } - .show() - } - - private val paletteAction : () -> Unit = { + private fun paletteAction() { DoubleColorPicker(this@OnScreenEditActivity).apply { setTitle(this@OnScreenEditActivity.getString(R.string.osc_background_color)) setDefaultColorButton(binding.onScreenControllerView.getBackGroundColor()) @@ -127,43 +53,25 @@ class OnScreenEditActivity : AppCompatActivity() { } } - private val toggleGridAction : () -> Unit = { + private fun toggleGridAction() { val snapToGrid = !appSettings.onScreenControlSnapToGrid appSettings.onScreenControlSnapToGrid = snapToGrid binding.onScreenControllerView.setSnapToGrid(snapToGrid) binding.alignmentGrid.isGone = !snapToGrid - fabMapping[Action.Grid]!!.setImageResource(Action.Grid.getIcon(!snapToGrid)) + binding.gridButton.setIconResource(if (!snapToGrid) R.drawable.ic_grid_on else R.drawable.ic_grid_off) } - private val resetAction : () -> Unit = { + private fun resetAction() { MaterialAlertDialogBuilder(this) - .setTitle(R.string.osc_reset) + .setTitle(getString(R.string.osc_reset, binding.onScreenControllerView.editButton.buttonId.short)) .setMessage(R.string.osc_reset_confirm) - .setPositiveButton(R.string.confirm) { _, _ -> binding.onScreenControllerView.resetControls() } + .setPositiveButton(R.string.confirm) { _, _ -> binding.onScreenControllerView.resetButton() } .setNegativeButton(R.string.cancel, null) .setOnDismissListener { fullScreen() } .show() } - private data class ActionEntry(val action : Action, val callback : () -> Unit) - - private val actions : List = listOf( - ActionEntry(Action.Restore, resetAction), - ActionEntry(Action.Toggle, toggleAction), - ActionEntry(Action.Move, moveAction), - ActionEntry(Action.Resize, resizeAction), - ActionEntry(Action.Grid, toggleGridAction), - ActionEntry(Action.Palette, paletteAction), - ActionEntry(Action.ZoomOut) { binding.onScreenControllerView.decreaseScale() }, - ActionEntry(Action.ZoomIn) { binding.onScreenControllerView.increaseScale() }, - ActionEntry(Action.OpacityMinus) { binding.onScreenControllerView.decreaseOpacity() }, - ActionEntry(Action.OpacityPlus) { binding.onScreenControllerView.increaseOpacity() }, - ActionEntry(Action.Close, closeAction), - ) - - private val fabMapping = mutableMapOf() - override fun onCreate(savedInstanceState : Bundle?) { super.onCreate(savedInstanceState) window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES @@ -194,20 +102,40 @@ class OnScreenEditActivity : AppCompatActivity() { binding.alignmentGrid.isGone = !snapToGrid binding.alignmentGrid.gridSize = OnScreenEditInfo.GridSize - actions.forEach { (action, callback) -> - binding.fabParent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, binding.fabParent, false).apply { - (this as FloatingActionButton).setImageDrawable(ContextCompat.getDrawable(context, action.getIcon(false))) - setOnClickListener { callback.invoke() } - fabMapping[action] = this - }) + binding.onScreenControllerView.setOnEditButtonChangedListener { button -> + updateActiveButtonDisplayInfo(button) } - fabMapping[Action.Grid]!!.setImageDrawable(ContextCompat.getDrawable(this, Action.Grid.getIcon(!snapToGrid))) + binding.selectAllButton.setOnClickListener { binding.onScreenControllerView.selectAllButtons() } + + populateSlider(binding.scaleSlider, getString(R.string.osc_scale)) { + binding.onScreenControllerView.setButtonScale(it) + } + populateSlider(binding.opacitySlider, getString(R.string.osc_opacity)) { + binding.onScreenControllerView.setButtonOpacity(it) + } + + binding.enabledCheckbox.setOnClickListener { _ -> + binding.onScreenControllerView.setButtonEnabled(binding.enabledCheckbox.isChecked) + } + + binding.moveUpButton.setOnClickListener { binding.onScreenControllerView.moveButtonUp() } + binding.moveDownButton.setOnClickListener { binding.onScreenControllerView.moveButtonDown() } + binding.moveLeftButton.setOnClickListener { binding.onScreenControllerView.moveButtonLeft() } + binding.moveRightButton.setOnClickListener { binding.onScreenControllerView.moveButtonRight() } + + binding.colorButton.setOnClickListener { paletteAction() } + binding.gridButton.setOnClickListener { toggleGridAction() } + binding.gridButton.setIconResource(if (!snapToGrid) R.drawable.ic_grid_on else R.drawable.ic_grid_off) + binding.resetButton.setOnClickListener { resetAction() } + + binding.onScreenControllerView.setEditMode(EditMode.Move) + binding.onScreenControllerView.selectAllButtons() } override fun onResume() { super.onResume() - + updateActiveButtonDisplayInfo(binding.onScreenControllerView.editButton) fullScreen() } @@ -222,4 +150,36 @@ class OnScreenEditActivity : AppCompatActivity() { or View.SYSTEM_UI_FLAG_FULLSCREEN) } } + + /** + * Initializes the slider in the range [0,100], with the given label and value listener + */ + @SuppressLint("SetTextI18n") + private fun populateSlider(slider : OscSliderBinding, label : String, valueListener : ((Int) -> Unit)? = null) { + slider.title.text = label + slider.slider.apply { + valueFrom = 0f + valueTo = 100f + stepSize = 0f + // Always update the value label + addOnChangeListener { _, value, _ -> + slider.valueLabel.text = "${value.roundToInt()}%" + } + // Only call the value listener if the user is dragging the slider + addOnChangeListener { _, value, fromUser -> + if (fromUser) + valueListener?.invoke(value.roundToInt()) + } + } + } + + /** + * Updates the control panel UI elements to reflect the currently selected button + */ + private fun updateActiveButtonDisplayInfo(button : ConfigurableButton) { + binding.enabledCheckbox.checkedState = button.config.groupEnabled + binding.currentButton.text = getString(R.string.osc_current_button, button.buttonId.short) + binding.scaleSlider.slider.value = (button.config.scale - OnScreenConfiguration.MinScale) / (OnScreenConfiguration.MaxScale - OnScreenConfiguration.MinScale) * 100f + binding.opacitySlider.slider.value = (button.config.alpha - OnScreenConfiguration.MinAlpha) / (OnScreenConfiguration.MaxAlpha - OnScreenConfiguration.MinAlpha).toFloat() * 100f + } } diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt index 8f259ee3..e9c53bd6 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenEditInfo.kt @@ -10,10 +10,11 @@ import android.util.TypedValue enum class EditMode { None, - Move, - Resize + Move } +typealias OnEditButtonChangedListener = ((ConfigurableButton) -> Unit) + /** * 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 @@ -27,7 +28,15 @@ class OnScreenEditInfo { /** * The button that is currently being edited */ - var editButton : OnScreenButton? = null + private lateinit var _editButton : ConfigurableButton + var editButton : ConfigurableButton + get() = _editButton + set(value) { + _editButton = value + onEditButtonChangedListener?.invoke(value) + } + + var onEditButtonChangedListener : OnEditButtonChangedListener? = null /** * Whether the buttons should snap to a grid when in edit mode @@ -36,6 +45,8 @@ class OnScreenEditInfo { var gridSize : Int = GridSize + var arrowKeyMoveAmount : Int = ArrowKeyMoveAmount + val isEditing get() = editMode != EditMode.None companion object { @@ -43,5 +54,10 @@ class OnScreenEditInfo { * The size of the grid, calculated from the value of 8dp */ var GridSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, Resources.getSystem().displayMetrics).toInt() + + /** + * The amount the button will be moved when using the arrow keys + */ + val ArrowKeyMoveAmount = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2f, Resources.getSystem().displayMetrics).toInt() } } diff --git a/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt b/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt index a8738b98..7514abd3 100644 --- a/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt +++ b/app/src/main/java/emu/skyline/input/onscreen/OnScreenItemDefinitions.kt @@ -136,15 +136,15 @@ class JoystickButton( fun outerToInnerRelative() = outerToInner().multiply(1f / radius) - override fun edit(x : Float, y : Float) { - super.edit(x, y) + override fun move(x : Float, y : Float) { + super.move(x, y) innerButton.relativeX = relativeX innerButton.relativeY = relativeY } - override fun resetRelativeValues() { - super.resetRelativeValues() + override fun resetConfig() { + super.resetConfig() innerButton.relativeX = relativeX innerButton.relativeY = relativeY @@ -241,24 +241,6 @@ class Controls(onScreenControllerView : OnScreenControllerView) { val allButtons = circularButtons + joysticks + rectangularButtons + triggerButtons - /** - * We can take any of the global scale variables from the buttons since the value is shared across all buttons - */ - var globalScale - get() = circularButtons.first().config.globalScale - set(value) { - circularButtons.first().config.globalScale = value - } - - /** - * We can take any of the alpha variables from the buttons since the value is shared across all buttons - */ - var alpha - get() = circularButtons.first().config.alpha - set(value) { - circularButtons.first().config.alpha = value - } - /** * We can take any of the global text color variables from the buttons */ diff --git a/app/src/main/res/drawable/ic_toggle_on.xml b/app/src/main/res/drawable/ic_arrow_drop_down.xml similarity index 60% rename from app/src/main/res/drawable/ic_toggle_on.xml rename to app/src/main/res/drawable/ic_arrow_drop_down.xml index b37c5447..b2f632aa 100644 --- a/app/src/main/res/drawable/ic_toggle_on.xml +++ b/app/src/main/res/drawable/ic_arrow_drop_down.xml @@ -1,10 +1,11 @@ + android:pathData="M7,10l5,5 5,-5z" /> diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_down.xml b/app/src/main/res/drawable/ic_keyboard_arrow_down.xml new file mode 100644 index 00000000..ab169a24 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_down.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_left.xml b/app/src/main/res/drawable/ic_keyboard_arrow_left.xml new file mode 100644 index 00000000..4de844a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_left.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_right.xml b/app/src/main/res/drawable/ic_keyboard_arrow_right.xml new file mode 100644 index 00000000..d5381115 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_right.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_keyboard_arrow_up.xml b/app/src/main/res/drawable/ic_keyboard_arrow_up.xml new file mode 100644 index 00000000..d430c5b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_keyboard_arrow_up.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_toggle_off.xml b/app/src/main/res/drawable/ic_toggle_off.xml deleted file mode 100644 index 02e4cc46..00000000 --- a/app/src/main/res/drawable/ic_toggle_off.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/top_sheet_bg.xml b/app/src/main/res/drawable/rounded_background.xml similarity index 77% rename from app/src/main/res/drawable/top_sheet_bg.xml rename to app/src/main/res/drawable/rounded_background.xml index e546794c..51fc664e 100644 --- a/app/src/main/res/drawable/top_sheet_bg.xml +++ b/app/src/main/res/drawable/rounded_background.xml @@ -2,6 +2,8 @@ diff --git a/app/src/main/res/layout/on_screen_edit_activity.xml b/app/src/main/res/layout/on_screen_edit_activity.xml index 855c0b1a..2ce1a393 100644 --- a/app/src/main/res/layout/on_screen_edit_activity.xml +++ b/app/src/main/res/layout/on_screen_edit_activity.xml @@ -1,5 +1,6 @@ + android:layout_height="match_parent" + tools:visibility="gone" /> + android:orientation="vertical" + android:translationY="12dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/osc_slider.xml b/app/src/main/res/layout/osc_slider.xml new file mode 100644 index 00000000..56462c2a --- /dev/null +++ b/app/src/main/res/layout/osc_slider.xml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 79f1a77b..a8ac6f94 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,7 +1,10 @@ - + 8dp 10dp 6dp 12dp + + + 0dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67b7505a..3fca09ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,6 +5,7 @@ An error has occurred Copied to clipboard Emulator + Enabled Settings Share Logs @@ -160,8 +161,15 @@ Edit On-Screen Controls layout Text color Background color - Reset On-Screen Controls - Are you sure you want to reset the On-Screen Controls? + Reset Button: %1$s + Are you sure you want to reset this button? + Toggle Mode + Current Button: %1$s + Select All + Scale + Opacity + Button color + Toggle grid Setup Guide Sequentially map every stick and button Joystick