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 \
"${EXTRA_CMAKE_FLAGS[@]}" \
-DENABLE_QT_TRANSLATION=ON \
-DCITRA_ENABLE_COMPATIBILITY_REPORTING=ON \
-DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
-DUSE_DISCORD_PRESENCE=ON
ninja

View File

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

View File

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

View File

@ -4,7 +4,7 @@ labels: ["bug"]
body:
- type: markdown
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
attributes:
label: Is there an existing issue for this?

View File

@ -256,4 +256,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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")
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) }

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
// Refer to the license.txt file included.
@ -156,9 +156,9 @@ object NativeLibrary {
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.
@ -262,10 +262,6 @@ object NativeLibrary {
get() = LimeApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT
@Keep
@JvmStatic
fun landscapeScreenLayout(): Int = EmulationMenuSettings.landscapeScreenLayout
@Keep
@JvmStatic
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
// Refer to the license.txt file included.
package io.github.lime3ds.android.display
import android.view.WindowManager
import io.github.lime3ds.android.NativeLibrary
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.utils.EmulationMenuSettings
class ScreenAdjustmentUtil(private val windowManager: WindowManager,
private val settings: Settings) {
class ScreenAdjustmentUtil(
private val windowManager: WindowManager,
private val settings: Settings
) {
fun swapScreen() {
val isEnabled = !EmulationMenuSettings.swapScreens
EmulationMenuSettings.swapScreens = isEnabled
@ -24,19 +25,35 @@ class ScreenAdjustmentUtil(private val windowManager: WindowManager,
BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
}
fun cycleLayouts() {
val nextLayout = (EmulationMenuSettings.landscapeScreenLayout + 1) % ScreenLayout.entries.size
changeScreenOrientation(ScreenLayout.from(nextLayout))
// TODO: figure out how to pull these from R.array
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(
EmulationMenuSettings.landscapeScreenLayout,
windowManager.defaultDisplay.rotation
)
IntSetting.SCREEN_LAYOUT.int = layoutOption.int
fun changePortraitOrientation(layoutOption: Int) {
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
fun changeScreenOrientation(layoutOption: Int) {
IntSetting.SCREEN_LAYOUT.int = layoutOption
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
// Refer to the license.txt file included.
@ -6,17 +6,30 @@ package io.github.lime3ds.android.display
enum class ScreenLayout(val int: Int) {
// These must match what is defined in src/common/settings.h
DEFAULT(0),
ORIGINAL(0),
SINGLE_SCREEN(1),
LARGE_SCREEN(2),
SIDE_SCREEN(3),
HYBRID_SCREEN(4),
MOBILE_PORTRAIT(5),
CUSTOM_LAYOUT(5),
MOBILE_LANDSCAPE(6);
companion object {
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
// Refer to the license.txt file included.
@ -14,6 +14,7 @@ enum class BooleanSetting(
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
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);
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
// 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_Y_SHIFT("cardboard_y_shift", 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),
NEW_3DS("is_new_3ds", Settings.SECTION_SYSTEM, 1),
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
// Refer to the license.txt file included.
@ -109,6 +109,8 @@ class Settings {
const val SECTION_AUDIO = "Audio"
const val SECTION_DEBUG = "Debugging"
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_B = "button_b"
@ -131,7 +133,6 @@ class Settings {
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
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
// 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.text.TextUtils
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.utils.SystemSaveGame
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...")
settings.saveSettings(activityView)
SystemSaveGame.save()
//added to ensure that layout changes take effect as soon as settings window closes
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
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
// 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.content.Context
import android.content.DialogInterface
import android.graphics.Color
import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.text.Editable
import android.text.InputFilter
import android.text.TextWatcher
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
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.dialog.MaterialAlertDialogBuilder
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.TimeFormat
import io.github.lime3ds.android.R
@ -73,7 +78,8 @@ class SettingsAdapter(
private var clickedPosition: Int
private var dialog: AlertDialog? = null
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 defaultCancelListener =
@ -256,18 +262,35 @@ class SettingsAdapter(
val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater)
textInputLayout = sliderBinding.textInput
textSliderValue = sliderBinding.textValue
textSliderValue!!.text = sliderProgress.toString()
sliderBinding.textUnits.text = item.units
textSliderValue!!.setText(sliderProgress.toString())
textInputLayout!!.suffixText = item.units
sliderBinding.slider.apply {
valueFrom = item.min.toFloat()
valueTo = item.max.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 ->
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
// 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.SharedPreferences
import android.content.res.Resources
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics
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.SystemSaveGame
import io.github.lime3ds.android.utils.ThemeUtil
import kotlin.math.min
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
private var menuTag: String? = null
@ -91,9 +93,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_CAMERA -> addCameraSettings(sl)
Settings.SECTION_CONTROLS -> addControlsSettings(sl)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_LAYOUT -> addLayoutSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_CUSTOM_LANDSCAPE -> addCustomLandscapeSettings(sl)
Settings.SECTION_CUSTOM_PORTRAIT -> addCustomPortraitSettings(sl)
else -> {
fragmentView.showToastMessage("Unimplemented menu", false)
return
@ -103,6 +108,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
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>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_settings))
sl.apply {
@ -146,6 +168,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_RENDERER
)
)
add(
SubmenuSetting(
R.string.preferences_layout,
0,
R.drawable.ic_fit_screen,
Settings.SECTION_LAYOUT
)
)
add(
SubmenuSetting(
R.string.preferences_audio,
@ -162,6 +192,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_DEBUG
)
)
add(
RunnableSetting(
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>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
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
// Refer to the license.txt file included.
@ -13,6 +13,8 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.text.Editable
import android.text.TextWatcher
import android.view.Choreographer
import android.view.LayoutInflater
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.DialogSliderBinding
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.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.ui.SettingsActivity
import io.github.lime3ds.android.features.settings.utils.SettingsFile
@ -142,7 +146,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
retainInstance = true
emulationState = EmulationState(game.path)
emulationActivity = requireActivity() as EmulationActivity
screenAdjustmentUtil = ScreenAdjustmentUtil(emulationActivity.windowManager, settingsViewModel.settings)
screenAdjustmentUtil =
ScreenAdjustmentUtil(emulationActivity.windowManager, settingsViewModel.settings)
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
}
@ -207,16 +212,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
})
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val titleId = if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
R.string.unlock_drawer
} else {
R.string.lock_drawer
}
val iconId = if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_UNLOCKED) {
R.drawable.ic_unlocked
} else {
R.drawable.ic_lock
}
val titleId =
if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
R.string.unlock_drawer
} else {
R.string.lock_drawer
}
val iconId =
if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_UNLOCKED) {
R.drawable.ic_unlocked
} else {
R.drawable.ic_lock
}
title = getString(titleId)
icon = ResourcesCompat.getDrawable(
@ -267,7 +274,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
R.id.menu_landscape_screen_layout -> {
showScreenLayoutMenu()
showLandscapeScreenLayoutMenu()
true
}
R.id.menu_portrait_screen_layout -> {
showPortraitScreenLayoutMenu()
true
}
@ -315,6 +327,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
SettingsFile.FILE_NAME_CONFIG,
""
)
true
}
@ -423,7 +436,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
private fun togglePause() {
if(emulationState.isPaused) {
if (emulationState.isPaused) {
emulationState.unpause()
} else {
emulationState.pause()
@ -769,7 +782,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.show()
}
private fun showScreenLayoutMenu() {
private fun showLandscapeScreenLayoutMenu() {
val popupMenu = PopupMenu(
requireContext(),
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)
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 ->
R.id.menu_screen_layout_single
ScreenLayout.SIDE_SCREEN.int ->
R.id.menu_screen_layout_sidebyside
ScreenLayout.MOBILE_PORTRAIT.int ->
R.id.menu_screen_layout_portrait
ScreenLayout.HYBRID_SCREEN.int ->
R.id.menu_screen_layout_hybrid
ScreenLayout.CUSTOM_LAYOUT.int ->
R.id.menu_screen_layout_custom
else -> R.id.menu_screen_layout_landscape
}
popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true)
@ -797,27 +813,83 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_screen_layout_landscape -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_LANDSCAPE)
true
}
R.id.menu_screen_layout_portrait -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_PORTRAIT)
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_LANDSCAPE.int)
true
}
R.id.menu_screen_layout_single -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SINGLE_SCREEN)
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SINGLE_SCREEN.int)
true
}
R.id.menu_screen_layout_sidebyside -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SIDE_SCREEN)
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SIDE_SCREEN.int)
true
}
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
}
@ -869,14 +941,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
sliderBinding.apply {
slider.valueTo = 150f
slider.valueFrom = 0f
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.OnChangeListener { slider: Slider, progress: Float, _: Boolean ->
textValue.text = (progress.toInt() + 50).toString()
setControlScale(slider.value.toInt(), target)
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)
}
})
textValue.text = (sliderBinding.slider.value.toInt() + 50).toString()
textUnits.text = "%"
textInput.suffixText = "%"
}
val previousProgress = sliderBinding.slider.value.toInt()
@ -899,15 +989,36 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
sliderBinding.apply {
slider.valueFrom = 0f
slider.valueTo = 100f
slider.value = preferences.getInt("controlOpacity", 50).toFloat()
slider.addOnChangeListener(
Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean ->
textValue.text = (progress.toInt()).toString()
setControlOpacity(slider.value.toInt())
})
textValue.text = (sliderBinding.slider.value.toInt()).toString()
textUnits.text = "%"
textValue.setText(slider.value.toInt().toString())
textValue.addTextChangedListener( object : TextWatcher {
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) {}
})
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()
@ -959,7 +1070,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_SWAP)
binding.surfaceInputOverlay.refreshControls()
}
private fun setControlOpacity(opacity: Int) {
preferences.edit()
.putInt("controlOpacity", opacity)

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
// Refer to the license.txt file included.
@ -7,7 +7,6 @@ package io.github.lime3ds.android.utils
import androidx.drawerlayout.widget.DrawerLayout
import androidx.preference.PreferenceManager
import io.github.lime3ds.android.LimeApplication
import io.github.lime3ds.android.display.ScreenLayout
object EmulationMenuSettings {
private val preferences =
@ -27,16 +26,7 @@ object EmulationMenuSettings {
.putBoolean("EmulationMenuSettings_DpadSlideEnable", value)
.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
get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false)
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
// Refer to the license.txt file included.
@ -174,8 +174,7 @@ void Config::ReadValues() {
// Layout
Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger(
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::MobileLandscape)));
ReadSetting("Layout", Settings::values.custom_layout);
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::LargeScreen)));
ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y);
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_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
ReadSetting("Utility", Settings::values.dump_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
// Refer to the license.txt file included.
@ -179,16 +179,20 @@ anaglyph_shader_name =
filter_mode =
[Layout]
# Layout for the screen inside the render window.
# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side
# Layout for the screen inside the render window, landscape mode
# 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 =
# Toggle custom layout (using the settings below) on or off.
# 0 (default): Off, 1: On
custom_layout =
# Screen placement when using Custom layout option
# 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_y =
custom_top_width =
@ -198,6 +202,22 @@ custom_bottom_y =
custom_bottom_width =
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.
# 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

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
// Refer to the license.txt file included.
@ -21,12 +21,6 @@ static bool IsPortraitMode() {
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) {
if (render_window == surface) {
return false;
@ -57,7 +51,6 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) {
}
void EmuWindow_Android::OnFramebufferSizeChanged() {
UpdateLandscapeScreenLayout();
const bool is_portrait_mode{IsPortraitMode()};
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
// 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_is_portrait_mode;
static jmethodID s_landscape_screen_layout;
static jmethodID s_portrait_screen_layout;
static jmethodID s_exit_emulation_activity;
static jmethodID s_request_camera_permission;
static jmethodID s_request_mic_permission;
@ -90,6 +91,10 @@ jmethodID GetLandscapeScreenLayout() {
return s_landscape_screen_layout;
}
jmethodID GetPortraitScreenLayout() {
return s_portrait_screen_layout;
}
jmethodID GetExitEmulationActivity() {
return s_exit_emulation_activity;
}
@ -172,8 +177,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_native_library_class, "onCoreError",
"(Lio/github/lime3ds/android/NativeLibrary$CoreError;Ljava/lang/String;)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 =
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
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
// Refer to the license.txt file included.
@ -27,6 +27,7 @@ jmethodID GetDisplayAlertPrompt();
jmethodID GetAlertPromptButton();
jmethodID GetIsPortraitMode();
jmethodID GetLandscapeScreenLayout();
jmethodID GetPortraitScreenLayout();
jmethodID GetExitEmulationActivity();
jmethodID GetRequestCameraPermission();
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
// Refer to the license.txt file included.
@ -346,15 +346,13 @@ void JNICALL Java_io_github_lime3ds_android_NativeLibrary_enableAdrenoTurboMode(
EnableAdrenoTurboMode(enable);
}
void Java_io_github_lime3ds_android_NativeLibrary_notifyOrientationChange(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, jint layout_option, jint rotation) {
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option);
void Java_io_github_lime3ds_android_NativeLibrary_updateFramebuffer([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jobject obj,
jboolean is_portrait_mode) {
auto& system = Core::System::GetInstance();
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,

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"?>
<RelativeLayout 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"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -13,25 +14,30 @@
android:layout_marginRight="@dimen/spacing_large"
android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/text_value"
android:layout_below="@+id/text_input"
android:layout_marginBottom="@dimen/spacing_medlarge" />
<TextView
android:layout_width="wrap_content"
<com.google.android.material.textfield.TextInputLayout
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"
tools:text="75"
android:id="@+id/text_value"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/spacing_medlarge"
android:layout_marginBottom="@dimen/spacing_medlarge" />
android:id="@+id/text_value"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="@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>

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: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
android:id="@+id/menu_swap_screens"
android:icon="@drawable/ic_splitscreen"
android:title="@string/emulation_swap_screens" />
<item

View File

@ -7,10 +7,6 @@
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" />
@ -23,6 +19,14 @@
android:id="@+id/menu_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>
</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>
</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">
<item>@string/auto_select</item>
<item>@string/system_region_jpn</item>

View File

@ -330,7 +330,7 @@
<string name="preferences_audio">Audio</string>
<string name="preferences_debug">Debug</string>
<string name="preferences_theme">Theme and Color</string>
<string name="preferences_layout">Layout</string>
<!-- ROM loading errors -->
<string name="loader_error_encrypted">Your ROM is Encrypted</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_cheats">Open Cheats</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_portrait">Portrait</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_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_touch_overlay_reset">Reset 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
// Refer to the license.txt file included.
// Copyright Dolphin Emulator Project
// Licensed under GPLv2 or any later version
#include <array>
#include <fstream>
#include <limits>
@ -707,8 +710,8 @@ std::string AppDataRoamingDirectory() {
/**
* @return The users home directory on POSIX systems
*/
static const std::string& GetHomeDirectory() {
static std::string home_path;
const std::string GetHomeDirectory() {
std::string home_path;
if (home_path.empty()) {
const char* envvar = getenv("HOME");
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
// Refer to the license.txt file included.
// Copyright Dolphin Emulator Project
// Licensed under GPLv2 or any later version
#pragma once
#include <array>
@ -207,6 +210,7 @@ void UpdateUserPath(UserPath path, const std::string& filename);
[[nodiscard]] const std::string& GetExeDirectory();
[[nodiscard]] std::string AppDataRoamingDirectory();
#else
[[nodiscard]] const std::string GetHomeDirectory();
[[nodiscard]] const std::string GetUserDirectory(const std::string& envvar);
#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
// Refer to the license.txt file included.
@ -108,6 +108,7 @@ void LogSettings() {
log_setting("Renderer_AnaglyphShader", values.anaglyph_shader_name.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_UprightScreen", values.upright_screen.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_sampling.SetGlobal(true);
values.layout_option.SetGlobal(true);
values.portrait_layout_option.SetGlobal(true);
values.swap_screen.SetGlobal(true);
values.upright_screen.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
// Refer to the license.txt file included.
@ -33,6 +33,7 @@ enum class InitTicks : u32 {
Fixed = 1,
};
/** Defines the layout option for desktop and mobile landscape */
enum class LayoutOption : u32 {
Default,
SingleScreen,
@ -42,18 +43,20 @@ enum class LayoutOption : u32 {
SeparateWindows,
#endif
HybridScreen,
#ifndef ANDROID // TODO: Implement custom layouts on Android
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
// clamped to the top of the frame, and the bottom screen is a bit bigger.
MobileLandscape,
};
/** Defines the layout option for mobile portrait */
enum class PortraitLayoutOption : u32 {
// formerly mobile portrait
PortraitTopFullWidth,
PortraitCustomLayout,
};
enum class StereoRenderOption : u32 {
Off = 0,
SideBySide = 1,
@ -482,21 +485,19 @@ struct Values {
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"};
SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled,
"texture_sampling"};
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
SwitchableSetting<bool> swap_screen{false, "swap_screen"};
SwitchableSetting<bool> upright_screen{false, "upright_screen"};
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
"large_screen_proportion"};
Setting<bool> custom_layout{false, "custom_layout"};
Setting<u16> custom_top_x{0, "custom_top_x"};
Setting<u16> custom_top_y{0, "custom_top_y"};
Setting<u16> custom_top_width{400, "custom_top_width"};
Setting<u16> custom_top_height{240, "custom_top_height"};
Setting<u16> custom_bottom_x{40, "custom_bottom_x"};
Setting<u16> custom_bottom_y{240, "custom_bottom_y"};
Setting<u16> custom_bottom_width{320, "custom_bottom_width"};
Setting<u16> custom_bottom_height{240, "custom_bottom_height"};
Setting<u16> custom_top_width{800, "custom_top_width"};
Setting<u16> custom_top_height{480, "custom_top_height"};
Setting<u16> custom_bottom_x{80, "custom_bottom_x"};
Setting<u16> custom_bottom_y{500, "custom_bottom_y"};
Setting<u16> custom_bottom_width{640, "custom_bottom_width"};
Setting<u16> custom_bottom_height{480, "custom_bottom_height"};
Setting<u16> custom_second_layer_opacity{100, "custom_second_layer_opacity"};
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_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_green{0.f, "bg_green"};
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
// 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) {
Layout::FramebufferLayout layout;
// If in portrait mode, only the MobilePortrait option really makes sense
const Settings::LayoutOption layout_option = is_portrait_mode
? Settings::LayoutOption::MobilePortrait
: Settings::values.layout_option.GetValue();
const auto min_size =
Layout::GetMinimumSizeFromLayout(layout_option, Settings::values.upright_screen.GetValue());
const Settings::LayoutOption layout_option = Settings::values.layout_option.GetValue();
const Settings::PortraitLayoutOption portrait_layout_option =
Settings::values.portrait_layout_option.GetValue();
const auto min_size = is_portrait_mode
? Layout::GetMinimumSizeFromPortraitLayout()
: 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());
width = std::max(width, min_size.first);
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 {
width = std::max(width, min_size.first);
height = std::max(height, min_size.second);
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:
layout =
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());
break;
#endif
case Settings::LayoutOption::MobilePortrait:
layout = Layout::MobilePortraitFrameLayout(width, height,
Settings::values.swap_screen.GetValue());
break;
case Settings::LayoutOption::MobileLandscape:
layout =
Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
false, 2.25f, Layout::VerticalAlignment::Top);
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:
default:
layout =
@ -241,8 +245,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
Settings::values.upright_screen.GetValue());
break;
}
UpdateMinimumWindowSize(min_size);
}
UpdateMinimumWindowSize(min_size);
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
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
// Refer to the license.txt file included.
@ -117,11 +117,11 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool u
return res;
}
FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool swapped) {
FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool swapped) {
ASSERT(width > 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
Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2};
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(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,
// 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);
}
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(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(),
Settings::values.custom_top_y.GetValue(),
(u32)(Settings::values.custom_top_x.GetValue() +
Settings::values.custom_top_width.GetValue()),
(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())};
Common::Rectangle<u32> top_screen{top_x, top_y, (u32)(top_x + top_width),
(u32)(top_y + top_height)};
Common::Rectangle<u32> bot_screen{bottom_x, bottom_y, (u32)(bottom_x + bottom_width),
(u32)(bottom_y + bottom_height)};
if (is_swapped) {
res.top_screen = bot_screen;
@ -411,102 +420,126 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped) {
return res;
}
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary) {
if (Settings::values.custom_layout.GetValue() == true) {
return CustomFrameLayout(std::max(Settings::values.custom_top_width.GetValue(),
Settings::values.custom_bottom_width.GetValue()),
std::max(Settings::values.custom_top_height.GetValue(),
Settings::values.custom_bottom_height.GetValue()),
Settings::values.swap_screen.GetValue());
}
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary,
bool is_portrait) {
int width, height;
switch (Settings::values.layout_option.GetValue()) {
case Settings::LayoutOption::SingleScreen:
#ifndef ANDROID
case Settings::LayoutOption::SeparateWindows:
#endif
{
const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
if (swap_screens) {
width = Core::kScreenBottomWidth * res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
if (is_portrait) {
auto layout_option = Settings::values.portrait_layout_option.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 * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
return PortraitTopFullFrameLayout(width, height,
Settings::values.swap_screen.GetValue());
}
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return SingleFrameLayout(width, height, swap_screens,
Settings::values.upright_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);
case Settings::LayoutOption::LargeScreen:
if (Settings::values.swap_screen.GetValue()) {
width = (Core::kScreenBottomWidth +
case Settings::LayoutOption::SingleScreen:
#ifndef ANDROID
case Settings::LayoutOption::SeparateWindows:
#endif
{
const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
if (swap_screens) {
width = Core::kScreenBottomWidth * res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = Core::kScreenTopWidth * res_scale;
height = Core::kScreenTopHeight * res_scale;
}
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return SingleFrameLayout(width, height, swap_screens,
Settings::values.upright_screen.GetValue());
}
case Settings::LayoutOption::LargeScreen:
if (Settings::values.swap_screen.GetValue()) {
width =
(Core::kScreenBottomWidth +
Core::kScreenTopWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = (Core::kScreenTopWidth +
height = Core::kScreenBottomHeight * res_scale;
} else {
width =
(Core::kScreenTopWidth +
Core::kScreenBottomWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale;
height = Core::kScreenTopHeight * res_scale;
}
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue(),
VerticalAlignment::Bottom);
case Settings::LayoutOption::SideScreen:
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
height = Core::kScreenTopHeight * res_scale;
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(), 1,
VerticalAlignment::Middle);
case Settings::LayoutOption::MobileLandscape: {
constexpr float large_screen_proportion = 2.25f;
if (Settings::values.swap_screen.GetValue()) {
width = (Core::kScreenBottomWidth +
static_cast<int>(Core::kScreenTopWidth / large_screen_proportion)) *
res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = (Core::kScreenTopWidth +
static_cast<int>(Core::kScreenBottomWidth / large_screen_proportion)) *
res_scale;
height = Core::kScreenTopHeight * res_scale;
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(), false,
large_screen_proportion, VerticalAlignment::Top);
}
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
case Settings::LayoutOption::Default:
default:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue(),
VerticalAlignment::Bottom);
case Settings::LayoutOption::SideScreen:
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
height = Core::kScreenTopHeight * res_scale;
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(), 1,
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: {
constexpr float large_screen_proportion = 2.25f;
if (Settings::values.swap_screen.GetValue()) {
width = (Core::kScreenBottomWidth +
static_cast<int>(Core::kScreenTopWidth / large_screen_proportion)) *
res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = (Core::kScreenTopWidth +
static_cast<int>(Core::kScreenBottomWidth / large_screen_proportion)) *
res_scale;
height = Core::kScreenTopHeight * res_scale;
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(), false,
large_screen_proportion, VerticalAlignment::Top);
}
case Settings::LayoutOption::Default:
default:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
}
UNREACHABLE();
}
@ -529,11 +562,27 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
u32 cardboard_screen_width;
u32 cardboard_screen_height;
switch (Settings::values.layout_option.GetValue()) {
case Settings::LayoutOption::MobileLandscape:
case Settings::LayoutOption::SideScreen:
// If orientation is portrait, only use MobilePortrait
if (!is_portrait) {
if (is_portrait) {
switch (Settings::values.portrait_layout_option.GetValue()) {
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
cardboard_screen_width = top_screen_width;
cardboard_screen_height = top_screen_height + bottom_screen_height;
bottom_screen_left += (top_screen_width - bottom_screen_width) / 2;
if (is_swapped)
top_screen_top += bottom_screen_height;
else
bottom_screen_top += top_screen_height;
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)
@ -541,28 +590,14 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
else
bottom_screen_left += top_screen_width;
break;
} else {
[[fallthrough]];
}
case Settings::LayoutOption::SingleScreen:
default:
if (!is_portrait) {
// Default values when using LayoutOption::SingleScreen
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;
} else {
[[fallthrough]];
}
case Settings::LayoutOption::MobilePortrait:
cardboard_screen_width = top_screen_width;
cardboard_screen_height = top_screen_height + bottom_screen_height;
bottom_screen_left += (top_screen_width - bottom_screen_width) / 2;
if (is_swapped)
top_screen_top += bottom_screen_height;
else
bottom_screen_top += top_screen_height;
break;
}
s32 cardboard_max_x_shift = (layout.width / 2 - cardboard_screen_width) / 2;
s32 cardboard_user_x_shift =
@ -596,6 +631,13 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& 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,
bool upright_screen) {
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
// Refer to the license.txt file included.
@ -61,7 +61,7 @@ struct FramebufferLayout {
Common::Rectangle<u32> top_screen;
Common::Rectangle<u32> bottom_screen;
bool is_rotated = true;
bool is_portrait = false;
bool additional_screen_enabled;
Common::Rectangle<u32> additional_screen;
@ -85,13 +85,14 @@ struct FramebufferLayout {
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 height Window framebuffer height in pixels
* @param is_swapped if true, the bottom screen will be displayed above the top screen
* @return Newly created FramebufferLayout object with mobile portrait screen regions initialized
*/
FramebufferLayout 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
@ -145,14 +146,17 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary
* @param height Window framebuffer height in pixels
* @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
* Read from the current settings to determine which layout to use.
* @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
@ -164,4 +168,6 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout);
std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout,
bool upright_screen);
std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout();
} // 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
// Refer to the license.txt file included.
@ -164,8 +164,6 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.swap_screen);
ReadSetting("Layout", Settings::values.upright_screen);
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_y);
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_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
ReadSetting("Utility", Settings::values.dump_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
// Refer to the license.txt file included.
@ -182,7 +182,7 @@ filter_mode =
[Layout]
# Layout for the screen inside the render window.
# 0 (default): Default Top Bottom Screen
# 0 (default): Default Above/Below Screen
# 1: Single Screen Only
# 2: Large Screen Small Screen
# 3: Side by Side
@ -191,10 +191,6 @@ filter_mode =
# 6: Custom Layout
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
# 0x, 0y is the top left corner of the render window.
custom_top_x =

View File

@ -415,7 +415,7 @@ int main(int argc, char** argv) {
return -1;
case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted:
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: "
"https://web.archive.org/web/20240304210021/https://citra-emu.org/"
"wiki/dumping-game-cartridges/");

View File

@ -28,9 +28,6 @@ add_executable(lime-qt
camera/qt_multimedia_camera.cpp
camera/qt_multimedia_camera.h
lime-qt.rc
compatdb.cpp
compatdb.h
compatdb.ui
configuration/config.cpp
configuration/config.h
configuration/configure.ui
@ -334,10 +331,6 @@ target_compile_definitions(lime-qt PRIVATE
-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)
target_sources(lime-qt PUBLIC
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
// Refer to the license.txt file included.
@ -520,8 +520,6 @@ void Config::ReadLayoutValues() {
if (global) {
ReadBasicSetting(Settings::values.mono_render_option);
ReadBasicSetting(Settings::values.custom_layout);
ReadBasicSetting(Settings::values.custom_top_x);
ReadBasicSetting(Settings::values.custom_top_y);
ReadBasicSetting(Settings::values.custom_top_width);
@ -538,6 +536,15 @@ void Config::ReadLayoutValues() {
ReadBasicSetting(Settings::values.screen_bottom_stretch);
ReadBasicSetting(Settings::values.screen_bottom_leftright_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();
@ -1072,8 +1079,6 @@ void Config::SaveLayoutValues() {
if (global) {
WriteBasicSetting(Settings::values.mono_render_option);
WriteBasicSetting(Settings::values.custom_layout);
WriteBasicSetting(Settings::values.custom_top_x);
WriteBasicSetting(Settings::values.custom_top_y);
WriteBasicSetting(Settings::values.custom_top_width);
@ -1090,6 +1095,14 @@ void Config::SaveLayoutValues() {
WriteBasicSetting(Settings::values.screen_bottom_stretch);
WriteBasicSetting(Settings::values.screen_bottom_leftright_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();

View File

@ -46,7 +46,6 @@
#include "lime_qt/bootmanager.h"
#include "lime_qt/camera/qt_multimedia_camera.h"
#include "lime_qt/camera/still_image_camera.h"
#include "lime_qt/compatdb.h"
#include "lime_qt/compatibility_list.h"
#include "lime_qt/configuration/config.h"
#include "lime_qt/configuration/configure_dialog.h"
@ -97,7 +96,6 @@
#include "core/savestate.h"
#include "core/system_titles.h"
#include "input_common/main.h"
#include "network/network_settings.h"
#include "ui_main.h"
#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
@ -339,9 +337,6 @@ GMainWindow::~GMainWindow() {
}
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);
secondary_window = new GRenderWindow(this, emu_thread.get(), system, true);
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_Upright_Screens,
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_Load_from_Newest_Slot, QStringLiteral("Load from Newest 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_Stop, &GMainWindow::OnStopGame);
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_Current_Game, &GMainWindow::OnConfigurePerGame);
@ -937,16 +933,8 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Save_Movie, &GMainWindow::OnSaveMovie);
connect_menu(ui->action_Movie_Read_Only_Mode,
[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] {
if (emulation_running && system.frame_limiter.IsFrameAdvancing()) {
ui->action_Enable_Frame_Advancing->setChecked(true);
ui->action_Advance_Frame->setEnabled(true);
system.frame_limiter.AdvanceFrame();
}
});
@ -971,7 +959,8 @@ void GMainWindow::ConnectMenuEvents() {
}
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{
ui->action_Stop,
@ -989,6 +978,7 @@ void GMainWindow::UpdateMenuState() {
}
ui->action_Capture_Screenshot->setEnabled(emulation_running && !is_paused);
ui->action_Advance_Frame->setEnabled(emulation_running && is_paused);
if (emulation_running && is_paused) {
ui->action_Pause->setText(tr("&Continue"));
@ -1408,12 +1398,7 @@ void GMainWindow::BootGame(const QString& filename) {
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) {
StartVideoDumping(video_dumping_path);
@ -1770,9 +1755,9 @@ void GMainWindow::OnGameListRemovePlayTimeData(u64 program_id) {
bool GMainWindow::CreateShortcutLink(const std::filesystem::path& shortcut_path,
const std::string& comment,
const std::filesystem::path& icon_path,
const std::filesystem::path& command,
const std::string& arguments, const std::string& categories,
const std::string& keywords, const std::string& name) try {
const std::string& command, const std::string& arguments,
const std::string& categories, const std::string& keywords,
const std::string& name, const bool& skip_tryexec) try {
#if defined(__linux__) || defined(__FreeBSD__) // Linux and FreeBSD
std::filesystem::path shortcut_path_full = shortcut_path / (name + ".desktop");
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)) {
fmt::print(shortcut_stream, "Icon={}\n", icon_path.string());
}
fmt::print(shortcut_stream, "TryExec={}\n", command.string());
fmt::print(shortcut_stream, "Exec={} {}\n", command.string(), arguments);
if (!skip_tryexec) {
fmt::print(shortcut_stream, "TryExec={}\n", command);
}
fmt::print(shortcut_stream, "Exec={} {}\n", command, arguments);
if (!categories.empty()) {
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");
return false;
}
hres = ps1->SetPath(command.c_str());
hres = ps1->SetPath(Common::UTF8ToUTF16W(command).data());
if (FAILED(hres)) {
LOG_ERROR(Frontend, "Failed to set path");
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,
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";
#if defined(_WIN32)
out_icon_path = FileUtil::GetUserPath(FileUtil::UserPath::IconsDir);
ico_extension = "ico";
#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
// Create icons directory if it doesn't exist
if (!FileUtil::CreateDir(out_icon_path.string())) {
if (!FileUtil::CreateFullPath(out_icon_path.string())) {
QMessageBox::critical(
this, tr("Create Icon"),
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
out_icon_path /= (program_id == 0 ? fmt::format("citra-{}.{}", game_file_name, ico_extension)
: fmt::format("citra-{:016X}.{}", program_id, ico_extension));
out_icon_path /= (program_id == 0 ? fmt::format("lime-{}.{}", game_file_name, ico_extension)
: fmt::format("lime-{:016X}.{}", program_id, ico_extension));
return true;
}
void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
GameListShortcutTarget target) {
// Get path to citra executable
const QStringList args = QApplication::arguments();
std::filesystem::path citra_command = args[0].toStdString();
// If relative path, make it an absolute path
if (citra_command.c_str()[0] == '.') {
citra_command = FileUtil::GetCurrentDir().value_or("") + DIR_SEP + citra_command.string();
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();
lime_command = args[0].toStdString();
// If relative path, make it an absolute path
if (lime_command.c_str()[0] == '.') {
lime_command = FileUtil::GetCurrentDir().value_or("") + DIR_SEP + lime_command;
}
}
// Shortcut path
@ -1943,8 +1938,7 @@ void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& ga
shortcut_path =
QStandardPaths::writableLocation(QStandardPaths::DesktopLocation).toStdString();
} else if (target == GameListShortcutTarget::Applications) {
shortcut_path =
QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation).toStdString();
shortcut_path = GetApplicationsDirectory();
}
// 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
const std::string appimage_ending =
std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
if (citra_command.string().ends_with(appimage_ending) &&
!UISettings::values.shortcut_already_warned) {
if (lime_command.ends_with(appimage_ending) && !UISettings::values.shortcut_already_warned) {
if (CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_APPIMAGE_VOLATILE_WARNING,
qt_game_title)) {
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)) {
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 keywords = "3ds;Nintendo;";
if (CreateShortcutLink(shortcut_path, comment, out_icon_path, citra_command, arguments,
categories, keywords, game_title)) {
if (CreateShortcutLink(shortcut_path, comment, out_icon_path, lime_command, arguments,
categories, keywords, game_title, skip_tryexec)) {
CreateShortcutMessagesGUI(this, CREATE_SHORTCUT_MSGBOX_SUCCESS, qt_game_title);
return;
}
@ -2200,7 +2193,7 @@ void GMainWindow::OnCIAInstallReport(Service::AM::InstallStatus status, QString
case Service::AM::InstallStatus::ErrorEncrypted:
QMessageBox::critical(this, tr("Encrypted File"),
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));
break;
case Service::AM::InstallStatus::ErrorFileNotFound:
@ -2293,6 +2286,7 @@ void GMainWindow::OnStartGame() {
PreventOSSleep();
emu_thread->SetRunning(true);
system.frame_limiter.SetFrameAdvancing(false);
graphics_api_button->setEnabled(false);
qRegisterMetaType<Core::System::ResultStatus>("Core::System::ResultStatus");
qRegisterMetaType<std::string>("std::string");
@ -2322,7 +2316,7 @@ void GMainWindow::OnRestartGame() {
}
void GMainWindow::OnPauseGame() {
emu_thread->SetRunning(false);
system.frame_limiter.SetFrameAdvancing(true);
qt_cameras->PauseCameras();
play_time_manager->Stop();
@ -2337,7 +2331,7 @@ void GMainWindow::OnPauseGame() {
void GMainWindow::OnPauseContinueGame() {
if (emulation_running) {
if (emu_thread->IsRunning()) {
if (emu_thread->IsRunning() && !system.frame_limiter.IsFrameAdvancing()) {
OnPauseGame();
} else {
OnStartGame();
@ -2361,17 +2355,6 @@ void GMainWindow::OnLoadComplete() {
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() {
if (!emulation_running) {
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
// 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,
std::filesystem::path& out_icon_path);
bool CreateShortcutLink(const std::filesystem::path& shortcut_path, const std::string& comment,
const std::filesystem::path& icon_path,
const std::filesystem::path& command, const std::string& arguments,
const std::string& categories, const std::string& keywords,
const std::string& name);
const std::filesystem::path& icon_path, const std::string& command,
const std::string& arguments, const std::string& categories,
const std::string& keywords, const std::string& name,
const bool& skip_tryexec);
private slots:
void OnStartGame();
@ -230,7 +230,6 @@ private slots:
void OnStopGame();
void OnSaveState();
void OnLoadState();
void OnMenuReportCompatibility();
/// Called whenever a user selects a game in the game list widget.
void OnGameListLoadFile(QString game_path);
void OnGameListOpenFolder(u64 program_id, GameListOpenTarget target);

View File

@ -182,15 +182,8 @@
<addaction name="action_Movie_Read_Only_Mode"/>
<addaction name="action_Save_Movie"/>
</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"/>
</widget>
<addaction name="action_Advance_Frame"/>
<addaction name="menu_Movie"/>
<addaction name="menu_Frame_Advance"/>
<addaction name="separator"/>
<addaction name="action_Capture_Screenshot"/>
<addaction name="action_Dump_Video"/>
@ -309,7 +302,7 @@
</action>
<action name="action_About">
<property name="text">
<string>About Lime</string>
<string>About Lime3DS</string>
</property>
<property name="menuRole">
<enum>QAction::AboutRole</enum>
@ -404,14 +397,6 @@
<string>Read-Only Mode</string>
</property>
</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">
<property name="enabled">
<bool>false</bool>
@ -575,15 +560,9 @@
</property>
</action>
<action name="action_Report_Compatibility">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Report Compatibility</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_Restart">
<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
// Refer to the license.txt file included.
#include <array>
#include <cmath>
#include <QPainter>
#include <QStandardPaths>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/loader/smdh.h"
#include "lime_qt/util/util.h"
@ -160,3 +163,15 @@ bool SaveIconToFile(const std::filesystem::path& icon_path, const QImage& image)
return false;
#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
// 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
*/
[[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];
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;
}
Surface& surface = slot_surfaces[cube.face_ids[i]];
Surface& surface = slot_surfaces[face_id];
if (cube.ticks[i] == surface.modification_tick) {
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
// 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()) {
DrawTopScreen(layout, top_screen);
glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity();
ApplySecondLayerOpacity(layout.is_portrait);
DrawBottomScreen(layout, bottom_screen);
} else {
DrawBottomScreen(layout, bottom_screen);
glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity();
ApplySecondLayerOpacity(layout.is_portrait);
DrawTopScreen(layout, top_screen);
}
@ -692,13 +692,14 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
DrawBottomScreen(layout, additional_screen);
}
}
ResetSecondLayerOpacity();
ResetSecondLayerOpacity(layout.is_portrait);
}
void RendererOpenGL::ApplySecondLayerOpacity() {
#ifndef ANDROID // TODO: Implement custom layouts on Android
if ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout ||
Settings::values.custom_layout) &&
void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) {
// TODO: Allow for second layer opacity in portrait mode android
if (!isPortrait &&
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) {
state.blend.src_rgb_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.color.alpha = Settings::values.custom_second_layer_opacity.GetValue() / 100.0f;
}
#endif
}
void RendererOpenGL::ResetSecondLayerOpacity() {
#ifndef ANDROID // TODO: Implement custom layouts on Android
if ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout ||
Settings::values.custom_layout) &&
void RendererOpenGL::ResetSecondLayerOpacity(bool isPortrait) {
if (!isPortrait &&
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) {
state.blend.src_rgb_func = GL_ONE;
state.blend.dst_rgb_func = GL_ZERO;
@ -720,7 +719,6 @@ void RendererOpenGL::ResetSecondLayerOpacity() {
state.blend.dst_a_func = GL_ZERO;
state.blend.color.alpha = 0.0f;
}
#endif
}
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
// Refer to the license.txt file included.
@ -65,8 +65,8 @@ private:
void ConfigureFramebufferTexture(TextureInfo& texture,
const Pica::FramebufferConfig& framebuffer);
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
void ApplySecondLayerOpacity();
void ResetSecondLayerOpacity();
void ApplySecondLayerOpacity(bool isPortrait = false);
void ResetSecondLayerOpacity(bool isPortrait = false);
void DrawBottomScreen(const Layout::FramebufferLayout& layout,
const Common::Rectangle<u32>& bottom_screen);
void DrawTopScreen(const Layout::FramebufferLayout& layout,