Use property delegate to handle preferences

* Add option to disable joystick recentering
This commit is contained in:
Willi Ye 2020-10-04 22:29:50 +02:00 committed by ◱ PixelyIon
parent 5c4aa95da6
commit 7526a985fb
25 changed files with 223 additions and 162 deletions

View File

@ -15,9 +15,9 @@ import android.util.Log
import android.view.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isInvisible
import androidx.preference.PreferenceManager
import emu.skyline.input.*
import emu.skyline.loader.getRomFormat
import emu.skyline.utils.Settings
import kotlinx.android.synthetic.main.emu_activity.*
import java.io.File
import kotlin.math.abs
@ -36,11 +36,6 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
*/
private var vibrators = HashMap<Int, Vibrator>()
/**
* A boolean flag denoting the current operation mode of the emulator (Docked = true/Handheld = false)
*/
private var operationMode = true
/**
* The surface object used for displaying frames
*/
@ -63,6 +58,8 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
*/
private lateinit var emulationThread : Thread
private val settings by lazy { Settings(this) }
/**
* This is the entry point into the emulation code for libskyline
*
@ -149,7 +146,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
if (controller.type != ControllerType.None) {
val type = when (controller.type) {
ControllerType.None -> throw IllegalArgumentException()
ControllerType.HandheldProController -> if (operationMode) ControllerType.ProController.id else ControllerType.HandheldProController.id
ControllerType.HandheldProController -> if (settings.operationMode) ControllerType.ProController.id else ControllerType.HandheldProController.id
ControllerType.ProController, ControllerType.JoyConLeft, ControllerType.JoyConRight -> controller.type.id
}
@ -212,9 +209,7 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
game_view.holder.addCallback(this)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
if (sharedPreferences.getBoolean("perf_stats", false)) {
if (settings.perfStats) {
perf_stats.postDelayed(object : Runnable {
override fun run() {
perf_stats.text = "${getFps()} FPS\n${getFrametime()}ms"
@ -223,15 +218,13 @@ class EmulationActivity : AppCompatActivity(), SurfaceHolder.Callback, View.OnTo
}, 250)
}
operationMode = sharedPreferences.getBoolean("operation_mode", operationMode)
@Suppress("DEPRECATION") val display = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) display!! else windowManager.defaultDisplay
display?.supportedModes?.maxBy { it.refreshRate + (it.physicalHeight * it.physicalWidth) }?.let { window.attributes.preferredDisplayModeId = it.modeId }
game_view.setOnTouchListener(this)
// Hide on screen controls when first controller is not set
on_screen_controller_view.isInvisible = !InputManager.controllers[0]!!.type.firstController || !sharedPreferences.getBoolean("on_screen_control", false)
on_screen_controller_view.isInvisible = !InputManager.controllers[0]!!.type.firstController || !settings.onScreenControl
on_screen_controller_view.setOnButtonStateChangedListener(::onButtonStateChanged)
on_screen_controller_view.setOnStickStateChangedListener(::onStickStateChanged)

View File

@ -14,13 +14,13 @@ import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import emu.skyline.adapter.GenericAdapter
import emu.skyline.adapter.HeaderViewItem
import emu.skyline.adapter.LogViewItem
import emu.skyline.utils.Settings
import kotlinx.android.synthetic.main.log_activity.*
import kotlinx.android.synthetic.main.titlebar.*
import org.json.JSONObject
@ -52,9 +52,10 @@ class LogActivity : AppCompatActivity() {
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val compact = prefs.getBoolean("log_compact", false)
val logLevel = prefs.getString("log_level", "3")!!.toInt()
val settings = Settings(this)
val compact = settings.logCompact
val logLevel = settings.logLevel.toInt()
val logLevels = resources.getStringArray(R.array.log_level)
log_list.adapter = adapter

View File

@ -37,6 +37,9 @@ import emu.skyline.data.ElementType
import emu.skyline.loader.LoaderResult
import emu.skyline.loader.RomFile
import emu.skyline.loader.RomFormat
import emu.skyline.utils.Settings
import emu.skyline.utils.loadSerializedList
import emu.skyline.utils.serialize
import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.android.synthetic.main.titlebar.*
import java.io.File
@ -50,10 +53,7 @@ class MainActivity : AppCompatActivity() {
private val TAG = MainActivity::class.java.simpleName
}
/**
* This is used to get/set shared preferences
*/
private val sharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
private val settings by lazy { Settings(this) }
/**
* The adapter used for adding elements to [app_list]
@ -62,7 +62,7 @@ class MainActivity : AppCompatActivity() {
private var reloading = AtomicBoolean()
private val layoutType get() = LayoutType.values()[sharedPreferences.getString("layout_type", "1")!!.toInt()]
private val layoutType get() = LayoutType.values()[settings.layoutType.toInt()]
private val missingIcon by lazy { ContextCompat.getDrawable(this, R.drawable.default_icon)!!.toBitmap(256, 256) }
@ -134,7 +134,7 @@ class MainActivity : AppCompatActivity() {
try {
runOnUiThread { adapter.removeAllItems() }
val searchLocation = DocumentFile.fromTreeUri(this, Uri.parse(sharedPreferences.getString("search_location", "")))!!
val searchLocation = DocumentFile.fromTreeUri(this, Uri.parse(settings.searchLocation))!!
val romElements = ArrayList<BaseElement>()
addEntries("nro", RomFormat.NRO, searchLocation, romElements)
@ -155,10 +155,10 @@ class MainActivity : AppCompatActivity() {
}
}
sharedPreferences.edit().putBoolean("refresh_required", false).apply()
settings.refreshRequired = false
} catch (e : IllegalArgumentException) {
runOnUiThread {
sharedPreferences.edit().remove("search_location").apply()
settings.searchLocation = ""
val intent = intent
finish()
@ -191,7 +191,7 @@ class MainActivity : AppCompatActivity() {
PreferenceManager.setDefaultValues(this, R.xml.preferences, false)
AppCompatDelegate.setDefaultNightMode(when ((sharedPreferences.getString("app_theme", "2")?.toInt())) {
AppCompatDelegate.setDefaultNightMode(when ((settings.appTheme.toInt())) {
0 -> AppCompatDelegate.MODE_NIGHT_NO
1 -> AppCompatDelegate.MODE_NIGHT_YES
2 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
@ -267,13 +267,13 @@ class MainActivity : AppCompatActivity() {
}
setAppListDecoration()
if (sharedPreferences.getString("search_location", "") == "") {
if (settings.searchLocation.isEmpty()) {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.flags = Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
startActivityForResult(intent, 1)
} else {
refreshAdapter(!sharedPreferences.getBoolean("refresh_required", false))
refreshAdapter(!settings.refreshRequired)
}
}
@ -301,7 +301,7 @@ class MainActivity : AppCompatActivity() {
}
private fun selectStartGame(appItem : AppItem) {
if (sharedPreferences.getBoolean("select_action", false))
if (settings.selectAction)
AppDialog.newInstance(appItem).show(supportFragmentManager, "game")
else if (appItem.loaderResult == LoaderResult.Success)
startActivity(Intent(this, EmulationActivity::class.java).apply { data = appItem.uri })
@ -341,9 +341,9 @@ class MainActivity : AppCompatActivity() {
1 -> {
val uri = intent!!.data!!
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
sharedPreferences.edit().putString("search_location", uri.toString()).apply()
settings.searchLocation = uri.toString()
refreshAdapter(!sharedPreferences.getBoolean("refresh_required", false))
refreshAdapter(!settings.refreshRequired)
}
2 -> {
@ -361,8 +361,7 @@ class MainActivity : AppCompatActivity() {
}
3 -> {
if (sharedPreferences.getBoolean("refresh_required", false))
refreshAdapter(false)
if (settings.refreshRequired) refreshAdapter(false)
}
}
}

View File

@ -8,6 +8,7 @@ package emu.skyline.adapter.controller
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import emu.skyline.R
import emu.skyline.adapter.GenericLayoutFactory
import emu.skyline.adapter.GenericViewHolder
@ -22,7 +23,9 @@ class ControllerCheckBoxViewItem(var title : String, var summary : String, var c
override fun getLayoutFactory() : GenericLayoutFactory = ControllerCheckBoxLayoutFactory
override fun bind(holder : GenericViewHolder, position : Int) {
holder.text_title.isGone = title.isEmpty()
holder.text_title.text = title
holder.text_subtitle.isGone = summary.isEmpty()
holder.text_subtitle.text = summary
holder.checkbox.isChecked = checked
holder.itemView.setOnClickListener {

View File

@ -9,7 +9,6 @@ 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
@ -20,6 +19,7 @@ import emu.skyline.input.dialog.ButtonDialog
import emu.skyline.input.dialog.RumbleDialog
import emu.skyline.input.dialog.StickDialog
import emu.skyline.input.onscreen.OnScreenEditActivity
import emu.skyline.utils.Settings
import kotlinx.android.synthetic.main.controller_activity.*
import kotlinx.android.synthetic.main.titlebar.*
@ -47,7 +47,7 @@ class ControllerActivity : AppCompatActivity() {
*/
val axisMap = mutableMapOf<AxisId, ControllerStickViewItem>()
private val sharedPrefs by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
private val settings by lazy { Settings(this) }
/**
* This function updates the [adapter] based on information from [InputManager]
@ -65,9 +65,15 @@ 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), 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()
val oscSummary = { checked : Boolean -> getString(if (checked) R.string.osc_shown else R.string.osc_not_shown) }
adapter.addItem(ControllerCheckBoxViewItem(getString(R.string.osc_enable), oscSummary.invoke(settings.onScreenControl), settings.onScreenControl) { item, position ->
item.summary = oscSummary.invoke(item.checked)
settings.onScreenControl = item.checked
adapter.notifyItemChanged(position)
})
adapter.addItem(ControllerCheckBoxViewItem(getString(R.string.osc_recenter_sticks), "", settings.onScreenControlRecenterSticks) { item, position ->
settings.onScreenControlRecenterSticks = item.checked
adapter.notifyItemChanged(position)
})

View File

@ -24,7 +24,7 @@ import kotlin.math.abs
*
* @param item This is used to hold the [ControllerButtonViewItem] between instances
*/
class ButtonDialog @JvmOverloads constructor(private val item : ControllerButtonViewItem? = null, private val position : Int? = null) : BottomSheetDialogFragment() {
class ButtonDialog @JvmOverloads constructor(private val item : ControllerButtonViewItem? = null) : BottomSheetDialogFragment() {
/**
* This inflates the layout of the dialog after initial view creation
*/

View File

@ -7,8 +7,7 @@ package emu.skyline.input.onscreen
import android.content.Context
import emu.skyline.input.ButtonId
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
import emu.skyline.utils.sharedPreferences
interface ControllerConfiguration {
var enabled : Boolean
@ -17,55 +16,18 @@ interface ControllerConfiguration {
var relativeY : Float
}
class ControllerConfigurationDummy(
defaultRelativeX : Float,
defaultRelativeY : Float
) : ControllerConfiguration {
class ControllerConfigurationDummy(defaultRelativeX : Float, defaultRelativeY : Float) : ControllerConfiguration {
override var enabled = true
override var globalScale = 1f
override var relativeX = defaultRelativeX
override var relativeY = defaultRelativeY
}
class ControllerConfigurationImpl(
private val context : Context,
private val buttonId : ButtonId,
defaultRelativeX : Float,
defaultRelativeY : Float
) : ControllerConfiguration {
private inline fun <reified T> config(default : T) = ControllerPrefs(context, "${buttonId.name}_", T::class.java, default)
class ControllerConfigurationImpl(private val context : Context, private val buttonId : ButtonId, defaultRelativeX : Float, defaultRelativeY : Float) : ControllerConfiguration {
private inline fun <reified T> config(default : T, prefix : String = "${buttonId.name}_") = sharedPreferences(context, default, prefix, "controller_config")
override var enabled by config(true)
override var globalScale by ControllerPrefs(context, "on_screen_controller_", Float::class.java, 1f)
override var globalScale by config(1f, "")
override var relativeX by config(defaultRelativeX)
override var relativeY by config(defaultRelativeY)
}
@Suppress("UNCHECKED_CAST")
private class ControllerPrefs<T>(context : Context, private val prefix : String, private val clazz : Class<T>, private val default : T) : ReadWriteProperty<Any, T> {
companion object {
const val CONTROLLER_CONFIG = "controller_config"
}
private val prefs = context.getSharedPreferences(CONTROLLER_CONFIG, Context.MODE_PRIVATE)
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)
Boolean::class.java, java.lang.Boolean::class.java -> putBoolean(prefix + property.name, value as Boolean)
else -> error("Unsupported type $clazz ${Float::class.java}")
}
}.apply()
}
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)
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

@ -17,6 +17,9 @@ import android.view.View
import android.view.View.OnTouchListener
import emu.skyline.input.ButtonId
import emu.skyline.input.ButtonState
import emu.skyline.utils.add
import emu.skyline.utils.multiply
import emu.skyline.utils.normalize
import kotlin.math.roundToLong
typealias OnButtonStateChangedListener = (buttonId : ButtonId, state : ButtonState) -> Unit
@ -32,6 +35,11 @@ class OnScreenControllerView @JvmOverloads constructor(
private var onButtonStateChangedListener : OnButtonStateChangedListener? = null
private var onStickStateChangedListener : OnStickStateChangedListener? = null
private val joystickAnimators = mutableMapOf<JoystickButton, Animator?>()
var recenterSticks = false
set(value) {
field = value
controls.joysticks.forEach { it.recenterSticks = recenterSticks }
}
override fun onDraw(canvas : Canvas) {
super.onDraw(canvas)
@ -95,7 +103,7 @@ class OnScreenControllerView @JvmOverloads constructor(
val value = animation.animatedValue as Float
val vector = direction.multiply(value)
val newPosition = position.add(vector)
joystick.onFingerMoved(newPosition.x, newPosition.y)
joystick.onFingerMoved(newPosition.x, newPosition.y, false)
onStickStateChangedListener?.invoke(joystick.buttonId, vector.multiply(1f / radius))
invalidate()
}
@ -129,6 +137,8 @@ class OnScreenControllerView @JvmOverloads constructor(
joystick.onFingerDown(x, y)
if (joystick.shortDoubleTapped)
onButtonStateChangedListener?.invoke(joystick.buttonId, ButtonState.Pressed)
if (recenterSticks)
onStickStateChangedListener?.invoke(joystick.buttonId, joystick.outerToInnerRelative())
performClick()
handled = true
}
@ -144,8 +154,7 @@ class OnScreenControllerView @JvmOverloads constructor(
}
}
}
handled.also { if (it) invalidate() else super.onTouchEvent(event) }
handled.also { if (it) invalidate() }
}
private val editingTouchHandler = OnTouchListener { _, event ->
@ -175,7 +184,7 @@ class OnScreenControllerView @JvmOverloads constructor(
}
}
false
}.also { handled -> if (handled) invalidate() else super.onTouchEvent(event) }
}.also { handled -> if (handled) invalidate() }
}
init {
@ -194,12 +203,12 @@ class OnScreenControllerView @JvmOverloads constructor(
}
fun increaseScale() {
controls.globalScale *= 1.1f
controls.globalScale += 0.05f
invalidate()
}
fun decreaseScale() {
controls.globalScale *= 0.9f
controls.globalScale -= 0.05f
invalidate()
}

View File

@ -16,6 +16,7 @@ import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import emu.skyline.R
import emu.skyline.utils.Settings
import kotlinx.android.synthetic.main.main_activity.fab_parent
import kotlinx.android.synthetic.main.on_screen_edit_activity.*
@ -31,7 +32,7 @@ class OnScreenEditActivity : AppCompatActivity() {
} else {
fullEditVisible = !fullEditVisible
toggleFabVisibility(fullEditVisible)
fabMapping[R.drawable.ic_close]!!.animate().rotationBy(if (fullEditVisible) -45f else 45f)
fabMapping[R.drawable.ic_close]!!.animate().rotation(if (fullEditVisible) 0f else 45f)
}
}
@ -70,7 +71,7 @@ class OnScreenEditActivity : AppCompatActivity() {
}
private val actions : List<Pair<Int, () -> Unit>> = listOf(
Pair(R.drawable.ic_refresh, { on_screen_controller_view.resetControls() }),
Pair(R.drawable.ic_restore, { 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() }),
@ -83,6 +84,7 @@ class OnScreenEditActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.on_screen_edit_activity)
on_screen_controller_view.recenterSticks = Settings(this).onScreenControlRecenterSticks
actions.forEach { pair ->
fab_parent.addView(FloatingActionButton(this).apply {

View File

@ -12,6 +12,8 @@ import androidx.core.graphics.minus
import emu.skyline.R
import emu.skyline.input.ButtonId
import emu.skyline.input.ButtonId.*
import emu.skyline.utils.add
import emu.skyline.utils.multiply
import kotlin.math.roundToInt
open class CircularButton(
@ -55,11 +57,12 @@ class JoystickButton(
defaultRelativeX,
defaultRelativeY,
defaultRelativeRadiusToX,
R.drawable.ic_stick_circle
R.drawable.ic_button
) {
private val innerButton = CircularButton(onScreenControllerView, buttonId, config.relativeX, config.relativeY, defaultRelativeRadiusToX * 0.75f, R.drawable.ic_stick)
var recenterSticks = false
private lateinit var initialTapPosition : PointF
private var fingerDownTime = 0L
private var fingerUpTime = 0L
var shortDoubleTapped = false
@ -76,12 +79,17 @@ class JoystickButton(
}
override fun onFingerDown(x : Float, y : Float) {
relativeX = x / width
relativeY = (y - heightDiff) / adjustedHeight
val relativeX = x / width
val relativeY = (y - heightDiff) / adjustedHeight
if (!recenterSticks) {
this.relativeX = relativeX
this.relativeY = relativeY
}
innerButton.relativeX = relativeX
innerButton.relativeY = relativeY
val currentTime = SystemClock.elapsedRealtime()
initialTapPosition = PointF(x, y)
val firstTapDiff = fingerUpTime - fingerDownTime
val secondTapDiff = currentTime - fingerUpTime
if (firstTapDiff in 0..500 && secondTapDiff in 0..500) {
@ -101,16 +109,17 @@ class JoystickButton(
drawable.alpha = 255
}
fun onFingerMoved(x : Float, y : Float) : PointF {
fun onFingerMoved(x : Float, y : Float, manualMove : Boolean = true) : PointF {
val position = PointF(currentX, currentY)
var finger = PointF(x, y)
val outerToInner = finger.minus(position)
val distance = outerToInner.length()
if (distance >= radius) {
if (distance > radius) {
finger = position.add(outerToInner.multiply(1f / distance * radius))
}
if (distance > radius * 0.075f) {
// If finger get moved to much, then don't trigger as joystick being pressed
if (manualMove && initialTapPosition.minus(finger).length() > radius * 0.075f) {
fingerDownTime = 0
fingerUpTime = 0
}
@ -122,6 +131,8 @@ class JoystickButton(
fun outerToInner() = PointF(innerButton.currentX, innerButton.currentY).minus(PointF(currentX, currentY))
fun outerToInnerRelative() = outerToInner().multiply(1f / radius)
override fun edit(x : Float, y : Float) {
super.edit(x, y)
@ -209,13 +220,13 @@ class Controls(onScreenControllerView : OnScreenControllerView) {
)
val rectangularButtons = listOf(
RectangularButton(onScreenControllerView, L, 0.1f, 0.25f, 0.075f, 0.08f),
RectangularButton(onScreenControllerView, R, 0.9f, 0.25f, 0.075f, 0.08f)
RectangularButton(onScreenControllerView, L, 0.1f, 0.25f, 0.09f, 0.1f),
RectangularButton(onScreenControllerView, R, 0.9f, 0.25f, 0.09f, 0.1f)
)
val triggerButtons = listOf(
TriggerButton(onScreenControllerView, ZL, 0.1f, 0.1f, 0.075f, 0.08f),
TriggerButton(onScreenControllerView, ZR, 0.9f, 0.1f, 0.075f, 0.08f)
TriggerButton(onScreenControllerView, ZL, 0.1f, 0.1f, 0.09f, 0.1f),
TriggerButton(onScreenControllerView, ZR, 0.9f, 0.1f, 0.09f, 0.1f)
)
val allButtons = circularButtons + joysticks + rectangularButtons + triggerButtons

View File

@ -3,7 +3,7 @@
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.input.onscreen
package emu.skyline.utils
import android.graphics.PointF

View File

@ -3,7 +3,7 @@
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline
package emu.skyline.utils
import java.io.File
import java.io.ObjectInputStream

View File

@ -0,0 +1,32 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.utils
import android.content.Context
class Settings(context : Context) {
var layoutType by sharedPreferences(context, "1")
var searchLocation by sharedPreferences(context, "")
var refreshRequired by sharedPreferences(context, false)
var appTheme by sharedPreferences(context, "2")
var selectAction by sharedPreferences(context, false)
var perfStats by sharedPreferences(context, false)
var operationMode by sharedPreferences(context, true)
var onScreenControl by sharedPreferences(context, false)
var onScreenControlRecenterSticks by sharedPreferences(context, false)
var logCompact by sharedPreferences(context, false)
var logLevel by sharedPreferences(context, "3")
}

View File

@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: MPL-2.0
* Copyright © 2020 Skyline Team and Contributors (https://github.com/skyline-emu/)
*/
package emu.skyline.utils
import android.content.Context
import androidx.preference.PreferenceManager
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
inline fun <reified T> sharedPreferences(context : Context, default : T, prefix : String = "", prefName : String? = null) = SharedPreferencesDelegate(context, T::class.java, default, prefix, prefName)
@Suppress("UNCHECKED_CAST")
class SharedPreferencesDelegate<T>(context : Context, private val clazz : Class<T>, private val default : T, private val prefix : String, prefName : String?) : ReadWriteProperty<Any, T> {
private val prefs = prefName?.let { context.getSharedPreferences(prefName, Context.MODE_PRIVATE) } ?: PreferenceManager.getDefaultSharedPreferences(context)
override fun setValue(thisRef : Any, property : KProperty<*>, value : T) = (prefix + pascalToSnakeCase(property.name)).let { keyName ->
prefs.edit().apply {
when (clazz) {
Float::class.java, java.lang.Float::class.java -> putFloat(keyName, value as Float)
Boolean::class.java, java.lang.Boolean::class.java -> putBoolean(keyName, value as Boolean)
String::class.java, java.lang.String::class.java -> putString(keyName, value as String)
else -> error("Unsupported type $clazz ${Float::class.java}")
}
}.apply()
}
override fun getValue(thisRef : Any, property : KProperty<*>) : T = (prefix + pascalToSnakeCase(property.name)).let { keyName ->
prefs.let {
@Suppress("IMPLICIT_CAST_TO_ANY")
when (clazz) {
Float::class.java, java.lang.Float::class.java -> it.getFloat(keyName, default as Float)
Boolean::class.java, java.lang.Boolean::class.java -> it.getBoolean(keyName, default as Boolean)
String::class.java, java.lang.String::class.java -> it.getString(keyName, default as String)
else -> error("Unsupported type $clazz")
}
} as T
}
private fun pascalToSnakeCase(text : String) = StringBuilder().apply {
text.forEachIndexed { index, c ->
if (index != 0 && c.isUpperCase()) append('_')
append(c.toLowerCase())
}.toString()
}
}

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">
<solid android:color="#50FFFFFF" />
<solid android:color="#20FFFFFF" />
<stroke
android:width="2.5dp"
android:color="#A0FFFFFF" />
android:color="#25FFFFFF" />
</shape>

View File

@ -2,10 +2,10 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#50FFFFFF" />
<solid android:color="#20FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:color="#25FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />

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="M12,3A9,9 0 0,0 3,12H0L4,16L8,12H5A7,7 0 0,1 12,5A7,7 0 0,1 19,12A7,7 0 0,1 12,19C10.5,19 9.09,18.5 7.94,17.7L6.5,19.14C8.04,20.3 9.94,21 12,21A9,9 0 0,0 21,12A9,9 0 0,0 12,3M14,12A2,2 0 0,0 12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12Z" />
</vector>

View File

@ -2,10 +2,10 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval">
<solid android:color="#50FFFFFF" />
<solid android:color="#10FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:color="#25FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />
@ -18,7 +18,7 @@
</item>
<item>
<shape android:shape="oval">
<solid android:color="#50000000" />
<solid android:color="#30000000" />
<size
android:width="30dp"
android:height="30dp" />

View File

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

View File

@ -2,10 +2,10 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#50FFFFFF" />
<solid android:color="#20FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:color="#25FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />

View File

@ -2,10 +2,10 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#50FFFFFF" />
<solid android:color="#20FFFFFF" />
<stroke
android:width="2dp"
android:color="#A0FFFFFF" />
android:color="#25FFFFFF" />
<size
android:width="25dp"
android:height="25dp" />

View File

@ -36,10 +36,10 @@
android:id="@+id/button_icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:alpha="0.15"
android:contentDescription="@string/buttons"
android:outlineProvider="bounds"
android:src="@drawable/ic_button" />
android:src="@drawable/ic_button"
android:tint="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/button_text"

View File

@ -1,52 +1,42 @@
<?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"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/controller_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true">
android:focusable="true"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/text_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceListItem"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@id/text_subtitle"
app:layout_constraintEnd_toStartOf="@id/checkbox"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title" />
<TextView
android:id="@+id/text_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="@android:color/tertiary_text_light"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/checkbox"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_title"
tools:text="Summary" />
</LinearLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
android:layout_gravity="center_vertical"
android:clickable="false" />
</LinearLayout>

View File

@ -50,7 +50,8 @@
android:alpha="0.4"
android:contentDescription="@string/buttons"
android:outlineProvider="bounds"
android:src="@drawable/ic_stick_circle" />
android:src="@drawable/ic_button"
android:tint="?android:attr/textColorPrimary" />
<RelativeLayout
@ -70,7 +71,8 @@
android:alpha="0.4"
android:contentDescription="@string/buttons"
android:outlineProvider="bounds"
android:src="@drawable/ic_stick" />
android:src="@drawable/ic_stick"
android:tint="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/stick_name"

View File

@ -60,6 +60,7 @@
<string name="joystick">Joystick</string>
<string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<string name="osc_recenter_sticks">Recenter sticks on touch</string>
<string name="controller">Controller</string>
<string name="config_controller">Configure Controller</string>
<string name="controller_type">Controller Type</string>