mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-22 20:29:15 +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),
|
LeftSR(1 shl 25, "SR", string.right_shoulder),
|
||||||
RightSL(1 shl 26, "SL", string.left_shoulder),
|
RightSL(1 shl 26, "SL", string.left_shoulder),
|
||||||
RightSR(1 shl 27, "SR", string.right_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(
|
abstract class OnScreenButton(
|
||||||
onScreenControllerView : OnScreenControllerView,
|
onScreenControllerView : OnScreenControllerView,
|
||||||
val buttonId : ButtonId,
|
final override val buttonId : ButtonId,
|
||||||
private val defaultRelativeX : Float,
|
private val defaultRelativeX : Float,
|
||||||
private val defaultRelativeY : Float,
|
private val defaultRelativeY : Float,
|
||||||
private val defaultRelativeWidth : Float,
|
private val defaultRelativeWidth : Float,
|
||||||
private val defaultRelativeHeight : Float,
|
private val defaultRelativeHeight : Float,
|
||||||
drawableId : Int,
|
drawableId : Int,
|
||||||
private val defaultEnabled : Boolean
|
private val defaultEnabled : Boolean
|
||||||
) {
|
) : ConfigurableButton {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Aspect ratio the default values were based on
|
* Aspect ratio the default values were based on
|
||||||
@ -37,7 +37,7 @@ abstract class OnScreenButton(
|
|||||||
const val CONFIGURED_ASPECT_RATIO = 2074f / 874f
|
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)!!
|
protected val drawable = ContextCompat.getDrawable(onScreenControllerView.context, drawableId)!!
|
||||||
|
|
||||||
@ -50,8 +50,8 @@ abstract class OnScreenButton(
|
|||||||
|
|
||||||
var relativeX = config.relativeX
|
var relativeX = config.relativeX
|
||||||
var relativeY = config.relativeY
|
var relativeY = config.relativeY
|
||||||
private val relativeWidth get() = defaultRelativeWidth * (config.globalScale + config.scale)
|
private val relativeWidth get() = defaultRelativeWidth * config.scale
|
||||||
private val relativeHeight get() = defaultRelativeHeight * (config.globalScale + config.scale)
|
private val relativeHeight get() = defaultRelativeHeight * config.scale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The width of the view this button is in, populated by the view during draw
|
* 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
|
relativeY = config.relativeY
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
fun saveConfigValues() {
|
||||||
* Starts an edit session
|
config.relativeX = relativeX
|
||||||
* @param x The x coordinate of the initial touch
|
config.relativeY = relativeY
|
||||||
* @param y The y coordinate of the initial touch
|
}
|
||||||
*/
|
|
||||||
open fun startEdit(x : Float, y : Float) {
|
override fun startMove(x : Float, y : Float) {
|
||||||
editInitialTouchPoint.set(x, y)
|
editInitialTouchPoint.set(x, y)
|
||||||
editInitialScale = config.scale
|
editInitialScale = config.scale
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun edit(x : Float, y : Float) {
|
override fun move(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) {
|
|
||||||
var adjustedX = x
|
var adjustedX = x
|
||||||
var adjustedY = y
|
var adjustedY = y
|
||||||
|
|
||||||
@ -216,34 +205,38 @@ abstract class OnScreenButton(
|
|||||||
relativeY = (adjustedY - heightDiff) / adjustedHeight
|
relativeY = (adjustedY - heightDiff) / adjustedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override fun endMove() {
|
||||||
* Resizes this button based on the distance of the given Y coordinate from the initial Y coordinate
|
saveConfigValues()
|
||||||
*/
|
|
||||||
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 moveUp() {
|
||||||
* Ends the current edit session
|
move(currentX, currentY - editInfo.arrowKeyMoveAmount)
|
||||||
*/
|
saveConfigValues()
|
||||||
open fun endEdit() {
|
|
||||||
config.relativeX = relativeX
|
|
||||||
config.relativeY = relativeY
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.relativeX = defaultRelativeX
|
||||||
config.relativeY = defaultRelativeY
|
config.relativeY = defaultRelativeY
|
||||||
|
|
||||||
relativeX = defaultRelativeX
|
relativeX = defaultRelativeX
|
||||||
relativeY = defaultRelativeY
|
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.SwitchColors
|
||||||
import emu.skyline.utils.sharedPreferences
|
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 {
|
companion object {
|
||||||
const val DefaultAlpha = 130
|
const val GroupDisabled = 0
|
||||||
const val DefaultGlobalScale = 1.15f
|
const val GroupEnabled = 1
|
||||||
const val DefaultScale = 0.0f
|
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")
|
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, "")
|
override var alpha by config(OnScreenConfiguration.DefaultAlpha)
|
||||||
var textColor by config(SwitchColors.BLACK.color)
|
override var textColor by config(OnScreenConfiguration.DefaultTextColor)
|
||||||
var backgroundColor by config(SwitchColors.WHITE.color)
|
override var backgroundColor by config(OnScreenConfiguration.DefaultBackgroundColor)
|
||||||
|
|
||||||
/**
|
override var scale by config(OnScreenConfiguration.DefaultScale)
|
||||||
* The global scale applied to all buttons
|
override var relativeX by config(defaultRelativeX)
|
||||||
*/
|
override var relativeY by config(defaultRelativeY)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import android.util.AttributeSet
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.View.OnTouchListener
|
import android.view.View.OnTouchListener
|
||||||
|
import androidx.annotation.IntRange
|
||||||
import emu.skyline.input.ButtonId
|
import emu.skyline.input.ButtonId
|
||||||
import emu.skyline.input.ButtonState
|
import emu.skyline.input.ButtonState
|
||||||
import emu.skyline.input.ControllerType
|
import emu.skyline.input.ControllerType
|
||||||
@ -37,14 +38,18 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
private val controllerTypeMappings = mapOf(*ControllerType.values().map {
|
private val controllerTypeMappings = mapOf(*ControllerType.values().map {
|
||||||
it to (setOf(*it.buttons) + setOf(*it.optionalButtons) to setOf(*it.sticks))
|
it to (setOf(*it.buttons) + setOf(*it.optionalButtons) to setOf(*it.sticks))
|
||||||
}.toTypedArray())
|
}.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
|
private var onButtonStateChangedListener : OnButtonStateChangedListener? = null
|
||||||
|
fun setOnButtonStateChangedListener(listener : OnButtonStateChangedListener) {
|
||||||
|
onButtonStateChangedListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
private var onStickStateChangedListener : OnStickStateChangedListener? = null
|
private var onStickStateChangedListener : OnStickStateChangedListener? = null
|
||||||
|
fun setOnStickStateChangedListener(listener : OnStickStateChangedListener) {
|
||||||
|
onStickStateChangedListener = listener
|
||||||
|
}
|
||||||
|
|
||||||
private val joystickAnimators = mutableMapOf<JoystickButton, Animator?>()
|
private val joystickAnimators = mutableMapOf<JoystickButton, Animator?>()
|
||||||
var controllerType : ControllerType? = null
|
var controllerType : ControllerType? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -62,8 +67,12 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
(controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { it.hapticFeedback = hapticFeedback }
|
(controls.circularButtons + controls.rectangularButtons + controls.triggerButtons).forEach { it.hapticFeedback = hapticFeedback }
|
||||||
}
|
}
|
||||||
|
|
||||||
val editInfo = OnScreenEditInfo()
|
internal val editInfo = OnScreenEditInfo()
|
||||||
val isEditing get() = editInfo.isEditing
|
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
|
// Populated externally by the activity, as retrieving the vibrator service inside the view crashes the layout editor
|
||||||
lateinit var vibrator : Vibrator
|
lateinit var vibrator : Vibrator
|
||||||
@ -222,40 +231,52 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
handled.also { if (it) invalidate() }
|
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) {
|
private val editingTouchHandler = OnTouchListener { _, event ->
|
||||||
MotionEvent.ACTION_DOWN,
|
run {
|
||||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
when (event.actionMasked) {
|
||||||
// Handle this event only if no other button is being edited
|
MotionEvent.ACTION_DOWN,
|
||||||
if (editInfo.editButton == null) {
|
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||||
handled = controls.allButtons.any { button ->
|
val touchedButton = controls.allButtons.firstOrNull { it.isTouched(event.x, event.y) } ?: return@OnTouchListener false
|
||||||
if (button.config.enabled && button.isTouched(event.x, event.y)) {
|
|
||||||
editInfo.editButton = button
|
// Update the selection if the user touched a button other than the selected one
|
||||||
button.startEdit(event.x, event.y)
|
if (touchedButton != editInfo.editButton) {
|
||||||
performClick()
|
activeEditButtonChanged = true
|
||||||
true
|
editInfo.editButton = touchedButton
|
||||||
} else false
|
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
true
|
||||||
handled.also { if (it) invalidate() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -264,39 +285,49 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
|
|
||||||
fun setEditMode(editMode : EditMode) {
|
fun setEditMode(editMode : EditMode) {
|
||||||
editInfo.editMode = editMode
|
editInfo.editMode = editMode
|
||||||
setOnTouchListener(if (isEditing) editingTouchHandler else playingTouchHandler )
|
setOnTouchListener(if (isEditing) editingTouchHandler else playingTouchHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetControls() {
|
fun selectAllButtons() {
|
||||||
controls.allButtons.forEach {
|
editInfo.editButton = allButtonsProxy
|
||||||
it.resetConfig()
|
}
|
||||||
}
|
|
||||||
controls.globalScale = OnScreenConfiguration.DefaultGlobalScale
|
fun setButtonEnabled(enabled : Boolean) {
|
||||||
controls.alpha = OnScreenConfiguration.DefaultAlpha
|
editInfo.editButton.config.enabled = enabled
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun increaseScale() {
|
fun setButtonScale(@IntRange(from = 0, to = 100) scale : Int) {
|
||||||
controls.globalScale += SCALE_STEP
|
fun toScaleRange(value : Int) : Float = (value / 100f) * (OnScreenConfiguration.MaxScale - OnScreenConfiguration.MinScale) + OnScreenConfiguration.MinScale
|
||||||
|
|
||||||
|
editInfo.editButton.config.scale = toScaleRange(scale)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decreaseScale() {
|
fun setButtonOpacity(@IntRange(from = 0, to = 100) opacity : Int) {
|
||||||
controls.globalScale -= SCALE_STEP
|
fun toAlphaRange(value : Int) : Int = ((value / 100f) * (OnScreenConfiguration.MaxAlpha - OnScreenConfiguration.MinAlpha)).toInt() + OnScreenConfiguration.MinAlpha
|
||||||
|
|
||||||
|
editInfo.editButton.config.alpha = toAlphaRange(opacity)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSnapToGrid(snap : Boolean) {
|
fun moveButtonUp() {
|
||||||
editInfo.snapToGrid = snap
|
editInfo.editButton.moveUp()
|
||||||
}
|
|
||||||
|
|
||||||
fun increaseOpacity() {
|
|
||||||
controls.alpha = (controls.alpha + ALPHA_STEP).coerceIn(ALPHA_RANGE)
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun decreaseOpacity() {
|
fun moveButtonDown() {
|
||||||
controls.alpha = (controls.alpha - ALPHA_STEP).coerceIn(ALPHA_RANGE)
|
editInfo.editButton.moveDown()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveButtonLeft() {
|
||||||
|
editInfo.editButton.moveLeft()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveButtonRight() {
|
||||||
|
editInfo.editButton.moveRight()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,23 +339,6 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
return controls.globalBackgroundColor
|
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) {
|
fun setTextColor(color : Int) {
|
||||||
for (button in controls.allButtons) {
|
for (button in controls.allButtons) {
|
||||||
button.config.textColor = color
|
button.config.textColor = color
|
||||||
@ -338,4 +352,90 @@ class OnScreenControllerView @JvmOverloads constructor(context : Context, attrs
|
|||||||
}
|
}
|
||||||
invalidate()
|
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
|
package emu.skyline.input.onscreen
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
import android.os.VibratorManager
|
import android.os.VibratorManager
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import emu.skyline.R
|
import emu.skyline.R
|
||||||
import emu.skyline.databinding.OnScreenEditActivityBinding
|
import emu.skyline.databinding.OnScreenEditActivityBinding
|
||||||
|
import emu.skyline.databinding.OscSliderBinding
|
||||||
import emu.skyline.settings.AppSettings
|
import emu.skyline.settings.AppSettings
|
||||||
import emu.skyline.utils.SwitchColors
|
import emu.skyline.utils.SwitchColors
|
||||||
import emu.skyline.utils.SwitchColors.*
|
import emu.skyline.utils.SwitchColors.*
|
||||||
import petrov.kristiyan.colorpicker.DoubleColorPicker
|
import petrov.kristiyan.colorpicker.DoubleColorPicker
|
||||||
import petrov.kristiyan.colorpicker.DoubleColorPicker.OnChooseDoubleColorListener
|
import petrov.kristiyan.colorpicker.DoubleColorPicker.OnChooseDoubleColorListener
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class OnScreenEditActivity : AppCompatActivity() {
|
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 val binding by lazy { OnScreenEditActivityBinding.inflate(layoutInflater) }
|
||||||
|
|
||||||
private var fullEditVisible = true
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var appSettings : AppSettings
|
lateinit var appSettings : AppSettings
|
||||||
|
|
||||||
private val closeAction : () -> Unit = {
|
private fun paletteAction() {
|
||||||
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 = {
|
|
||||||
DoubleColorPicker(this@OnScreenEditActivity).apply {
|
DoubleColorPicker(this@OnScreenEditActivity).apply {
|
||||||
setTitle(this@OnScreenEditActivity.getString(R.string.osc_background_color))
|
setTitle(this@OnScreenEditActivity.getString(R.string.osc_background_color))
|
||||||
setDefaultColorButton(binding.onScreenControllerView.getBackGroundColor())
|
setDefaultColorButton(binding.onScreenControllerView.getBackGroundColor())
|
||||||
@ -127,43 +53,25 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val toggleGridAction : () -> Unit = {
|
private fun toggleGridAction() {
|
||||||
val snapToGrid = !appSettings.onScreenControlSnapToGrid
|
val snapToGrid = !appSettings.onScreenControlSnapToGrid
|
||||||
appSettings.onScreenControlSnapToGrid = snapToGrid
|
appSettings.onScreenControlSnapToGrid = snapToGrid
|
||||||
|
|
||||||
binding.onScreenControllerView.setSnapToGrid(snapToGrid)
|
binding.onScreenControllerView.setSnapToGrid(snapToGrid)
|
||||||
binding.alignmentGrid.isGone = !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)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.osc_reset)
|
.setTitle(getString(R.string.osc_reset, binding.onScreenControllerView.editButton.buttonId.short))
|
||||||
.setMessage(R.string.osc_reset_confirm)
|
.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)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.setOnDismissListener { fullScreen() }
|
.setOnDismissListener { fullScreen() }
|
||||||
.show()
|
.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?) {
|
override fun onCreate(savedInstanceState : Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
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.isGone = !snapToGrid
|
||||||
binding.alignmentGrid.gridSize = OnScreenEditInfo.GridSize
|
binding.alignmentGrid.gridSize = OnScreenEditInfo.GridSize
|
||||||
|
|
||||||
actions.forEach { (action, callback) ->
|
binding.onScreenControllerView.setOnEditButtonChangedListener { button ->
|
||||||
binding.fabParent.addView(LayoutInflater.from(this).inflate(R.layout.on_screen_edit_mini_fab, binding.fabParent, false).apply {
|
updateActiveButtonDisplayInfo(button)
|
||||||
(this as FloatingActionButton).setImageDrawable(ContextCompat.getDrawable(context, action.getIcon(false)))
|
|
||||||
setOnClickListener { callback.invoke() }
|
|
||||||
fabMapping[action] = this
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
updateActiveButtonDisplayInfo(binding.onScreenControllerView.editButton)
|
||||||
fullScreen()
|
fullScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,4 +150,36 @@ class OnScreenEditActivity : AppCompatActivity() {
|
|||||||
or View.SYSTEM_UI_FLAG_FULLSCREEN)
|
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 {
|
enum class EditMode {
|
||||||
None,
|
None,
|
||||||
Move,
|
Move
|
||||||
Resize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typealias OnEditButtonChangedListener = ((ConfigurableButton) -> Unit)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A small class that holds information about the current edit session
|
* 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
|
* 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
|
* 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
|
* Whether the buttons should snap to a grid when in edit mode
|
||||||
@ -36,6 +45,8 @@ class OnScreenEditInfo {
|
|||||||
|
|
||||||
var gridSize : Int = GridSize
|
var gridSize : Int = GridSize
|
||||||
|
|
||||||
|
var arrowKeyMoveAmount : Int = ArrowKeyMoveAmount
|
||||||
|
|
||||||
val isEditing get() = editMode != EditMode.None
|
val isEditing get() = editMode != EditMode.None
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -43,5 +54,10 @@ class OnScreenEditInfo {
|
|||||||
* The size of the grid, calculated from the value of 8dp
|
* The size of the grid, calculated from the value of 8dp
|
||||||
*/
|
*/
|
||||||
var GridSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, Resources.getSystem().displayMetrics).toInt()
|
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)
|
fun outerToInnerRelative() = outerToInner().multiply(1f / radius)
|
||||||
|
|
||||||
override fun edit(x : Float, y : Float) {
|
override fun move(x : Float, y : Float) {
|
||||||
super.edit(x, y)
|
super.move(x, y)
|
||||||
|
|
||||||
innerButton.relativeX = relativeX
|
innerButton.relativeX = relativeX
|
||||||
innerButton.relativeY = relativeY
|
innerButton.relativeY = relativeY
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetRelativeValues() {
|
override fun resetConfig() {
|
||||||
super.resetRelativeValues()
|
super.resetConfig()
|
||||||
|
|
||||||
innerButton.relativeX = relativeX
|
innerButton.relativeX = relativeX
|
||||||
innerButton.relativeY = relativeY
|
innerButton.relativeY = relativeY
|
||||||
@ -241,24 +241,6 @@ class Controls(onScreenControllerView : OnScreenControllerView) {
|
|||||||
|
|
||||||
val allButtons = circularButtons + joysticks + rectangularButtons + triggerButtons
|
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
|
* 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"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="24dp"
|
android:width="24dp"
|
||||||
android:height="24dp"
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
android:tint="#000000"
|
android:tint="#000000"
|
||||||
android:viewportWidth="24"
|
android:viewportWidth="24"
|
||||||
android:viewportHeight="24">
|
android:viewportHeight="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
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>
|
</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">
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<solid android:color="?attr/colorSurface" />
|
<solid android:color="?attr/colorSurface" />
|
||||||
<corners
|
<corners
|
||||||
|
android:topLeftRadius="28dp"
|
||||||
|
android:topRightRadius="28dp"
|
||||||
android:bottomLeftRadius="28dp"
|
android:bottomLeftRadius="28dp"
|
||||||
android:bottomRightRadius="28dp" />
|
android:bottomRightRadius="28dp" />
|
||||||
</shape>
|
</shape>
|
@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@ -8,7 +9,8 @@
|
|||||||
<emu.skyline.views.AlignmentGridView
|
<emu.skyline.views.AlignmentGridView
|
||||||
android:id="@+id/alignment_grid"
|
android:id="@+id/alignment_grid"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<emu.skyline.input.onscreen.OnScreenControllerView
|
<emu.skyline.input.onscreen.OnScreenControllerView
|
||||||
android:id="@+id/on_screen_controller_view"
|
android:id="@+id/on_screen_controller_view"
|
||||||
@ -16,15 +18,261 @@
|
|||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/fab_parent"
|
android:id="@+id/control_panel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="top|center_horizontal"
|
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:backgroundTint="?attr/colorPrimaryContainer"
|
||||||
|
android:clickable="true"
|
||||||
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:padding="10dp"
|
android:translationY="12dp">
|
||||||
tools:layout_height="72dp"
|
|
||||||
tools:layout_width="512dp" />
|
<!-- 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>
|
</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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<dimen name="grid_padding">8dp</dimen>
|
<dimen name="grid_padding">8dp</dimen>
|
||||||
<dimen name="onScreenItemHorizontalMargin">10dp</dimen>
|
<dimen name="onScreenItemHorizontalMargin">10dp</dimen>
|
||||||
<dimen name="cornerRadius">6dp</dimen>
|
<dimen name="cornerRadius">6dp</dimen>
|
||||||
<dimen name="cornerRadiusMedium">12dp</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>
|
</resources>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<string name="error">An error has occurred</string>
|
<string name="error">An error has occurred</string>
|
||||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||||
<string name="emulator">Emulator</string>
|
<string name="emulator">Emulator</string>
|
||||||
|
<string name="enabled">Enabled</string>
|
||||||
<!-- Toolbar Main -->
|
<!-- Toolbar Main -->
|
||||||
<string name="settings">Settings</string>
|
<string name="settings">Settings</string>
|
||||||
<string name="share_logs">Share Logs</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_edit">Edit On-Screen Controls layout</string>
|
||||||
<string name="osc_text_color">Text color</string>
|
<string name="osc_text_color">Text color</string>
|
||||||
<string name="osc_background_color">Background color</string>
|
<string name="osc_background_color">Background color</string>
|
||||||
<string name="osc_reset">Reset 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 the On-Screen Controls?</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">Setup Guide</string>
|
||||||
<string name="setup_guide_description">Sequentially map every stick and button</string>
|
<string name="setup_guide_description">Sequentially map every stick and button</string>
|
||||||
<string name="joystick">Joystick</string>
|
<string name="joystick">Joystick</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user