Add on screen controls layout edit settings

This commit is contained in:
Willi Ye 2020-10-03 19:20:36 +02:00 committed by ◱ PixelyIon
parent 30cf1c4b6a
commit 5c4aa95da6
23 changed files with 285 additions and 80 deletions

View File

@ -61,7 +61,9 @@
</activity>
<activity
android:name="emu.skyline.input.onscreen.OnScreenEditActivity"
android:exported="true">
android:exported="true"
android:screenOrientation="landscape"
tools:ignore="LockedOrientationActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="emu.skyline.input.ControllerActivity" />

View File

@ -231,7 +231,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
game_view.setOnTouchListener(this)
// Hide on screen controls when first controller is not set
on_screen_controller_view.isInvisible = InputManager.controllers[0]!!.type == ControllerType.None
on_screen_controller_view.isInvisible = !InputManager.controllers[0]!!.type.firstController || !sharedPreferences.getBoolean("on_screen_control", false)
on_screen_controller_view.setOnButtonStateChangedListener(::onButtonStateChanged)
on_screen_controller_view.setOnStickStateChangedListener(::onStickStateChanged)

View File

@ -33,6 +33,7 @@ import emu.skyline.adapter.LayoutType
import emu.skyline.data.AppItem
import emu.skyline.data.BaseElement
import emu.skyline.data.BaseHeader
import emu.skyline.data.ElementType
import emu.skyline.loader.LoaderResult
import emu.skyline.loader.RomFile
import emu.skyline.loader.RomFormat
@ -111,7 +112,7 @@ class MainActivity : AppCompatActivity() {
if (loadFromFile) {
try {
loadSerializedList<BaseElement>(romsFile).forEach {
if (it is BaseHeader)
if (it.elementType == ElementType.Header && it is BaseHeader)
adapter.addItem(HeaderViewItem(it.title))
else if (it is AppItem)
adapter.addItem(it.toViewItem())

View File

@ -12,4 +12,4 @@ enum class ElementType {
Item
}
abstract class BaseElement(elementType : ElementType) : Serializable
abstract class BaseElement(val elementType : ElementType) : Serializable

View File

@ -9,6 +9,7 @@ import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import emu.skyline.R
@ -46,6 +47,8 @@ class ControllerActivity : AppCompatActivity() {
*/
val axisMap = mutableMapOf<AxisId, ControllerStickViewItem>()
private val sharedPrefs by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
/**
* This function updates the [adapter] based on information from [InputManager]
*/
@ -62,8 +65,9 @@ class ControllerActivity : AppCompatActivity() {
if (id == 0 && controller.type.firstController) {
adapter.addItem(HeaderViewItem(getString(R.string.osc)))
adapter.addItem(ControllerCheckBoxViewItem(getString(R.string.osc_enable), getString(R.string.osc_not_shown), false) { item, position ->
adapter.addItem(ControllerCheckBoxViewItem(getString(R.string.osc_enable), getString(R.string.osc_not_shown), sharedPrefs.getBoolean("on_screen_control", false)) { item, position ->
item.summary = getString(if (item.checked) R.string.osc_shown else R.string.osc_not_shown)
sharedPrefs.edit().putBoolean("on_screen_control", item.checked).apply()
adapter.notifyItemChanged(position)
})

View File

@ -5,6 +5,7 @@
package emu.skyline.input
import emu.skyline.R.string
import java.io.Serializable
import java.util.*
import kotlin.math.abs
@ -13,22 +14,22 @@ import kotlin.math.abs
* This enumerates all of the buttons that the emulator recognizes
*/
enum class ButtonId(val short : String? = null, val long : Int? = null) {
A("A", emu.skyline.R.string.a_button),
B("B", emu.skyline.R.string.b_button),
X("X", emu.skyline.R.string.x_button),
Y("Y", emu.skyline.R.string.y_button),
LeftStick("L"),
RightStick("R"),
L("L", emu.skyline.R.string.left_shoulder),
R("R", emu.skyline.R.string.right_shoulder),
ZL("ZL", emu.skyline.R.string.left_trigger),
ZR("ZR", emu.skyline.R.string.right_trigger),
Plus("+", emu.skyline.R.string.plus_button),
Minus("-", emu.skyline.R.string.minus_button),
DpadLeft("", emu.skyline.R.string.left),
DpadUp("", emu.skyline.R.string.up),
DpadRight("", emu.skyline.R.string.right),
DpadDown("", emu.skyline.R.string.down),
A("A", string.a_button),
B("B", string.b_button),
X("X", string.x_button),
Y("Y", string.y_button),
LeftStick("L", string.left_stick),
RightStick("R", string.right_stick),
L("L", string.left_shoulder),
R("R", string.right_shoulder),
ZL("ZL", string.left_trigger),
ZR("ZR", string.right_trigger),
Plus("+", string.plus_button),
Minus("-", string.minus_button),
DpadLeft("", string.left),
DpadUp("", string.up),
DpadRight("", string.right),
DpadDown("", string.down),
LeftStickLeft,
LeftStickUp,
LeftStickRight,
@ -37,11 +38,11 @@ enum class ButtonId(val short : String? = null, val long : Int? = null) {
RightStickUp,
RightStickRight,
RightStickDown,
LeftSL("SL", emu.skyline.R.string.left_shoulder),
LeftSR("SR", emu.skyline.R.string.right_shoulder),
RightSL("SL", emu.skyline.R.string.left_shoulder),
RightSR("SR", emu.skyline.R.string.right_shoulder),
Menu("", emu.skyline.R.string.emu_menu_button);
LeftSL("SL", string.left_shoulder),
LeftSR("SR", string.right_shoulder),
RightSL("SL", string.left_shoulder),
RightSR("SR", string.right_shoulder),
Menu("", string.emu_menu_button);
/**
* This returns the value as setting the [ordinal]-th bit in a [Long]

View File

@ -11,6 +11,7 @@ import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
interface ControllerConfiguration {
var enabled : Boolean
var globalScale : Float
var relativeX : Float
var relativeY : Float
@ -20,6 +21,7 @@ class ControllerConfigurationDummy(
defaultRelativeX : Float,
defaultRelativeY : Float
) : ControllerConfiguration {
override var enabled = true
override var globalScale = 1f
override var relativeX = defaultRelativeX
override var relativeY = defaultRelativeY
@ -31,10 +33,10 @@ class ControllerConfigurationImpl(
defaultRelativeX : Float,
defaultRelativeY : Float
) : ControllerConfiguration {
override var globalScale by ControllerPrefs(context, "on_screen_controller_", Float::class.java, 1f)
private inline fun <reified T> config(default : T) = ControllerPrefs(context, "${buttonId.name}_", T::class.java, default)
override var enabled by config(true)
override var globalScale by ControllerPrefs(context, "on_screen_controller_", Float::class.java, 1f)
override var relativeX by config(defaultRelativeX)
override var relativeY by config(defaultRelativeY)
}
@ -50,8 +52,8 @@ private class ControllerPrefs<T>(context : Context, private val prefix : String,
override fun setValue(thisRef : Any, property : KProperty<*>, value : T) {
prefs.edit().apply {
when (clazz) {
Float::class.java,
java.lang.Float::class.java -> putFloat(prefix + property.name, value as Float)
Float::class.java, java.lang.Float::class.java -> putFloat(prefix + property.name, value as Float)
Boolean::class.java, java.lang.Boolean::class.java -> putBoolean(prefix + property.name, value as Boolean)
else -> error("Unsupported type $clazz ${Float::class.java}")
}
}.apply()
@ -59,10 +61,11 @@ private class ControllerPrefs<T>(context : Context, private val prefix : String,
override fun getValue(thisRef : Any, property : KProperty<*>) : T =
prefs.let {
@Suppress("IMPLICIT_CAST_TO_ANY")
when (clazz) {
Float::class.java,
java.lang.Float::class.java -> it.getFloat(prefix + property.name, default as Float)
else -> error("Unsupported type $clazz ${Float::class.java}")
Float::class.java, java.lang.Float::class.java -> it.getFloat(prefix + property.name, default as Float)
Boolean::class.java, java.lang.Boolean::class.java -> it.getBoolean(prefix + property.name, default as Boolean)
else -> error("Unsupported type $clazz")
}
} as T
}

View File

@ -36,10 +36,12 @@ class OnScreenControllerView @JvmOverloads constructor(
override fun onDraw(canvas : Canvas) {
super.onDraw(canvas)
(controls.circularButtons + controls.rectangularButtons + controls.triggerButtons + controls.joysticks).forEach {
it.width = width
it.height = height
it.render(canvas)
controls.allButtons.forEach {
if (it.config.enabled) {
it.width = width
it.height = height
it.render(canvas)
}
}
}
@ -63,7 +65,7 @@ class OnScreenControllerView @JvmOverloads constructor(
}
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
if (button.isTouched(x, y)) {
if (button.config.enabled && button.isTouched(x, y)) {
button.touchPointerId = pointerId
button.onFingerDown(x, y)
performClick()
@ -120,7 +122,7 @@ class OnScreenControllerView @JvmOverloads constructor(
}
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
if (joystick.isTouched(x, y)) {
if (joystick.config.enabled && joystick.isTouched(x, y)) {
joystickAnimators[joystick]?.cancel()
joystickAnimators[joystick] = null
joystick.touchPointerId = pointerId
@ -147,7 +149,7 @@ class OnScreenControllerView @JvmOverloads constructor(
}
private val editingTouchHandler = OnTouchListener { _, event ->
(controls.circularButtons + controls.rectangularButtons + controls.triggerButtons + controls.joysticks).any { button ->
controls.allButtons.any { button ->
when (event.actionMasked) {
MotionEvent.ACTION_UP,
MotionEvent.ACTION_POINTER_UP,
@ -159,7 +161,7 @@ class OnScreenControllerView @JvmOverloads constructor(
}
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_POINTER_DOWN -> {
if (button.isTouched(event.x, event.y)) {
if (button.config.enabled && button.isTouched(event.x, event.y)) {
button.startEdit()
performClick()
return@any true
@ -183,7 +185,10 @@ class OnScreenControllerView @JvmOverloads constructor(
fun setEditMode(editMode : Boolean) = setOnTouchListener(if (editMode) editingTouchHandler else playingTouchHandler)
fun resetControls() {
(controls.circularButtons + controls.rectangularButtons + controls.triggerButtons + controls.joysticks).forEach { it.resetRelativeValues() }
controls.allButtons.forEach {
it.resetRelativeValues()
it.config.enabled = true
}
controls.globalScale = 1f
invalidate()
}
@ -205,4 +210,11 @@ class OnScreenControllerView @JvmOverloads constructor(
fun setOnStickStateChangedListener(listener : OnStickStateChangedListener) {
onStickStateChangedListener = listener
}
fun getButtonProps() = controls.allButtons.map { Pair(it.buttonId, it.config.enabled) }
fun setButtonEnabled(buttonId : ButtonId, enabled : Boolean) {
controls.allButtons.first { it.buttonId == buttonId }.config.enabled = enabled
invalidate()
}
}

View File

@ -5,8 +5,113 @@
package emu.skyline.input.onscreen
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowInsets
import android.view.WindowInsetsController
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import emu.skyline.R
import kotlinx.android.synthetic.main.main_activity.fab_parent
import kotlinx.android.synthetic.main.on_screen_edit_activity.*
class OnScreenEditActivity : AppCompatActivity() {
private var fullEditVisible = true
private var editMode = false
private val closeAction : () -> Unit = {
if (editMode) {
toggleFabVisibility(true)
on_screen_controller_view.setEditMode(false)
editMode = false
} else {
fullEditVisible = !fullEditVisible
toggleFabVisibility(fullEditVisible)
fabMapping[R.drawable.ic_close]!!.animate().rotationBy(if (fullEditVisible) -45f else 45f)
}
}
private fun toggleFabVisibility(visible : Boolean) {
actions.forEach {
if (it.first != R.drawable.ic_close)
if (visible) fabMapping[it.first]!!.show()
else fabMapping[it.first]!!.hide()
}
}
private val editAction = {
editMode = true
on_screen_controller_view.setEditMode(true)
toggleFabVisibility(false)
}
private val toggleAction : () -> Unit = {
val buttonProps = on_screen_controller_view.getButtonProps()
val checkArray = buttonProps.map { it.second }.toBooleanArray()
MaterialAlertDialogBuilder(this)
.setMultiChoiceItems(buttonProps.map {
val longText = getString(it.first.long!!)
if (it.first.short == longText) longText else "$longText: ${it.first.short}"
}.toTypedArray(), checkArray) { _, which, isChecked ->
checkArray[which] = isChecked
}.setPositiveButton(R.string.confirm) { _, _ ->
buttonProps.forEachIndexed { index, pair ->
if (checkArray[index] != pair.second)
on_screen_controller_view.setButtonEnabled(pair.first, checkArray[index])
}
}.setNegativeButton(R.string.cancel, null)
.setOnDismissListener { fullScreen() }
.show()
}
private val actions : List<Pair<Int, () -> Unit>> = listOf(
Pair(R.drawable.ic_refresh, { on_screen_controller_view.resetControls() }),
Pair(R.drawable.ic_toggle, toggleAction),
Pair(R.drawable.ic_edit, editAction),
Pair(R.drawable.ic_zoom_out, { on_screen_controller_view.decreaseScale() }),
Pair(R.drawable.ic_zoom_in, { on_screen_controller_view.increaseScale() }),
Pair(R.drawable.ic_close, closeAction)
)
private val fabMapping = mutableMapOf<Int, FloatingActionButton>()
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.on_screen_edit_activity)
actions.forEach { pair ->
fab_parent.addView(FloatingActionButton(this).apply {
size = FloatingActionButton.SIZE_MINI
setColorFilter(Color.WHITE)
setImageDrawable(ContextCompat.getDrawable(context, pair.first))
setOnClickListener { pair.second.invoke() }
fabMapping[pair.first] = this
})
}
}
override fun onResume() {
super.onResume()
fullScreen()
}
private fun fullScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.hide(WindowInsets.Type.navigationBars() or WindowInsets.Type.systemBars() or WindowInsets.Type.systemGestures() or WindowInsets.Type.statusBars())
window.insetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} else {
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_FULLSCREEN)
}
}
}

View File

@ -86,6 +86,7 @@ class JoystickButton(
val secondTapDiff = currentTime - fingerUpTime
if (firstTapDiff in 0..500 && secondTapDiff in 0..500) {
shortDoubleTapped = true
drawable.alpha = 50
}
fingerDownTime = currentTime
}
@ -97,6 +98,7 @@ class JoystickButton(
fingerUpTime = SystemClock.elapsedRealtime()
shortDoubleTapped = false
drawable.alpha = 255
}
fun onFingerMoved(x : Float, y : Float) : PointF {
@ -152,8 +154,7 @@ open class RectangularButton(
defaultRelativeHeight,
drawableId
) {
override fun isTouched(x : Float, y : Float) =
currentBounds.contains(x.roundToInt(), y.roundToInt())
override fun isTouched(x : Float, y : Float) = currentBounds.contains(x.roundToInt(), y.roundToInt())
override fun onFingerDown(x : Float, y : Float) {
drawable.alpha = (255 * 0.5f).roundToInt()
@ -217,6 +218,8 @@ class Controls(onScreenControllerView : OnScreenControllerView) {
TriggerButton(onScreenControllerView, ZR, 0.9f, 0.1f, 0.075f, 0.08f)
)
val allButtons = circularButtons + joysticks + rectangularButtons + triggerButtons
/**
* We can take any of the global scale variables from the buttons
*/

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
android:shape="oval">
<solid android:color="#50FFFFFF" />
<stroke
android:width="2.5dp"
android:color="#A0FFFFFF" />
android:width="2.5dp"
android:color="#A0FFFFFF" />
</shape>

View File

@ -0,0 +1,10 @@
<?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="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</vector>

View File

@ -0,0 +1,10 @@
<?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="M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z" />
</vector>

View File

@ -4,11 +4,11 @@
<shape android:shape="rectangle">
<solid android:color="#50FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:width="2dp"
android:color="#A0FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />
android:width="25dp"
android:height="25dp" />
<corners android:radius="10dp" />
</shape>
</item>

View File

@ -4,24 +4,24 @@
<shape android:shape="oval">
<solid android:color="#50FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:width="2dp"
android:color="#A0FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />
android:width="25dp"
android:height="25dp" />
<padding
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="#50000000" />
<size
android:width="30dp"
android:height="30dp" />
android:width="30dp"
android:height="30dp" />
</shape>
</item>
</layer-list>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
android:shape="oval">
<solid android:color="#50FFFFFF" />
<stroke
android:width="2.5dp"
android:color="#A0FFFFFF" />
android:width="2.5dp"
android:color="#A0FFFFFF" />
</shape>

View File

@ -0,0 +1,10 @@
<?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>

View File

@ -4,16 +4,16 @@
<shape android:shape="rectangle">
<solid android:color="#50FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:width="2dp"
android:color="#A0FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />
android:width="25dp"
android:height="25dp" />
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="50dp"
android:topRightRadius="10dp" />
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="50dp"
android:topRightRadius="10dp" />
</shape>
</item>
</layer-list>

View File

@ -4,16 +4,16 @@
<shape android:shape="rectangle">
<solid android:color="#50FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:width="2dp"
android:color="#A0FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />
android:width="25dp"
android:height="25dp" />
<corners
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="50dp" />
android:bottomLeftRadius="10dp"
android:bottomRightRadius="10dp"
android:topLeftRadius="10dp"
android:topRightRadius="50dp" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,10 @@
<?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="M15.5,14L20.5,19L19,20.5L14,15.5V14.71L13.73,14.43C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.43,13.73L14.71,14H15.5M9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14M12,10H10V12H9V10H7V9H9V7H10V9H12V10Z" />
</vector>

View File

@ -0,0 +1,10 @@
<?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="M15.5,14H14.71L14.43,13.73C15.41,12.59 16,11.11 16,9.5A6.5,6.5 0 0,0 9.5,3A6.5,6.5 0 0,0 3,9.5A6.5,6.5 0 0,0 9.5,16C11.11,16 12.59,15.41 13.73,14.43L14,14.71V15.5L19,20.5L20.5,19L15.5,14M9.5,14C7,14 5,12 5,9.5C5,7 7,5 9.5,5C12,5 14,7 14,9.5C14,12 12,14 9.5,14M7,9H12V10H7V9Z" />
</vector>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black">
<emu.skyline.input.onscreen.OnScreenControllerView
android:id="@+id/on_screen_controller_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<emu.skyline.views.CustomLinearLayout
android:id="@+id/fab_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_margin="16dp"
android:orientation="horizontal" />
</FrameLayout>

View File

@ -57,6 +57,9 @@
<string name="osc_not_shown">On-Screen Controls won\'t be shown</string>
<string name="osc_shown">On-Screen Controls will be shown</string>
<string name="osc_edit">Edit On-Screen Controls layout</string>
<string name="joystick">Joystick</string>
<string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<string name="controller">Controller</string>
<string name="config_controller">Configure Controller</string>
<string name="controller_type">Controller Type</string>
@ -106,6 +109,8 @@
<string name="left">Left</string>
<string name="right">Right</string>
<string name="dpad">D-pad</string>
<string name="left_stick">Left Stick</string>
<string name="right_stick">Right Stick</string>
<string name="face_buttons">Face Buttons</string>
<string name="shoulder_trigger"><![CDATA[Shoulder & Trigger Buttons]]></string>
<string name="shoulder_rail">Shoulder Buttons on Joy-Con Rail</string>