Added bottom hot corners

This commit is contained in:
Wei Liu 2025-09-09 16:20:09 +08:00 committed by OpenSauce
parent e0b8e8440a
commit 170457b070
9 changed files with 512 additions and 2 deletions

View File

@ -8,6 +8,7 @@ import android.annotation.SuppressLint
import android.app.ActivityManager import android.app.ActivityManager
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.res.Configuration
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.SharedPreferences import android.content.SharedPreferences
@ -49,6 +50,7 @@ import androidx.navigation.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.appcompat.app.AlertDialog
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import java.io.File import java.io.File
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
@ -74,6 +76,9 @@ import org.citra.citra_emu.model.Game
import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.EmulationMenuSettings
import org.citra.citra_emu.overlay.HotCornerOverlay
import org.citra.citra_emu.utils.HotCornerSettings
import org.citra.citra_emu.utils.TurboHelper
import org.citra.citra_emu.utils.FileUtil import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GameHelper import org.citra.citra_emu.utils.GameHelper
import org.citra.citra_emu.utils.GameIconUtils import org.citra.citra_emu.utils.GameIconUtils
@ -103,7 +108,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
private val settingsViewModel: SettingsViewModel by viewModels() private val settingsViewModel: SettingsViewModel by viewModels()
private val settings get() = settingsViewModel.settings private val settings get() = settingsViewModel.settings
private val onPause = Runnable{ togglePause() } private val onPause = Runnable{ togglePauseAndSyncMenu() }
private val onShutdown = Runnable{ emulationState.stop() } private val onShutdown = Runnable{ emulationState.stop() }
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
@ -183,6 +188,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
binding.surfaceEmulation.holder.addCallback(this) binding.surfaceEmulation.holder.addCallback(this)
// Setup hot corner overlay
binding.hotCornerOverlay.apply {
actionListener = object : HotCornerOverlay.OnActionListener {
override fun onHotCornerAction(action: HotCornerSettings.HotCornerAction) {
when (action) {
HotCornerSettings.HotCornerAction.NONE -> {}
HotCornerSettings.HotCornerAction.PAUSE_RESUME -> togglePauseAndSyncMenu()
HotCornerSettings.HotCornerAction.TOGGLE_TURBO -> TurboHelper.toggleTurbo(true)
HotCornerSettings.HotCornerAction.QUICK_SAVE -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(context, getString(R.string.saving), Toast.LENGTH_SHORT).show()
}
HotCornerSettings.HotCornerAction.QUICK_LOAD -> {
val loaded = NativeLibrary.loadStateIfAvailable(NativeLibrary.QUICKSAVE_SLOT)
val resId = if (loaded) R.string.loading else R.string.quickload_not_found
Toast.makeText(context, getString(resId), Toast.LENGTH_SHORT).show()
}
HotCornerSettings.HotCornerAction.OPEN_MENU -> {
if (!binding.drawerLayout.isOpen) binding.drawerLayout.open()
}
HotCornerSettings.HotCornerAction.SWAP_SCREENS -> screenAdjustmentUtil.swapScreen()
}
}
}
refresh()
}
binding.doneControlConfig.setOnClickListener { binding.doneControlConfig.setOnClickListener {
binding.doneControlConfig.visibility = View.GONE binding.doneControlConfig.visibility = View.GONE
binding.surfaceInputOverlay.setIsInEditMode(false) binding.surfaceInputOverlay.setIsInEditMode(false)
@ -471,9 +502,23 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
} }
private fun togglePauseAndSyncMenu() {
togglePause()
binding.inGameMenu.menu.findItem(R.id.menu_emulation_pause)?.let { menuItem ->
if (emulationState.isPaused) {
menuItem.title = resources.getString(R.string.resume_emulation)
menuItem.icon = ResourcesCompat.getDrawable(resources, R.drawable.ic_play, requireContext().theme)
} else {
menuItem.title = resources.getString(R.string.pause_emulation)
menuItem.icon = ResourcesCompat.getDrawable(resources, R.drawable.ic_pause, requireContext().theme)
}
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Choreographer.getInstance().postFrameCallback(this) Choreographer.getInstance().postFrameCallback(this)
binding.hotCornerOverlay.refresh()
if (NativeLibrary.isRunning()) { if (NativeLibrary.isRunning()) {
emulationState.pause() emulationState.pause()
@ -823,6 +868,28 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true true
} }
R.id.menu_hot_corner_radius -> {
runAfterDrawerClosed { showHotCornerRadiusDialog() }
true
}
R.id.menu_hot_corner_portrait_bl -> {
showHotCornerSelectDialog(Configuration.ORIENTATION_PORTRAIT, HotCornerSettings.HotCornerPosition.BOTTOM_LEFT)
true
}
R.id.menu_hot_corner_portrait_br -> {
showHotCornerSelectDialog(Configuration.ORIENTATION_PORTRAIT, HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT)
true
}
R.id.menu_hot_corner_landscape_bl -> {
showHotCornerSelectDialog(Configuration.ORIENTATION_LANDSCAPE, HotCornerSettings.HotCornerPosition.BOTTOM_LEFT)
true
}
R.id.menu_hot_corner_landscape_br -> {
showHotCornerSelectDialog(Configuration.ORIENTATION_LANDSCAPE, HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT)
true
}
else -> true else -> true
} }
} }
@ -830,6 +897,110 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.show() popupMenu.show()
} }
private fun showHotCornerSelectDialog(orientation: Int, position: HotCornerSettings.HotCornerPosition) {
val actions = arrayOf(
getString(R.string.hot_corner_action_none),
getString(R.string.hot_corner_action_pause_resume),
getString(R.string.hot_corner_action_toggle_turbo),
getString(R.string.hot_corner_action_quick_save),
getString(R.string.hot_corner_action_quick_load),
getString(R.string.hot_corner_action_open_menu),
getString(R.string.hot_corner_action_swap_screens)
)
val values = HotCornerSettings.HotCornerAction.values()
val current = HotCornerSettings.getAction(orientation, position)
var selectedIndex = values.indexOf(current)
MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.hot_corner_settings)
.setSingleChoiceItems(actions, selectedIndex) { _: DialogInterface?, which: Int ->
selectedIndex = which
}
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
HotCornerSettings.setAction(orientation, position, values[selectedIndex])
binding.hotCornerOverlay.refresh()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun showHotCornerRadiusDialog() {
val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
val max = 125
val min = 0
val previousDp = HotCornerSettings.getRadiusDp()
var currentDp = previousDp
sliderBinding.apply {
slider.valueFrom = min.toFloat()
slider.valueTo = max.toFloat()
slider.value = previousDp.toFloat()
textValue.setText(previousDp.toString())
textInput.suffixText = "dp"
slider.addOnChangeListener { _: Slider, value: Float, _: Boolean ->
currentDp = value.toInt()
if (textValue.text.toString() != currentDp.toString()) {
textValue.setText(currentDp.toString())
textValue.setSelection(textValue.length())
}
// Live preview
binding.hotCornerOverlay.showPreview(true)
binding.hotCornerOverlay.updatePreviewRadiusDp(currentDp)
}
}
// Ensure preview is visible with current value before interaction
binding.hotCornerOverlay.showPreview(true)
binding.hotCornerOverlay.updatePreviewRadiusDp(previousDp)
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.hot_corner_radius)
.setView(sliderBinding.root)
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int ->
// Revert to previous value
HotCornerSettings.setRadiusDp(previousDp)
binding.hotCornerOverlay.showPreview(false)
binding.hotCornerOverlay.refresh()
}
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
// Persist current value
HotCornerSettings.setRadiusDp(currentDp)
binding.hotCornerOverlay.showPreview(false)
binding.hotCornerOverlay.refresh()
}
.setNeutralButton(R.string.slider_default, null)
.create()
dialog.setOnShowListener {
val neutral = dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
neutral.setOnClickListener {
currentDp = 72
sliderBinding.slider.value = 72f
binding.hotCornerOverlay.showPreview(true)
binding.hotCornerOverlay.updatePreviewRadiusDp(currentDp)
}
}
dialog.show()
}
private fun runAfterDrawerClosed(action: () -> Unit) {
if (!binding.drawerLayout.isOpen) {
action()
return
}
binding.drawerLayout.addDrawerListener(object : DrawerListener {
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {}
override fun onDrawerOpened(drawerView: View) {}
override fun onDrawerStateChanged(newState: Int) {}
override fun onDrawerClosed(drawerView: View) {
binding.drawerLayout.removeDrawerListener(this)
action()
}
})
binding.drawerLayout.close()
}
private fun showAmiiboMenu() { private fun showAmiiboMenu() {
val popupMenu = PopupMenu( val popupMenu = PopupMenu(
requireContext(), requireContext(),

View File

@ -0,0 +1,210 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.overlay
import android.content.Context
import android.content.res.Configuration
import android.util.AttributeSet
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.view.Gravity
import android.view.View
import android.view.MotionEvent
import android.widget.FrameLayout
import org.citra.citra_emu.R
import org.citra.citra_emu.utils.HotCornerSettings
class HotCornerOverlay @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
interface OnActionListener {
fun onHotCornerAction(action: HotCornerSettings.HotCornerAction)
}
var actionListener: OnActionListener? = null
private var previewEnabled = false
private var previewRadiusPx: Int? = null
init {
isClickable = false
isFocusable = false
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
}
fun refresh() {
removeAllViews()
val orientation = resources.configuration.orientation
val radiusDp = HotCornerSettings.getRadiusDp()
val sizePx = previewRadiusPx ?: (radiusDp * resources.displayMetrics.density).toInt()
addCornerIfNeeded(
sizePx,
Gravity.BOTTOM or Gravity.START,
HotCornerSettings.getAction(orientation, HotCornerSettings.HotCornerPosition.BOTTOM_LEFT)
)
addCornerIfNeeded(
sizePx,
Gravity.BOTTOM or Gravity.END,
HotCornerSettings.getAction(orientation, HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT)
)
if (previewEnabled) addPreviewOverlay(sizePx)
}
private fun addCornerIfNeeded(sizePx: Int, gravity: Int, action: HotCornerSettings.HotCornerAction) {
if (action == HotCornerSettings.HotCornerAction.NONE) return
val position = if ((gravity and Gravity.START) == Gravity.START) {
HotCornerSettings.HotCornerPosition.BOTTOM_LEFT
} else {
HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT
}
val v = QuarterCircleTouchView(context, position, sizePx).apply {
layoutParams = LayoutParams(sizePx, sizePx, gravity)
contentDescription = action.name
setOnClickListener { actionListener?.onHotCornerAction(action) }
}
addView(v)
}
fun showPreview(enable: Boolean) {
previewEnabled = enable
if (!enable) previewRadiusPx = null
refresh()
}
fun updatePreviewRadiusDp(dp: Int) {
previewRadiusPx = (dp * resources.displayMetrics.density).toInt()
refresh()
}
private fun addPreviewOverlay(sizePx: Int) {
val overlayColor = 0x80FF0000.toInt() // 50% alpha red
val left = QuarterCirclePreviewView(
context,
HotCornerSettings.HotCornerPosition.BOTTOM_LEFT,
sizePx,
overlayColor
).apply {
layoutParams = LayoutParams(sizePx, sizePx, Gravity.BOTTOM or Gravity.START)
}
val right = QuarterCirclePreviewView(
context,
HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT,
sizePx,
overlayColor
).apply {
layoutParams = LayoutParams(sizePx, sizePx, Gravity.BOTTOM or Gravity.END)
}
addView(left)
addView(right)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
refresh()
}
}
private class QuarterCircleTouchView(
context: Context,
private val position: HotCornerSettings.HotCornerPosition,
private val radiusPx: Int
) : View(context) {
private var downInside = false
init {
isClickable = true
isFocusable = false
background = null
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val inside = isInsideQuarterCircle(event.x, event.y)
return when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (inside) {
downInside = true
true
} else {
downInside = false
false
}
}
MotionEvent.ACTION_UP -> {
val handled = downInside && inside
if (handled) performClick()
downInside = false
handled
}
MotionEvent.ACTION_CANCEL -> {
downInside = false
false
}
else -> downInside
}
}
override fun performClick(): Boolean {
return super.performClick()
}
private fun isInsideQuarterCircle(x: Float, y: Float): Boolean {
val r = radiusPx.toFloat()
val (cx, cy) = when (position) {
HotCornerSettings.HotCornerPosition.BOTTOM_LEFT -> 0f to r
HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT -> r to r
}
val dx = x - cx
val dy = y - cy
// Constrain to the visible quadrant only
val inQuadrant = when (position) {
HotCornerSettings.HotCornerPosition.BOTTOM_LEFT -> (dx >= 0f && dy <= 0f)
HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT -> (dx <= 0f && dy <= 0f)
}
return inQuadrant && (dx * dx + dy * dy <= r * r)
}
}
private class QuarterCirclePreviewView(
context: Context,
private val position: HotCornerSettings.HotCornerPosition,
private val radiusPx: Int,
color: Int
) : View(context) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
this.color = color
}
private val oval = RectF()
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val r = radiusPx.toFloat()
when (position) {
HotCornerSettings.HotCornerPosition.BOTTOM_LEFT -> {
// Center at (0, r), rect spans [-r, 0]..[r, 2r]
oval.set(-r, 0f, r, 2f * r)
canvas.drawArc(oval, 270f, 90f, true, paint)
}
HotCornerSettings.HotCornerPosition.BOTTOM_RIGHT -> {
// Center at (r, r), rect spans [0, 0]..[2r, 2r]
oval.set(0f, 0f, 2f * r, 2f * r)
canvas.drawArc(oval, 180f, 90f, true, paint)
}
}
}
}

View File

@ -598,6 +598,12 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
fun setIsInEditMode(isInEditMode: Boolean) { fun setIsInEditMode(isInEditMode: Boolean) {
this.isInEditMode = isInEditMode this.isInEditMode = isInEditMode
// Hide hot corners while editing overlay to avoid drag conflicts
try {
val root = rootView
val hotCorner = root.findViewById<HotCornerOverlay>(R.id.hot_corner_overlay)
hotCorner?.visibility = if (isInEditMode) View.GONE else View.VISIBLE
} catch (_: Exception) {}
} }
private fun defaultOverlay() { private fun defaultOverlay() {

View File

@ -0,0 +1,58 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.utils
import android.content.res.Configuration
import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication
/**
* Stores per-orientation hot corner actions.
* Defaults:
* - Portrait: BL=TOGGLE_TURBO, BR=PAUSE_RESUME
* - Landscape: BL=PAUSE_RESUME, BR=NONE
*/
object HotCornerSettings {
private val preferences =
PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext)
private const val KEY_RADIUS_DP = "HotCorner_radius_dp"
enum class HotCornerPosition { BOTTOM_LEFT, BOTTOM_RIGHT }
enum class HotCornerAction(val keySuffix: String) {
NONE("none"),
PAUSE_RESUME("pause_resume"),
TOGGLE_TURBO("toggle_turbo"),
QUICK_SAVE("quick_save"),
QUICK_LOAD("quick_load"),
OPEN_MENU("open_menu"),
SWAP_SCREENS("swap_screens")
}
private fun key(orientation: Int, position: HotCornerPosition): String {
val orient = if (orientation == Configuration.ORIENTATION_LANDSCAPE) "land" else "port"
val pos = if (position == HotCornerPosition.BOTTOM_RIGHT) "br" else "bl"
return "HotCorner_${orient}_${pos}_action"
}
fun getAction(orientation: Int, position: HotCornerPosition): HotCornerAction {
val stored = preferences.getString(key(orientation, position), null)
val defaultAction = HotCornerAction.NONE
val value = stored ?: defaultAction.name
return runCatching { HotCornerAction.valueOf(value) }.getOrElse { defaultAction }
}
fun setAction(orientation: Int, position: HotCornerPosition, action: HotCornerAction) {
preferences.edit().putString(key(orientation, position), action.name).apply()
}
fun getRadiusDp(): Int {
return preferences.getInt(KEY_RADIUS_DP, 72)
}
fun setRadiusDp(value: Int) {
preferences.edit().putInt(KEY_RADIUS_DP, value).apply()
}
}

View File

@ -32,6 +32,16 @@
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:visibility="invisible" /> android:visibility="invisible" />
<!-- Hot corner overlay: hosts invisible corner touch targets; placed above input overlay -->
<org.citra.citra_emu.overlay.HotCornerOverlay
android:id="@+id/hot_corner_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:clickable="false"
android:focusable="false"
android:visibility="visible" />
<Button <Button
android:id="@+id/done_control_config" android:id="@+id/done_control_config"
style="@style/Widget.Material3.Button.ElevatedButton" style="@style/Widget.Material3.Button.ElevatedButton"

View File

@ -24,6 +24,29 @@
android:id="@+id/menu_emulation_toggle_controls" android:id="@+id/menu_emulation_toggle_controls"
android:title="@string/emulation_toggle_controls" /> android:title="@string/emulation_toggle_controls" />
<!-- Hot corner settings moved up -->
<item
android:id="@+id/menu_hot_corner_settings"
android:title="@string/hot_corner_settings">
<menu>
<item
android:id="@+id/menu_hot_corner_portrait_bl"
android:title="@string/hot_corner_portrait_bl" />
<item
android:id="@+id/menu_hot_corner_portrait_br"
android:title="@string/hot_corner_portrait_br" />
<item
android:id="@+id/menu_hot_corner_landscape_bl"
android:title="@string/hot_corner_landscape_bl" />
<item
android:id="@+id/menu_hot_corner_landscape_br"
android:title="@string/hot_corner_landscape_br" />
<item
android:id="@+id/menu_hot_corner_radius"
android:title="@string/hot_corner_radius" />
</menu>
</item>
<item <item
android:id="@+id/menu_emulation_adjust_scales" android:id="@+id/menu_emulation_adjust_scales"
android:title="@string/emulation_control_scale"> android:title="@string/emulation_control_scale">
@ -104,5 +127,4 @@
<item <item
android:id="@+id/menu_emulation_reset_overlay" android:id="@+id/menu_emulation_reset_overlay"
android:title="@string/emulation_touch_overlay_reset" /> android:title="@string/emulation_touch_overlay_reset" />
</menu> </menu>

View File

@ -472,6 +472,21 @@
<string name="lock_drawer">锁定侧滑菜单</string> <string name="lock_drawer">锁定侧滑菜单</string>
<string name="unlock_drawer">解锁侧滑菜单</string> <string name="unlock_drawer">解锁侧滑菜单</string>
<!-- Hot corner strings -->
<string name="hot_corner_settings">底部热区设置</string>
<string name="hot_corner_portrait_bl">竖屏 左下角</string>
<string name="hot_corner_portrait_br">竖屏 右下角</string>
<string name="hot_corner_landscape_bl">横屏 左下角</string>
<string name="hot_corner_landscape_br">横屏 右下角</string>
<string name="hot_corner_action_none"></string>
<string name="hot_corner_action_pause_resume">暂停/继续</string>
<string name="hot_corner_action_toggle_turbo">加速</string>
<string name="hot_corner_action_quick_save">快速保存</string>
<string name="hot_corner_action_quick_load">快速加载</string>
<string name="hot_corner_action_open_menu">打开菜单</string>
<string name="hot_corner_action_swap_screens">交换上下屏</string>
<string name="hot_corner_radius">热区大小设置</string>
<string name="write_permission_needed">您需要允许 Lime3DS 对外部存储器进行读写访问,以便模拟器正常工作。</string> <string name="write_permission_needed">您需要允许 Lime3DS 对外部存储器进行读写访问,以便模拟器正常工作。</string>
<string name="load_settings">正在加载设置…</string> <string name="load_settings">正在加载设置…</string>

View File

@ -16,4 +16,7 @@
<dimen name="spacing_fab">24dp</dimen> <dimen name="spacing_fab">24dp</dimen>
<dimen name="dialog_margin">20dp</dimen> <dimen name="dialog_margin">20dp</dimen>
<!-- Hot corner area size -->
<dimen name="hot_corner_size">50dp</dimen>
</resources> </resources>

View File

@ -502,6 +502,21 @@
<string name="lock_drawer">Lock Drawer</string> <string name="lock_drawer">Lock Drawer</string>
<string name="unlock_drawer">Unlock Drawer</string> <string name="unlock_drawer">Unlock Drawer</string>
<!-- Hot corner strings -->
<string name="hot_corner_settings">Bottom Hot Corners</string>
<string name="hot_corner_portrait_bl">Portrait Bottom-Left</string>
<string name="hot_corner_portrait_br">Portrait Bottom-Right</string>
<string name="hot_corner_landscape_bl">Landscape Bottom-Left</string>
<string name="hot_corner_landscape_br">Landscape Bottom-Right</string>
<string name="hot_corner_action_none">None</string>
<string name="hot_corner_action_pause_resume">Pause/Resume</string>
<string name="hot_corner_action_toggle_turbo">Turbo Speed</string>
<string name="hot_corner_action_quick_save">Quicksave</string>
<string name="hot_corner_action_quick_load">Quickload</string>
<string name="hot_corner_action_open_menu">Open Menu</string>
<string name="hot_corner_action_swap_screens">Swap Screens</string>
<string name="hot_corner_radius">Hot Corner Size</string>
<string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string> <string name="write_permission_needed">You need to allow write access to external storage for the emulator to work</string>
<string name="load_settings">Loading Settings…</string> <string name="load_settings">Loading Settings…</string>