Merge branch 'master' into ui-netsettings

This commit is contained in:
OpenSauce 2025-04-12 14:01:53 +01:00 committed by GitHub
commit e09bf8ee61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 663 additions and 144 deletions

View file

@ -50,11 +50,11 @@ jobs:
- name: Build - name: Build
run: ./.ci/linux.sh run: ./.ci/linux.sh
- name: Pack - name: Pack
if: ${{ matrix.target == 'appimage' }}
run: ./.ci/pack.sh run: ./.ci/pack.sh
if: ${{ matrix.target == 'appimage' }}
- name: Upload - name: Upload
uses: actions/upload-artifact@v4
if: ${{ matrix.target == 'appimage' }} if: ${{ matrix.target == 'appimage' }}
uses: actions/upload-artifact@v4
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
@ -148,19 +148,19 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Set up MSVC - name: Set up MSVC
if: ${{ matrix.target == 'msvc' }}
uses: ilammy/msvc-dev-cmd@v1 uses: ilammy/msvc-dev-cmd@v1
if: ${{ matrix.target == 'msvc' }}
- name: Install extra tools (MSVC) - name: Install extra tools (MSVC)
run: choco install ccache ninja wget
if: ${{ matrix.target == 'msvc' }} if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget
- name: Install vulkan-sdk (MSVC) - name: Install vulkan-sdk (MSVC)
if: ${{ matrix.target == 'msvc' }}
run: | run: |
wget https://sdk.lunarg.com/sdk/download/1.3.296.0/windows/VulkanSDK-1.3.296.0-Installer.exe -O D:/a/_temp/vulkan.exe wget https://sdk.lunarg.com/sdk/download/1.3.296.0/windows/VulkanSDK-1.3.296.0-Installer.exe -O D:/a/_temp/vulkan.exe
D:/a/_temp/vulkan.exe --accept-licenses --default-answer --confirm-command install D:/a/_temp/vulkan.exe --accept-licenses --default-answer --confirm-command install
if: ${{ matrix.target == 'msvc' }}
- name: Set up MSYS2 - name: Set up MSYS2
uses: msys2/setup-msys2@v2
if: ${{ matrix.target == 'msys2' }} if: ${{ matrix.target == 'msys2' }}
uses: msys2/setup-msys2@v2
with: with:
msystem: clang64 msystem: clang64
update: true update: true
@ -168,10 +168,16 @@ jobs:
pacboy: >- pacboy: >-
toolchain:p ccache:p cmake:p ninja:p spirv-tools:p toolchain:p ccache:p cmake:p ninja:p spirv-tools:p
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
- name: Install extra tools (MSYS2)
if: ${{ matrix.target == 'msys2' }}
uses: crazy-max/ghaction-chocolatey@v3
with:
args: install ptime wget
- name: Install NSIS - name: Install NSIS
if: ${{ github.ref_type == 'tag' }}
run: | run: |
Invoke-WebRequest https://deac-riga.dl.sourceforge.net/project/nsis/NSIS%203/3.10/nsis-3.10-setup.exe?viasf=1 -OutFile C:\WINDOWS\Temp\nsis-3.10-setup.exe wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
Invoke-Expression "& C:\WINDOWS\Temp\nsis-3.10-setup.exe \S" ptime D:/a/_temp/nsis-setup.exe /S
shell: pwsh shell: pwsh
- name: Disable line ending translation - name: Disable line ending translation
run: git config --global core.autocrlf input run: git config --global core.autocrlf input
@ -241,8 +247,8 @@ jobs:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/ path: src/android/app/artifacts/
ios: ios:
runs-on: macos-14
if: ${{ !startsWith(github.ref, 'refs/tags/') }} if: ${{ !startsWith(github.ref, 'refs/tags/') }}
runs-on: macos-14
env: env:
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_COMPILERCHECK: content CCACHE_COMPILERCHECK: content

View file

@ -186,6 +186,17 @@ object NativeLibrary {
external fun unlinkConsole() external fun unlinkConsole()
/**
* Turbo speed.
*/
external fun toggleTurboSpeed(enabled: Boolean)
external fun getTurboSpeedSlider(): Int
external fun setTurboSpeedSlider(value: Int)
private var coreErrorAlertResult = false private var coreErrorAlertResult = false
private val coreErrorAlertLock = Object() private val coreErrorAlertLock = Object()

View file

@ -33,6 +33,7 @@ import org.citra.citra_emu.contracts.OpenFileResultContract
import org.citra.citra_emu.databinding.ActivityEmulationBinding import org.citra.citra_emu.databinding.ActivityEmulationBinding
import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.features.hotkeys.HotkeyUtility import org.citra.citra_emu.features.hotkeys.HotkeyUtility
import org.citra.citra_emu.features.hotkeys.HotkeyFunctions
import org.citra.citra_emu.features.settings.model.BooleanSetting import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.model.SettingsViewModel
@ -55,6 +56,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyFunctions: HotkeyFunctions
private lateinit var hotkeyUtility: HotkeyUtility private lateinit var hotkeyUtility: HotkeyUtility
private val emulationFragment: EmulationFragment private val emulationFragment: EmulationFragment
@ -75,7 +77,8 @@ class EmulationActivity : AppCompatActivity() {
binding = ActivityEmulationBinding.inflate(layoutInflater) binding = ActivityEmulationBinding.inflate(layoutInflater)
screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings) screenAdjustmentUtil = ScreenAdjustmentUtil(this, windowManager, settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this) hotkeyFunctions = HotkeyFunctions(settingsViewModel.settings)
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, hotkeyFunctions, this)
setContentView(binding.root) setContentView(binding.root)
val navHostFragment = val navHostFragment =
@ -138,6 +141,7 @@ class EmulationActivity : AppCompatActivity() {
} }
override fun onDestroy() { override fun onDestroy() {
hotkeyFunctions.resetTurboSpeed()
EmulationLifecycleUtil.clear() EmulationLifecycleUtil.clear()
isEmulationRunning = false isEmulationRunning = false
instance = null instance = null

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -41,7 +41,8 @@ enum class SmallScreenPosition(val int: Int) {
enum class PortraitScreenLayout(val int: Int) { enum class PortraitScreenLayout(val int: Int) {
// These must match what is defined in src/common/settings.h // These must match what is defined in src/common/settings.h
TOP_FULL_WIDTH(0), TOP_FULL_WIDTH(0),
CUSTOM_PORTRAIT_LAYOUT(1); CUSTOM_PORTRAIT_LAYOUT(1),
ORIGINAL(2);
companion object { companion object {
fun from(int: Int): PortraitScreenLayout { fun from(int: Int): PortraitScreenLayout {

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -10,5 +10,6 @@ enum class Hotkey(val button: Int) {
CLOSE_GAME(10003), CLOSE_GAME(10003),
PAUSE_OR_RESUME(10004), PAUSE_OR_RESUME(10004),
QUICKSAVE(10005), QUICKSAVE(10005),
QUICKLOAD(10006); QUICKLOAD(10006),
TURBO_SPEED(10007);
} }

View file

@ -0,0 +1,57 @@
// 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.features.hotkeys
import android.widget.Toast
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.features.settings.utils.SettingsFile
class HotkeyFunctions (
private val settings: Settings
) {
private var normalSpeed = IntSetting.FRAME_LIMIT.int
var isTurboSpeedEnabled = false
// Turbo Speed
fun setTurboSpeed(enabled: Boolean) {
isTurboSpeedEnabled = enabled
toggleTurboSpeed()
}
fun toggleTurboSpeed() {
if (isTurboSpeedEnabled) {
normalSpeed = IntSetting.FRAME_LIMIT.int
NativeLibrary.toggleTurboSpeed(true)
NativeLibrary.setTurboSpeedSlider(IntSetting.TURBO_SPEED.int)
IntSetting.FRAME_LIMIT.int = IntSetting.TURBO_SPEED.int
} else {
NativeLibrary.toggleTurboSpeed(false)
NativeLibrary.setTurboSpeedSlider(normalSpeed)
IntSetting.FRAME_LIMIT.int = normalSpeed
}
settings.saveSetting(IntSetting.FRAME_LIMIT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
val context = CitraApplication.appContext
Toast.makeText(context,
"Changed Emulation Speed to: ${IntSetting.FRAME_LIMIT.int}%", Toast.LENGTH_SHORT).show()
}
fun resetTurboSpeed() {
if (isTurboSpeedEnabled) {
isTurboSpeedEnabled = false
NativeLibrary.toggleTurboSpeed(false)
IntSetting.FRAME_LIMIT.int = normalSpeed
settings.saveSetting(IntSetting.FRAME_LIMIT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -11,9 +11,12 @@ import org.citra.citra_emu.R
import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationLifecycleUtil
import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil
class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, private val context: Context) { class HotkeyUtility(
private val screenAdjustmentUtil: ScreenAdjustmentUtil,
private val hotkeyFunctions: HotkeyFunctions,
private val context: Context) {
val hotkeyButtons = Hotkey.entries.map { it.button } private val hotkeyButtons = Hotkey.entries.map { it.button }
fun handleHotkey(bindedButton: Int): Boolean { fun handleHotkey(bindedButton: Int): Boolean {
if(hotkeyButtons.contains(bindedButton)) { if(hotkeyButtons.contains(bindedButton)) {
@ -22,6 +25,7 @@ class HotkeyUtility(private val screenAdjustmentUtil: ScreenAdjustmentUtil, priv
Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts() Hotkey.CYCLE_LAYOUT.button -> screenAdjustmentUtil.cycleLayouts()
Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame() Hotkey.CLOSE_GAME.button -> EmulationLifecycleUtil.closeGame()
Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume() Hotkey.PAUSE_OR_RESUME.button -> EmulationLifecycleUtil.pauseOrResume()
Hotkey.TURBO_SPEED.button -> hotkeyFunctions.setTurboSpeed(!hotkeyFunctions.isTurboSpeedEnabled)
Hotkey.QUICKSAVE.button -> { Hotkey.QUICKSAVE.button -> {
NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT) NativeLibrary.saveState(NativeLibrary.QUICKSAVE_SLOT)
Toast.makeText(context, Toast.makeText(context,

View file

@ -65,7 +65,9 @@ enum class IntSetting(
DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0), DELAY_RENDER_THREAD_US("delay_game_render_thread_us", Settings.SECTION_RENDERER, 0),
USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0), USE_ARTIC_BASE_CONTROLLER("use_artic_base_controller", Settings.SECTION_CONTROLS, 0),
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2), ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, 0); DISABLE_RIGHT_EYE_RENDER("disable_right_eye_render", Settings.SECTION_RENDERER, 0),
TURBO_SPEED("turbo_speed", Settings.SECTION_CORE, 200);
override var int: Int = defaultValue override var int: Int = defaultValue
override val valueAsString: String override val valueAsString: String
@ -95,6 +97,7 @@ enum class IntSetting(
AUDIO_INPUT_TYPE, AUDIO_INPUT_TYPE,
USE_ARTIC_BASE_CONTROLLER, USE_ARTIC_BASE_CONTROLLER,
SHADERS_ACCURATE_MUL, SHADERS_ACCURATE_MUL,
FRAME_LIMIT
) )
fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key } fun from(key: String): IntSetting? = IntSetting.values().firstOrNull { it.key == key }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -140,6 +140,7 @@ class Settings {
const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game" const val HOTKEY_PAUSE_OR_RESUME = "hotkey_pause_or_resume_game"
const val HOTKEY_QUICKSAVE = "hotkey_quickload" const val HOTKEY_QUICKSAVE = "hotkey_quickload"
const val HOTKEY_QUICKlOAD = "hotkey_quickpause" const val HOTKEY_QUICKlOAD = "hotkey_quickpause"
const val HOTKEY_TURBO_SPEED = "hotkey_turbo_speed"
val buttonKeys = listOf( val buttonKeys = listOf(
KEY_BUTTON_A, KEY_BUTTON_A,
@ -205,7 +206,8 @@ class Settings {
HOTKEY_CLOSE_GAME, HOTKEY_CLOSE_GAME,
HOTKEY_PAUSE_OR_RESUME, HOTKEY_PAUSE_OR_RESUME,
HOTKEY_QUICKSAVE, HOTKEY_QUICKSAVE,
HOTKEY_QUICKlOAD HOTKEY_QUICKlOAD,
HOTKEY_TURBO_SPEED
) )
val hotkeyTitles = listOf( val hotkeyTitles = listOf(
R.string.emulation_swap_screens, R.string.emulation_swap_screens,
@ -214,6 +216,7 @@ class Settings {
R.string.emulation_toggle_pause, R.string.emulation_toggle_pause,
R.string.emulation_quicksave, R.string.emulation_quicksave,
R.string.emulation_quickload, R.string.emulation_quickload,
R.string.emulation_toggle_turbo_speed
) )
const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch" const val PREF_FIRST_APP_LAUNCH = "FirstApplicationLaunch"

View file

@ -12,7 +12,8 @@ class DateTimeSetting(
titleId: Int, titleId: Int,
descriptionId: Int, descriptionId: Int,
val key: String? = null, val key: String? = null,
private val defaultValue: String? = null private val defaultValue: String? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_DATETIME_SETTING override val type = TYPE_DATETIME_SETTING

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -133,6 +133,7 @@ class InputBindingSetting(
Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button Settings.HOTKEY_PAUSE_OR_RESUME -> Hotkey.PAUSE_OR_RESUME.button
Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button Settings.HOTKEY_QUICKSAVE -> Hotkey.QUICKSAVE.button
Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button Settings.HOTKEY_QUICKlOAD -> Hotkey.QUICKLOAD.button
Settings.HOTKEY_TURBO_SPEED -> Hotkey.TURBO_SPEED.button
else -> -1 else -> -1
} }

View file

@ -1,4 +1,4 @@
// Copyright Citra Emulator Project / Lime3DS Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -28,6 +28,13 @@ abstract class SettingsItem(
return setting?.isRuntimeEditable ?: false return setting?.isRuntimeEditable ?: false
} }
open var isEnabled: Boolean = true
val isActive: Boolean
get() {
return this.isEditable && this.isEnabled
}
companion object { companion object {
const val TYPE_HEADER = 0 const val TYPE_HEADER = 0
const val TYPE_SWITCH = 1 const val TYPE_SWITCH = 1

View file

@ -15,7 +15,8 @@ class SingleChoiceSetting(
val choicesId: Int, val choicesId: Int,
val valuesId: Int, val valuesId: Int,
val key: String? = null, val key: String? = null,
val defaultValue: Int? = null val defaultValue: Int? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SINGLE_CHOICE override val type = TYPE_SINGLE_CHOICE

View file

@ -20,7 +20,8 @@ class SliderSetting(
val max: Int, val max: Int,
val units: String, val units: String,
val key: String? = null, val key: String? = null,
val defaultValue: Float? = null val defaultValue: Float? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER override val type = TYPE_SLIDER
val selectedFloat: Float val selectedFloat: Float

View file

@ -14,6 +14,7 @@ class StringInputSetting(
val key: String? = null, val key: String? = null,
val defaultValue: String, val defaultValue: String,
val characterLimit: Int = 0, val characterLimit: Int = 0,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_INPUT override val type = TYPE_STRING_INPUT

View file

@ -15,7 +15,8 @@ class StringSingleChoiceSetting(
val choices: Array<String>, val choices: Array<String>,
val values: Array<String>?, val values: Array<String>?,
val key: String? = null, val key: String? = null,
private val defaultValue: String? = null private val defaultValue: String? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_STRING_SINGLE_CHOICE override val type = TYPE_STRING_SINGLE_CHOICE

View file

@ -13,7 +13,8 @@ class SwitchSetting(
titleId: Int, titleId: Int,
descriptionId: Int, descriptionId: Int,
val key: String? = null, val key: String? = null,
val defaultValue: Any? = null val defaultValue: Any? = null,
override var isEnabled: Boolean = true
) : SettingsItem(setting, titleId, descriptionId) { ) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SWITCH override val type = TYPE_SWITCH

View file

@ -7,7 +7,6 @@ package org.citra.citra_emu.features.settings.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.graphics.Color
import android.icu.util.Calendar import android.icu.util.Calendar
import android.icu.util.TimeZone import android.icu.util.TimeZone
import android.text.Editable import android.text.Editable
@ -17,11 +16,11 @@ import android.text.TextWatcher
import android.text.format.DateFormat import android.text.format.DateFormat
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -66,7 +65,6 @@ import org.citra.citra_emu.features.settings.ui.viewholder.SwitchSettingViewHold
import org.citra.citra_emu.fragments.MessageDialogFragment import org.citra.citra_emu.fragments.MessageDialogFragment
import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment import org.citra.citra_emu.fragments.MotionBottomSheetDialogFragment
import org.citra.citra_emu.utils.SystemSaveGame import org.citra.citra_emu.utils.SystemSaveGame
import java.lang.IllegalStateException
import java.lang.NumberFormatException import java.lang.NumberFormatException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -153,15 +151,71 @@ class SettingsAdapter(
return getItem(position)?.type ?: -1 return getItem(position)?.type ?: -1
} }
fun setSettingsList(settings: ArrayList<SettingsItem>?) { fun setSettingsList(newSettings: ArrayList<SettingsItem>?) {
this.settings = settings ?: arrayListOf() if (settings == null) {
notifyDataSetChanged() settings = newSettings ?: arrayListOf()
notifyDataSetChanged()
return
}
val oldSettings = settings
val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize() = oldSettings?.size ?: 0
override fun getNewListSize() = newSettings?.size ?: 0
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldSettings?.get(oldItemPosition)?.setting
val newItem = newSettings?.get(newItemPosition)?.setting
return oldItem?.key == newItem?.key
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldSettings?.get(oldItemPosition)
val newItem = newSettings?.get(newItemPosition)
if (oldItem == null || newItem == null || oldItem.type != newItem.type) {
return false
}
return when (oldItem.type) {
SettingsItem.TYPE_SLIDER -> {
(oldItem as SliderSetting).isEnabled == (newItem as SliderSetting).isEnabled
}
SettingsItem.TYPE_SWITCH -> {
(oldItem as SwitchSetting).isEnabled == (newItem as SwitchSetting).isEnabled
}
SettingsItem.TYPE_SINGLE_CHOICE -> {
(oldItem as SingleChoiceSetting).isEnabled == (newItem as SingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_DATETIME_SETTING -> {
(oldItem as DateTimeSetting).isEnabled == (newItem as DateTimeSetting).isEnabled
}
SettingsItem.TYPE_STRING_SINGLE_CHOICE -> {
(oldItem as StringSingleChoiceSetting).isEnabled == (newItem as StringSingleChoiceSetting).isEnabled
}
SettingsItem.TYPE_STRING_INPUT -> {
(oldItem as StringInputSetting).isEnabled == (newItem as StringInputSetting).isEnabled
}
else -> {
oldItem == newItem
}
}
}
})
settings = newSettings ?: arrayListOf()
diffResult.dispatchUpdatesTo(this)
} }
fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) { fun onBooleanClick(item: SwitchSetting, position: Int, checked: Boolean) {
val setting = item.setChecked(checked) val setting = item.setChecked(checked)
fragmentView.putSetting(setting) fragmentView.putSetting(setting)
fragmentView.onSettingChanged() fragmentView.onSettingChanged()
// If statement is required otherwise the app will crash on activity recreate ex. theme settings
if (fragmentView.activityView != null)
// Reload the settings list to update the UI
fragmentView.loadSettingsList()
} }
private fun onSingleChoiceClick(item: SingleChoiceSetting) { private fun onSingleChoiceClick(item: SingleChoiceSetting) {
@ -247,6 +301,7 @@ class SettingsAdapter(
notifyItemChanged(clickedPosition) notifyItemChanged(clickedPosition)
val setting = item.setSelectedValue(rtcString) val setting = item.setSelectedValue(rtcString)
fragmentView.putSetting(setting) fragmentView.putSetting(setting)
fragmentView.loadSettingsList()
clickedItem = null clickedItem = null
} }
datePicker.show( datePicker.show(
@ -402,6 +457,7 @@ class SettingsAdapter(
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!") else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
} }
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog() closeDialog()
} }
} }
@ -425,6 +481,7 @@ class SettingsAdapter(
} }
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog() closeDialog()
} }
} }
@ -447,6 +504,7 @@ class SettingsAdapter(
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
} }
} }
fragmentView.loadSettingsList()
closeDialog() closeDialog()
} }
} }
@ -459,6 +517,7 @@ class SettingsAdapter(
} }
val setting = it.setSelectedValue(textInputValue ?: "") val setting = it.setSelectedValue(textInputValue ?: "")
fragmentView?.putSetting(setting) fragmentView?.putSetting(setting)
fragmentView.loadSettingsList()
closeDialog() closeDialog()
} }
} }
@ -488,6 +547,7 @@ class SettingsAdapter(
} }
notifyItemChanged(position) notifyItemChanged(position)
fragmentView.onSettingChanged() fragmentView.onSettingChanged()
fragmentView.loadSettingsList()
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.show() .show()
@ -495,10 +555,19 @@ class SettingsAdapter(
return true return true
} }
fun onClickDisabledSetting() { fun onClickDisabledSetting(isRuntimeDisabled: Boolean) {
MessageDialogFragment.newInstance( val titleId = if (isRuntimeDisabled)
R.string.setting_not_editable, R.string.setting_not_editable
else
R.string.setting_disabled
val messageId = if (isRuntimeDisabled)
R.string.setting_not_editable_description R.string.setting_not_editable_description
else
R.string.setting_disabled_description
MessageDialogFragment.newInstance(
titleId,
messageId
).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG) ).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG)
} }

View file

@ -247,6 +247,18 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.FRAME_LIMIT.defaultValue.toFloat() IntSetting.FRAME_LIMIT.defaultValue.toFloat()
) )
) )
add(
SliderSetting(
IntSetting.TURBO_SPEED,
R.string.turbo_speed,
R.string.turbo_speed_description,
100,
400,
"%",
IntSetting.TURBO_SPEED.key,
IntSetting.TURBO_SPEED.defaultValue.toFloat()
)
)
} }
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -47,7 +47,7 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingValue.text = dateFormatter.format(zonedTime) binding.textSettingValue.text = dateFormatter.format(zonedTime)
if (setting.isEditable) { if (setting.isActive) {
binding.textSettingName.alpha = 1f binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f binding.textSettingValue.alpha = 1f
@ -59,18 +59,18 @@ class DateTimeViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {
if (setting.isEditable) { if (setting.isActive) {
adapter.onDateTimeClick(setting, bindingAdapterPosition) adapter.onDateTimeClick(setting, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
} }
override fun onLongClick(clicked: View): Boolean { override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) { if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
return false return false
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -45,7 +45,7 @@ class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter
if (setting.isEditable) { if (setting.isEditable) {
adapter.onInputBindingClick(setting, bindingAdapterPosition) adapter.onInputBindingClick(setting, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
} }
@ -53,7 +53,7 @@ class InputBindingSettingViewHolder(val binding: ListItemSettingBinding, adapter
if (setting.isEditable) { if (setting.isEditable) {
adapter.onLongClick(setting.setting!!, bindingAdapterPosition) adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
return false return false
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -60,7 +60,7 @@ class RunnableViewHolder(val binding: ListItemSettingBinding, adapter: SettingsA
override fun onClick(clicked: View) { override fun onClick(clicked: View) {
if (!setting.isRuntimeRunnable && EmulationActivity.isRunning()) { if (!setting.isRuntimeRunnable && EmulationActivity.isRunning()) {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(true)
} else { } else {
setting.runnable.invoke() setting.runnable.invoke()
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -27,7 +27,7 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
binding.textSettingValue.visibility = View.VISIBLE binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = getTextSetting() binding.textSettingValue.text = getTextSetting()
if (setting.isEditable) { if (setting.isActive) {
binding.textSettingName.alpha = 1f binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f binding.textSettingValue.alpha = 1f
@ -65,8 +65,8 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {
if (!setting.isEditable) { if (!setting.isEditable || !setting.isEnabled) {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
return return
} }
@ -84,10 +84,10 @@ class SingleChoiceViewHolder(val binding: ListItemSettingBinding, adapter: Setti
} }
override fun onLongClick(clicked: View): Boolean { override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) { if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
return false return false
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -35,7 +35,7 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
else -> "${(setting.setting as AbstractIntSetting).int}${setting.units}" else -> "${(setting.setting as AbstractIntSetting).int}${setting.units}"
} }
if (setting.isEditable) { if (setting.isActive) {
binding.textSettingName.alpha = 1f binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f binding.textSettingValue.alpha = 1f
@ -47,18 +47,18 @@ class SliderViewHolder(val binding: ListItemSettingBinding, adapter: SettingsAda
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {
if (setting.isEditable) { if (setting.isActive) {
adapter.onSliderClick(setting, bindingAdapterPosition) adapter.onSliderClick(setting, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
} }
override fun onLongClick(clicked: View): Boolean { override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) { if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
return false return false
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -25,21 +25,31 @@ class StringInputViewHolder(val binding: ListItemSettingBinding, adapter: Settin
} }
binding.textSettingValue.visibility = View.VISIBLE binding.textSettingValue.visibility = View.VISIBLE
binding.textSettingValue.text = setting.setting?.valueAsString binding.textSettingValue.text = setting.setting?.valueAsString
if (setting.isActive) {
binding.textSettingName.alpha = 1f
binding.textSettingDescription.alpha = 1f
binding.textSettingValue.alpha = 1f
} else {
binding.textSettingName.alpha = 0.5f
binding.textSettingDescription.alpha = 0.5f
binding.textSettingValue.alpha = 0.5f
}
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {
if (!setting.isEditable) { if (!setting.isEditable || !setting.isEnabled) {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
return return
} }
adapter.onStringInputClick((setting as StringInputSetting), bindingAdapterPosition) adapter.onStringInputClick((setting as StringInputSetting), bindingAdapterPosition)
} }
override fun onLongClick(clicked: View): Boolean { override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) { if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
return false return false
} }

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -33,26 +33,26 @@ class SwitchSettingViewHolder(val binding: ListItemSettingSwitchBinding, adapter
adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked) adapter.onBooleanClick(item, bindingAdapterPosition, binding.switchWidget.isChecked)
} }
binding.switchWidget.isEnabled = setting.isEditable binding.switchWidget.isEnabled = setting.isActive
val textAlpha = if (setting.isEditable) 1f else 0.5f val textAlpha = if (setting.isActive) 1f else 0.5f
binding.textSettingName.alpha = textAlpha binding.textSettingName.alpha = textAlpha
binding.textSettingDescription.alpha = textAlpha binding.textSettingDescription.alpha = textAlpha
} }
override fun onClick(clicked: View) { override fun onClick(clicked: View) {
if (setting.isEditable) { if (setting.isActive) {
binding.switchWidget.toggle() binding.switchWidget.toggle()
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
} }
override fun onLongClick(clicked: View): Boolean { override fun onLongClick(clicked: View): Boolean {
if (setting.isEditable) { if (setting.isActive) {
return adapter.onLongClick(setting.setting!!, bindingAdapterPosition) return adapter.onLongClick(setting.setting!!, bindingAdapterPosition)
} else { } else {
adapter.onClickDisabledSetting() adapter.onClickDisabledSetting(!setting.isEditable)
} }
return false return false
} }

View file

@ -455,6 +455,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
Choreographer.getInstance().postFrameCallback(this) Choreographer.getInstance().postFrameCallback(this)
if (NativeLibrary.isRunning()) { if (NativeLibrary.isRunning()) {
NativeLibrary.unPauseEmulation() NativeLibrary.unPauseEmulation()
binding.inGameMenu.menu.findItem(R.id.menu_emulation_pause)?.let { menuItem ->
menuItem.title = resources.getString(R.string.pause_emulation)
menuItem.icon = ResourcesCompat.getDrawable(
resources,
R.drawable.ic_pause,
requireContext().theme
)
}
return return
} }
@ -894,10 +902,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) { val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) {
PortraitScreenLayout.TOP_FULL_WIDTH.int -> PortraitScreenLayout.TOP_FULL_WIDTH.int ->
R.id.menu_portrait_layout_top_full R.id.menu_portrait_layout_top_full
PortraitScreenLayout.ORIGINAL.int ->
R.id.menu_portrait_layout_original
PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int -> PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int ->
R.id.menu_portrait_layout_custom R.id.menu_portrait_layout_custom
else -> else ->
R.id.menu_portrait_layout_top_full R.id.menu_portrait_layout_top_full
@ -912,6 +920,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true true
} }
R.id.menu_portrait_layout_original -> {
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.ORIGINAL.int)
true
}
R.id.menu_portrait_layout_custom -> { R.id.menu_portrait_layout_custom -> {
Toast.makeText( Toast.makeText(
requireContext(), requireContext(),

View file

@ -148,8 +148,8 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.use_vsync_new); ReadSetting("Renderer", Settings::values.use_vsync_new);
ReadSetting("Renderer", Settings::values.texture_filter); ReadSetting("Renderer", Settings::values.texture_filter);
ReadSetting("Renderer", Settings::values.texture_sampling); ReadSetting("Renderer", Settings::values.texture_sampling);
ReadSetting("Renderer", Settings::values.turbo_speed);
// Work around to map Android setting for enabling the frame limiter to the format Citra expects // Workaround to map Android setting for enabling the frame limiter to the format Citra expects
if (sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)) { if (sdl2_config->GetBoolean("Renderer", "use_frame_limit", true)) {
ReadSetting("Renderer", Settings::values.frame_limit); ReadSetting("Renderer", Settings::values.frame_limit);
} else { } else {

View file

@ -773,6 +773,22 @@ void Java_org_citra_citra_1emu_NativeLibrary_logDeviceInfo([[maybe_unused]] JNIE
LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level()); LOG_INFO(Frontend, "Host OS: Android API level {}", android_get_device_api_level());
} }
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_toggleTurboSpeed([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jobject obj,
jboolean enabled) {
Settings::values.turbo_speed = enabled ? true : false;
}
jint JNICALL Java_org_citra_citra_1emu_NativeLibrary_getTurboSpeedSlider(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
return static_cast<jint>(Settings::values.turbo_speed);
}
void JNICALL Java_org_citra_citra_1emu_NativeLibrary_setTurboSpeedSlider(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, jint value) {
Settings::values.turbo_speed = value;
}
jboolean Java_org_citra_citra_1emu_NativeLibrary_isFullConsoleLinked(JNIEnv* env, jobject obj) { jboolean Java_org_citra_citra_1emu_NativeLibrary_isFullConsoleLinked(JNIEnv* env, jobject obj) {
return HW::UniqueData::IsFullConsoleLinked(); return HW::UniqueData::IsFullConsoleLinked();
} }

View file

@ -7,6 +7,10 @@
android:id="@+id/menu_portrait_layout_top_full" android:id="@+id/menu_portrait_layout_top_full"
android:title="@string/emulation_portrait_layout_top_full" /> android:title="@string/emulation_portrait_layout_top_full" />
<item
android:id="@+id/menu_portrait_layout_original"
android:title="@string/emulation_screen_layout_original" />
<item <item
android:id="@+id/menu_portrait_layout_custom" android:id="@+id/menu_portrait_layout_custom"
android:title="@string/emulation_screen_layout_custom" /> android:title="@string/emulation_screen_layout_custom" />

View file

@ -31,11 +31,13 @@
<string-array name="portraitLayouts"> <string-array name="portraitLayouts">
<item>@string/emulation_portrait_layout_top_full</item> <item>@string/emulation_portrait_layout_top_full</item>
<item>@string/emulation_screen_layout_original</item>
<item>@string/emulation_screen_layout_custom</item> <item>@string/emulation_screen_layout_custom</item>
</string-array> </string-array>
<integer-array name="portraitLayoutValues"> <integer-array name="portraitLayoutValues">
<item>0</item> <item>0</item>
<item>2</item>
<item>1</item> <item>1</item>
</integer-array> </integer-array>
@ -309,6 +311,7 @@
</integer-array> </integer-array>
<string-array name="resolutionFactorNames"> <string-array name="resolutionFactorNames">
<item>@string/internal_resolution_setting_auto</item>
<item>@string/internal_resolution_setting_1x</item> <item>@string/internal_resolution_setting_1x</item>
<item>@string/internal_resolution_setting_2x</item> <item>@string/internal_resolution_setting_2x</item>
<item>@string/internal_resolution_setting_3x</item> <item>@string/internal_resolution_setting_3x</item>
@ -321,6 +324,7 @@
<item>@string/internal_resolution_setting_10x</item> <item>@string/internal_resolution_setting_10x</item>
</string-array> </string-array>
<integer-array name="resolutionFactorValues"> <integer-array name="resolutionFactorValues">
<item>0</item>
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
<item>3</item> <item>3</item>

View file

@ -251,13 +251,17 @@
<string name="asynchronous_gpu">Enable asynchronous GPU emulation</string> <string name="asynchronous_gpu">Enable asynchronous GPU emulation</string>
<string name="asynchronous_gpu_description">Uses a separate thread to emulate the GPU asynchronously. When enabled, performance will be improved.</string> <string name="asynchronous_gpu_description">Uses a separate thread to emulate the GPU asynchronously. When enabled, performance will be improved.</string>
<string name="frame_limit_enable">Limit Speed</string> <string name="frame_limit_enable">Limit Speed</string>
<string name="expand_to_cutout_area">Expand to Cutout Area</string> <string name="frame_limit_enable_description">When enabled, emulation speed will be limited to a specified percentage of normal speed. If disabled, emulation speed will be uncapped and the turbo speed hotkey will not work.</string>
<string name="expand_to_cutout_area_description">Expands the display area to include the cutout (or notch) area.</string>
<string name="frame_limit_enable_description">When enabled, emulation speed will be limited to a specified percentage of normal speed.</string>
<string name="frame_limit_slider">Limit Speed Percent</string> <string name="frame_limit_slider">Limit Speed Percent</string>
<string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. With the default of 100% emulation will be limited to normal speed. Values higher or lower will increase or decrease the speed limit.</string> <string name="frame_limit_slider_description">Specifies the percentage to limit emulation speed. With the default of 100% emulation will be limited to normal speed. Values higher or lower will increase or decrease the speed limit.</string>
<string name="expand_to_cutout_area">Expand to Cutout Area</string>
<string name="expand_to_cutout_area_description">Expands the display area to include the cutout (or notch) area.</string>
<string name="emulation_toggle_turbo_speed">Turbo Speed</string>
<string name="turbo_speed">Turbo Speed</string>
<string name="turbo_speed_description">Emulation speed limit used while the turbo hotkey is active.</string>
<string name="internal_resolution">Internal Resolution</string> <string name="internal_resolution">Internal Resolution</string>
<string name="internal_resolution_description">Specifies the resolution used to render at. A high resolution will improve visual quality a lot but is also quite heavy on performance and might cause glitches in certain applications.</string> <string name="internal_resolution_description">Specifies the resolution used to render at. A high resolution will improve visual quality a lot but is also quite heavy on performance and might cause glitches in certain applications.</string>
<string name="internal_resolution_setting_auto">Auto (Screen Size)</string>
<string name="internal_resolution_setting_1x">Native (400x240)</string> <string name="internal_resolution_setting_1x">Native (400x240)</string>
<string name="internal_resolution_setting_2x">2x Native (800x480)</string> <string name="internal_resolution_setting_2x">2x Native (800x480)</string>
<string name="internal_resolution_setting_3x">3x Native (1200x720)</string> <string name="internal_resolution_setting_3x">3x Native (1200x720)</string>
@ -337,6 +341,8 @@
<string name="select_rtc_time">Select RTC time</string> <string name="select_rtc_time">Select RTC time</string>
<string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string> <string name="reset_setting_confirmation">Do you want to reset this setting back to its default value?</string>
<string name="setting_not_editable">You can\'t edit this now</string> <string name="setting_not_editable">You can\'t edit this now</string>
<string name="setting_disabled">Setting disabled</string>
<string name="setting_disabled_description">This setting is currently disabled due to another setting not being the appropriate value.</string>
<string name="setting_not_editable_description">This option can\'t be changed while a game is running.</string> <string name="setting_not_editable_description">This option can\'t be changed while a game is running.</string>
<string name="auto_select">Auto-Select</string> <string name="auto_select">Auto-Select</string>

View file

@ -697,25 +697,36 @@ void GMainWindow::InitializeRecentFileMenuActions() {
void GMainWindow::InitializeSaveStateMenuActions() { void GMainWindow::InitializeSaveStateMenuActions() {
for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) { for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
actions_load_state[i] = new QAction(this); actions_load_state[i] = new QAction(this);
actions_load_state[i]->setData(i + 1); actions_load_state[i]->setData(i);
connect(actions_load_state[i], &QAction::triggered, this, &GMainWindow::OnLoadState); connect(actions_load_state[i], &QAction::triggered, this, &GMainWindow::OnLoadState);
ui->menu_Load_State->addAction(actions_load_state[i]); if (i > 0)
ui->menu_Load_State->addAction(actions_load_state[i]);
actions_save_state[i] = new QAction(this); actions_save_state[i] = new QAction(this);
actions_save_state[i]->setData(i + 1); actions_save_state[i]->setData(i);
connect(actions_save_state[i], &QAction::triggered, this, &GMainWindow::OnSaveState); connect(actions_save_state[i], &QAction::triggered, this, &GMainWindow::OnSaveState);
ui->menu_Save_State->addAction(actions_save_state[i]); if (i > 0)
ui->menu_Save_State->addAction(actions_save_state[i]);
} }
connect(ui->action_Load_from_Newest_Slot, &QAction::triggered, this, [this] { connect(ui->action_Load_from_Newest_Slot, &QAction::triggered, this, [this] {
UpdateSaveStates(); UpdateSaveStates();
if (newest_slot != 0) { if (newest_slot != 0) {
actions_load_state[newest_slot - 1]->trigger(); actions_load_state[newest_slot]->trigger();
} }
}); });
connect(ui->action_Save_to_Oldest_Slot, &QAction::triggered, this, [this] { connect(ui->action_Save_to_Oldest_Slot, &QAction::triggered, this, [this] {
UpdateSaveStates(); UpdateSaveStates();
actions_save_state[oldest_slot - 1]->trigger(); actions_save_state[oldest_slot]->trigger();
});
// Quick save / load uses slot
connect(ui->action_Quick_Save, &QAction::triggered, this, [this] {
UpdateSaveStates();
actions_save_state[0]->trigger();
});
connect(ui->action_Quick_Load, &QAction::triggered, this, [this] {
UpdateSaveStates();
actions_load_state[0]->trigger();
}); });
connect(ui->menu_Load_State->menuAction(), &QAction::hovered, this, connect(ui->menu_Load_State->menuAction(), &QAction::hovered, this,
@ -758,8 +769,12 @@ void GMainWindow::InitializeHotkeys() {
link_action_shortcut(ui->action_Screen_Layout_Upright_Screens, link_action_shortcut(ui->action_Screen_Layout_Upright_Screens,
QStringLiteral("Rotate Screens Upright")); QStringLiteral("Rotate Screens Upright"));
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame")); link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot")); link_action_shortcut(ui->action_Load_from_Newest_Slot,
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot")); QStringLiteral("Load from Newest Non-Quicksave Slot"));
link_action_shortcut(ui->action_Save_to_Oldest_Slot,
QStringLiteral("Save to Oldest Non-Quicksave Slot"));
link_action_shortcut(ui->action_Quick_Save, QStringLiteral("Quick Save"));
link_action_shortcut(ui->action_Quick_Load, QStringLiteral("Quick Load"));
link_action_shortcut(ui->action_View_Lobby, QStringLiteral("Multiplayer Browse Public Rooms")); link_action_shortcut(ui->action_View_Lobby, QStringLiteral("Multiplayer Browse Public Rooms"));
link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room")); link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
link_action_shortcut(ui->action_Connect_To_Room, link_action_shortcut(ui->action_Connect_To_Room,
@ -784,6 +799,11 @@ void GMainWindow::InitializeHotkeys() {
} }
}); });
connect_shortcut(QStringLiteral("Toggle Per-Application Speed"), [&] { connect_shortcut(QStringLiteral("Toggle Per-Application Speed"), [&] {
if (!hotkey_registry
.GetKeySequence(QStringLiteral("Main Window"), QStringLiteral("Toggle Turbo Mode"))
.isEmpty()) {
return;
}
Settings::values.frame_limit.SetGlobal(!Settings::values.frame_limit.UsingGlobal()); Settings::values.frame_limit.SetGlobal(!Settings::values.frame_limit.UsingGlobal());
UpdateStatusBar(); UpdateStatusBar();
}); });
@ -791,31 +811,12 @@ void GMainWindow::InitializeHotkeys() {
[&] { Settings::values.dump_textures = !Settings::values.dump_textures; }); [&] { Settings::values.dump_textures = !Settings::values.dump_textures; });
connect_shortcut(QStringLiteral("Toggle Custom Textures"), connect_shortcut(QStringLiteral("Toggle Custom Textures"),
[&] { Settings::values.custom_textures = !Settings::values.custom_textures; }); [&] { Settings::values.custom_textures = !Settings::values.custom_textures; });
// We use "static" here in order to avoid capturing by lambda due to a MSVC bug, which makes
// the variable hold a garbage value after this function exits connect_shortcut(QStringLiteral("Toggle Turbo Mode"), &GMainWindow::ToggleEmulationSpeed);
static constexpr u16 SPEED_LIMIT_STEP = 5;
connect_shortcut(QStringLiteral("Increase Speed Limit"), [&] { connect_shortcut(QStringLiteral("Increase Speed Limit"), [&] { AdjustSpeedLimit(true); });
if (Settings::values.frame_limit.GetValue() == 0) {
return; connect_shortcut(QStringLiteral("Decrease Speed Limit"), [&] { AdjustSpeedLimit(false); });
}
if (Settings::values.frame_limit.GetValue() < 995 - SPEED_LIMIT_STEP) {
Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() +
SPEED_LIMIT_STEP);
} else {
Settings::values.frame_limit = 0;
}
UpdateStatusBar();
});
connect_shortcut(QStringLiteral("Decrease Speed Limit"), [&] {
if (Settings::values.frame_limit.GetValue() == 0) {
Settings::values.frame_limit = 995;
} else if (Settings::values.frame_limit.GetValue() > SPEED_LIMIT_STEP) {
Settings::values.frame_limit.SetValue(Settings::values.frame_limit.GetValue() -
SPEED_LIMIT_STEP);
UpdateStatusBar();
}
UpdateStatusBar();
});
connect_shortcut(QStringLiteral("Audio Mute/Unmute"), &GMainWindow::OnMute); connect_shortcut(QStringLiteral("Audio Mute/Unmute"), &GMainWindow::OnMute);
connect_shortcut(QStringLiteral("Audio Volume Down"), &GMainWindow::OnDecreaseVolume); connect_shortcut(QStringLiteral("Audio Volume Down"), &GMainWindow::OnDecreaseVolume);
@ -1592,7 +1593,7 @@ void GMainWindow::UpdateSaveStates() {
ui->menu_Save_State->setEnabled(true); ui->menu_Save_State->setEnabled(true);
ui->action_Load_from_Newest_Slot->setEnabled(false); ui->action_Load_from_Newest_Slot->setEnabled(false);
oldest_slot = newest_slot = 0; oldest_slot = newest_slot = 1;
oldest_slot_time = std::numeric_limits<u64>::max(); oldest_slot_time = std::numeric_limits<u64>::max();
newest_slot_time = 0; newest_slot_time = 0;
@ -1603,13 +1604,30 @@ void GMainWindow::UpdateSaveStates() {
auto savestates = Core::ListSaveStates(title_id, movie.GetCurrentMovieID()); auto savestates = Core::ListSaveStates(title_id, movie.GetCurrentMovieID());
for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) { for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
actions_load_state[i]->setEnabled(false); actions_load_state[i]->setEnabled(false);
actions_load_state[i]->setText(tr("Slot %1").arg(i + 1)); if (i == 0) {
actions_save_state[i]->setText(tr("Slot %1").arg(i + 1)); actions_load_state[i]->setText(tr("Quick Load"));
actions_save_state[i]->setText(tr("Quick Save"));
} else {
actions_load_state[i]->setText(tr("Slot %1").arg(i));
actions_save_state[i]->setText(tr("Slot %1").arg(i));
}
} }
for (const auto& savestate : savestates) { for (const auto& savestate : savestates) {
const bool display_name = const bool display_name =
savestate.status == Core::SaveStateInfo::ValidationStatus::RevisionDismatch && savestate.status == Core::SaveStateInfo::ValidationStatus::RevisionDismatch &&
!savestate.build_name.empty(); !savestate.build_name.empty();
actions_load_state[savestate.slot]->setEnabled(true);
if (savestate.slot == 0) {
const auto text = tr("%2 %3")
.arg(QDateTime::fromSecsSinceEpoch(savestate.time)
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")))
.arg(display_name ? QString::fromStdString(savestate.build_name)
: QLatin1String())
.trimmed();
ui->action_Quick_Save->setText(tr("Quick Save - %1").arg(text).trimmed());
ui->action_Quick_Load->setText(tr("Quick Load - %1").arg(text).trimmed());
continue;
}
const auto text = const auto text =
tr("Slot %1 - %2 %3") tr("Slot %1 - %2 %3")
.arg(savestate.slot) .arg(savestate.slot)
@ -1618,12 +1636,10 @@ void GMainWindow::UpdateSaveStates() {
.arg(display_name ? QString::fromStdString(savestate.build_name) : QLatin1String()) .arg(display_name ? QString::fromStdString(savestate.build_name) : QLatin1String())
.trimmed(); .trimmed();
actions_load_state[savestate.slot - 1]->setEnabled(true); actions_load_state[savestate.slot]->setText(text);
actions_load_state[savestate.slot - 1]->setText(text); actions_save_state[savestate.slot]->setText(text);
actions_save_state[savestate.slot - 1]->setText(text);
ui->action_Load_from_Newest_Slot->setEnabled(true); ui->action_Load_from_Newest_Slot->setEnabled(true);
if (savestate.time > newest_slot_time) { if (savestate.time > newest_slot_time) {
newest_slot = savestate.slot; newest_slot = savestate.slot;
newest_slot_time = savestate.time; newest_slot_time = savestate.time;
@ -1633,7 +1649,8 @@ void GMainWindow::UpdateSaveStates() {
oldest_slot_time = savestate.time; oldest_slot_time = savestate.time;
} }
} }
for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) { // Value as 1 because quicksave slot is not used for this calculation
for (u32 i = 1; i < Core::SaveStateSlotCount; ++i) {
if (!actions_load_state[i]->isEnabled()) { if (!actions_load_state[i]->isEnabled()) {
// Prefer empty slot // Prefer empty slot
oldest_slot = i + 1; oldest_slot = i + 1;
@ -2362,6 +2379,7 @@ void GMainWindow::OnMenuRecentFile() {
} }
void GMainWindow::OnStartGame() { void GMainWindow::OnStartGame() {
GetInitialFrameLimit();
qt_cameras->ResumeCameras(); qt_cameras->ResumeCameras();
PreventOSSleep(); PreventOSSleep();
@ -2421,6 +2439,12 @@ void GMainWindow::OnPauseContinueGame() {
} }
void GMainWindow::OnStopGame() { void GMainWindow::OnStopGame() {
if (turbo_mode_active) {
turbo_mode_active = false;
Settings::values.frame_limit.SetValue(initial_frame_limit);
UpdateStatusBar();
}
play_time_manager->Stop(); play_time_manager->Stop();
// Update game list to show new play time // Update game list to show new play time
game_list->PopulateAsync(UISettings::values.game_dirs); game_list->PopulateAsync(UISettings::values.game_dirs);
@ -2572,6 +2596,54 @@ void GMainWindow::ChangeSmallScreenPosition() {
UpdateSecondaryWindowVisibility(); UpdateSecondaryWindowVisibility();
} }
void GMainWindow::GetInitialFrameLimit() {
initial_frame_limit = Settings::values.frame_limit.GetValue();
turbo_mode_active = false;
}
void GMainWindow::ToggleEmulationSpeed() {
static bool key_pressed = false; // Prevent spam on hold
if (!key_pressed) {
key_pressed = true;
turbo_mode_active = !turbo_mode_active;
if (turbo_mode_active) {
Settings::values.frame_limit.SetValue(Settings::values.turbo_speed.GetValue());
} else {
Settings::values.frame_limit.SetValue(initial_frame_limit);
}
UpdateStatusBar();
QTimer::singleShot(200, [] { key_pressed = false; });
}
}
void GMainWindow::AdjustSpeedLimit(bool increase) {
if (!turbo_mode_active) {
return;
}
const int SPEED_LIMIT_STEP = 5;
int turbo_speed = Settings::values.turbo_speed.GetValue();
if (increase) {
if (turbo_speed < 995) {
Settings::values.turbo_speed.SetValue(turbo_speed + SPEED_LIMIT_STEP);
Settings::values.frame_limit.SetValue(turbo_speed + SPEED_LIMIT_STEP);
}
} else {
if (turbo_speed > SPEED_LIMIT_STEP) {
Settings::values.turbo_speed.SetValue(turbo_speed - SPEED_LIMIT_STEP);
Settings::values.frame_limit.SetValue(turbo_speed - SPEED_LIMIT_STEP);
}
}
if (turbo_mode_active) {
UpdateStatusBar();
}
}
void GMainWindow::ToggleScreenLayout() { void GMainWindow::ToggleScreenLayout() {
const Settings::LayoutOption new_layout = []() { const Settings::LayoutOption new_layout = []() {
switch (Settings::values.layout_option.GetValue()) { switch (Settings::values.layout_option.GetValue()) {

View file

@ -222,6 +222,7 @@ private:
private slots: private slots:
void OnStartGame(); void OnStartGame();
void GetInitialFrameLimit();
void OnRestartGame(); void OnRestartGame();
void OnPauseGame(); void OnPauseGame();
void OnPauseContinueGame(); void OnPauseContinueGame();
@ -260,6 +261,8 @@ private slots:
void ToggleSecondaryFullscreen(); void ToggleSecondaryFullscreen();
void ChangeScreenLayout(); void ChangeScreenLayout();
void ChangeSmallScreenPosition(); void ChangeSmallScreenPosition();
void ToggleEmulationSpeed();
void AdjustSpeedLimit(bool increase);
void UpdateSecondaryWindowVisibility(); void UpdateSecondaryWindowVisibility();
void ToggleScreenLayout(); void ToggleScreenLayout();
void OnSwapScreens(); void OnSwapScreens();
@ -348,6 +351,9 @@ private:
UserDataMigrator user_data_migrator; UserDataMigrator user_data_migrator;
std::unique_ptr<QtConfig> config; std::unique_ptr<QtConfig> config;
// Hotkeys
bool turbo_mode_active = false;
// Whether emulation is currently running in Citra. // Whether emulation is currently running in Citra.
bool emulation_running = false; bool emulation_running = false;
std::unique_ptr<EmuThread> emu_thread; std::unique_ptr<EmuThread> emu_thread;
@ -405,6 +411,8 @@ private:
u32 newest_slot; u32 newest_slot;
u64 newest_slot_time; u64 newest_slot_time;
int initial_frame_limit;
// Secondary window actions // Secondary window actions
QAction* action_secondary_fullscreen; QAction* action_secondary_fullscreen;
QAction* action_secondary_toggle_screen; QAction* action_secondary_toggle_screen;

View file

@ -53,7 +53,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> QtConfi
// This must be in alphabetical order according to action name as it must have the same order as // This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered. // UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off // clang-format off
const std::array<UISettings::Shortcut, 35> QtConfig::default_hotkeys {{ const std::array<UISettings::Shortcut, 38> QtConfig::default_hotkeys {{
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}}, {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
@ -69,23 +69,26 @@ const std::array<UISettings::Shortcut, 35> QtConfig::default_hotkeys {{
{QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}}, {QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}}, {QStringLiteral("Load from Newest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
{QStringLiteral("Multiplayer Browse Public Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Browse Public Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}},
{QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}}, {QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}},
{QStringLiteral("Quick Save"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral("Quick Load"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}}, {QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}}, {QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
{QStringLiteral("Save to Oldest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), Qt::WindowShortcut}}, {QStringLiteral("Save to Oldest Non-Quicksave Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), Qt::WindowShortcut}},
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}}, {QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
{QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}}, {QStringLiteral("Swap Screens"), QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}},
{QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle 3D"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+3"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Turbo Mode"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Custom Textures"), QStringLiteral("Main Window"), {QStringLiteral("F7"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Filter Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Frame Advancing"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Per-Application Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Per-Application Speed"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Z"), Qt::ApplicationShortcut}},
{QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Screen Layout"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}}, {QStringLiteral("Toggle Status Bar"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+S"), Qt::WindowShortcut}},
{QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Toggle Texture Dumping"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
@ -806,6 +809,7 @@ void QtConfig::ReadUIValues() {
ReadBasicSetting(UISettings::values.display_titlebar); ReadBasicSetting(UISettings::values.display_titlebar);
ReadBasicSetting(UISettings::values.show_filter_bar); ReadBasicSetting(UISettings::values.show_filter_bar);
ReadBasicSetting(UISettings::values.show_status_bar); ReadBasicSetting(UISettings::values.show_status_bar);
ReadBasicSetting(Settings::values.turbo_speed);
ReadBasicSetting(UISettings::values.confirm_before_closing); ReadBasicSetting(UISettings::values.confirm_before_closing);
ReadBasicSetting(UISettings::values.save_state_warning); ReadBasicSetting(UISettings::values.save_state_warning);
ReadBasicSetting(UISettings::values.first_start); ReadBasicSetting(UISettings::values.first_start);
@ -1310,6 +1314,7 @@ void QtConfig::SaveUIValues() {
WriteBasicSetting(UISettings::values.show_filter_bar); WriteBasicSetting(UISettings::values.show_filter_bar);
WriteBasicSetting(UISettings::values.show_status_bar); WriteBasicSetting(UISettings::values.show_status_bar);
WriteBasicSetting(UISettings::values.confirm_before_closing); WriteBasicSetting(UISettings::values.confirm_before_closing);
WriteBasicSetting(Settings::values.turbo_speed);
WriteBasicSetting(UISettings::values.save_state_warning); WriteBasicSetting(UISettings::values.save_state_warning);
WriteBasicSetting(UISettings::values.first_start); WriteBasicSetting(UISettings::values.first_start);
WriteBasicSetting(UISettings::values.callout_flags); WriteBasicSetting(UISettings::values.callout_flags);

View file

@ -26,7 +26,7 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs; static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<UISettings::Shortcut, 35> default_hotkeys; static const std::array<UISettings::Shortcut, 38> default_hotkeys;
private: private:
void Initialize(const std::string& config_name); void Initialize(const std::string& config_name);

View file

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -28,7 +28,7 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor
bool enable_web_config) bool enable_web_config)
: QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_}, : QDialog(parent), ui{std::make_unique<Ui::ConfigureDialog>()}, registry{registry_},
system{system_}, is_powered_on{system.IsPoweredOn()}, system{system_}, is_powered_on{system.IsPoweredOn()},
general_tab{std::make_unique<ConfigureGeneral>(this)}, general_tab{std::make_unique<ConfigureGeneral>(is_powered_on, this)},
system_tab{std::make_unique<ConfigureSystem>(system, this)}, system_tab{std::make_unique<ConfigureSystem>(system, this)},
input_tab{std::make_unique<ConfigureInput>(system, this)}, input_tab{std::make_unique<ConfigureInput>(system, this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)}, hotkeys_tab{std::make_unique<ConfigureHotkeys>(this)},

View file

@ -24,11 +24,16 @@ static constexpr int SettingsToSlider(int value) {
return (value - 5) / 5; return (value - 5) / 5;
} }
ConfigureGeneral::ConfigureGeneral(QWidget* parent) ConfigureGeneral::ConfigureGeneral(bool is_powered_on, QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureGeneral>()) { : QWidget(parent), ui(std::make_unique<Ui::ConfigureGeneral>()), is_powered_on{is_powered_on} {
ui->setupUi(this); ui->setupUi(this);
connect(ui->turbo_speed, &QSlider::valueChanged, this, [&](int value) {
Settings::values.turbo_speed.SetValue(SliderToSettings(value));
ui->turbo_speed_display_label->setText(
QStringLiteral("%1%").arg(Settings::values.turbo_speed.GetValue()));
});
// Set a minimum width for the label to prevent the slider from changing size. // Set a minimum width for the label to prevent the slider from changing size.
// This scales across DPIs, and is acceptable for uncapitalized strings. // This scales across DPIs, and is acceptable for uncapitalized strings.
const auto width = static_cast<int>(tr("unthrottled").size() * 6); const auto width = static_cast<int>(tr("unthrottled").size() * 6);
@ -75,6 +80,10 @@ ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() { void ConfigureGeneral::SetConfiguration() {
if (Settings::IsConfiguringGlobal()) { if (Settings::IsConfiguringGlobal()) {
ui->turbo_speed->setValue(SettingsToSlider(Settings::values.turbo_speed.GetValue()));
ui->turbo_speed_display_label->setText(
QStringLiteral("%1%").arg(Settings::values.turbo_speed.GetValue()));
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue()); ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
ui->toggle_background_pause->setChecked( ui->toggle_background_pause->setChecked(
UISettings::values.pause_when_in_background.GetValue()); UISettings::values.pause_when_in_background.GetValue());
@ -103,7 +112,10 @@ void ConfigureGeneral::SetConfiguration() {
} }
if (!Settings::IsConfiguringGlobal()) { if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.frame_limit.UsingGlobal()) { if (is_powered_on) {
ui->emulation_speed_combo->setEnabled(false);
ui->frame_limit->setEnabled(false);
} else if (Settings::values.frame_limit.UsingGlobal()) {
ui->emulation_speed_combo->setCurrentIndex(0); ui->emulation_speed_combo->setCurrentIndex(0);
ui->frame_limit->setEnabled(false); ui->frame_limit->setEnabled(false);
} else { } else {
@ -182,7 +194,11 @@ void ConfigureGeneral::RetranslateUI() {
void ConfigureGeneral::SetupPerGameUI() { void ConfigureGeneral::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) { if (Settings::IsConfiguringGlobal()) {
ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal()); if (is_powered_on) {
ui->frame_limit->setEnabled(false);
} else {
ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());
}
return; return;
} }
@ -198,6 +214,7 @@ void ConfigureGeneral::SetupPerGameUI() {
ConfigurationShared::SetHighlight(ui->widget_screenshot, index == 1); ConfigurationShared::SetHighlight(ui->widget_screenshot, index == 1);
}); });
ui->turbo_speed->setVisible(false);
ui->general_group->setVisible(false); ui->general_group->setVisible(false);
ui->button_reset_defaults->setVisible(false); ui->button_reset_defaults->setVisible(false);
ui->toggle_gamemode->setVisible(false); ui->toggle_gamemode->setVisible(false);

View file

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -17,7 +17,7 @@ class ConfigureGeneral : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
explicit ConfigureGeneral(QWidget* parent = nullptr); explicit ConfigureGeneral(bool is_powered_on, QWidget* parent = nullptr);
~ConfigureGeneral() override; ~ConfigureGeneral() override;
void ResetDefaults(); void ResetDefaults();
@ -29,4 +29,5 @@ public:
private: private:
std::unique_ptr<Ui::ConfigureGeneral> ui; std::unique_ptr<Ui::ConfigureGeneral> ui;
bool is_powered_on;
}; };

View file

@ -147,6 +147,82 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QWidget" name="turbo_speed_layout" native="true">
<layout class="QHBoxLayout" name="turbo_speed_layout_inner">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="turbo_speed_label">
<property name="text">
<string>Turbo Speed:</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="turbo_speed">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>198</number>
</property>
<property name="singleStep">
<number>5</number>
</property>
<property name="pageStep">
<number>15</number>
</property>
<property name="value">
<number>19</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>32</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="turbo_speed_display_label">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View file

@ -1,4 +1,4 @@
// Copyright 2017 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -98,6 +98,37 @@ void ConfigureHotkeys::Configure(QModelIndex index) {
} }
const auto [key_sequence_used, used_action] = IsUsedKey(key_sequence); const auto [key_sequence_used, used_action] = IsUsedKey(key_sequence);
// Check for turbo/per-game speed conflict. Needed to prevent the user from binding both hotkeys
// to the same action. Which cuases problems resetting the frame limit.to the inititla value.
const QString current_action =
model->data(model->index(index.row(), 0, index.parent())).toString();
const bool is_turbo = current_action == tr("Toggle Turbo Mode");
const bool is_per_game = current_action == tr("Toggle Per-Game Speed");
if (is_turbo || is_per_game) {
QString other_action = is_turbo ? tr("Toggle Per-Game Speed") : tr("Toggle Turbo Mode");
QKeySequence other_sequence;
for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* const parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
if (parent->child(r2, 0)->text() == other_action) {
other_sequence = QKeySequence::fromString(
parent->child(r2, hotkey_column)->text(), QKeySequence::NativeText);
break;
}
}
}
// Show warning if either hotkey is already set
if (!key_sequence.isEmpty() && !other_sequence.isEmpty()) {
QMessageBox::warning(
this, tr("Conflicting Key Sequence"),
tr("The per-game speed and turbo speed hotkeys cannot be bound at the same time."));
return;
}
}
if (key_sequence_used && key_sequence != QKeySequence(previous_key.toString())) { if (key_sequence_used && key_sequence != QKeySequence(previous_key.toString())) {
QMessageBox::warning( QMessageBox::warning(
this, tr("Conflicting Key Sequence"), this, tr("Conflicting Key Sequence"),

View file

@ -1,5 +1,10 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -35,7 +40,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const QString
const bool is_powered_on = system.IsPoweredOn(); const bool is_powered_on = system.IsPoweredOn();
audio_tab = std::make_unique<ConfigureAudio>(is_powered_on, this); audio_tab = std::make_unique<ConfigureAudio>(is_powered_on, this);
general_tab = std::make_unique<ConfigureGeneral>(this); general_tab = std::make_unique<ConfigureGeneral>(is_powered_on, this);
enhancements_tab = std::make_unique<ConfigureEnhancements>(this); enhancements_tab = std::make_unique<ConfigureEnhancements>(this);
layout_tab = std::make_unique<ConfigureLayout>(this); layout_tab = std::make_unique<ConfigureLayout>(this);
graphics_tab = graphics_tab =

View file

@ -100,6 +100,7 @@
<string>Save State</string> <string>Save State</string>
</property> </property>
<addaction name="action_Save_to_Oldest_Slot"/> <addaction name="action_Save_to_Oldest_Slot"/>
<addaction name="action_Quick_Save"/>
<addaction name="separator"/> <addaction name="separator"/>
</widget> </widget>
<widget class="QMenu" name="menu_Load_State"> <widget class="QMenu" name="menu_Load_State">
@ -107,6 +108,7 @@
<string>Load State</string> <string>Load State</string>
</property> </property>
<addaction name="action_Load_from_Newest_Slot"/> <addaction name="action_Load_from_Newest_Slot"/>
<addaction name="action_Quick_Load"/>
<addaction name="separator"/> <addaction name="separator"/>
</widget> </widget>
<addaction name="action_Pause"/> <addaction name="action_Pause"/>
@ -343,11 +345,21 @@
<string>Save to Oldest Slot</string> <string>Save to Oldest Slot</string>
</property> </property>
</action> </action>
<action name="action_Quick_Save">
<property name="text">
<string>Quick Save</string>
</property>
</action>
<action name="action_Load_from_Newest_Slot"> <action name="action_Load_from_Newest_Slot">
<property name="text"> <property name="text">
<string>Load from Newest Slot</string> <string>Load from Newest Slot</string>
</property> </property>
</action> </action>
<action name="action_Quick_Load">
<property name="text">
<string>Quick Load</string>
</property>
</action>
<action name="action_Configure"> <action name="action_Configure">
<property name="text"> <property name="text">
<string>Configure...</string> <string>Configure...</string>

View file

@ -4,6 +4,7 @@
// Copyright Dolphin Emulator Project // Copyright Dolphin Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array> #include <array>
#include <fstream> #include <fstream>

View file

@ -4,6 +4,7 @@
// Copyright Dolphin Emulator Project // Copyright Dolphin Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once #pragma once

View file

@ -51,6 +51,7 @@ enum class PortraitLayoutOption : u32 {
// formerly mobile portrait // formerly mobile portrait
PortraitTopFullWidth, PortraitTopFullWidth,
PortraitCustomLayout, PortraitCustomLayout,
PortraitOriginal
}; };
/** Defines where the small screen will appear relative to the large screen /** Defines where the small screen will appear relative to the large screen
@ -497,6 +498,7 @@ struct Values {
Setting<bool> use_shader_jit{true, "use_shader_jit"}; Setting<bool> use_shader_jit{true, "use_shader_jit"};
SwitchableSetting<u32, true> resolution_factor{1, 0, 10, "resolution_factor"}; SwitchableSetting<u32, true> resolution_factor{1, 0, 10, "resolution_factor"};
SwitchableSetting<double, true> frame_limit{100, 0, 1000, "frame_limit"}; SwitchableSetting<double, true> frame_limit{100, 0, 1000, "frame_limit"};
SwitchableSetting<int, true> turbo_speed{200, 0, 1000, "turbo_speed"};
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::NoFilter, "texture_filter"}; SwitchableSetting<TextureFilter> texture_filter{TextureFilter::NoFilter, "texture_filter"};
SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled, SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled,
"texture_sampling"}; "texture_sampling"};

View file

@ -221,6 +221,10 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
layout = Layout::CustomFrameLayout( layout = Layout::CustomFrameLayout(
width, height, Settings::values.swap_screen.GetValue(), is_portrait_mode); width, height, Settings::values.swap_screen.GetValue(), is_portrait_mode);
break; break;
case Settings::PortraitLayoutOption::PortraitOriginal:
layout = Layout::PortraitOriginalLayout(width, height,
Settings::values.swap_screen.GetValue());
break;
} }
} else { } else {
switch (layout_option) { switch (layout_option) {

View file

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -51,6 +51,18 @@ FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool swapped
return res; return res;
} }
FramebufferLayout PortraitOriginalLayout(u32 width, u32 height, bool swapped) {
ASSERT(width > 0);
ASSERT(height > 0);
const float scale_factor = 1;
FramebufferLayout res = LargeFrameLayout(width, height, swapped, false, scale_factor,
Settings::SmallScreenPosition::BelowLarge);
const int shiftY = -(int)(swapped ? res.bottom_screen.top : res.top_screen.top);
res.top_screen = res.top_screen.TranslateY(shiftY);
res.bottom_screen = res.bottom_screen.TranslateY(shiftY);
return res;
}
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool upright) { FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
@ -346,7 +358,7 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary, FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary,
bool is_portrait) { bool is_portrait) {
int width, height; u32 width, height;
if (is_portrait) { if (is_portrait) {
auto layout_option = Settings::values.portrait_layout_option.GetValue(); auto layout_option = Settings::values.portrait_layout_option.GetValue();
switch (layout_option) { switch (layout_option) {
@ -363,9 +375,14 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
Settings::values.swap_screen.GetValue(), is_portrait); Settings::values.swap_screen.GetValue(), is_portrait);
case Settings::PortraitLayoutOption::PortraitTopFullWidth: case Settings::PortraitLayoutOption::PortraitTopFullWidth:
width = Core::kScreenTopWidth * res_scale; width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale; height = static_cast<int>(Core::kScreenTopHeight + Core::kScreenBottomHeight * 1.25) *
res_scale;
return PortraitTopFullFrameLayout(width, height, return PortraitTopFullFrameLayout(width, height,
Settings::values.swap_screen.GetValue()); Settings::values.swap_screen.GetValue());
case Settings::PortraitLayoutOption::PortraitOriginal:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
return PortraitOriginalLayout(width, height, Settings::values.swap_screen.GetValue());
} }
} else { } else {
auto layout_option = Settings::values.layout_option.GetValue(); auto layout_option = Settings::values.layout_option.GetValue();
@ -443,6 +460,24 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
Settings::values.upright_screen.GetValue(), 1, Settings::values.upright_screen.GetValue(), 1,
Settings::SmallScreenPosition::MiddleRight); Settings::SmallScreenPosition::MiddleRight);
case Settings::LayoutOption::HybridScreen:
height = Core::kScreenTopHeight * res_scale;
if (Settings::values.swap_screen.GetValue()) {
width = Core::kScreenBottomWidth;
} else {
width = Core::kScreenTopWidth;
}
// 2.25f comes from HybridScreenLayout's scale_factor value.
width = static_cast<int>((width + (Core::kScreenTopWidth / 2.25f)) * res_scale);
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
case Settings::LayoutOption::Default: case Settings::LayoutOption::Default:
default: default:
width = Core::kScreenTopWidth * res_scale; width = Core::kScreenTopWidth * res_scale;
@ -479,6 +514,7 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
if (is_portrait) { if (is_portrait) {
switch (Settings::values.portrait_layout_option.GetValue()) { switch (Settings::values.portrait_layout_option.GetValue()) {
case Settings::PortraitLayoutOption::PortraitTopFullWidth: case Settings::PortraitLayoutOption::PortraitTopFullWidth:
case Settings::PortraitLayoutOption::PortraitOriginal:
cardboard_screen_width = top_screen_width; cardboard_screen_width = top_screen_width;
cardboard_screen_height = top_screen_height + bottom_screen_height; cardboard_screen_height = top_screen_height + bottom_screen_height;
bottom_screen_left += (top_screen_width - bottom_screen_width) / 2; bottom_screen_left += (top_screen_width - bottom_screen_width) / 2;

View file

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project // Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
@ -62,8 +62,8 @@ FramebufferLayout reverseLayout(FramebufferLayout layout);
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, bool upright); FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
/** /**
* Factory method for constructing the mobile Full Width Top layout * Factory method for constructing the mobile Full Width (Default) layout
* Two screens at top, full width, no gap between them * Two screens at top, full width (so different heights)
* @param width Window framebuffer width in pixels * @param width Window framebuffer width in pixels
* @param height Window framebuffer height in pixels * @param height Window framebuffer height in pixels
* @param is_swapped if true, the bottom screen will be displayed above the top screen * @param is_swapped if true, the bottom screen will be displayed above the top screen
@ -71,6 +71,16 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, boo
*/ */
FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool is_swapped); FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool is_swapped);
/**
* Factory method for constructing the mobile Original layout
* Two screens at top, equal heights
* @param width Window framebuffer width in pixels
* @param height Window framebuffer height in pixels
* @param is_swapped if true, the bottom screen will be displayed above the top screen
* @return Newly created FramebufferLayout object with mobile portrait screen regions initialized
*/
FramebufferLayout PortraitOriginalLayout(u32 width, u32 height, bool is_swapped);
/** /**
* Factory method for constructing a FramebufferLayout with only the top or bottom screen * Factory method for constructing a FramebufferLayout with only the top or bottom screen
* @param width Window framebuffer width in pixels * @param width Window framebuffer width in pixels