Merge branch 'master' into global-hotkeys

This commit is contained in:
OpenSauce 2024-08-15 12:34:02 +01:00 committed by GitHub
commit 7b569b70d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 1085 additions and 903 deletions

View File

@ -19,7 +19,6 @@ cmake .. -G Ninja \
-DCMAKE_C_COMPILER=clang-18 \ -DCMAKE_C_COMPILER=clang-18 \
"${EXTRA_CMAKE_FLAGS[@]}" \ "${EXTRA_CMAKE_FLAGS[@]}" \
-DENABLE_QT_TRANSLATION=ON \ -DENABLE_QT_TRANSLATION=ON \
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=ON \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DUSE_DISCORD_PRESENCE=ON -DUSE_DISCORD_PRESENCE=ON
ninja ninja

View File

@ -7,7 +7,6 @@ cmake .. -GNinja \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \ -DENABLE_QT_TRANSLATION=ON \
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=ON \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DUSE_DISCORD_PRESENCE=ON -DUSE_DISCORD_PRESENCE=ON
ninja ninja

View File

@ -6,7 +6,6 @@ cmake .. -G Ninja \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \ -DCMAKE_C_COMPILER_LAUNCHER=ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DENABLE_QT_TRANSLATION=ON \ -DENABLE_QT_TRANSLATION=ON \
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=ON \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DUSE_DISCORD_PRESENCE=ON -DUSE_DISCORD_PRESENCE=ON
ninja ninja

View File

@ -4,7 +4,7 @@ labels: ["bug"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Lime. value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Lime3DS.
- type: checkboxes - type: checkboxes
attributes: attributes:
label: Is there an existing issue for this? label: Is there an existing issue for this?

View File

@ -256,4 +256,4 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
asset_paths: '["./**/*.tar.*","./**/*.AppImage","./**/*.zip","./**/*.apk"]' asset_paths: '["./**/*.tar.*","./**/*.AppImage","./**/*.zip","./**/*.apk","./**/*.aab",]'

View File

@ -268,6 +268,11 @@ android.applicationVariants.configureEach {
include("*.apk") include("*.apk")
into(layout.buildDirectory.dir("bundle")) into(layout.buildDirectory.dir("bundle"))
} }
project.copy {
from(layout.buildDirectory.dir("outputs/bundle/${variant.name}"))
include("*.aab")
into(layout.buildDirectory.dir("bundle"))
}
} }
} }
tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) } tasks.named("bundle${capitalizedName}").configure { finalizedBy(copyTask) }

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -156,9 +156,9 @@ object NativeLibrary {
external fun getPerfStats(): DoubleArray external fun getPerfStats(): DoubleArray
/** /**
* Notifies the core emulation that the orientation has changed. * Notifies the core emulation that the layout should be updated
*/ */
external fun notifyOrientationChange(layoutOption: Int, rotation: Int) external fun updateFramebuffer(isPortrait: Boolean)
/** /**
* Swaps the top and bottom screens. * Swaps the top and bottom screens.
@ -262,10 +262,6 @@ object NativeLibrary {
get() = LimeApplication.appContext.resources.configuration.orientation == get() = LimeApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT Configuration.ORIENTATION_PORTRAIT
@Keep
@JvmStatic
fun landscapeScreenLayout(): Int = EmulationMenuSettings.landscapeScreenLayout
@Keep @Keep
@JvmStatic @JvmStatic
fun displayAlertMsg(title: String, message: String, yesNo: Boolean): Boolean { fun displayAlertMsg(title: String, message: String, yesNo: Boolean): Boolean {

View File

@ -1,9 +1,8 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
package io.github.lime3ds.android.display package io.github.lime3ds.android.display
import android.view.WindowManager import android.view.WindowManager
import io.github.lime3ds.android.NativeLibrary import io.github.lime3ds.android.NativeLibrary
import io.github.lime3ds.android.features.settings.model.BooleanSetting import io.github.lime3ds.android.features.settings.model.BooleanSetting
@ -12,8 +11,10 @@ import io.github.lime3ds.android.features.settings.model.Settings
import io.github.lime3ds.android.features.settings.utils.SettingsFile import io.github.lime3ds.android.features.settings.utils.SettingsFile
import io.github.lime3ds.android.utils.EmulationMenuSettings import io.github.lime3ds.android.utils.EmulationMenuSettings
class ScreenAdjustmentUtil(private val windowManager: WindowManager, class ScreenAdjustmentUtil(
private val settings: Settings) { private val windowManager: WindowManager,
private val settings: Settings
) {
fun swapScreen() { fun swapScreen() {
val isEnabled = !EmulationMenuSettings.swapScreens val isEnabled = !EmulationMenuSettings.swapScreens
EmulationMenuSettings.swapScreens = isEnabled EmulationMenuSettings.swapScreens = isEnabled
@ -24,19 +25,35 @@ class ScreenAdjustmentUtil(private val windowManager: WindowManager,
BooleanSetting.SWAP_SCREEN.boolean = isEnabled BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
} }
fun cycleLayouts() { fun cycleLayouts() {
val nextLayout = (EmulationMenuSettings.landscapeScreenLayout + 1) % ScreenLayout.entries.size // TODO: figure out how to pull these from R.array
changeScreenOrientation(ScreenLayout.from(nextLayout)) val landscape_values = intArrayOf(6,1,3,4,0,5);
val portrait_values = intArrayOf(0,1);
if (NativeLibrary.isPortraitMode) {
val current_layout = IntSetting.PORTRAIT_SCREEN_LAYOUT.int
val pos = portrait_values.indexOf(current_layout)
val layout_option = portrait_values[(pos + 1) % portrait_values.size]
changePortraitOrientation(layout_option)
} else {
val current_layout = IntSetting.SCREEN_LAYOUT.int
val pos = landscape_values.indexOf(current_layout)
val layout_option = landscape_values[(pos + 1) % landscape_values.size]
changeScreenOrientation(layout_option)
} }
fun changeScreenOrientation(layoutOption: ScreenLayout) { }
EmulationMenuSettings.landscapeScreenLayout = layoutOption.int
NativeLibrary.notifyOrientationChange( fun changePortraitOrientation(layoutOption: Int) {
EmulationMenuSettings.landscapeScreenLayout, IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
windowManager.defaultDisplay.rotation settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
) NativeLibrary.reloadSettings()
IntSetting.SCREEN_LAYOUT.int = layoutOption.int NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
fun changeScreenOrientation(layoutOption: Int) {
IntSetting.SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -6,17 +6,30 @@ package io.github.lime3ds.android.display
enum class ScreenLayout(val int: Int) { enum class ScreenLayout(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
DEFAULT(0), ORIGINAL(0),
SINGLE_SCREEN(1), SINGLE_SCREEN(1),
LARGE_SCREEN(2), LARGE_SCREEN(2),
SIDE_SCREEN(3), SIDE_SCREEN(3),
HYBRID_SCREEN(4), HYBRID_SCREEN(4),
MOBILE_PORTRAIT(5), CUSTOM_LAYOUT(5),
MOBILE_LANDSCAPE(6); MOBILE_LANDSCAPE(6);
companion object { companion object {
fun from(int: Int): ScreenLayout { fun from(int: Int): ScreenLayout {
return entries.firstOrNull { it.int == int } ?: DEFAULT return entries.firstOrNull { it.int == int } ?: MOBILE_LANDSCAPE
}
}
}
enum class PortraitScreenLayout(val int: Int) {
// These must match what is defined in src/common/settings.h
TOP_FULL_WIDTH(0),
CUSTOM_PORTRAIT_LAYOUT(1);
companion object {
fun from(int: Int): PortraitScreenLayout {
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH;
} }
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -14,6 +14,7 @@ enum class BooleanSetting(
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false), PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true), ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false), SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
ADRENO_GPU_BOOST("adreno_gpu_boost", Settings.SECTION_RENDERER, false); ADRENO_GPU_BOOST("adreno_gpu_boost", Settings.SECTION_RENDERER, false);
override var boolean: Boolean = defaultValue override var boolean: Boolean = defaultValue

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -23,6 +23,23 @@ enum class IntSetting(
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0), CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0), CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0), SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0),
LANDSCAPE_TOP_X("custom_top_x",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y("custom_top_y",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH("custom_top_width",Settings.SECTION_LAYOUT,800),
LANDSCAPE_TOP_HEIGHT("custom_top_height",Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_X("custom_bottom_x",Settings.SECTION_LAYOUT,80),
LANDSCAPE_BOTTOM_Y("custom_bottom_y",Settings.SECTION_LAYOUT,480),
LANDSCAPE_BOTTOM_WIDTH("custom_bottom_width",Settings.SECTION_LAYOUT,640),
LANDSCAPE_BOTTOM_HEIGHT("custom_bottom_height",Settings.SECTION_LAYOUT,480),
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_X("custom_portrait_top_x",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_Y("custom_portrait_top_y",Settings.SECTION_LAYOUT,0),
PORTRAIT_TOP_WIDTH("custom_portrait_top_width",Settings.SECTION_LAYOUT,800),
PORTRAIT_TOP_HEIGHT("custom_portrait_top_height",Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_X("custom_portrait_bottom_x",Settings.SECTION_LAYOUT,80),
PORTRAIT_BOTTOM_Y("custom_portrait_bottom_y",Settings.SECTION_LAYOUT,480),
PORTRAIT_BOTTOM_WIDTH("custom_portrait_bottom_width",Settings.SECTION_LAYOUT,640),
PORTRAIT_BOTTOM_HEIGHT("custom_portrait_bottom_height",Settings.SECTION_LAYOUT,480),
AUDIO_INPUT_TYPE("output_type", Settings.SECTION_AUDIO, 0), AUDIO_INPUT_TYPE("output_type", Settings.SECTION_AUDIO, 0),
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, 1), NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, 1),
LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, 0), LLE_APPLETS("lle_applets", Settings.SECTION_SYSTEM, 0),

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -109,6 +109,8 @@ class Settings {
const val SECTION_AUDIO = "Audio" const val SECTION_AUDIO = "Audio"
const val SECTION_DEBUG = "Debugging" const val SECTION_DEBUG = "Debugging"
const val SECTION_THEME = "Theme" const val SECTION_THEME = "Theme"
const val SECTION_CUSTOM_LANDSCAPE = "Custom Landscape Layout"
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
const val KEY_BUTTON_A = "button_a" const val KEY_BUTTON_A = "button_a"
const val KEY_BUTTON_B = "button_b" const val KEY_BUTTON_B = "button_b"
@ -131,7 +133,6 @@ class Settings {
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal" const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical" const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal" const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap" const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout" const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
const val HOTKEY_CLOSE_GAME = "hotkey_close_game" const val HOTKEY_CLOSE_GAME = "hotkey_close_game"

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -7,6 +7,7 @@ package io.github.lime3ds.android.features.settings.ui
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import io.github.lime3ds.android.NativeLibrary import io.github.lime3ds.android.NativeLibrary
import io.github.lime3ds.android.features.settings.model.IntSetting
import io.github.lime3ds.android.features.settings.model.Settings import io.github.lime3ds.android.features.settings.model.Settings
import io.github.lime3ds.android.utils.SystemSaveGame import io.github.lime3ds.android.utils.SystemSaveGame
import io.github.lime3ds.android.utils.DirectoryInitialization import io.github.lime3ds.android.utils.DirectoryInitialization
@ -56,6 +57,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
settings.saveSettings(activityView) settings.saveSettings(activityView)
SystemSaveGame.save() SystemSaveGame.save()
//added to ensure that layout changes take effect as soon as settings window closes
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
} }
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
} }

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -7,13 +7,16 @@ package io.github.lime3ds.android.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.InputFilter import android.text.InputFilter
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.TextView 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
@ -22,6 +25,8 @@ 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
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat import com.google.android.material.timepicker.TimeFormat
import io.github.lime3ds.android.R import io.github.lime3ds.android.R
@ -73,7 +78,8 @@ class SettingsAdapter(
private var clickedPosition: Int private var clickedPosition: Int
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private var sliderProgress = 0 private var sliderProgress = 0
private var textSliderValue: TextView? = null private var textSliderValue: TextInputEditText? = null
private var textInputLayout: TextInputLayout? = null
private var textInputValue: String = "" private var textInputValue: String = ""
private var defaultCancelListener = private var defaultCancelListener =
@ -256,18 +262,35 @@ class SettingsAdapter(
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater) val sliderBinding = DialogSliderBinding.inflate(inflater)
textInputLayout = sliderBinding.textInput
textSliderValue = sliderBinding.textValue textSliderValue = sliderBinding.textValue
textSliderValue!!.text = sliderProgress.toString() textSliderValue!!.setText(sliderProgress.toString())
sliderBinding.textUnits.text = item.units textInputLayout!!.suffixText = item.units
sliderBinding.slider.apply { sliderBinding.slider.apply {
valueFrom = item.min.toFloat() valueFrom = item.min.toFloat()
valueTo = item.max.toFloat() valueTo = item.max.toFloat()
value = sliderProgress.toFloat() value = sliderProgress.toFloat()
textSliderValue!!.addTextChangedListener( object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val textValue = s.toString().toIntOrNull();
if (textValue == null || textValue < valueFrom || textValue > valueTo) {
textInputLayout!!.error ="Inappropriate value"
} else {
textInputLayout!!.error = null
value = textValue.toFloat();
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
addOnChangeListener { _: Slider, value: Float, _: Boolean -> addOnChangeListener { _: Slider, value: Float, _: Boolean ->
sliderProgress = value.toInt() sliderProgress = value.toInt()
textSliderValue!!.text = sliderProgress.toString() if (textSliderValue!!.text.toString() != value.toInt().toString()) {
textSliderValue!!.setText(value.toInt().toString())
textSliderValue!!.setSelection(textSliderValue!!.length())
}
} }
} }

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -6,6 +6,7 @@ package io.github.lime3ds.android.features.settings.ui
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Resources
import android.hardware.camera2.CameraAccessException import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager import android.hardware.camera2.CameraManager
@ -42,6 +43,7 @@ import io.github.lime3ds.android.utils.GpuDriverHelper
import io.github.lime3ds.android.utils.Log import io.github.lime3ds.android.utils.Log
import io.github.lime3ds.android.utils.SystemSaveGame import io.github.lime3ds.android.utils.SystemSaveGame
import io.github.lime3ds.android.utils.ThemeUtil import io.github.lime3ds.android.utils.ThemeUtil
import kotlin.math.min
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) { class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
private var menuTag: String? = null private var menuTag: String? = null
@ -91,9 +93,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_CAMERA -> addCameraSettings(sl) Settings.SECTION_CAMERA -> addCameraSettings(sl)
Settings.SECTION_CONTROLS -> addControlsSettings(sl) Settings.SECTION_CONTROLS -> addControlsSettings(sl)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl) Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_LAYOUT -> addLayoutSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl) Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl) Settings.SECTION_DEBUG -> addDebugSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl) Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_CUSTOM_LANDSCAPE -> addCustomLandscapeSettings(sl)
Settings.SECTION_CUSTOM_PORTRAIT -> addCustomPortraitSettings(sl)
else -> { else -> {
fragmentView.showToastMessage("Unimplemented menu", false) fragmentView.showToastMessage("Unimplemented menu", false)
return return
@ -103,6 +108,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
fragmentView.showSettingsList(settingsList!!) fragmentView.showSettingsList(settingsList!!)
} }
/** Returns the portrait mode width */
private fun getWidth(): Int {
val dm = Resources.getSystem().displayMetrics;
return if (dm.widthPixels < dm.heightPixels)
dm.widthPixels
else
dm.heightPixels
}
private fun getHeight(): Int {
val dm = Resources.getSystem().displayMetrics;
return if (dm.widthPixels < dm.heightPixels)
dm.heightPixels
else
dm.widthPixels
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) { private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_settings)) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_settings))
sl.apply { sl.apply {
@ -146,6 +168,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_RENDERER Settings.SECTION_RENDERER
) )
) )
add(
SubmenuSetting(
R.string.preferences_layout,
0,
R.drawable.ic_fit_screen,
Settings.SECTION_LAYOUT
)
)
add( add(
SubmenuSetting( SubmenuSetting(
R.string.preferences_audio, R.string.preferences_audio,
@ -162,6 +192,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_DEBUG Settings.SECTION_DEBUG
) )
) )
add( add(
RunnableSetting( RunnableSetting(
R.string.reset_to_default, R.string.reset_to_default,
@ -873,6 +904,262 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
} }
private fun addLayoutSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_layout))
sl.apply {
add(
SingleChoiceSetting(
IntSetting.SCREEN_LAYOUT,
R.string.emulation_switch_screen_layout,
0,
R.array.landscapeLayouts,
R.array.landscapeLayoutValues,
IntSetting.SCREEN_LAYOUT.key,
IntSetting.SCREEN_LAYOUT.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.PORTRAIT_SCREEN_LAYOUT,
R.string.emulation_switch_portrait_layout,
0,
R.array.portraitLayouts,
R.array.portraitLayoutValues,
IntSetting.PORTRAIT_SCREEN_LAYOUT.key,
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
)
)
add(
SubmenuSetting(
R.string.emulation_landscape_custom_layout,
0,
R.drawable.ic_fit_screen,
Settings.SECTION_CUSTOM_LANDSCAPE
)
)
add(
SubmenuSetting(
R.string.emulation_portrait_custom_layout,
0,
R.drawable.ic_portrait_fit_screen,
Settings.SECTION_CUSTOM_PORTRAIT
)
)
}
}
private fun addCustomLandscapeSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.emulation_landscape_custom_layout))
sl.apply {
add(HeaderSetting(R.string.emulation_top_screen))
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_X,
R.string.emulation_custom_layout_x,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_TOP_X.key,
IntSetting.LANDSCAPE_TOP_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_Y,
R.string.emulation_custom_layout_y,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_TOP_Y.key,
IntSetting.LANDSCAPE_TOP_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_TOP_WIDTH.key,
IntSetting.LANDSCAPE_TOP_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_TOP_HEIGHT.key,
IntSetting.LANDSCAPE_TOP_HEIGHT.defaultValue.toFloat()
)
)
add(HeaderSetting(R.string.emulation_bottom_screen))
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_X,
R.string.emulation_custom_layout_x,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_BOTTOM_X.key,
IntSetting.LANDSCAPE_BOTTOM_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_Y,
R.string.emulation_custom_layout_y,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_BOTTOM_Y.key,
IntSetting.LANDSCAPE_BOTTOM_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_BOTTOM_WIDTH.key,
IntSetting.LANDSCAPE_BOTTOM_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.key,
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.defaultValue.toFloat()
)
)
}
}
private fun addCustomPortraitSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.emulation_portrait_custom_layout))
sl.apply {
add(HeaderSetting(R.string.emulation_top_screen))
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_X,
R.string.emulation_custom_layout_x,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_TOP_X.key,
IntSetting.PORTRAIT_TOP_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_Y,
R.string.emulation_custom_layout_y,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_TOP_Y.key,
IntSetting.PORTRAIT_TOP_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_TOP_WIDTH.key,
IntSetting.PORTRAIT_TOP_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_TOP_HEIGHT.key,
IntSetting.PORTRAIT_TOP_HEIGHT.defaultValue.toFloat()
)
)
add(HeaderSetting(R.string.emulation_bottom_screen))
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_X,
R.string.emulation_custom_layout_x,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_BOTTOM_X.key,
IntSetting.PORTRAIT_BOTTOM_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_Y,
R.string.emulation_custom_layout_y,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_BOTTOM_Y.key,
IntSetting.PORTRAIT_BOTTOM_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_BOTTOM_WIDTH.key,
IntSetting.PORTRAIT_BOTTOM_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_BOTTOM_HEIGHT.key,
IntSetting.PORTRAIT_BOTTOM_HEIGHT.defaultValue.toFloat()
)
)
}
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) { private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
sl.apply { sl.apply {

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -13,6 +13,8 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.SystemClock import android.os.SystemClock
import android.text.Editable
import android.text.TextWatcher
import android.view.Choreographer import android.view.Choreographer
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
@ -51,8 +53,10 @@ import io.github.lime3ds.android.activities.EmulationActivity
import io.github.lime3ds.android.databinding.DialogCheckboxBinding import io.github.lime3ds.android.databinding.DialogCheckboxBinding
import io.github.lime3ds.android.databinding.DialogSliderBinding import io.github.lime3ds.android.databinding.DialogSliderBinding
import io.github.lime3ds.android.databinding.FragmentEmulationBinding import io.github.lime3ds.android.databinding.FragmentEmulationBinding
import io.github.lime3ds.android.display.PortraitScreenLayout
import io.github.lime3ds.android.display.ScreenAdjustmentUtil import io.github.lime3ds.android.display.ScreenAdjustmentUtil
import io.github.lime3ds.android.display.ScreenLayout import io.github.lime3ds.android.display.ScreenLayout
import io.github.lime3ds.android.features.settings.model.IntSetting
import io.github.lime3ds.android.features.settings.model.SettingsViewModel import io.github.lime3ds.android.features.settings.model.SettingsViewModel
import io.github.lime3ds.android.features.settings.ui.SettingsActivity import io.github.lime3ds.android.features.settings.ui.SettingsActivity
import io.github.lime3ds.android.features.settings.utils.SettingsFile import io.github.lime3ds.android.features.settings.utils.SettingsFile
@ -142,7 +146,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
retainInstance = true retainInstance = true
emulationState = EmulationState(game.path) emulationState = EmulationState(game.path)
emulationActivity = requireActivity() as EmulationActivity emulationActivity = requireActivity() as EmulationActivity
screenAdjustmentUtil = ScreenAdjustmentUtil(emulationActivity.windowManager, settingsViewModel.settings) screenAdjustmentUtil =
ScreenAdjustmentUtil(emulationActivity.windowManager, settingsViewModel.settings)
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
} }
@ -207,12 +212,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
}) })
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val titleId = if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) { val titleId =
if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
R.string.unlock_drawer R.string.unlock_drawer
} else { } else {
R.string.lock_drawer R.string.lock_drawer
} }
val iconId = if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_UNLOCKED) { val iconId =
if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_UNLOCKED) {
R.drawable.ic_unlocked R.drawable.ic_unlocked
} else { } else {
R.drawable.ic_lock R.drawable.ic_lock
@ -267,7 +274,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
R.id.menu_landscape_screen_layout -> { R.id.menu_landscape_screen_layout -> {
showScreenLayoutMenu() showLandscapeScreenLayoutMenu()
true
}
R.id.menu_portrait_screen_layout -> {
showPortraitScreenLayoutMenu()
true true
} }
@ -315,6 +327,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
SettingsFile.FILE_NAME_CONFIG, SettingsFile.FILE_NAME_CONFIG,
"" ""
) )
true true
} }
@ -769,7 +782,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.show() popupMenu.show()
} }
private fun showScreenLayoutMenu() { private fun showLandscapeScreenLayoutMenu() {
val popupMenu = PopupMenu( val popupMenu = PopupMenu(
requireContext(), requireContext(),
binding.inGameMenu.findViewById(R.id.menu_landscape_screen_layout) binding.inGameMenu.findViewById(R.id.menu_landscape_screen_layout)
@ -777,19 +790,22 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu) popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu)
val layoutOptionMenuItem = when (EmulationMenuSettings.landscapeScreenLayout) { val layoutOptionMenuItem = when (IntSetting.SCREEN_LAYOUT.int) {
ScreenLayout.ORIGINAL.int ->
R.id.menu_screen_layout_original
ScreenLayout.SINGLE_SCREEN.int -> ScreenLayout.SINGLE_SCREEN.int ->
R.id.menu_screen_layout_single R.id.menu_screen_layout_single
ScreenLayout.SIDE_SCREEN.int -> ScreenLayout.SIDE_SCREEN.int ->
R.id.menu_screen_layout_sidebyside R.id.menu_screen_layout_sidebyside
ScreenLayout.MOBILE_PORTRAIT.int ->
R.id.menu_screen_layout_portrait
ScreenLayout.HYBRID_SCREEN.int -> ScreenLayout.HYBRID_SCREEN.int ->
R.id.menu_screen_layout_hybrid R.id.menu_screen_layout_hybrid
ScreenLayout.CUSTOM_LAYOUT.int ->
R.id.menu_screen_layout_custom
else -> R.id.menu_screen_layout_landscape else -> R.id.menu_screen_layout_landscape
} }
popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true) popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true)
@ -797,27 +813,83 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.setOnMenuItemClickListener { popupMenu.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.menu_screen_layout_landscape -> { R.id.menu_screen_layout_landscape -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_LANDSCAPE) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_LANDSCAPE.int)
true
}
R.id.menu_screen_layout_portrait -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_PORTRAIT)
true true
} }
R.id.menu_screen_layout_single -> { R.id.menu_screen_layout_single -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SINGLE_SCREEN) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SINGLE_SCREEN.int)
true true
} }
R.id.menu_screen_layout_sidebyside -> { R.id.menu_screen_layout_sidebyside -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SIDE_SCREEN) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SIDE_SCREEN.int)
true true
} }
R.id.menu_screen_layout_hybrid -> { R.id.menu_screen_layout_hybrid -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.HYBRID_SCREEN) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.HYBRID_SCREEN.int)
true
}
R.id.menu_screen_layout_original -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.ORIGINAL.int)
true
}
R.id.menu_screen_layout_custom -> {
Toast.makeText(
requireContext(),
R.string.emulation_adjust_custom_layout,
Toast.LENGTH_LONG
).show()
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.CUSTOM_LAYOUT.int)
true
}
else -> true
}
}
popupMenu.show()
}
private fun showPortraitScreenLayoutMenu() {
val popupMenu = PopupMenu(
requireContext(),
binding.inGameMenu.findViewById(R.id.menu_portrait_screen_layout)
)
popupMenu.menuInflater.inflate(R.menu.menu_portrait_screen_layout, popupMenu.menu)
val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) {
PortraitScreenLayout.TOP_FULL_WIDTH.int ->
R.id.menu_portrait_layout_top_full
PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int ->
R.id.menu_portrait_layout_custom
else ->
R.id.menu_portrait_layout_top_full
}
popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true)
popupMenu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_portrait_layout_top_full -> {
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.TOP_FULL_WIDTH.int)
true
}
R.id.menu_portrait_layout_custom -> {
Toast.makeText(
requireContext(),
R.string.emulation_adjust_custom_layout,
Toast.LENGTH_LONG
).show()
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int)
true true
} }
@ -869,14 +941,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
sliderBinding.apply { sliderBinding.apply {
slider.valueTo = 150f slider.valueTo = 150f
slider.valueFrom = 0f
slider.value = preferences.getInt(target, 50).toFloat() slider.value = preferences.getInt(target, 50).toFloat()
textValue.setText((slider.value + 50).toInt().toString())
textValue.addTextChangedListener( object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val value = s.toString().toIntOrNull()
if (value == null || value < 50 || value > 150) {
textInput.error = "Inappropriate Value"
} else {
textInput.error = null
slider.value = value.toFloat() - 50
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
slider.addOnChangeListener( slider.addOnChangeListener(
Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean -> Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean ->
textValue.text = (progress.toInt() + 50).toString() if (textValue.text.toString() != (slider.value + 50).toInt().toString()) {
textValue.setText((slider.value + 50).toInt().toString())
textValue.setSelection(textValue.length())
setControlScale(slider.value.toInt(), target) setControlScale(slider.value.toInt(), target)
}
}) })
textValue.text = (sliderBinding.slider.value.toInt() + 50).toString() textInput.suffixText = "%"
textUnits.text = "%"
} }
val previousProgress = sliderBinding.slider.value.toInt() val previousProgress = sliderBinding.slider.value.toInt()
@ -899,15 +989,36 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val sliderBinding = DialogSliderBinding.inflate(layoutInflater) val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
sliderBinding.apply { sliderBinding.apply {
slider.valueFrom = 0f
slider.valueTo = 100f slider.valueTo = 100f
slider.value = preferences.getInt("controlOpacity", 50).toFloat() slider.value = preferences.getInt("controlOpacity", 50).toFloat()
slider.addOnChangeListener( textValue.setText(slider.value.toInt().toString())
Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean ->
textValue.text = (progress.toInt()).toString() textValue.addTextChangedListener( object : TextWatcher {
setControlOpacity(slider.value.toInt()) override fun afterTextChanged(s: Editable) {
val value = s.toString().toIntOrNull()
if (value == null || value < slider.valueFrom || value > slider.valueTo) {
textInput.error = "Inappropriate Value"
} else {
textInput.error = null
slider.value = value.toFloat()
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
}) })
textValue.text = (sliderBinding.slider.value.toInt()).toString()
textUnits.text = "%"
slider.addOnChangeListener { _: Slider, value: Float, _: Boolean ->
if (textValue.text.toString() != slider.value.toInt().toString()) {
textValue.setText(slider.value.toInt().toString())
textValue.setSelection(textValue.length())
setControlOpacity(slider.value.toInt())
}
}
textInput.suffixText = "%"
} }
val previousProgress = sliderBinding.slider.value.toInt() val previousProgress = sliderBinding.slider.value.toInt()

View File

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -7,7 +7,6 @@ package io.github.lime3ds.android.utils
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import io.github.lime3ds.android.LimeApplication import io.github.lime3ds.android.LimeApplication
import io.github.lime3ds.android.display.ScreenLayout
object EmulationMenuSettings { object EmulationMenuSettings {
private val preferences = private val preferences =
@ -27,16 +26,7 @@ object EmulationMenuSettings {
.putBoolean("EmulationMenuSettings_DpadSlideEnable", value) .putBoolean("EmulationMenuSettings_DpadSlideEnable", value)
.apply() .apply()
} }
var landscapeScreenLayout: Int
get() = preferences.getInt(
"EmulationMenuSettings_LandscapeScreenLayout",
ScreenLayout.MOBILE_LANDSCAPE.int
)
set(value) {
preferences.edit()
.putInt("EmulationMenuSettings_LandscapeScreenLayout", value)
.apply()
}
var showFps: Boolean var showFps: Boolean
get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false) get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false)
set(value) { set(value) {

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -174,8 +174,7 @@ void Config::ReadValues() {
// Layout // Layout
Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger( Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger(
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::MobileLandscape))); "Layout", "layout_option", static_cast<int>(Settings::LayoutOption::LargeScreen)));
ReadSetting("Layout", Settings::values.custom_layout);
ReadSetting("Layout", Settings::values.custom_top_x); ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y); ReadSetting("Layout", Settings::values.custom_top_y);
ReadSetting("Layout", Settings::values.custom_top_width); ReadSetting("Layout", Settings::values.custom_top_width);
@ -188,6 +187,19 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.cardboard_x_shift); ReadSetting("Layout", Settings::values.cardboard_x_shift);
ReadSetting("Layout", Settings::values.cardboard_y_shift); ReadSetting("Layout", Settings::values.cardboard_y_shift);
Settings::values.portrait_layout_option =
static_cast<Settings::PortraitLayoutOption>(sdl2_config->GetInteger(
"Layout", "portrait_layout_option",
static_cast<int>(Settings::PortraitLayoutOption::PortraitTopFullWidth)));
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
ReadSetting("Layout", Settings::values.custom_portrait_top_height);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_x);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_y);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_width);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_height);
// Utility // Utility
ReadSetting("Utility", Settings::values.dump_textures); ReadSetting("Utility", Settings::values.dump_textures);
ReadSetting("Utility", Settings::values.custom_textures); ReadSetting("Utility", Settings::values.custom_textures);

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -179,16 +179,20 @@ anaglyph_shader_name =
filter_mode = filter_mode =
[Layout] [Layout]
# Layout for the screen inside the render window. # Layout for the screen inside the render window, landscape mode
# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side # 0: Top/Bottom *currently unsupported on android*
# 1: Single Screen Only,
# 2: *currently unsupported on android*
# 3: Side by Side
# 4: Hybrid
# 5: Custom Layout
# 6: (default) Large screen / small screen
layout_option = layout_option =
# Toggle custom layout (using the settings below) on or off.
# 0 (default): Off, 1: On
custom_layout =
# Screen placement when using Custom layout option # Screen placement when using Custom layout option
# 0x, 0y is the top left corner of the render window. # 0x, 0y is the top left corner of the render window.
# suggested aspect ratio for top screen is 5:3
# suggested aspect ratio for bottom screen is 4:3
custom_top_x = custom_top_x =
custom_top_y = custom_top_y =
custom_top_width = custom_top_width =
@ -198,6 +202,22 @@ custom_bottom_y =
custom_bottom_width = custom_bottom_width =
custom_bottom_height = custom_bottom_height =
# Layout for the portrait mode
# 0 (default): Top and bottom screens at top, full width
# 1: Custom Layout
portrait_layout_option =
# Screen placement when using Portrait Custom layout option
# 0x, 0y is the top left corner of the render window.
custom_portrait_top_x =
custom_portrait_top_y =
custom_portrait_top_width =
custom_portrait_top_height =
custom_portrait_bottom_x =
custom_portrait_bottom_y =
custom_portrait_bottom_width =
custom_portrait_bottom_height =
# Swaps the prominent screen with the other screen. # Swaps the prominent screen with the other screen.
# For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen. # For example, if Single Screen is chosen, setting this to 1 will display the bottom screen instead of the top screen.
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent # 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent

View File

@ -1,4 +1,4 @@
// Copyright 2019 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -21,12 +21,6 @@ static bool IsPortraitMode() {
IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode()); IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode());
} }
static void UpdateLandscapeScreenLayout() {
Settings::values.layout_option =
static_cast<Settings::LayoutOption>(IDCache::GetEnvForThread()->CallStaticIntMethod(
IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout()));
}
bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
if (render_window == surface) { if (render_window == surface) {
return false; return false;
@ -57,7 +51,6 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) {
} }
void EmuWindow_Android::OnFramebufferSizeChanged() { void EmuWindow_Android::OnFramebufferSizeChanged() {
UpdateLandscapeScreenLayout();
const bool is_portrait_mode{IsPortraitMode()}; const bool is_portrait_mode{IsPortraitMode()};
const int bigger{window_width > window_height ? window_width : window_height}; const int bigger{window_width > window_height ? window_width : window_height};

View File

@ -1,4 +1,4 @@
// Copyright 2019 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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,6 +27,7 @@ static jclass s_native_library_class;
static jmethodID s_on_core_error; static jmethodID s_on_core_error;
static jmethodID s_is_portrait_mode; static jmethodID s_is_portrait_mode;
static jmethodID s_landscape_screen_layout; static jmethodID s_landscape_screen_layout;
static jmethodID s_portrait_screen_layout;
static jmethodID s_exit_emulation_activity; static jmethodID s_exit_emulation_activity;
static jmethodID s_request_camera_permission; static jmethodID s_request_camera_permission;
static jmethodID s_request_mic_permission; static jmethodID s_request_mic_permission;
@ -90,6 +91,10 @@ jmethodID GetLandscapeScreenLayout() {
return s_landscape_screen_layout; return s_landscape_screen_layout;
} }
jmethodID GetPortraitScreenLayout() {
return s_portrait_screen_layout;
}
jmethodID GetExitEmulationActivity() { jmethodID GetExitEmulationActivity() {
return s_exit_emulation_activity; return s_exit_emulation_activity;
} }
@ -172,8 +177,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_native_library_class, "onCoreError", s_native_library_class, "onCoreError",
"(Lio/github/lime3ds/android/NativeLibrary$CoreError;Ljava/lang/String;)Z"); "(Lio/github/lime3ds/android/NativeLibrary$CoreError;Ljava/lang/String;)Z");
s_is_portrait_mode = env->GetStaticMethodID(s_native_library_class, "isPortraitMode", "()Z"); s_is_portrait_mode = env->GetStaticMethodID(s_native_library_class, "isPortraitMode", "()Z");
s_landscape_screen_layout =
env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I");
s_exit_emulation_activity = s_exit_emulation_activity =
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
s_request_camera_permission = s_request_camera_permission =

View File

@ -1,4 +1,4 @@
// Copyright 2019 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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,6 +27,7 @@ jmethodID GetDisplayAlertPrompt();
jmethodID GetAlertPromptButton(); jmethodID GetAlertPromptButton();
jmethodID GetIsPortraitMode(); jmethodID GetIsPortraitMode();
jmethodID GetLandscapeScreenLayout(); jmethodID GetLandscapeScreenLayout();
jmethodID GetPortraitScreenLayout();
jmethodID GetExitEmulationActivity(); jmethodID GetExitEmulationActivity();
jmethodID GetRequestCameraPermission(); jmethodID GetRequestCameraPermission();
jmethodID GetRequestMicPermission(); jmethodID GetRequestMicPermission();

View File

@ -1,4 +1,4 @@
// Copyright 2019 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -346,15 +346,13 @@ void JNICALL Java_io_github_lime3ds_android_NativeLibrary_enableAdrenoTurboMode(
EnableAdrenoTurboMode(enable); EnableAdrenoTurboMode(enable);
} }
void Java_io_github_lime3ds_android_NativeLibrary_notifyOrientationChange( void Java_io_github_lime3ds_android_NativeLibrary_updateFramebuffer([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, jint layout_option, jint rotation) { [[maybe_unused]] jobject obj,
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option); jboolean is_portrait_mode) {
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) { if (system.IsPoweredOn()) {
system.GPU().Renderer().UpdateCurrentFramebufferLayout(!(rotation % 2)); system.GPU().Renderer().UpdateCurrentFramebufferLayout(is_portrait_mode);
} }
InputManager::screen_rotation = rotation;
Camera::NDK::g_rotation = rotation;
} }
void Java_io_github_lime3ds_android_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv* env, void Java_io_github_lime3ds_android_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv* env,

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="90"
android:toDegrees="0"
android:drawable="@drawable/ic_fit_screen">
</rotate>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -13,11 +14,22 @@
android:layout_marginRight="@dimen/spacing_large" android:layout_marginRight="@dimen/spacing_large"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_below="@+id/text_value" android:layout_below="@+id/text_input"
android:layout_marginBottom="@dimen/spacing_medlarge" /> android:layout_marginBottom="@dimen/spacing_medlarge" />
<TextView <com.google.android.material.textfield.TextInputLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_marginLeft="@dimen/spacing_large"
android:layout_marginRight="@dimen/spacing_large"
android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_height="wrap_content"
android:id="@+id/text_input"
app:suffixText="%">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:inputType="number"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="75" tools:text="75"
android:id="@+id/text_value" android:id="@+id/text_value"
@ -26,12 +38,6 @@
android:layout_marginTop="@dimen/spacing_medlarge" android:layout_marginTop="@dimen/spacing_medlarge"
android:layout_marginBottom="@dimen/spacing_medlarge" /> android:layout_marginBottom="@dimen/spacing_medlarge" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="%"
android:id="@+id/text_units"
android:layout_alignTop="@+id/text_value"
android:layout_toEndOf="@+id/text_value" />
</com.google.android.material.textfield.TextInputLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -1,127 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="io.github.lime3ds.android.activities.EmulationActivity">
<item
android:id="@+id/menu_emulation_save_state"
android:title="@string/emulation_save_state">
<menu/>
</item>
<item
android:id="@+id/menu_emulation_load_state"
android:title="@string/emulation_load_state">
<menu/>
</item>
<item
android:id="@+id/menu_emulation_configure_controls"
android:title="@string/emulation_configure_controls">
<menu>
<item
android:id="@+id/menu_emulation_edit_layout"
android:title="@string/emulation_edit_layout" />
<item
android:id="@+id/menu_emulation_toggle_controls"
android:title="@string/emulation_toggle_controls" />
<item
android:id="@+id/menu_emulation_adjust_scale"
android:title="@string/emulation_control_scale" />
<group android:checkableBehavior="all">
<item
android:id="@+id/menu_emulation_joystick_rel_center"
android:checkable="true"
android:title="@string/emulation_control_joystick_rel_center"/>
<item
android:id="@+id/menu_emulation_dpad_slide_enable"
android:checkable="true"
android:title="@string/emulation_control_dpad_slide_enable" />
</group>
<item
android:id="@+id/menu_emulation_reset_overlay"
android:title="@string/emulation_touch_overlay_reset" />
</menu>
</item>
<item
android:id="@+id/menu_emulation_amiibo"
android:title="@string/menu_emulation_amiibo">
<menu>
<item
android:id="@+id/menu_emulation_amiibo_load"
android:title="@string/menu_emulation_amiibo_load" />
<item
android:id="@+id/menu_emulation_amiibo_remove"
android:title="@string/menu_emulation_amiibo_remove" />
</menu>
</item>
<item
android:id="@+id/menu_emulation_switch_screen_layout"
app:showAsAction="never"
android:title="@string/emulation_switch_screen_layout">
<menu>
<group android:checkableBehavior="single">
<item
android:id="@+id/menu_screen_layout_landscape"
android:title="@string/emulation_screen_layout_landscape" />
<item
android:id="@+id/menu_screen_layout_portrait"
android:title="@string/emulation_screen_layout_portrait" />
<item
android:id="@+id/menu_screen_layout_single"
android:title="@string/emulation_screen_layout_single" />
<item
android:id="@+id/menu_screen_layout_sidebyside"
android:title="@string/emulation_screen_layout_sidebyside" />
<item
android:id="@+id/menu_screen_layout_hybrid"
android:title="@string/emulation_screen_layout_hybrid" />
</group>
</menu>
</item>
<item
android:id="@+id/menu_emulation_swap_screens"
app:showAsAction="never"
android:title="@string/emulation_swap_screens"
android:checkable="true" />
<item
android:id="@+id/menu_emulation_show_fps"
app:showAsAction="never"
android:title="@string/emulation_show_fps"
android:checkable="true" />
<item
android:id="@+id/menu_emulation_show_overlay"
app:showAsAction="never"
android:title="@string/emulation_show_overlay"
android:checkable="true" />
<item
android:id="@+id/menu_emulation_open_cheats"
app:showAsAction="never"
android:title="@string/emulation_open_cheats" />
<item
android:id="@+id/menu_emulation_open_settings"
app:showAsAction="never"
android:title="@string/emulation_open_settings" />
<item
android:id="@+id/menu_emulation_close_game"
app:showAsAction="never"
android:title="@string/emulation_close_game" />
</menu>

View File

@ -27,9 +27,15 @@
android:icon="@drawable/ic_fit_screen" android:icon="@drawable/ic_fit_screen"
android:title="@string/emulation_switch_screen_layout" /> android:title="@string/emulation_switch_screen_layout" />
<item
android:id="@+id/menu_portrait_screen_layout"
android:icon="@drawable/ic_portrait_fit_screen"
android:title="@string/emulation_switch_portrait_layout" />
<item <item
android:id="@+id/menu_swap_screens" android:id="@+id/menu_swap_screens"
android:icon="@drawable/ic_splitscreen" android:icon="@drawable/ic_splitscreen"
android:title="@string/emulation_swap_screens" /> android:title="@string/emulation_swap_screens" />
<item <item

View File

@ -7,10 +7,6 @@
android:id="@+id/menu_screen_layout_landscape" android:id="@+id/menu_screen_layout_landscape"
android:title="@string/emulation_screen_layout_landscape" /> android:title="@string/emulation_screen_layout_landscape" />
<item
android:id="@+id/menu_screen_layout_portrait"
android:title="@string/emulation_screen_layout_portrait" />
<item <item
android:id="@+id/menu_screen_layout_single" android:id="@+id/menu_screen_layout_single"
android:title="@string/emulation_screen_layout_single" /> android:title="@string/emulation_screen_layout_single" />
@ -23,6 +19,14 @@
android:id="@+id/menu_screen_layout_hybrid" android:id="@+id/menu_screen_layout_hybrid"
android:title="@string/emulation_screen_layout_hybrid" /> android:title="@string/emulation_screen_layout_hybrid" />
<item
android:id="@+id/menu_screen_layout_original"
android:title="@string/emulation_screen_layout_original" />
<item
android:id="@+id/menu_screen_layout_custom"
android:title="@string/emulation_screen_layout_custom" />
</group> </group>
</menu> </menu>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/menu_portrait_layout_top_full"
android:title="@string/emulation_portrait_layout_top_full" />
<item
android:id="@+id/menu_portrait_layout_custom"
android:title="@string/emulation_screen_layout_custom" />
</group>
</menu>

View File

@ -11,6 +11,37 @@
<item>1</item> <item>1</item>
</integer-array> </integer-array>
<string-array name="landscapeLayouts">
<item>@string/emulation_screen_layout_landscape</item>
<item>@string/emulation_screen_layout_single</item>
<item>@string/emulation_screen_layout_sidebyside</item>
<item>@string/emulation_screen_layout_hybrid</item>
<item>@string/emulation_screen_layout_original</item>
<item>@string/emulation_screen_layout_custom</item>
</string-array>
<!-- start with 6 because that is the MobileLandscape layout in cpp files
- skip 0 because top/bottom rarely makes sense in landscape
- skip 2 because that is "Large Screen" which the default replaces in mobile
-->
<integer-array name="landscapeLayoutValues">
<item>6</item>
<item>1</item>
<item>3</item>
<item>4</item>
<item>0</item>
<item>5</item>
</integer-array>
<string-array name="portraitLayouts">
<item>@string/emulation_portrait_layout_top_full</item>
<item>@string/emulation_screen_layout_custom</item>
</string-array>
<integer-array name="portraitLayoutValues">
<item>0</item>
<item>1</item>
</integer-array>
<string-array name="regionNames"> <string-array name="regionNames">
<item>@string/auto_select</item> <item>@string/auto_select</item>
<item>@string/system_region_jpn</item> <item>@string/system_region_jpn</item>

View File

@ -330,7 +330,7 @@
<string name="preferences_audio">Audio</string> <string name="preferences_audio">Audio</string>
<string name="preferences_debug">Debug</string> <string name="preferences_debug">Debug</string>
<string name="preferences_theme">Theme and Color</string> <string name="preferences_theme">Theme and Color</string>
<string name="preferences_layout">Layout</string>
<!-- ROM loading errors --> <!-- ROM loading errors -->
<string name="loader_error_encrypted">Your ROM is Encrypted</string> <string name="loader_error_encrypted">Your ROM is Encrypted</string>
<string name="loader_error_invalid_format">Invalid ROM format</string> <string name="loader_error_invalid_format">Invalid ROM format</string>
@ -359,12 +359,25 @@
<string name="emulation_open_settings">Open Settings</string> <string name="emulation_open_settings">Open Settings</string>
<string name="emulation_open_cheats">Open Cheats</string> <string name="emulation_open_cheats">Open Cheats</string>
<string name="emulation_switch_screen_layout">Landscape Screen Layout</string> <string name="emulation_switch_screen_layout">Landscape Screen Layout</string>
<string name="emulation_switch_portrait_layout">Portrait Screen Layout</string>
<string name="emulation_screen_layout_landscape">Default</string> <string name="emulation_screen_layout_landscape">Default</string>
<string name="emulation_screen_layout_portrait">Portrait</string> <string name="emulation_screen_layout_portrait">Portrait</string>
<string name="emulation_screen_layout_single">Single Screen</string> <string name="emulation_screen_layout_single">Single Screen</string>
<string name="emulation_screen_layout_sidebyside">Side by Side Screens</string> <string name="emulation_screen_layout_sidebyside">Side by Side Screens</string>
<string name="emulation_screen_layout_hybrid">Hybrid Screens</string> <string name="emulation_screen_layout_hybrid">Hybrid Screens</string>
<string name="emulation_cycle_landscape_layouts">Cycle Landscape Layouts</string> <string name="emulation_screen_layout_original">Original</string>
<string name="emulation_portrait_layout_top_full">Default</string>
<string name="emulation_screen_layout_custom">Custom Layout</string>
<string name="emulation_adjust_custom_layout">Adjust Custom Layout in Settings</string>
<string name="emulation_landscape_custom_layout">Landscape Custom Layout</string>
<string name="emulation_portrait_custom_layout">Portrait Custom Layout</string>
<string name="emulation_top_screen">Top Screen</string>
<string name="emulation_bottom_screen">Bottom Screen</string>
<string name="emulation_custom_layout_x">X-Position</string>
<string name="emulation_custom_layout_y">Y-Position</string>
<string name="emulation_custom_layout_width">Width</string>
<string name="emulation_custom_layout_height">Height</string>
<string name="emulation_cycle_landscape_layouts">Cycle Layouts</string>
<string name="emulation_swap_screens">Swap Screens</string> <string name="emulation_swap_screens">Swap Screens</string>
<string name="emulation_touch_overlay_reset">Reset Overlay</string> <string name="emulation_touch_overlay_reset">Reset Overlay</string>
<string name="emulation_show_overlay">Show Overlay</string> <string name="emulation_show_overlay">Show Overlay</string>

View File

@ -1,7 +1,10 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
// Copyright Dolphin Emulator Project
// Licensed under GPLv2 or any later version
#include <array> #include <array>
#include <fstream> #include <fstream>
#include <limits> #include <limits>
@ -707,8 +710,8 @@ std::string AppDataRoamingDirectory() {
/** /**
* @return The users home directory on POSIX systems * @return The users home directory on POSIX systems
*/ */
static const std::string& GetHomeDirectory() { const std::string GetHomeDirectory() {
static std::string home_path; std::string home_path;
if (home_path.empty()) { if (home_path.empty()) {
const char* envvar = getenv("HOME"); const char* envvar = getenv("HOME");
if (envvar) { if (envvar) {

View File

@ -1,7 +1,10 @@
// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
// Copyright Dolphin Emulator Project
// Licensed under GPLv2 or any later version
#pragma once #pragma once
#include <array> #include <array>
@ -207,6 +210,7 @@ void UpdateUserPath(UserPath path, const std::string& filename);
[[nodiscard]] const std::string& GetExeDirectory(); [[nodiscard]] const std::string& GetExeDirectory();
[[nodiscard]] std::string AppDataRoamingDirectory(); [[nodiscard]] std::string AppDataRoamingDirectory();
#else #else
[[nodiscard]] const std::string GetHomeDirectory();
[[nodiscard]] const std::string GetUserDirectory(const std::string& envvar); [[nodiscard]] const std::string GetUserDirectory(const std::string& envvar);
#endif #endif

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -108,6 +108,7 @@ void LogSettings() {
log_setting("Renderer_AnaglyphShader", values.anaglyph_shader_name.GetValue()); log_setting("Renderer_AnaglyphShader", values.anaglyph_shader_name.GetValue());
} }
log_setting("Layout_LayoutOption", values.layout_option.GetValue()); log_setting("Layout_LayoutOption", values.layout_option.GetValue());
log_setting("Layout_PortraitLayoutOption", values.portrait_layout_option.GetValue());
log_setting("Layout_SwapScreen", values.swap_screen.GetValue()); log_setting("Layout_SwapScreen", values.swap_screen.GetValue());
log_setting("Layout_UprightScreen", values.upright_screen.GetValue()); log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue()); log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue());
@ -196,6 +197,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.texture_filter.SetGlobal(true); values.texture_filter.SetGlobal(true);
values.texture_sampling.SetGlobal(true); values.texture_sampling.SetGlobal(true);
values.layout_option.SetGlobal(true); values.layout_option.SetGlobal(true);
values.portrait_layout_option.SetGlobal(true);
values.swap_screen.SetGlobal(true); values.swap_screen.SetGlobal(true);
values.upright_screen.SetGlobal(true); values.upright_screen.SetGlobal(true);
values.large_screen_proportion.SetGlobal(true); values.large_screen_proportion.SetGlobal(true);

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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,6 +33,7 @@ enum class InitTicks : u32 {
Fixed = 1, Fixed = 1,
}; };
/** Defines the layout option for desktop and mobile landscape */
enum class LayoutOption : u32 { enum class LayoutOption : u32 {
Default, Default,
SingleScreen, SingleScreen,
@ -42,18 +43,20 @@ enum class LayoutOption : u32 {
SeparateWindows, SeparateWindows,
#endif #endif
HybridScreen, HybridScreen,
#ifndef ANDROID // TODO: Implement custom layouts on Android
CustomLayout, CustomLayout,
#endif
// Similiar to default, but better for mobile devices in portrait mode. Top screen in clamped to
// the top of the frame, and the bottom screen is enlarged to match the top screen.
MobilePortrait,
// Similiar to LargeScreen, but better for mobile devices in landscape mode. The screens are // Similiar to LargeScreen, but better for mobile devices in landscape mode. The screens are
// clamped to the top of the frame, and the bottom screen is a bit bigger. // clamped to the top of the frame, and the bottom screen is a bit bigger.
MobileLandscape, MobileLandscape,
}; };
/** Defines the layout option for mobile portrait */
enum class PortraitLayoutOption : u32 {
// formerly mobile portrait
PortraitTopFullWidth,
PortraitCustomLayout,
};
enum class StereoRenderOption : u32 { enum class StereoRenderOption : u32 {
Off = 0, Off = 0,
SideBySide = 1, SideBySide = 1,
@ -482,21 +485,19 @@ struct Values {
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"}; SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"};
SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled, SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled,
"texture_sampling"}; "texture_sampling"};
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"}; SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
SwitchableSetting<bool> swap_screen{false, "swap_screen"}; SwitchableSetting<bool> swap_screen{false, "swap_screen"};
SwitchableSetting<bool> upright_screen{false, "upright_screen"}; SwitchableSetting<bool> upright_screen{false, "upright_screen"};
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f, SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
"large_screen_proportion"}; "large_screen_proportion"};
Setting<bool> custom_layout{false, "custom_layout"};
Setting<u16> custom_top_x{0, "custom_top_x"}; Setting<u16> custom_top_x{0, "custom_top_x"};
Setting<u16> custom_top_y{0, "custom_top_y"}; Setting<u16> custom_top_y{0, "custom_top_y"};
Setting<u16> custom_top_width{400, "custom_top_width"}; Setting<u16> custom_top_width{800, "custom_top_width"};
Setting<u16> custom_top_height{240, "custom_top_height"}; Setting<u16> custom_top_height{480, "custom_top_height"};
Setting<u16> custom_bottom_x{40, "custom_bottom_x"}; Setting<u16> custom_bottom_x{80, "custom_bottom_x"};
Setting<u16> custom_bottom_y{240, "custom_bottom_y"}; Setting<u16> custom_bottom_y{500, "custom_bottom_y"};
Setting<u16> custom_bottom_width{320, "custom_bottom_width"}; Setting<u16> custom_bottom_width{640, "custom_bottom_width"};
Setting<u16> custom_bottom_height{240, "custom_bottom_height"}; Setting<u16> custom_bottom_height{480, "custom_bottom_height"};
Setting<u16> custom_second_layer_opacity{100, "custom_second_layer_opacity"}; Setting<u16> custom_second_layer_opacity{100, "custom_second_layer_opacity"};
SwitchableSetting<bool> screen_top_stretch{false, "screen_top_stretch"}; SwitchableSetting<bool> screen_top_stretch{false, "screen_top_stretch"};
@ -506,6 +507,17 @@ struct Values {
Setting<u16> screen_bottom_leftright_padding{0, "screen_bottom_leftright_padding"}; Setting<u16> screen_bottom_leftright_padding{0, "screen_bottom_leftright_padding"};
Setting<u16> screen_bottom_topbottom_padding{0, "screen_bottom_topbottom_padding"}; Setting<u16> screen_bottom_topbottom_padding{0, "screen_bottom_topbottom_padding"};
SwitchableSetting<PortraitLayoutOption> portrait_layout_option{
PortraitLayoutOption::PortraitTopFullWidth, "portrait_layout_option"};
Setting<u16> custom_portrait_top_x{0, "custom_portrait_top_x"};
Setting<u16> custom_portrait_top_y{0, "custom_portrait_top_y"};
Setting<u16> custom_portrait_top_width{800, "custom_portrait_top_width"};
Setting<u16> custom_portrait_top_height{480, "custom_portrait_top_height"};
Setting<u16> custom_portrait_bottom_x{80, "custom_portrait_bottom_x"};
Setting<u16> custom_portrait_bottom_y{500, "custom_portrait_bottom_y"};
Setting<u16> custom_portrait_bottom_width{640, "custom_portrait_bottom_width"};
Setting<u16> custom_portrait_bottom_height{480, "custom_portrait_bottom_height"};
SwitchableSetting<float> bg_red{0.f, "bg_red"}; SwitchableSetting<float> bg_red{0.f, "bg_red"};
SwitchableSetting<float> bg_green{0.f, "bg_green"}; SwitchableSetting<float> bg_green{0.f, "bg_green"};
SwitchableSetting<float> bg_blue{0.f, "bg_blue"}; SwitchableSetting<float> bg_blue{0.f, "bg_blue"};

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -176,20 +176,33 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_portrait_mode) { void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_portrait_mode) {
Layout::FramebufferLayout layout; Layout::FramebufferLayout layout;
// If in portrait mode, only the MobilePortrait option really makes sense const Settings::LayoutOption layout_option = Settings::values.layout_option.GetValue();
const Settings::LayoutOption layout_option = is_portrait_mode const Settings::PortraitLayoutOption portrait_layout_option =
? Settings::LayoutOption::MobilePortrait Settings::values.portrait_layout_option.GetValue();
: Settings::values.layout_option.GetValue(); const auto min_size = is_portrait_mode
const auto min_size = ? Layout::GetMinimumSizeFromPortraitLayout()
Layout::GetMinimumSizeFromLayout(layout_option, Settings::values.upright_screen.GetValue()); : Layout::GetMinimumSizeFromLayout(
layout_option, Settings::values.upright_screen.GetValue());
if (Settings::values.custom_layout.GetValue() == true) {
layout = Layout::CustomFrameLayout(width, height, Settings::values.swap_screen.GetValue());
} else {
width = std::max(width, min_size.first); width = std::max(width, min_size.first);
height = std::max(height, min_size.second); height = std::max(height, min_size.second);
if (is_portrait_mode) {
switch (portrait_layout_option) {
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
layout = Layout::PortraitTopFullFrameLayout(width, height,
Settings::values.swap_screen.GetValue());
break;
case Settings::PortraitLayoutOption::PortraitCustomLayout:
layout = Layout::CustomFrameLayout(
width, height, Settings::values.swap_screen.GetValue(), is_portrait_mode);
break;
}
} else {
switch (layout_option) { switch (layout_option) {
case Settings::LayoutOption::CustomLayout:
layout = Layout::CustomFrameLayout(
width, height, Settings::values.swap_screen.GetValue(), is_portrait_mode);
break;
case Settings::LayoutOption::SingleScreen: case Settings::LayoutOption::SingleScreen:
layout = layout =
Layout::SingleFrameLayout(width, height, Settings::values.swap_screen.GetValue(), Layout::SingleFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
@ -219,21 +232,12 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
Settings::values.upright_screen.GetValue()); Settings::values.upright_screen.GetValue());
break; break;
#endif #endif
case Settings::LayoutOption::MobilePortrait:
layout = Layout::MobilePortraitFrameLayout(width, height,
Settings::values.swap_screen.GetValue());
break;
case Settings::LayoutOption::MobileLandscape: case Settings::LayoutOption::MobileLandscape:
layout = layout =
Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(), Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
false, 2.25f, Layout::VerticalAlignment::Top); false, 2.25f, Layout::VerticalAlignment::Top);
break; break;
#ifndef ANDROID // TODO: Implement custom layouts on Android
case Settings::LayoutOption::CustomLayout:
layout =
Layout::CustomFrameLayout(width, height, Settings::values.swap_screen.GetValue());
break;
#endif
case Settings::LayoutOption::Default: case Settings::LayoutOption::Default:
default: default:
layout = layout =
@ -241,8 +245,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
Settings::values.upright_screen.GetValue()); Settings::values.upright_screen.GetValue());
break; break;
} }
UpdateMinimumWindowSize(min_size);
} }
UpdateMinimumWindowSize(min_size);
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) { if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
layout = Layout::GetCardboardSettings(layout); layout = Layout::GetCardboardSettings(layout);
} }

View File

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -117,11 +117,11 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool u
return res; return res;
} }
FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool swapped) { FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool swapped) {
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}}; FramebufferLayout res{width, height, true, true, {}, {}, true, true};
// Default layout gives equal screen sizes to the top and bottom screen // Default layout gives equal screen sizes to the top and bottom screen
Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2}; Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2};
Common::Rectangle<u32> top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO); Common::Rectangle<u32> top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
@ -305,7 +305,7 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}, !upright, true, {}}; FramebufferLayout res{width, height, true, true, {}, {}, !upright, false, true, {}};
// Split the window into two parts. Give 2.25x width to the main screen, // Split the window into two parts. Give 2.25x width to the main screen,
// and make a bar on the right side with 1x width top screen and 1.25x width bottom screen // and make a bar on the right side with 1x width top screen and 1.25x width bottom screen
@ -382,24 +382,33 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary
return SingleFrameLayout(width, height, is_secondary, upright); return SingleFrameLayout(width, height, is_secondary, upright);
} }
FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped) { FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool is_portrait_mode) {
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}, !Settings::values.upright_screen}; FramebufferLayout res{
width, height, true, true, {}, {}, !Settings::values.upright_screen, is_portrait_mode};
u16 top_x = is_portrait_mode ? Settings::values.custom_portrait_top_x.GetValue()
: Settings::values.custom_top_x.GetValue();
u16 top_width = is_portrait_mode ? Settings::values.custom_portrait_top_width.GetValue()
: Settings::values.custom_top_width.GetValue();
u16 top_y = is_portrait_mode ? Settings::values.custom_portrait_top_y.GetValue()
: Settings::values.custom_top_y.GetValue();
u16 top_height = is_portrait_mode ? Settings::values.custom_portrait_top_height.GetValue()
: Settings::values.custom_top_height.GetValue();
u16 bottom_x = is_portrait_mode ? Settings::values.custom_portrait_bottom_x.GetValue()
: Settings::values.custom_bottom_x.GetValue();
u16 bottom_width = is_portrait_mode ? Settings::values.custom_portrait_bottom_width.GetValue()
: Settings::values.custom_bottom_width.GetValue();
u16 bottom_y = is_portrait_mode ? Settings::values.custom_portrait_bottom_y.GetValue()
: Settings::values.custom_bottom_y.GetValue();
u16 bottom_height = is_portrait_mode ? Settings::values.custom_portrait_bottom_height.GetValue()
: Settings::values.custom_bottom_height.GetValue();
Common::Rectangle<u32> top_screen{Settings::values.custom_top_x.GetValue(), Common::Rectangle<u32> top_screen{top_x, top_y, (u32)(top_x + top_width),
Settings::values.custom_top_y.GetValue(), (u32)(top_y + top_height)};
(u32)(Settings::values.custom_top_x.GetValue() + Common::Rectangle<u32> bot_screen{bottom_x, bottom_y, (u32)(bottom_x + bottom_width),
Settings::values.custom_top_width.GetValue()), (u32)(bottom_y + bottom_height)};
(u32)(Settings::values.custom_top_y.GetValue() +
Settings::values.custom_top_height.GetValue())};
Common::Rectangle<u32> bot_screen{Settings::values.custom_bottom_x.GetValue(),
Settings::values.custom_bottom_y.GetValue(),
(u32)(Settings::values.custom_bottom_x.GetValue() +
Settings::values.custom_bottom_width.GetValue()),
(u32)(Settings::values.custom_bottom_y.GetValue() +
Settings::values.custom_bottom_height.GetValue())};
if (is_swapped) { if (is_swapped) {
res.top_screen = bot_screen; res.top_screen = bot_screen;
@ -411,17 +420,43 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped) {
return res; return res;
} }
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary) { FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary,
if (Settings::values.custom_layout.GetValue() == true) { bool is_portrait) {
return CustomFrameLayout(std::max(Settings::values.custom_top_width.GetValue(), int width, height;
Settings::values.custom_bottom_width.GetValue()), if (is_portrait) {
std::max(Settings::values.custom_top_height.GetValue(), auto layout_option = Settings::values.portrait_layout_option.GetValue();
Settings::values.custom_bottom_height.GetValue()), switch (layout_option) {
case Settings::PortraitLayoutOption::PortraitCustomLayout:
return CustomFrameLayout(
std::max(Settings::values.custom_portrait_top_x.GetValue() +
Settings::values.custom_portrait_top_width.GetValue(),
Settings::values.custom_portrait_bottom_x.GetValue() +
Settings::values.custom_portrait_bottom_width.GetValue()),
std::max(Settings::values.custom_portrait_top_y.GetValue() +
Settings::values.custom_portrait_top_height.GetValue(),
Settings::values.custom_portrait_bottom_y.GetValue() +
Settings::values.custom_portrait_bottom_height.GetValue()),
Settings::values.swap_screen.GetValue(), is_portrait);
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
return PortraitTopFullFrameLayout(width, height,
Settings::values.swap_screen.GetValue()); Settings::values.swap_screen.GetValue());
} }
} else {
auto layout_option = Settings::values.layout_option.GetValue();
switch (layout_option) {
case Settings::LayoutOption::CustomLayout:
return CustomFrameLayout(std::max(Settings::values.custom_top_x.GetValue() +
Settings::values.custom_top_width.GetValue(),
Settings::values.custom_bottom_x.GetValue() +
Settings::values.custom_bottom_width.GetValue()),
std::max(Settings::values.custom_top_y.GetValue() +
Settings::values.custom_top_height.GetValue(),
Settings::values.custom_bottom_y.GetValue() +
Settings::values.custom_bottom_height.GetValue()),
Settings::values.swap_screen.GetValue(), is_portrait);
int width, height;
switch (Settings::values.layout_option.GetValue()) {
case Settings::LayoutOption::SingleScreen: case Settings::LayoutOption::SingleScreen:
#ifndef ANDROID #ifndef ANDROID
case Settings::LayoutOption::SeparateWindows: case Settings::LayoutOption::SeparateWindows:
@ -444,13 +479,15 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
case Settings::LayoutOption::LargeScreen: case Settings::LayoutOption::LargeScreen:
if (Settings::values.swap_screen.GetValue()) { if (Settings::values.swap_screen.GetValue()) {
width = (Core::kScreenBottomWidth + width =
(Core::kScreenBottomWidth +
Core::kScreenTopWidth / Core::kScreenTopWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) * static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale; res_scale;
height = Core::kScreenBottomHeight * res_scale; height = Core::kScreenBottomHeight * res_scale;
} else { } else {
width = (Core::kScreenTopWidth + width =
(Core::kScreenTopWidth +
Core::kScreenBottomWidth / Core::kScreenBottomWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) * static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale; res_scale;
@ -475,11 +512,6 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
Settings::values.upright_screen.GetValue(), 1, Settings::values.upright_screen.GetValue(), 1,
VerticalAlignment::Middle); VerticalAlignment::Middle);
case Settings::LayoutOption::MobilePortrait:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
return MobilePortraitFrameLayout(width, height, Settings::values.swap_screen.GetValue());
case Settings::LayoutOption::MobileLandscape: { case Settings::LayoutOption::MobileLandscape: {
constexpr float large_screen_proportion = 2.25f; constexpr float large_screen_proportion = 2.25f;
if (Settings::values.swap_screen.GetValue()) { if (Settings::values.swap_screen.GetValue()) {
@ -508,6 +540,7 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
return DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(), return DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue()); Settings::values.upright_screen.GetValue());
} }
}
UNREACHABLE(); UNREACHABLE();
} }
@ -529,32 +562,9 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
u32 cardboard_screen_width; u32 cardboard_screen_width;
u32 cardboard_screen_height; u32 cardboard_screen_height;
switch (Settings::values.layout_option.GetValue()) { if (is_portrait) {
case Settings::LayoutOption::MobileLandscape: switch (Settings::values.portrait_layout_option.GetValue()) {
case Settings::LayoutOption::SideScreen: case Settings::PortraitLayoutOption::PortraitTopFullWidth:
// If orientation is portrait, only use MobilePortrait
if (!is_portrait) {
cardboard_screen_width = top_screen_width + bottom_screen_width;
cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height;
if (is_swapped)
top_screen_left += bottom_screen_width;
else
bottom_screen_left += top_screen_width;
break;
} else {
[[fallthrough]];
}
case Settings::LayoutOption::SingleScreen:
default:
if (!is_portrait) {
// Default values when using LayoutOption::SingleScreen
cardboard_screen_width = is_swapped ? bottom_screen_width : top_screen_width;
cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height;
break;
} else {
[[fallthrough]];
}
case Settings::LayoutOption::MobilePortrait:
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;
@ -563,6 +573,31 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
else else
bottom_screen_top += top_screen_height; bottom_screen_top += top_screen_height;
break; break;
default:
cardboard_screen_width = is_swapped ? bottom_screen_width : top_screen_width;
cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height;
}
} else {
switch (Settings::values.layout_option.GetValue()) {
case Settings::LayoutOption::MobileLandscape:
case Settings::LayoutOption::SideScreen:
// If orientation is portrait, only use MobilePortrait
cardboard_screen_width = top_screen_width + bottom_screen_width;
cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height;
if (is_swapped)
top_screen_left += bottom_screen_width;
else
bottom_screen_left += top_screen_width;
break;
case Settings::LayoutOption::SingleScreen:
default:
cardboard_screen_width = is_swapped ? bottom_screen_width : top_screen_width;
cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height;
break;
}
} }
s32 cardboard_max_x_shift = (layout.width / 2 - cardboard_screen_width) / 2; s32 cardboard_max_x_shift = (layout.width / 2 - cardboard_screen_width) / 2;
s32 cardboard_user_x_shift = s32 cardboard_user_x_shift =
@ -596,6 +631,13 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
return new_layout; return new_layout;
} }
std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout() {
u32 min_width, min_height;
min_width = Core::kScreenTopWidth;
min_height = Core::kScreenTopHeight + Core::kScreenBottomHeight;
return std::make_pair(min_width, min_height);
}
std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout, std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout,
bool upright_screen) { bool upright_screen) {
u32 min_width, min_height; u32 min_width, min_height;

View File

@ -1,4 +1,4 @@
// Copyright 2016 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -61,7 +61,7 @@ struct FramebufferLayout {
Common::Rectangle<u32> top_screen; Common::Rectangle<u32> top_screen;
Common::Rectangle<u32> bottom_screen; Common::Rectangle<u32> bottom_screen;
bool is_rotated = true; bool is_rotated = true;
bool is_portrait = false;
bool additional_screen_enabled; bool additional_screen_enabled;
Common::Rectangle<u32> additional_screen; Common::Rectangle<u32> additional_screen;
@ -85,13 +85,14 @@ struct FramebufferLayout {
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 a mobile portrait FramebufferLayout * Factory method for constructing the mobile Full Width Top layout
* Two screens at top, full width, no gap between them
* @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
* @return Newly created FramebufferLayout object with mobile portrait screen regions initialized * @return Newly created FramebufferLayout object with mobile portrait screen regions initialized
*/ */
FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool is_swapped); FramebufferLayout PortraitTopFullFrameLayout(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
@ -145,14 +146,17 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary
* @param height Window framebuffer height in pixels * @param height Window framebuffer height in pixels
* @return Newly created FramebufferLayout object with default screen regions initialized * @return Newly created FramebufferLayout object with default screen regions initialized
*/ */
FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped); FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped,
bool is_portrait_mode = false);
/** /**
* Convenience method to get frame layout by resolution scale * Convenience method to get frame layout by resolution scale
* Read from the current settings to determine which layout to use. * Read from the current settings to determine which layout to use.
* @param res_scale resolution scale factor * @param res_scale resolution scale factor
* @param is_portrait_mode defaults to false
*/ */
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary = false); FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary = false,
bool is_portrait_mode = false);
/** /**
* Convenience method for transforming a frame layout when using Cardboard VR * Convenience method for transforming a frame layout when using Cardboard VR
@ -164,4 +168,6 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout);
std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout, std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout,
bool upright_screen); bool upright_screen);
std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout();
} // namespace Layout } // namespace Layout

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -164,8 +164,6 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.swap_screen); ReadSetting("Layout", Settings::values.swap_screen);
ReadSetting("Layout", Settings::values.upright_screen); ReadSetting("Layout", Settings::values.upright_screen);
ReadSetting("Layout", Settings::values.large_screen_proportion); ReadSetting("Layout", Settings::values.large_screen_proportion);
ReadSetting("Layout", Settings::values.custom_layout);
ReadSetting("Layout", Settings::values.custom_top_x); ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y); ReadSetting("Layout", Settings::values.custom_top_y);
ReadSetting("Layout", Settings::values.custom_top_width); ReadSetting("Layout", Settings::values.custom_top_width);
@ -183,6 +181,16 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.screen_bottom_leftright_padding); ReadSetting("Layout", Settings::values.screen_bottom_leftright_padding);
ReadSetting("Layout", Settings::values.screen_bottom_topbottom_padding); ReadSetting("Layout", Settings::values.screen_bottom_topbottom_padding);
ReadSetting("Layout", Settings::values.portrait_layout_option);
ReadSetting("Layout", Settings::values.custom_portrait_top_x);
ReadSetting("Layout", Settings::values.custom_portrait_top_y);
ReadSetting("Layout", Settings::values.custom_portrait_top_width);
ReadSetting("Layout", Settings::values.custom_portrait_top_height);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_x);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_y);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_width);
ReadSetting("Layout", Settings::values.custom_portrait_bottom_height);
// Utility // Utility
ReadSetting("Utility", Settings::values.dump_textures); ReadSetting("Utility", Settings::values.dump_textures);
ReadSetting("Utility", Settings::values.custom_textures); ReadSetting("Utility", Settings::values.custom_textures);

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -182,7 +182,7 @@ filter_mode =
[Layout] [Layout]
# Layout for the screen inside the render window. # Layout for the screen inside the render window.
# 0 (default): Default Top Bottom Screen # 0 (default): Default Above/Below Screen
# 1: Single Screen Only # 1: Single Screen Only
# 2: Large Screen Small Screen # 2: Large Screen Small Screen
# 3: Side by Side # 3: Side by Side
@ -191,10 +191,6 @@ filter_mode =
# 6: Custom Layout # 6: Custom Layout
layout_option = layout_option =
# Toggle custom layout (using the settings below) on or off.
# 0 (default): Off, 1: On
custom_layout =
# Screen placement when using Custom layout option # Screen placement when using Custom layout option
# 0x, 0y is the top left corner of the render window. # 0x, 0y is the top left corner of the render window.
custom_top_x = custom_top_x =

View File

@ -415,7 +415,7 @@ int main(int argc, char** argv) {
return -1; return -1;
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted: case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted:
LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before " LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before "
"being used with Lime. \n\n For more information on dumping and " "being used with Lime3DS. \n\n For more information on dumping and "
"decrypting games, please refer to: " "decrypting games, please refer to: "
"https://web.archive.org/web/20240304210021/https://citra-emu.org/" "https://web.archive.org/web/20240304210021/https://citra-emu.org/"
"wiki/dumping-game-cartridges/"); "wiki/dumping-game-cartridges/");

View File

@ -28,9 +28,6 @@ add_executable(lime-qt
camera/qt_multimedia_camera.cpp camera/qt_multimedia_camera.cpp
camera/qt_multimedia_camera.h camera/qt_multimedia_camera.h
lime-qt.rc lime-qt.rc
compatdb.cpp
compatdb.h
compatdb.ui
configuration/config.cpp configuration/config.cpp
configuration/config.h configuration/config.h
configuration/configure.ui configuration/configure.ui
@ -334,10 +331,6 @@ target_compile_definitions(lime-qt PRIVATE
-DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_TO_ASCII
) )
if (CITRA_ENABLE_COMPATIBILITY_REPORTING)
target_compile_definitions(lime-qt PRIVATE -DCITRA_ENABLE_COMPATIBILITY_REPORTING)
endif()
if (USE_DISCORD_PRESENCE) if (USE_DISCORD_PRESENCE)
target_sources(lime-qt PUBLIC target_sources(lime-qt PUBLIC
discord_impl.cpp discord_impl.cpp

View File

@ -1,81 +0,0 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QButtonGroup>
#include <QMessageBox>
#include <QPushButton>
#include <QtConcurrent/qtconcurrentrun.h>
#include "core/core.h"
#include "lime_qt/compatdb.h"
#include "ui_compatdb.h"
CompatDB::CompatDB(QWidget* parent)
: QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui{std::make_unique<Ui::CompatDB>()} {
ui->setupUi(this);
connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);
connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this,
&CompatDB::OnTestcaseSubmitted);
}
CompatDB::~CompatDB() = default;
enum class CompatDBPage {
Intro = 0,
Selection = 1,
Final = 2,
};
void CompatDB::Submit() {
QButtonGroup* compatibility = new QButtonGroup(this);
compatibility->addButton(ui->radioButton_Perfect, 0);
compatibility->addButton(ui->radioButton_Great, 1);
compatibility->addButton(ui->radioButton_Okay, 2);
compatibility->addButton(ui->radioButton_Bad, 3);
compatibility->addButton(ui->radioButton_IntroMenu, 4);
compatibility->addButton(ui->radioButton_WontBoot, 5);
switch ((static_cast<CompatDBPage>(currentId()))) {
case CompatDBPage::Selection:
if (compatibility->checkedId() == -1) {
button(NextButton)->setEnabled(false);
}
break;
case CompatDBPage::Final:
back();
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
button(NextButton)->setEnabled(false);
button(NextButton)->setText(tr("Submitting"));
button(CancelButton)->setVisible(false);
break;
default:
LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
}
}
void CompatDB::OnTestcaseSubmitted() {
if (!testcase_watcher.result()) {
QMessageBox::critical(this, tr("Communication error"),
tr("An error occurred while sending the Testcase"));
button(NextButton)->setEnabled(true);
button(NextButton)->setText(tr("Next"));
button(CancelButton)->setVisible(true);
} else {
next();
// older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
// workaround
button(CancelButton)->setVisible(false);
}
}
void CompatDB::EnableNext() {
button(NextButton)->setEnabled(true);
}

View File

@ -1,30 +0,0 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QFutureWatcher>
#include <QWizard>
namespace Ui {
class CompatDB;
}
class CompatDB : public QWizard {
Q_OBJECT
public:
explicit CompatDB(QWidget* parent = nullptr);
~CompatDB();
private:
QFutureWatcher<bool> testcase_watcher;
std::unique_ptr<Ui::CompatDB> ui;
void Submit();
void OnTestcaseSubmitted();
void EnableNext();
};

View File

@ -1,215 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CompatDB</class>
<widget class="QWizard" name="CompatDB">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>482</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>410</height>
</size>
</property>
<property name="windowTitle">
<string>Report Compatibility</string>
</property>
<property name="options">
<set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set>
</property>
<widget class="QWizardPage" name="wizard_Info">
<property name="title">
<string>Report Game Compatibility</string>
</property>
<attribute name="pageId">
<string notr="true">0</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lbl_Spiel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://citra-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;Lime3DS Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of Lime3DS you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected Citra account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="wizard_Report">
<property name="title">
<string>Report Game Compatibility</string>
</property>
<attribute name="pageId">
<string notr="true">1</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="2" column="0">
<widget class="QRadioButton" name="radioButton_Perfect">
<property name="text">
<string>Perfect</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lbl_Perfect">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QRadioButton" name="radioButton_Great">
<property name="text">
<string>Great</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="lbl_Great">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QRadioButton" name="radioButton_Okay">
<property name="text">
<string>Okay</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="lbl_Okay">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QRadioButton" name="radioButton_Bad">
<property name="text">
<string>Bad</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="lbl_Bad">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QRadioButton" name="radioButton_IntroMenu">
<property name="text">
<string>Intro/Menu</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="lbl_IntroMenu">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QRadioButton" name="radioButton_WontBoot">
<property name="text">
<string>Won't Boot</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="lbl_WontBoot">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="lbl_Independent">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of Lime?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="wizard_ThankYou">
<property name="title">
<string>Thank you for your submission!</string>
</property>
<attribute name="pageId">
<string notr="true">2</string>
</attribute>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -520,8 +520,6 @@ void Config::ReadLayoutValues() {
if (global) { if (global) {
ReadBasicSetting(Settings::values.mono_render_option); ReadBasicSetting(Settings::values.mono_render_option);
ReadBasicSetting(Settings::values.custom_layout);
ReadBasicSetting(Settings::values.custom_top_x); ReadBasicSetting(Settings::values.custom_top_x);
ReadBasicSetting(Settings::values.custom_top_y); ReadBasicSetting(Settings::values.custom_top_y);
ReadBasicSetting(Settings::values.custom_top_width); ReadBasicSetting(Settings::values.custom_top_width);
@ -538,6 +536,15 @@ void Config::ReadLayoutValues() {
ReadBasicSetting(Settings::values.screen_bottom_stretch); ReadBasicSetting(Settings::values.screen_bottom_stretch);
ReadBasicSetting(Settings::values.screen_bottom_leftright_padding); ReadBasicSetting(Settings::values.screen_bottom_leftright_padding);
ReadBasicSetting(Settings::values.screen_bottom_topbottom_padding); ReadBasicSetting(Settings::values.screen_bottom_topbottom_padding);
ReadBasicSetting(Settings::values.portrait_layout_option);
ReadBasicSetting(Settings::values.custom_portrait_top_x);
ReadBasicSetting(Settings::values.custom_portrait_top_y);
ReadBasicSetting(Settings::values.custom_portrait_top_width);
ReadBasicSetting(Settings::values.custom_portrait_top_height);
ReadBasicSetting(Settings::values.custom_portrait_bottom_x);
ReadBasicSetting(Settings::values.custom_portrait_bottom_y);
ReadBasicSetting(Settings::values.custom_portrait_bottom_width);
ReadBasicSetting(Settings::values.custom_portrait_bottom_height);
} }
qt_config->endGroup(); qt_config->endGroup();
@ -1072,8 +1079,6 @@ void Config::SaveLayoutValues() {
if (global) { if (global) {
WriteBasicSetting(Settings::values.mono_render_option); WriteBasicSetting(Settings::values.mono_render_option);
WriteBasicSetting(Settings::values.custom_layout);
WriteBasicSetting(Settings::values.custom_top_x); WriteBasicSetting(Settings::values.custom_top_x);
WriteBasicSetting(Settings::values.custom_top_y); WriteBasicSetting(Settings::values.custom_top_y);
WriteBasicSetting(Settings::values.custom_top_width); WriteBasicSetting(Settings::values.custom_top_width);
@ -1090,6 +1095,14 @@ void Config::SaveLayoutValues() {
WriteBasicSetting(Settings::values.screen_bottom_stretch); WriteBasicSetting(Settings::values.screen_bottom_stretch);
WriteBasicSetting(Settings::values.screen_bottom_leftright_padding); WriteBasicSetting(Settings::values.screen_bottom_leftright_padding);
WriteBasicSetting(Settings::values.screen_bottom_topbottom_padding); WriteBasicSetting(Settings::values.screen_bottom_topbottom_padding);
WriteBasicSetting(Settings::values.custom_portrait_top_x);
WriteBasicSetting(Settings::values.custom_portrait_top_y);
WriteBasicSetting(Settings::values.custom_portrait_top_width);
WriteBasicSetting(Settings::values.custom_portrait_top_height);
WriteBasicSetting(Settings::values.custom_portrait_bottom_x);
WriteBasicSetting(Settings::values.custom_portrait_bottom_y);
WriteBasicSetting(Settings::values.custom_portrait_bottom_width);
WriteBasicSetting(Settings::values.custom_portrait_bottom_height);
} }
qt_config->endGroup(); qt_config->endGroup();

View File

@ -46,7 +46,6 @@
#include "lime_qt/bootmanager.h" #include "lime_qt/bootmanager.h"
#include "lime_qt/camera/qt_multimedia_camera.h" #include "lime_qt/camera/qt_multimedia_camera.h"
#include "lime_qt/camera/still_image_camera.h" #include "lime_qt/camera/still_image_camera.h"
#include "lime_qt/compatdb.h"
#include "lime_qt/compatibility_list.h" #include "lime_qt/compatibility_list.h"
#include "lime_qt/configuration/config.h" #include "lime_qt/configuration/config.h"
#include "lime_qt/configuration/configure_dialog.h" #include "lime_qt/configuration/configure_dialog.h"
@ -97,7 +96,6 @@
#include "core/savestate.h" #include "core/savestate.h"
#include "core/system_titles.h" #include "core/system_titles.h"
#include "input_common/main.h" #include "input_common/main.h"
#include "network/network_settings.h"
#include "ui_main.h" #include "ui_main.h"
#include "video_core/gpu.h" #include "video_core/gpu.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
@ -339,9 +337,6 @@ GMainWindow::~GMainWindow() {
} }
void GMainWindow::InitializeWidgets() { void GMainWindow::InitializeWidgets() {
#ifdef CITRA_ENABLE_COMPATIBILITY_REPORTING
ui->action_Report_Compatibility->setVisible(true);
#endif
render_window = new GRenderWindow(this, emu_thread.get(), system, false); render_window = new GRenderWindow(this, emu_thread.get(), system, false);
secondary_window = new GRenderWindow(this, emu_thread.get(), system, true); secondary_window = new GRenderWindow(this, emu_thread.get(), system, true);
render_window->hide(); render_window->hide();
@ -640,8 +635,6 @@ void GMainWindow::InitializeHotkeys() {
link_action_shortcut(ui->action_Screen_Layout_Swap_Screens, QStringLiteral("Swap Screens")); link_action_shortcut(ui->action_Screen_Layout_Swap_Screens, QStringLiteral("Swap Screens"));
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_Enable_Frame_Advancing,
QStringLiteral("Toggle Frame Advancing"));
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, QStringLiteral("Load from Newest Slot"));
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot")); link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
@ -897,7 +890,10 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Pause, &GMainWindow::OnPauseContinueGame); connect_menu(ui->action_Pause, &GMainWindow::OnPauseContinueGame);
connect_menu(ui->action_Stop, &GMainWindow::OnStopGame); connect_menu(ui->action_Stop, &GMainWindow::OnStopGame);
connect_menu(ui->action_Restart, [this] { BootGame(QString(game_path)); }); connect_menu(ui->action_Restart, [this] { BootGame(QString(game_path)); });
connect_menu(ui->action_Report_Compatibility, &GMainWindow::OnMenuReportCompatibility); connect_menu(ui->action_Report_Compatibility, []() {
QDesktopServices::openUrl(QUrl(QStringLiteral(
"https://github.com/Lime3DS/compatibility-list/blob/master/CONTRIBUTING.md")));
});
connect_menu(ui->action_Configure, &GMainWindow::OnConfigure); connect_menu(ui->action_Configure, &GMainWindow::OnConfigure);
connect_menu(ui->action_Configure_Current_Game, &GMainWindow::OnConfigurePerGame); connect_menu(ui->action_Configure_Current_Game, &GMainWindow::OnConfigurePerGame);
@ -937,16 +933,8 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Save_Movie, &GMainWindow::OnSaveMovie); connect_menu(ui->action_Save_Movie, &GMainWindow::OnSaveMovie);
connect_menu(ui->action_Movie_Read_Only_Mode, connect_menu(ui->action_Movie_Read_Only_Mode,
[this](bool checked) { movie.SetReadOnly(checked); }); [this](bool checked) { movie.SetReadOnly(checked); });
connect_menu(ui->action_Enable_Frame_Advancing, [this] {
if (emulation_running) {
system.frame_limiter.SetFrameAdvancing(ui->action_Enable_Frame_Advancing->isChecked());
ui->action_Advance_Frame->setEnabled(ui->action_Enable_Frame_Advancing->isChecked());
}
});
connect_menu(ui->action_Advance_Frame, [this] { connect_menu(ui->action_Advance_Frame, [this] {
if (emulation_running && system.frame_limiter.IsFrameAdvancing()) { if (emulation_running && system.frame_limiter.IsFrameAdvancing()) {
ui->action_Enable_Frame_Advancing->setChecked(true);
ui->action_Advance_Frame->setEnabled(true);
system.frame_limiter.AdvanceFrame(); system.frame_limiter.AdvanceFrame();
} }
}); });
@ -971,7 +959,8 @@ void GMainWindow::ConnectMenuEvents() {
} }
void GMainWindow::UpdateMenuState() { void GMainWindow::UpdateMenuState() {
const bool is_paused = !emu_thread || !emu_thread->IsRunning(); const bool is_paused =
!emu_thread || !emu_thread->IsRunning() || system.frame_limiter.IsFrameAdvancing();
const std::array running_actions{ const std::array running_actions{
ui->action_Stop, ui->action_Stop,
@ -989,6 +978,7 @@ void GMainWindow::UpdateMenuState() {
} }
ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused); ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
ui->action_Advance_Frame->setEnabled(emulation_running && is_paused);
if (emulation_running && is_paused) { if (emulation_running && is_paused) {
ui->action_Pause->setText(tr("&Continue")); ui->action_Pause->setText(tr("&Continue"));
@ -1408,12 +1398,7 @@ void GMainWindow::BootGame(const QString& filename) {
movie_playback_path.clear(); movie_playback_path.clear();
} }
if (ui->action_Enable_Frame_Advancing->isChecked()) {
ui->action_Advance_Frame->setEnabled(true);
system.frame_limiter.SetFrameAdvancing(true);
} else {
ui->action_Advance_Frame->setEnabled(false); ui->action_Advance_Frame->setEnabled(false);
}
if (video_dumping_on_start) { if (video_dumping_on_start) {
StartVideoDumping(video_dumping_path); StartVideoDumping(video_dumping_path);
@ -1770,9 +1755,9 @@ void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path, bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
const std::string& comment, const std::string& comment,
const std::filesystem::path& icon_path, const std::filesystem::path& icon_path,
const std::filesystem::path& command, const std::string& command, const std::string& arguments,
const std::string& arguments, const std::string& categories, const std::string& categories, const std::string& keywords,
const std::string& keywords, const std::string& name) try { const std::string& name, const bool& skip_tryexec) try {
#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD #if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD
std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop"); std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc); std::ofstream shortcut_stream(shortcut_path_full, std::ios::binary | std::ios::trunc);
@ -1791,8 +1776,10 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
if (std::filesystem::is_regular_file(icon_path)) { if (std::filesystem::is_regular_file(icon_path)) {
fmt::print(shortcut_stream, "Icon={}\n", icon_path.string()); fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
} }
fmt::print(shortcut_stream, "TryExec={}\n", command.string()); if (!skip_tryexec) {
fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments); fmt::print(shortcut_stream, "TryExec={}\n", command);
}
fmt::print(shortcut_stream, "Exec={} {}\n", command, arguments);
if (!categories.empty()) { if (!categories.empty()) {
fmt::print(shortcut_stream, "Categories={}\n", categories); fmt::print(shortcut_stream, "Categories={}\n", categories);
} }
@ -1823,7 +1810,7 @@ bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
LOG_ERROR(Frontend, "Failed to create IShellLinkW instance"); LOG_ERROR(Frontend, "Failed to create IShellLinkW instance");
return false; return false;
} }
hres = ps1->SetPath(command.c_str()); hres = ps1->SetPath(Common::UTF8ToUTF16W(command).data());
if (FAILED(hres)) { if (FAILED(hres)) {
LOG_ERROR(Frontend, "Failed to set path"); LOG_ERROR(Frontend, "Failed to set path");
return false; return false;
@ -1902,16 +1889,16 @@ bool GMainWindow::CreateShortcutMessagesGUI(QWidget* parent, int message,
bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
std::filesystem::path& out_icon_path) { std::filesystem::path& out_icon_path) {
// Get path to Citra icons directory & icon extension // Get path to Lime3DS icons directory & icon extension
std::string ico_extension = "png"; std::string ico_extension = "png";
#if defined(_WIN32) #if defined(_WIN32)
out_icon_path = FileUtil::GetUserPath(FileUtil::UserPath::IconsDir); out_icon_path = FileUtil::GetUserPath(FileUtil::UserPath::IconsDir);
ico_extension = "ico"; ico_extension = "ico";
#elif defined(__linux__) || defined(__FreeBSD__) #elif defined(__linux__) || defined(__FreeBSD__)
out_icon_path = FileUtil::GetUserDirectory("XDG_DATA_HOME") + "/icons/hicolor/256x256"; out_icon_path = FileUtil::GetUserDirectory("XDG_DATA_HOME") + "/icons/hicolor/256x256/";
#endif #endif
// Create icons directory if it doesn't exist // Create icons directory if it doesn't exist
if (!FileUtil::CreateDir(out_icon_path.string())) { if (!FileUtil::CreateFullPath(out_icon_path.string())) {
QMessageBox::critical( QMessageBox::critical(
this, tr("Create Icon"), this, tr("Create Icon"),
tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.") tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
@ -1922,19 +1909,27 @@ bool GMainWindow::MakeShortcutIcoPath(const u64 program_id, const std::string_vi
} }
// Create icon file path // Create icon file path
out_icon_path /= (program_id == 0 ? fmt::format("citra-{}.{}", game_file_name, ico_extension) out_icon_path /= (program_id == 0 ? fmt::format("lime-{}.{}", game_file_name, ico_extension)
: fmt::format("citra-{:016X}.{}", program_id, ico_extension)); : fmt::format("lime-{:016X}.{}", program_id, ico_extension));
return true; return true;
} }
void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path, void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target) { GameListShortcutTarget target) {
// Get path to citra executable std::string lime_command{};
bool skip_tryexec = false;
const char* env_flatpak_id = getenv("FLATPAK_ID");
if (env_flatpak_id) {
lime_command = fmt::format("flatpak run {}", env_flatpak_id);
skip_tryexec = true;
} else {
// Get path to Lime3DS executable
const QStringList args = QApplication::arguments(); const QStringList args = QApplication::arguments();
std::filesystem::path citra_command = args[0].toStdString(); lime_command = args[0].toStdString();
// If relative path, make it an absolute path // If relative path, make it an absolute path
if (citra_command.c_str()[0] == '.') { if (lime_command.c_str()[0] == '.') {
citra_command = FileUtil::GetCurrentDir().value_or("") + DIR_SEP + citra_command.string(); lime_command = FileUtil::GetCurrentDir().value_or("") + DIR_SEP + lime_command;
}
} }
// Shortcut path // Shortcut path
@ -1943,8 +1938,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
shortcut_path = shortcut_path =
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString(); QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
} else if (target == GameListShortcutTarget::Applications) { } else if (target == GameListShortcutTarget::Applications) {
shortcut_path = shortcut_path = GetApplicationsDirectory();
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
} }
// Icon path and title // Icon path and title
@ -1990,8 +1984,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
// Warn once if we are making a shortcut to a volatile AppImage // Warn once if we are making a shortcut to a volatile AppImage
const std::string appimage_ending = const std::string appimage_ending =
std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage"); std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
if (citra_command.string().ends_with(appimage_ending) && if (lime_command.ends_with(appimage_ending) && !UISettings::values.shortcut_already_warned) {
!UISettings::values.shortcut_already_warned) {
if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_APPIMAGE_VOLATILE_WARNING, if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_APPIMAGE_VOLATILE_WARNING,
qt_game_title)) { qt_game_title)) {
return; return;
@ -2004,12 +1997,12 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT, qt_game_title)) { if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_FULLSCREEN_PROMPT, qt_game_title)) {
arguments = "-f " + arguments; arguments = "-f " + arguments;
} }
const std::string comment = fmt::format("Start {:s} with the Citra Emulator", game_title); const std::string comment = fmt::format("Start {:s} with the Lime3DS Emulator", game_title);
const std::string categories = "Game;Emulator;Qt;"; const std::string categories = "Game;Emulator;Qt;";
const std::string keywords = "3ds;Nintendo;"; const std::string keywords = "3ds;Nintendo;";
if (CreateShortcutLink(shortcut_path, comment, out_icon_path, citra_command, arguments, if (CreateShortcutLink(shortcut_path, comment, out_icon_path, lime_command, arguments,
categories, keywords, game_title)) { categories, keywords, game_title, skip_tryexec)) {
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_SUCCESS, qt_game_title); CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_SUCCESS, qt_game_title);
return; return;
} }
@ -2200,7 +2193,7 @@ void GMainWindow::OnCIAInstallReport(Service::AM::InstallStatus status, QString
case Service::AM::InstallStatus::ErrorEncrypted: case Service::AM::InstallStatus::ErrorEncrypted:
QMessageBox::critical(this, tr("Encrypted File"), QMessageBox::critical(this, tr("Encrypted File"),
tr("%1 must be decrypted " tr("%1 must be decrypted "
"before being used with Lime. A real 3DS is required.") "before being used with Lime3DS. A real 3DS is required.")
.arg(filename)); .arg(filename));
break; break;
case Service::AM::InstallStatus::ErrorFileNotFound: case Service::AM::InstallStatus::ErrorFileNotFound:
@ -2293,6 +2286,7 @@ void GMainWindow::OnStartGame() {
PreventOSSleep(); PreventOSSleep();
emu_thread->SetRunning(true); emu_thread->SetRunning(true);
system.frame_limiter.SetFrameAdvancing(false);
graphics_api_button->setEnabled(false); graphics_api_button->setEnabled(false);
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus"); qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string"); qRegisterMetaType<std::string>("std::string");
@ -2322,7 +2316,7 @@ void GMainWindow::OnRestartGame() {
} }
void GMainWindow::OnPauseGame() { void GMainWindow::OnPauseGame() {
emu_thread->SetRunning(false); system.frame_limiter.SetFrameAdvancing(true);
qt_cameras->PauseCameras(); qt_cameras->PauseCameras();
play_time_manager->Stop(); play_time_manager->Stop();
@ -2337,7 +2331,7 @@ void GMainWindow::OnPauseGame() {
void GMainWindow::OnPauseContinueGame() { void GMainWindow::OnPauseContinueGame() {
if (emulation_running) { if (emulation_running) {
if (emu_thread->IsRunning()) { if (emu_thread->IsRunning() && !system.frame_limiter.IsFrameAdvancing()) {
OnPauseGame(); OnPauseGame();
} else { } else {
OnStartGame(); OnStartGame();
@ -2361,17 +2355,6 @@ void GMainWindow::OnLoadComplete() {
UpdateSecondaryWindowVisibility(); UpdateSecondaryWindowVisibility();
} }
void GMainWindow::OnMenuReportCompatibility() {
if (!NetSettings::values.citra_token.empty() && !NetSettings::values.citra_username.empty()) {
CompatDB compatdb{this};
compatdb.exec();
} else {
QMessageBox::critical(this, tr("Missing Citra Account"),
tr("You must link your Citra account to submit test cases."
"<br/>Go to Emulation &gt; Configure... &gt; Web to do so."));
}
}
void GMainWindow::ToggleFullscreen() { void GMainWindow::ToggleFullscreen() {
if (!emulation_running) { if (!emulation_running) {
return; return;

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -217,10 +217,10 @@ private:
bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name, bool MakeShortcutIcoPath(const u64 program_id, const std::string_view game_file_name,
std::filesystem::path& out_icon_path); std::filesystem::path& out_icon_path);
bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment, bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment,
const std::filesystem::path& icon_path, const std::filesystem::path& icon_path, const std::string& command,
const std::filesystem::path& command, const std::string& arguments, const std::string& arguments, const std::string& categories,
const std::string& categories, const std::string& keywords, const std::string& keywords, const std::string& name,
const std::string& name); const bool& skip_tryexec);
private slots: private slots:
void OnStartGame(); void OnStartGame();
@ -230,7 +230,6 @@ private slots:
void OnStopGame(); void OnStopGame();
void OnSaveState(); void OnSaveState();
void OnLoadState(); void OnLoadState();
void OnMenuReportCompatibility();
/// Called whenever a user selects a game in the game list widget. /// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path); void OnGameListLoadFile(QString game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target); void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);

View File

@ -182,15 +182,8 @@
<addaction name="action_Movie_Read_Only_Mode"/> <addaction name="action_Movie_Read_Only_Mode"/>
<addaction name="action_Save_Movie"/> <addaction name="action_Save_Movie"/>
</widget> </widget>
<widget class="QMenu" name="menu_Frame_Advance">
<property name="title">
<string>Frame Advance</string>
</property>
<addaction name="action_Enable_Frame_Advancing"/>
<addaction name="action_Advance_Frame"/> <addaction name="action_Advance_Frame"/>
</widget>
<addaction name="menu_Movie"/> <addaction name="menu_Movie"/>
<addaction name="menu_Frame_Advance"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/> <addaction name="action_Capture_Screenshot"/>
<addaction name="action_Dump_Video"/> <addaction name="action_Dump_Video"/>
@ -309,7 +302,7 @@
</action> </action>
<action name="action_About"> <action name="action_About">
<property name="text"> <property name="text">
<string>About Lime</string> <string>About Lime3DS</string>
</property> </property>
<property name="menuRole"> <property name="menuRole">
<enum>QAction::AboutRole</enum> <enum>QAction::AboutRole</enum>
@ -404,14 +397,6 @@
<string>Read-Only Mode</string> <string>Read-Only Mode</string>
</property> </property>
</action> </action>
<action name="action_Enable_Frame_Advancing">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Enable Frame Advancing</string>
</property>
</action>
<action name="action_Advance_Frame"> <action name="action_Advance_Frame">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
@ -575,15 +560,9 @@
</property> </property>
</action> </action>
<action name="action_Report_Compatibility"> <action name="action_Report_Compatibility">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>Report Compatibility</string> <string>Report Compatibility</string>
</property> </property>
<property name="visible">
<bool>false</bool>
</property>
</action> </action>
<action name="action_Restart"> <action name="action_Restart">
<property name="enabled"> <property name="enabled">

View File

@ -1,10 +1,13 @@
// Copyright 2015 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
#include <array> #include <array>
#include <cmath> #include <cmath>
#include <QPainter> #include <QPainter>
#include <QStandardPaths>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/loader/smdh.h" #include "core/loader/smdh.h"
#include "lime_qt/util/util.h" #include "lime_qt/util/util.h"
@ -160,3 +163,15 @@ bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image)
return false; return false;
#endif #endif
} }
const std::string GetApplicationsDirectory() {
// This alternate method is required for Flatpak compatibility as
// QStandardPaths::ApplicationsLocation returns a path inside the Flatpak data directory instead of
// $HOME/.local/share
#if defined(__linux__) || defined(__FreeBSD__)
return FileUtil::GetHomeDirectory() + DIR_SEP + ".local" + DIR_SEP + "share" + DIR_SEP +
"applications";
#else
return QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
#endif
}

View File

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -36,3 +36,8 @@ QPixmap GetQPixmapFromSMDH(const std::vector<u8>& smdh_data);
* @return bool If the operation succeeded * @return bool If the operation succeeded
*/ */
[[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image); [[nodiscard]] bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image);
/**
* @return The users applications directory
*/
[[nodiscard]] const std::string GetApplicationsDirectory();

View File

@ -647,10 +647,11 @@ typename T::Surface& RasterizerCache<T>::GetTextureCube(const TextureCubeConfig&
Surface& cube_surface = slot_surfaces[cube.surface_id]; Surface& cube_surface = slot_surfaces[cube.surface_id];
for (u32 i = 0; i < addresses.size(); i++) { for (u32 i = 0; i < addresses.size(); i++) {
if (!addresses[i]) { const SurfaceId& face_id = cube.face_ids[i];
if (!addresses[i] || !face_id) {
continue; continue;
} }
Surface& surface = slot_surfaces[cube.face_ids[i]]; Surface& surface = slot_surfaces[face_id];
if (cube.ticks[i] == surface.modification_tick) { if (cube.ticks[i] == surface.modification_tick) {
continue; continue;
} }

View File

@ -1,4 +1,4 @@
// Copyright 2022 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -675,12 +675,12 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
if (!Settings::values.swap_screen.GetValue()) { if (!Settings::values.swap_screen.GetValue()) {
DrawTopScreen(layout, top_screen); DrawTopScreen(layout, top_screen);
glUniform1i(uniform_layer, 0); glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity(); ApplySecondLayerOpacity(layout.is_portrait);
DrawBottomScreen(layout, bottom_screen); DrawBottomScreen(layout, bottom_screen);
} else { } else {
DrawBottomScreen(layout, bottom_screen); DrawBottomScreen(layout, bottom_screen);
glUniform1i(uniform_layer, 0); glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity(); ApplySecondLayerOpacity(layout.is_portrait);
DrawTopScreen(layout, top_screen); DrawTopScreen(layout, top_screen);
} }
@ -692,13 +692,14 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
DrawBottomScreen(layout, additional_screen); DrawBottomScreen(layout, additional_screen);
} }
} }
ResetSecondLayerOpacity(); ResetSecondLayerOpacity(layout.is_portrait);
} }
void RendererOpenGL::ApplySecondLayerOpacity() { void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) {
#ifndef ANDROID // TODO: Implement custom layouts on Android // TODO: Allow for second layer opacity in portrait mode android
if ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout ||
Settings::values.custom_layout) && if (!isPortrait &&
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) { Settings::values.custom_second_layer_opacity.GetValue() < 100) {
state.blend.src_rgb_func = GL_CONSTANT_ALPHA; state.blend.src_rgb_func = GL_CONSTANT_ALPHA;
state.blend.src_a_func = GL_CONSTANT_ALPHA; state.blend.src_a_func = GL_CONSTANT_ALPHA;
@ -706,13 +707,11 @@ void RendererOpenGL::ApplySecondLayerOpacity() {
state.blend.dst_rgb_func = GL_ONE_MINUS_CONSTANT_ALPHA; state.blend.dst_rgb_func = GL_ONE_MINUS_CONSTANT_ALPHA;
state.blend.color.alpha = Settings::values.custom_second_layer_opacity.GetValue() / 100.0f; state.blend.color.alpha = Settings::values.custom_second_layer_opacity.GetValue() / 100.0f;
} }
#endif
} }
void RendererOpenGL::ResetSecondLayerOpacity() { void RendererOpenGL::ResetSecondLayerOpacity(bool isPortrait) {
#ifndef ANDROID // TODO: Implement custom layouts on Android if (!isPortrait &&
if ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout || (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
Settings::values.custom_layout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) { Settings::values.custom_second_layer_opacity.GetValue() < 100) {
state.blend.src_rgb_func = GL_ONE; state.blend.src_rgb_func = GL_ONE;
state.blend.dst_rgb_func = GL_ZERO; state.blend.dst_rgb_func = GL_ZERO;
@ -720,7 +719,6 @@ void RendererOpenGL::ResetSecondLayerOpacity() {
state.blend.dst_a_func = GL_ZERO; state.blend.dst_a_func = GL_ZERO;
state.blend.color.alpha = 0.0f; state.blend.color.alpha = 0.0f;
} }
#endif
} }
void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout, void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,

View File

@ -1,4 +1,4 @@
// Copyright 2022 Citra Emulator Project // Copyright Citra Emulator Project / Lime3DS 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.
@ -65,8 +65,8 @@ private:
void ConfigureFramebufferTexture(TextureInfo& texture, void ConfigureFramebufferTexture(TextureInfo& texture,
const Pica::FramebufferConfig& framebuffer); const Pica::FramebufferConfig& framebuffer);
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped); void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
void ApplySecondLayerOpacity(); void ApplySecondLayerOpacity(bool isPortrait = false);
void ResetSecondLayerOpacity(); void ResetSecondLayerOpacity(bool isPortrait = false);
void DrawBottomScreen(const Layout::FramebufferLayout& layout, void DrawBottomScreen(const Layout::FramebufferLayout& layout,
const Common::Rectangle<u32>& bottom_screen); const Common::Rectangle<u32>& bottom_screen);
void DrawTopScreen(const Layout::FramebufferLayout& layout, void DrawTopScreen(const Layout::FramebufferLayout& layout,