mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2025-12-16 12:08:49 +00:00
Added bottom hot corners
This commit is contained in:
parent
e0b8e8440a
commit
170457b070
@ -8,6 +8,7 @@ import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.res.Configuration
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.SharedPreferences
|
||||
@ -49,6 +50,7 @@ import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.slider.Slider
|
||||
import java.io.File
|
||||
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.DirectoryInitializationState
|
||||
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.GameHelper
|
||||
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 settings get() = settingsViewModel.settings
|
||||
|
||||
private val onPause = Runnable{ togglePause() }
|
||||
private val onPause = Runnable{ togglePauseAndSyncMenu() }
|
||||
private val onShutdown = Runnable{ emulationState.stop() }
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
@ -183,6 +188,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
}
|
||||
|
||||
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.visibility = View.GONE
|
||||
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() {
|
||||
super.onResume()
|
||||
Choreographer.getInstance().postFrameCallback(this)
|
||||
binding.hotCornerOverlay.refresh()
|
||||
if (NativeLibrary.isRunning()) {
|
||||
emulationState.pause()
|
||||
|
||||
@ -823,6 +868,28 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -830,6 +897,110 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
||||
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() {
|
||||
val popupMenu = PopupMenu(
|
||||
requireContext(),
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -598,6 +598,12 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex
|
||||
|
||||
fun setIsInEditMode(isInEditMode: Boolean) {
|
||||
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() {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -32,6 +32,16 @@
|
||||
android:focusableInTouchMode="true"
|
||||
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
|
||||
android:id="@+id/done_control_config"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
|
||||
@ -24,6 +24,29 @@
|
||||
android:id="@+id/menu_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
|
||||
android:id="@+id/menu_emulation_adjust_scales"
|
||||
android:title="@string/emulation_control_scale">
|
||||
@ -104,5 +127,4 @@
|
||||
<item
|
||||
android:id="@+id/menu_emulation_reset_overlay"
|
||||
android:title="@string/emulation_touch_overlay_reset" />
|
||||
|
||||
</menu>
|
||||
|
||||
@ -472,6 +472,21 @@
|
||||
<string name="lock_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="load_settings">正在加载设置…</string>
|
||||
|
||||
|
||||
@ -16,4 +16,7 @@
|
||||
<dimen name="spacing_fab">24dp</dimen>
|
||||
|
||||
<dimen name="dialog_margin">20dp</dimen>
|
||||
|
||||
<!-- Hot corner area size -->
|
||||
<dimen name="hot_corner_size">50dp</dimen>
|
||||
</resources>
|
||||
|
||||
@ -502,6 +502,21 @@
|
||||
<string name="lock_drawer">Lock 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="load_settings">Loading Settings…</string>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user