mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-12-22 17:31:52 +01:00
Introduce a control panel to edit buttons instead of a fab bar
An unusual big commit, unfortunately needed because none of these changes would make sense nor work individual. A quick list of what have been done follows. * Introduced a control panel to control buttons replacing the FAB bar * `ConfigurableButton` and `OnScreenConfiguration` interfaces have been introduced to allow for easier proxying of actions when applying the same changes to all buttons * Button resize logic has been stripped from the buttons in favor of the new sliders * General cleanup and renaming of various methods to better reflect their functionality
This commit is contained in:
parent
49de8a8f38
commit
d7e38e9556
@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 <reified T> 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)
|
||||
}
|
||||
|
@ -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<JoystickButton, Animator?>()
|
||||
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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ActionEntry> = 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<Action, FloatingActionButton>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -1,10 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h10c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5zM17,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3z" />
|
||||
android:pathData="M7,10l5,5 5,-5z" />
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_keyboard_arrow_down.xml
Normal file
11
app/src/main/res/drawable/ic_keyboard_arrow_down.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="#000000"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6 -6,-6 1.41,-1.41z" />
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_keyboard_arrow_left.xml
Normal file
11
app/src/main/res/drawable/ic_keyboard_arrow_left.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.41,16.59L10.83,12l4.58,-4.59L14,6l-6,6 6,6 1.41,-1.41z" />
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_keyboard_arrow_right.xml
Normal file
11
app/src/main/res/drawable/ic_keyboard_arrow_right.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8.59,16.59L13.17,12 8.59,7.41 10,6l6,6 -6,6 -1.41,-1.41z" />
|
||||
</vector>
|
11
app/src/main/res/drawable/ic_keyboard_arrow_up.xml
Normal file
11
app/src/main/res/drawable/ic_keyboard_arrow_up.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<vector android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:tint="#000000"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z" />
|
||||
</vector>
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M7,10A2,2 0 0,1 9,12A2,2 0 0,1 7,14A2,2 0 0,1 5,12A2,2 0 0,1 7,10M17,7A5,5 0 0,1 22,12A5,5 0 0,1 17,17H7A5,5 0 0,1 2,12A5,5 0 0,1 7,7H17M7,9A3,3 0 0,0 4,12A3,3 0 0,0 7,15H17A3,3 0 0,0 20,12A3,3 0 0,0 17,9H7Z" />
|
||||
</vector>
|
@ -2,6 +2,8 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="?attr/colorSurface" />
|
||||
<corners
|
||||
android:topLeftRadius="28dp"
|
||||
android:topRightRadius="28dp"
|
||||
android:bottomLeftRadius="28dp"
|
||||
android:bottomRightRadius="28dp" />
|
||||
</shape>
|
@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@ -8,7 +9,8 @@
|
||||
<emu.skyline.views.AlignmentGridView
|
||||
android:id="@+id/alignment_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
android:layout_height="match_parent"
|
||||
tools:visibility="gone" />
|
||||
|
||||
<emu.skyline.input.onscreen.OnScreenControllerView
|
||||
android:id="@+id/on_screen_controller_view"
|
||||
@ -16,15 +18,261 @@
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/fab_parent"
|
||||
android:id="@+id/control_panel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|center_horizontal"
|
||||
android:background="@drawable/top_sheet_bg"
|
||||
android:alpha="0.85"
|
||||
android:animateLayoutChanges="true"
|
||||
android:background="@drawable/rounded_background"
|
||||
android:backgroundTint="?attr/colorPrimaryContainer"
|
||||
android:clickable="true"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp"
|
||||
tools:layout_height="72dp"
|
||||
tools:layout_width="512dp" />
|
||||
android:orientation="vertical"
|
||||
android:translationY="12dp">
|
||||
|
||||
<!-- Tob Bar -->
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/top_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="8dp">
|
||||
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:minHeight="32dp"
|
||||
android:paddingBottom="0dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/close_button"
|
||||
style="@style/Widget.Material3.Button.IconButton"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="end"
|
||||
android:padding="2dp"
|
||||
app:icon="@drawable/ic_close"
|
||||
app:iconTint="?attr/colorOnSurfaceVariant"
|
||||
tools:ignore="ContentDescription,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<!-- Control Panel Content -->
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/current_button"
|
||||
style="?attr/textAppearanceTitleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@string/osc_current_button"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/select_all_button"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="42dp"
|
||||
android:text="@string/select_all"
|
||||
app:layout_constraintBottom_toBottomOf="@id/current_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/current_button"
|
||||
tools:ignore="TextContrastCheck,TouchTargetSizeCheck,VisualLintBounds" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/title_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="current_button,select_all_button" />
|
||||
|
||||
<!-- Checkboxes -->
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/enabled_checkbox"
|
||||
style="?attr/checkboxStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="-6dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:minHeight="0dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:text="@string/enabled"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/current_button"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/toggle_mode_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="0dp"
|
||||
android:paddingVertical="8dp"
|
||||
android:text="@string/osc_toggle_mode"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="@+id/enabled_checkbox"
|
||||
app:layout_constraintTop_toBottomOf="@id/enabled_checkbox"
|
||||
tools:ignore="TouchTargetSizeCheck" />
|
||||
|
||||
|
||||
<!-- Sliders -->
|
||||
|
||||
<include
|
||||
android:id="@+id/scale_slider"
|
||||
layout="@layout/osc_slider"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toggle_mode_checkbox" />
|
||||
|
||||
<include
|
||||
android:id="@+id/opacity_slider"
|
||||
layout="@layout/osc_slider"
|
||||
android:layout_width="160dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
app:layout_constraintStart_toStartOf="@id/scale_slider"
|
||||
app:layout_constraintTop_toBottomOf="@id/scale_slider" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/sliders_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="end"
|
||||
app:constraint_referenced_ids="scale_slider,opacity_slider,enabled_checkbox,toggle_mode_checkbox" />
|
||||
|
||||
|
||||
<!-- Horizontal (Left and Right) Move Buttons -->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/move_left_button"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:icon="@drawable/ic_keyboard_arrow_left"
|
||||
app:layout_constraintBottom_toBottomOf="@id/actions_barrier"
|
||||
app:layout_constraintEnd_toStartOf="@id/move_right_button"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/sliders_barrier"
|
||||
app:layout_constraintTop_toTopOf="@id/title_barrier"
|
||||
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/move_right_button"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_keyboard_arrow_right"
|
||||
app:layout_constraintBottom_toBottomOf="@id/actions_barrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/move_left_button"
|
||||
app:layout_constraintTop_toTopOf="@id/title_barrier"
|
||||
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
|
||||
|
||||
|
||||
<!-- Vertical (Up and Down) Move Buttons -->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/move_up_button"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:icon="@drawable/ic_keyboard_arrow_up"
|
||||
app:layout_constraintBottom_toTopOf="@id/move_down_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/sliders_barrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_barrier"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/move_down_button"
|
||||
style="@style/Widget.Material3.Button.IconButton.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:icon="@drawable/ic_keyboard_arrow_down"
|
||||
app:layout_constraintBottom_toTopOf="@id/actions_barrier"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/sliders_barrier"
|
||||
app:layout_constraintTop_toBottomOf="@id/move_up_button"
|
||||
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
|
||||
|
||||
|
||||
<!-- Action Buttons -->
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/color_button"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/button_color"
|
||||
app:icon="@drawable/ic_palette"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/grid_button"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:layout_constraintStart_toEndOf="@id/sliders_barrier"
|
||||
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/grid_button"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/toggle_grid"
|
||||
app:icon="@drawable/ic_grid_on"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/reset_button"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/color_button"
|
||||
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/reset_button"
|
||||
style="@style/Widget.Material3.Button.IconButton.Filled"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:tooltipText="@string/reset"
|
||||
app:ensureMinTouchTargetSize="false"
|
||||
app:icon="@drawable/ic_restore"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/grid_button"
|
||||
tools:ignore="ContentDescription,SpeakableTextPresentCheck,TouchTargetSizeCheck" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/actions_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="top"
|
||||
app:constraint_referenced_ids="reset_button,grid_button,color_button" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
41
app/src/main/res/layout/osc_slider.xml
Normal file
41
app/src/main/res/layout/osc_slider.xml
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
style="?attr/textAppearanceBodyMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Slider" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/valueLabel"
|
||||
style="?attr/textAppearanceBodyMedium"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="50%" />
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/slider"
|
||||
style="?attr/sliderStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingVertical="8dp"
|
||||
app:haloRadius="18dp"
|
||||
app:labelBehavior="gone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/title"
|
||||
app:thumbRadius="8dp"
|
||||
app:trackColorInactive="@null"
|
||||
tools:ignore="SpeakableTextPresentCheck,TouchTargetSizeCheck"
|
||||
tools:value="0.5" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,7 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<dimen name="grid_padding">8dp</dimen>
|
||||
<dimen name="onScreenItemHorizontalMargin">10dp</dimen>
|
||||
<dimen name="cornerRadius">6dp</dimen>
|
||||
<dimen name="cornerRadiusMedium">12dp</dimen>
|
||||
|
||||
<!-- Remove the default material slider vertical padding -->
|
||||
<dimen name="mtrl_slider_widget_height" tools:override="true">0dp</dimen>
|
||||
</resources>
|
||||
|
@ -5,6 +5,7 @@
|
||||
<string name="error">An error has occurred</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
<string name="emulator">Emulator</string>
|
||||
<string name="enabled">Enabled</string>
|
||||
<!-- Toolbar Main -->
|
||||
<string name="settings">Settings</string>
|
||||
<string name="share_logs">Share Logs</string>
|
||||
@ -160,8 +161,15 @@
|
||||
<string name="osc_edit">Edit On-Screen Controls layout</string>
|
||||
<string name="osc_text_color">Text color</string>
|
||||
<string name="osc_background_color">Background color</string>
|
||||
<string name="osc_reset">Reset On-Screen Controls</string>
|
||||
<string name="osc_reset_confirm">Are you sure you want to reset the On-Screen Controls?</string>
|
||||
<string name="osc_reset">Reset Button: %1$s</string>
|
||||
<string name="osc_reset_confirm">Are you sure you want to reset this button?</string>
|
||||
<string name="osc_toggle_mode">Toggle Mode</string>
|
||||
<string name="osc_current_button">Current Button: %1$s</string>
|
||||
<string name="select_all">Select All</string>
|
||||
<string name="osc_scale">Scale</string>
|
||||
<string name="osc_opacity">Opacity</string>
|
||||
<string name="button_color">Button color</string>
|
||||
<string name="toggle_grid">Toggle grid</string>
|
||||
<string name="setup_guide">Setup Guide</string>
|
||||
<string name="setup_guide_description">Sequentially map every stick and button</string>
|
||||
<string name="joystick">Joystick</string>
|
||||
|
Loading…
Reference in New Issue
Block a user