android: Implemented custom layout customization GUI

This commit also changes the name of the 'Portrait' landscape layout to 'Original'

Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
David Griswold 2024-08-11 16:18:59 +01:00 committed by OpenSauce04
parent b293a253f5
commit e884d5f3f3
31 changed files with 546 additions and 316 deletions

View File

@ -156,11 +156,10 @@ object NativeLibrary {
external fun getPerfStats(): DoubleArray external fun getPerfStats(): DoubleArray
/** /**
* Notifies the core emulation that the orientation has changed. * Notifies the core emulation that the layout should be updated
*/ */
external fun notifyOrientationChange(layoutOption: Int, rotation: Int, isPortrait: Boolean) external fun updateFramebuffer(isPortrait: Boolean)
external fun notifyPortraitLayoutChange(layoutOption: Int, rotation: Int, isPortrait: Boolean)
/** /**
* Swaps the top and bottom screens. * Swaps the top and bottom screens.
*/ */
@ -263,14 +262,6 @@ object NativeLibrary {
get() = CitraApplication.appContext.resources.configuration.orientation == get() = CitraApplication.appContext.resources.configuration.orientation ==
Configuration.ORIENTATION_PORTRAIT Configuration.ORIENTATION_PORTRAIT
@Keep
@JvmStatic
fun landscapeScreenLayout(): Int = EmulationMenuSettings.landscapeScreenLayout
@Keep
@JvmStatic
fun portraitScreenLayout(): Int = EmulationMenuSettings.portraitScreenLayout
@Keep @Keep
@JvmStatic @JvmStatic
fun displayAlertMsg(title: String, message: String, yesNo: Boolean): Boolean { fun displayAlertMsg(title: String, message: String, yesNo: Boolean): Boolean {

View File

@ -1,17 +0,0 @@
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.display
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

@ -26,40 +26,35 @@ class ScreenAdjustmentUtil(
BooleanSetting.SWAP_SCREEN.boolean = isEnabled BooleanSetting.SWAP_SCREEN.boolean = isEnabled
settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(BooleanSetting.SWAP_SCREEN, SettingsFile.FILE_NAME_CONFIG)
} }
// TODO: Consider how cycling should handle custom layout
// right now it simply skips it
fun cycleLayouts() { fun cycleLayouts() {
val nextLayout = if (NativeLibrary.isPortraitMode) { // TODO: figure out how to pull these from R.array
(EmulationMenuSettings.portraitScreenLayout + 1) % (PortraitScreenLayout.entries.size - 1) 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 { } else {
(EmulationMenuSettings.landscapeScreenLayout + 1) % (ScreenLayout.entries.size - 1) 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)
} }
settings.loadSettings()
changeScreenOrientation(nextLayout)
} }
fun changePortraitOrientation(layoutOption: Int) { fun changePortraitOrientation(layoutOption: Int) {
EmulationMenuSettings.portraitScreenLayout = layoutOption
NativeLibrary.notifyPortraitLayoutChange(
EmulationMenuSettings.portraitScreenLayout,
windowManager.defaultDisplay.rotation,
NativeLibrary::isPortraitMode.get()
)
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
} }
fun changeScreenOrientation(layoutOption: Int) { fun changeScreenOrientation(layoutOption: Int) {
EmulationMenuSettings.landscapeScreenLayout = layoutOption
NativeLibrary.notifyOrientationChange(
EmulationMenuSettings.landscapeScreenLayout,
windowManager.defaultDisplay.rotation,
NativeLibrary::isPortraitMode.get()
)
IntSetting.SCREEN_LAYOUT.int = layoutOption IntSetting.SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
} }
} }

View File

@ -6,7 +6,7 @@ package org.citra.citra_emu.display
enum class ScreenLayout(val int: Int) { enum class ScreenLayout(val int: Int) {
// These must match what is defined in src/common/settings.h // These must match what is defined in src/common/settings.h
TOP_BOTTOM(0), ORIGINAL(0),
SINGLE_SCREEN(1), SINGLE_SCREEN(1),
LARGE_SCREEN(2), LARGE_SCREEN(2),
SIDE_SCREEN(3), SIDE_SCREEN(3),
@ -21,3 +21,15 @@ enum class ScreenLayout(val int: Int) {
} }
} }
} }
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

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

View File

@ -109,6 +109,8 @@ class Settings {
const val SECTION_AUDIO = "Audio" const val SECTION_AUDIO = "Audio"
const val SECTION_DEBUG = "Debugging" const val SECTION_DEBUG = "Debugging"
const val SECTION_THEME = "Theme" const val SECTION_THEME = "Theme"
const val SECTION_CUSTOM_LANDSCAPE = "Custom Landscape Layout"
const val SECTION_CUSTOM_PORTRAIT = "Custom Portrait Layout"
const val KEY_BUTTON_A = "button_a" const val KEY_BUTTON_A = "button_a"
const val KEY_BUTTON_B = "button_b" const val KEY_BUTTON_B = "button_b"

View File

@ -7,6 +7,7 @@ package org.citra.citra_emu.features.settings.ui
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import org.citra.citra_emu.NativeLibrary import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.Settings import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.utils.SystemSaveGame import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.DirectoryInitialization
@ -56,6 +57,9 @@ class SettingsActivityPresenter(private val activityView: SettingsActivityView)
Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...")
settings.saveSettings(activityView) settings.saveSettings(activityView)
SystemSaveGame.save() SystemSaveGame.save()
//added to ensure that layout changes take effect as soon as settings window closes
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
} }
NativeLibrary.reloadSettings() NativeLibrary.reloadSettings()
} }

View File

@ -7,13 +7,16 @@ package org.citra.citra_emu.features.settings.ui
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.graphics.Color
import android.icu.util.Calendar import android.icu.util.Calendar
import android.icu.util.TimeZone import android.icu.util.TimeZone
import android.text.Editable
import android.text.InputFilter import android.text.InputFilter
import android.text.TextWatcher
import android.text.format.DateFormat import android.text.format.DateFormat
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
@ -22,6 +25,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.datepicker.MaterialDatePicker import com.google.android.material.datepicker.MaterialDatePicker
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout
import com.google.android.material.timepicker.MaterialTimePicker import com.google.android.material.timepicker.MaterialTimePicker
import com.google.android.material.timepicker.TimeFormat import com.google.android.material.timepicker.TimeFormat
import org.citra.citra_emu.R import org.citra.citra_emu.R
@ -73,7 +78,8 @@ class SettingsAdapter(
private var clickedPosition: Int private var clickedPosition: Int
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private var sliderProgress = 0 private var sliderProgress = 0
private var textSliderValue: TextView? = null private var textSliderValue: TextInputEditText? = null
private var textInputLayout: TextInputLayout? = null
private var textInputValue: String = "" private var textInputValue: String = ""
private var defaultCancelListener = private var defaultCancelListener =
@ -256,18 +262,36 @@ class SettingsAdapter(
val inflater = LayoutInflater.from(context) val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater) val sliderBinding = DialogSliderBinding.inflate(inflater)
textInputLayout = sliderBinding.textInput
textSliderValue = sliderBinding.textValue textSliderValue = sliderBinding.textValue
textSliderValue!!.text = sliderProgress.toString() textSliderValue!!.setText(sliderProgress.toString())
sliderBinding.textUnits.text = item.units //sliderBinding.textUnits.text = item.units
textInputLayout!!.suffixText = item.units
sliderBinding.slider.apply { sliderBinding.slider.apply {
valueFrom = item.min.toFloat() valueFrom = item.min.toFloat()
valueTo = item.max.toFloat() valueTo = item.max.toFloat()
value = sliderProgress.toFloat() value = sliderProgress.toFloat()
textSliderValue!!.addTextChangedListener( object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val textValue = s.toString().toIntOrNull();
if (textValue == null || textValue < valueFrom || textValue > valueTo) {
textInputLayout!!.error ="Inappropriate value"
} else {
textInputLayout!!.error = null
value = textValue.toFloat();
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
addOnChangeListener { _: Slider, value: Float, _: Boolean -> addOnChangeListener { _: Slider, value: Float, _: Boolean ->
sliderProgress = value.toInt() sliderProgress = value.toInt()
textSliderValue!!.text = sliderProgress.toString() if (textSliderValue!!.text.toString() != value.toInt().toString()) {
textSliderValue!!.setText(value.toInt().toString())
textSliderValue!!.setSelection(textSliderValue!!.length())
}
} }
} }

View File

@ -6,12 +6,14 @@ package org.citra.citra_emu.features.settings.ui
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.res.Resources
import android.hardware.camera2.CameraAccessException import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraCharacteristics import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager import android.hardware.camera2.CameraManager
import android.os.Build import android.os.Build
import android.text.TextUtils import android.text.TextUtils
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import kotlin.math.min
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R import org.citra.citra_emu.R
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
@ -91,9 +93,12 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_CAMERA -> addCameraSettings(sl) Settings.SECTION_CAMERA -> addCameraSettings(sl)
Settings.SECTION_CONTROLS -> addControlsSettings(sl) Settings.SECTION_CONTROLS -> addControlsSettings(sl)
Settings.SECTION_RENDERER -> addGraphicsSettings(sl) Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
Settings.SECTION_LAYOUT -> addLayoutSettings(sl)
Settings.SECTION_AUDIO -> addAudioSettings(sl) Settings.SECTION_AUDIO -> addAudioSettings(sl)
Settings.SECTION_DEBUG -> addDebugSettings(sl) Settings.SECTION_DEBUG -> addDebugSettings(sl)
Settings.SECTION_THEME -> addThemeSettings(sl) Settings.SECTION_THEME -> addThemeSettings(sl)
Settings.SECTION_CUSTOM_LANDSCAPE -> addCustomLandscapeSettings(sl)
Settings.SECTION_CUSTOM_PORTRAIT -> addCustomPortraitSettings(sl)
else -> { else -> {
fragmentView.showToastMessage("Unimplemented menu", false) fragmentView.showToastMessage("Unimplemented menu", false)
return return
@ -103,6 +108,23 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
fragmentView.showSettingsList(settingsList!!) fragmentView.showSettingsList(settingsList!!)
} }
/** Returns the portrait mode width */
private fun getWidth(): Int {
val dm = Resources.getSystem().displayMetrics;
return if (dm.widthPixels < dm.heightPixels)
dm.widthPixels
else
dm.heightPixels
}
private fun getHeight(): Int {
val dm = Resources.getSystem().displayMetrics;
return if (dm.widthPixels < dm.heightPixels)
dm.heightPixels
else
dm.widthPixels
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) { private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_settings)) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_settings))
sl.apply { sl.apply {
@ -146,6 +168,14 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_RENDERER Settings.SECTION_RENDERER
) )
) )
add(
SubmenuSetting(
R.string.preferences_layout,
0,
R.drawable.ic_fit_screen,
Settings.SECTION_LAYOUT
)
)
add( add(
SubmenuSetting( SubmenuSetting(
R.string.preferences_audio, R.string.preferences_audio,
@ -162,6 +192,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_DEBUG Settings.SECTION_DEBUG
) )
) )
add( add(
RunnableSetting( RunnableSetting(
R.string.reset_to_default, R.string.reset_to_default,
@ -895,6 +926,262 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
} }
} }
private fun addLayoutSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle("Layout")
sl.apply {
add(
SingleChoiceSetting(
IntSetting.SCREEN_LAYOUT,
R.string.emulation_switch_screen_layout,
0,
R.array.landscapeLayouts,
R.array.landscapeLayoutValues,
IntSetting.SCREEN_LAYOUT.key,
IntSetting.SCREEN_LAYOUT.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.PORTRAIT_SCREEN_LAYOUT,
R.string.emulation_switch_portrait_layout,
0,
R.array.portraitLayouts,
R.array.portraitLayoutValues,
IntSetting.PORTRAIT_SCREEN_LAYOUT.key,
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
)
)
add(
SubmenuSetting(
R.string.emulation_landscape_custom_layout,
0,
R.drawable.ic_fit_screen,
Settings.SECTION_CUSTOM_LANDSCAPE
)
)
add(
SubmenuSetting(
R.string.emulation_portrait_custom_layout,
0,
R.drawable.ic_portrait_fit_screen,
Settings.SECTION_CUSTOM_PORTRAIT
)
)
}
}
private fun addCustomLandscapeSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.emulation_landscape_custom_layout))
sl.apply {
add(HeaderSetting(R.string.emulation_top_screen))
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_X,
R.string.emulation_custom_layout_x,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_TOP_X.key,
IntSetting.LANDSCAPE_TOP_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_Y,
R.string.emulation_custom_layout_y,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_TOP_Y.key,
IntSetting.LANDSCAPE_TOP_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_TOP_WIDTH.key,
IntSetting.LANDSCAPE_TOP_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_TOP_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_TOP_HEIGHT.key,
IntSetting.LANDSCAPE_TOP_HEIGHT.defaultValue.toFloat()
)
)
add(HeaderSetting(R.string.emulation_bottom_screen))
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_X,
R.string.emulation_custom_layout_x,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_BOTTOM_X.key,
IntSetting.LANDSCAPE_BOTTOM_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_Y,
R.string.emulation_custom_layout_y,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_BOTTOM_Y.key,
IntSetting.LANDSCAPE_BOTTOM_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getHeight(),
"px",
IntSetting.LANDSCAPE_BOTTOM_WIDTH.key,
IntSetting.LANDSCAPE_BOTTOM_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.LANDSCAPE_BOTTOM_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getWidth(),
"px",
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.key,
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.defaultValue.toFloat()
)
)
}
}
private fun addCustomPortraitSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.emulation_portrait_custom_layout))
sl.apply {
add(HeaderSetting(R.string.emulation_top_screen))
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_X,
R.string.emulation_custom_layout_x,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_TOP_X.key,
IntSetting.PORTRAIT_TOP_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_Y,
R.string.emulation_custom_layout_y,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_TOP_Y.key,
IntSetting.PORTRAIT_TOP_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_TOP_WIDTH.key,
IntSetting.PORTRAIT_TOP_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_TOP_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_TOP_HEIGHT.key,
IntSetting.PORTRAIT_TOP_HEIGHT.defaultValue.toFloat()
)
)
add(HeaderSetting(R.string.emulation_bottom_screen))
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_X,
R.string.emulation_custom_layout_x,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_BOTTOM_X.key,
IntSetting.PORTRAIT_BOTTOM_X.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_Y,
R.string.emulation_custom_layout_y,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_BOTTOM_Y.key,
IntSetting.PORTRAIT_BOTTOM_Y.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_WIDTH,
R.string.emulation_custom_layout_width,
0,
0,
getWidth(),
"px",
IntSetting.PORTRAIT_BOTTOM_WIDTH.key,
IntSetting.PORTRAIT_BOTTOM_WIDTH.defaultValue.toFloat()
)
)
add(
SliderSetting(
IntSetting.PORTRAIT_BOTTOM_HEIGHT,
R.string.emulation_custom_layout_height,
0,
0,
getHeight(),
"px",
IntSetting.PORTRAIT_BOTTOM_HEIGHT.key,
IntSetting.PORTRAIT_BOTTOM_HEIGHT.defaultValue.toFloat()
)
)
}
}
private fun addAudioSettings(sl: ArrayList<SettingsItem>) { private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio)) settingsActivity.setToolbarTitle(settingsActivity.getString(R.string.preferences_audio))
sl.apply { sl.apply {

View File

@ -13,6 +13,8 @@ import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.SystemClock import android.os.SystemClock
import android.text.Editable
import android.text.TextWatcher
import android.view.Choreographer import android.view.Choreographer
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
@ -54,6 +56,7 @@ import org.citra.citra_emu.databinding.FragmentEmulationBinding
import org.citra.citra_emu.display.PortraitScreenLayout import org.citra.citra_emu.display.PortraitScreenLayout
import org.citra.citra_emu.display.ScreenAdjustmentUtil import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.display.ScreenLayout import org.citra.citra_emu.display.ScreenLayout
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.model.SettingsViewModel
import org.citra.citra_emu.features.settings.ui.SettingsActivity import org.citra.citra_emu.features.settings.ui.SettingsActivity
import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.features.settings.utils.SettingsFile
@ -324,6 +327,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
SettingsFile.FILE_NAME_CONFIG, SettingsFile.FILE_NAME_CONFIG,
"" ""
) )
true true
} }
@ -808,7 +812,10 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu) popupMenu.menuInflater.inflate(R.menu.menu_landscape_screen_layout, popupMenu.menu)
val layoutOptionMenuItem = when (EmulationMenuSettings.landscapeScreenLayout) { val layoutOptionMenuItem = when (IntSetting.SCREEN_LAYOUT.int) {
ScreenLayout.ORIGINAL.int ->
R.id.menu_screen_layout_original
ScreenLayout.SINGLE_SCREEN.int -> ScreenLayout.SINGLE_SCREEN.int ->
R.id.menu_screen_layout_single R.id.menu_screen_layout_single
@ -847,7 +854,17 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true true
} }
R.id.menu_screen_layout_original -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.ORIGINAL.int)
true
}
R.id.menu_screen_layout_custom -> { 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) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.CUSTOM_LAYOUT.int)
true true
} }
@ -867,7 +884,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.menuInflater.inflate(R.menu.menu_portrait_screen_layout, popupMenu.menu) popupMenu.menuInflater.inflate(R.menu.menu_portrait_screen_layout, popupMenu.menu)
val layoutOptionMenuItem = when (EmulationMenuSettings.portraitScreenLayout) { val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) {
PortraitScreenLayout.TOP_FULL_WIDTH.int -> PortraitScreenLayout.TOP_FULL_WIDTH.int ->
R.id.menu_portrait_layout_top_full R.id.menu_portrait_layout_top_full
@ -889,6 +906,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
R.id.menu_portrait_layout_custom -> { 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) screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int)
true true
} }
@ -941,14 +963,32 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
sliderBinding.apply { sliderBinding.apply {
slider.valueTo = 150f slider.valueTo = 150f
slider.valueFrom = 0f
slider.value = preferences.getInt(target, 50).toFloat() slider.value = preferences.getInt(target, 50).toFloat()
textValue.setText((slider.value + 50).toInt().toString())
textValue.addTextChangedListener( object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val value = s.toString().toIntOrNull()
if (value == null || value < 50 || value > 150) {
textInput.error = "Inappropriate Value"
} else {
textInput.error = null
slider.value = value.toFloat() - 50
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
slider.addOnChangeListener( slider.addOnChangeListener(
Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean -> Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean ->
textValue.text = (progress.toInt() + 50).toString() if (textValue.text.toString() != (slider.value + 50).toInt().toString()) {
textValue.setText((slider.value + 50).toInt().toString())
textValue.setSelection(textValue.length())
setControlScale(slider.value.toInt(), target) setControlScale(slider.value.toInt(), target)
}
}) })
textValue.text = (sliderBinding.slider.value.toInt() + 50).toString() textInput.suffixText = "%"
textUnits.text = "%"
} }
val previousProgress = sliderBinding.slider.value.toInt() val previousProgress = sliderBinding.slider.value.toInt()
@ -971,15 +1011,36 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val sliderBinding = DialogSliderBinding.inflate(layoutInflater) val sliderBinding = DialogSliderBinding.inflate(layoutInflater)
sliderBinding.apply { sliderBinding.apply {
slider.valueFrom = 0f
slider.valueTo = 100f slider.valueTo = 100f
slider.value = preferences.getInt("controlOpacity", 50).toFloat() slider.value = preferences.getInt("controlOpacity", 50).toFloat()
slider.addOnChangeListener( textValue.setText(slider.value.toInt().toString())
Slider.OnChangeListener { slider: Slider, progress: Float, _: Boolean ->
textValue.text = (progress.toInt()).toString() textValue.addTextChangedListener( object : TextWatcher {
setControlOpacity(slider.value.toInt()) override fun afterTextChanged(s: Editable) {
val value = s.toString().toIntOrNull()
if (value == null || value < slider.valueFrom || value > slider.valueTo) {
textInput.error = "Inappropriate Value"
} else {
textInput.error = null
slider.value = value.toFloat()
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
}) })
textValue.text = (sliderBinding.slider.value.toInt()).toString()
textUnits.text = "%"
slider.addOnChangeListener { _: Slider, value: Float, _: Boolean ->
if (textValue.text.toString() != slider.value.toInt().toString()) {
textValue.setText(slider.value.toInt().toString())
textValue.setSelection(textValue.length())
setControlOpacity(slider.value.toInt())
}
}
textInput.suffixText = "%"
} }
val previousProgress = sliderBinding.slider.value.toInt() val previousProgress = sliderBinding.slider.value.toInt()
@ -1008,7 +1069,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
private fun resetScale(target: String) { private fun resetScale(target: String) {
preferences.edit().putInt( preferences.edit().putInt(
target, target,
50 100
).apply() ).apply()
} }

View File

@ -7,8 +7,6 @@ package org.citra.citra_emu.utils
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import org.citra.citra_emu.CitraApplication import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.display.PortraitScreenLayout
import org.citra.citra_emu.display.ScreenLayout
object EmulationMenuSettings { object EmulationMenuSettings {
private val preferences = private val preferences =
@ -28,26 +26,7 @@ object EmulationMenuSettings {
.putBoolean("EmulationMenuSettings_DpadSlideEnable", value) .putBoolean("EmulationMenuSettings_DpadSlideEnable", value)
.apply() .apply()
} }
var landscapeScreenLayout: Int
get() = preferences.getInt(
"EmulationMenuSettings_LandscapeScreenLayout",
ScreenLayout.LARGE_SCREEN.int
)
set(value) {
preferences.edit()
.putInt("EmulationMenuSettings_LandscapeScreenLayout", value)
.apply()
}
var portraitScreenLayout: Int
get() = preferences.getInt(
"EmulationMenuSettings_PortraitScreenLayout",
PortraitScreenLayout.TOP_FULL_WIDTH.int
)
set(value) {
preferences.edit()
.putInt("EmulationMenuSettings_PortraitScreenLayout", value)
.apply()
}
var showFps: Boolean var showFps: Boolean
get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false) get() = preferences.getBoolean("EmulationMenuSettings_ShowFps", false)
set(value) { set(value) {

View File

@ -176,7 +176,6 @@ void Config::ReadValues() {
// Layout // Layout
Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger( Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger(
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::LargeScreen))); "Layout", "layout_option", static_cast<int>(Settings::LayoutOption::LargeScreen)));
ReadSetting("Layout", Settings::values.custom_layout);
ReadSetting("Layout", Settings::values.custom_top_x); ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y); ReadSetting("Layout", Settings::values.custom_top_y);
ReadSetting("Layout", Settings::values.custom_top_width); ReadSetting("Layout", Settings::values.custom_top_width);

View File

@ -184,16 +184,19 @@ delay_game_render_thread_us =
[Layout] [Layout]
# Layout for the screen inside the render window, landscape mode # Layout for the screen inside the render window, landscape mode
# 0 (default): Default Top Bottom Screen, # 0: Top/Bottom *currently unsupported on android*
# 1: Single Screen Only, # 1: Single Screen Only,
# 2: Large Screen Small Screen # 2: *currently unsupported on android*
# 3: Side by Side # 3: Side by Side
# 4: Hybrid # 4: Hybrid
# 5: Custom Layout # 5: Custom Layout
# 6: (default) Large screen / small screen
layout_option = layout_option =
# Screen placement when using Custom layout option # Screen placement when using Custom layout option
# 0x, 0y is the top left corner of the render window. # 0x, 0y is the top left corner of the render window.
# suggested aspect ratio for top screen is 5:3
# suggested aspect ratio for bottom screen is 4:3
custom_top_x = custom_top_x =
custom_top_y = custom_top_y =
custom_top_width = custom_top_width =

View File

@ -21,18 +21,6 @@ static bool IsPortraitMode() {
IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode()); IDCache::GetNativeLibraryClass(), IDCache::GetIsPortraitMode());
} }
static void UpdateLandscapeScreenLayout() {
Settings::values.layout_option =
static_cast<Settings::LayoutOption>(IDCache::GetEnvForThread()->CallStaticIntMethod(
IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout()));
}
static void UpdatePortraitScreenLayout() {
Settings::values.portrait_layout_option =
static_cast<Settings::PortraitLayoutOption>(IDCache::GetEnvForThread()->CallStaticIntMethod(
IDCache::GetNativeLibraryClass(), IDCache::GetPortraitScreenLayout()));
}
bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) { bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
if (render_window == surface) { if (render_window == surface) {
return false; return false;
@ -61,8 +49,6 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) {
} }
void EmuWindow_Android::OnFramebufferSizeChanged() { void EmuWindow_Android::OnFramebufferSizeChanged() {
UpdateLandscapeScreenLayout();
UpdatePortraitScreenLayout();
const bool is_portrait_mode{IsPortraitMode()}; const bool is_portrait_mode{IsPortraitMode()};
const int bigger{window_width > window_height ? window_width : window_height}; const int bigger{window_width > window_height ? window_width : window_height};

View File

@ -177,10 +177,6 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_native_library_class, "onCoreError", s_native_library_class, "onCoreError",
"(Lorg/citra/citra_emu/NativeLibrary$CoreError;Ljava/lang/String;)Z"); "(Lorg/citra/citra_emu/NativeLibrary$CoreError;Ljava/lang/String;)Z");
s_is_portrait_mode = env->GetStaticMethodID(s_native_library_class, "isPortraitMode", "()Z"); s_is_portrait_mode = env->GetStaticMethodID(s_native_library_class, "isPortraitMode", "()Z");
s_landscape_screen_layout =
env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I");
s_portrait_screen_layout =
env->GetStaticMethodID(s_native_library_class, "portraitScreenLayout", "()I");
s_exit_emulation_activity = s_exit_emulation_activity =
env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V");
s_request_camera_permission = s_request_camera_permission =

View File

@ -353,25 +353,14 @@ void Java_org_citra_citra_1emu_NativeLibrary_notifyOrientationChange([[maybe_unu
jint rotation, jint rotation,
jboolean portrait) { jboolean portrait) {
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option); Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option);
}
void Java_org_citra_citra_1emu_NativeLibrary_updateFramebuffer([[maybe_unused]] JNIEnv* env,
[[maybe_unused]] jobject obj,
jboolean is_portrait_mode) {
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) { if (system.IsPoweredOn()) {
system.GPU().Renderer().UpdateCurrentFramebufferLayout(is_portrait_mode);
system.GPU().Renderer().UpdateCurrentFramebufferLayout(portrait);
} }
InputManager::screen_rotation = rotation;
Camera::NDK::g_rotation = rotation;
}
void Java_io_github_lime3ds_android_NativeLibrary_notifyPortraitLayoutChange(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, jint layout_option, jint rotation) {
Settings::values.portrait_layout_option =
static_cast<Settings::PortraitLayoutOption>(layout_option);
auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) {
system.GPU().Renderer().UpdateCurrentFramebufferLayout(!(rotation % 2));
}
InputManager::screen_rotation = rotation;
Camera::NDK::g_rotation = rotation;
} }
void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv* env, void Java_org_citra_citra_1emu_NativeLibrary_swapScreens([[maybe_unused]] JNIEnv* env,

View File

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

View File

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

View File

@ -1,127 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="org.citra.citra_emu.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_portrait_layout_top_full"
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

@ -29,12 +29,13 @@
<item <item
android:id="@+id/menu_portrait_screen_layout" android:id="@+id/menu_portrait_screen_layout"
android:icon="@drawable/ic_fit_screen" android:icon="@drawable/ic_portrait_fit_screen"
android:title="@string/emulation_switch_portrait_layout" /> android:title="@string/emulation_switch_portrait_layout" />
<item <item
android:id="@+id/menu_swap_screens" android:id="@+id/menu_swap_screens"
android:icon="@drawable/ic_splitscreen" android:icon="@drawable/ic_splitscreen"
android:title="@string/emulation_swap_screens" /> android:title="@string/emulation_swap_screens" />
<item <item

View File

@ -19,9 +19,14 @@
android:id="@+id/menu_screen_layout_hybrid" android:id="@+id/menu_screen_layout_hybrid"
android:title="@string/emulation_screen_layout_hybrid" /> android:title="@string/emulation_screen_layout_hybrid" />
<item
android:id="@+id/menu_screen_layout_original"
android:title="@string/emulation_screen_layout_original" />
<item <item
android:id="@+id/menu_screen_layout_custom" android:id="@+id/menu_screen_layout_custom"
android:title="@string/emulation_screen_layout_custom" /> android:title="@string/emulation_screen_layout_custom" />
</group> </group>
</menu> </menu>

View File

@ -10,6 +10,7 @@
<item <item
android:id="@+id/menu_portrait_layout_custom" android:id="@+id/menu_portrait_layout_custom"
android:title="@string/emulation_screen_layout_custom" /> android:title="@string/emulation_screen_layout_custom" />
</group> </group>
</menu> </menu>

View File

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

View File

@ -334,7 +334,7 @@
<string name="preferences_audio">Audio</string> <string name="preferences_audio">Audio</string>
<string name="preferences_debug">Debug</string> <string name="preferences_debug">Debug</string>
<string name="preferences_theme">Theme and Color</string> <string name="preferences_theme">Theme and Color</string>
<string name="preferences_layout">Layout</string>
<!-- ROM loading errors --> <!-- ROM loading errors -->
<string name="loader_error_encrypted">Your ROM is Encrypted</string> <string name="loader_error_encrypted">Your ROM is Encrypted</string>
<string name="loader_error_invalid_format">Invalid ROM format</string> <string name="loader_error_invalid_format">Invalid ROM format</string>
@ -369,8 +369,18 @@
<string name="emulation_screen_layout_single">Single Screen</string> <string name="emulation_screen_layout_single">Single Screen</string>
<string name="emulation_screen_layout_sidebyside">Side by Side Screens</string> <string name="emulation_screen_layout_sidebyside">Side by Side Screens</string>
<string name="emulation_screen_layout_hybrid">Hybrid Screens</string> <string name="emulation_screen_layout_hybrid">Hybrid Screens</string>
<string name="emulation_screen_layout_original">Original</string>
<string name="emulation_portrait_layout_top_full">Default</string> <string name="emulation_portrait_layout_top_full">Default</string>
<string name="emulation_screen_layout_custom">Custom Layout</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_cycle_landscape_layouts">Cycle Layouts</string>
<string name="emulation_swap_screens">Swap Screens</string> <string name="emulation_swap_screens">Swap Screens</string>
<string name="emulation_touch_overlay_reset">Reset Overlay</string> <string name="emulation_touch_overlay_reset">Reset Overlay</string>

View File

@ -166,8 +166,6 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.swap_screen); ReadSetting("Layout", Settings::values.swap_screen);
ReadSetting("Layout", Settings::values.upright_screen); ReadSetting("Layout", Settings::values.upright_screen);
ReadSetting("Layout", Settings::values.large_screen_proportion); ReadSetting("Layout", Settings::values.large_screen_proportion);
ReadSetting("Layout", Settings::values.custom_layout);
ReadSetting("Layout", Settings::values.custom_top_x); ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y); ReadSetting("Layout", Settings::values.custom_top_y);
ReadSetting("Layout", Settings::values.custom_top_width); ReadSetting("Layout", Settings::values.custom_top_width);

View File

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

View File

@ -521,8 +521,6 @@ void Config::ReadLayoutValues() {
if (global) { if (global) {
ReadBasicSetting(Settings::values.mono_render_option); ReadBasicSetting(Settings::values.mono_render_option);
ReadBasicSetting(Settings::values.custom_layout);
ReadBasicSetting(Settings::values.custom_top_x); ReadBasicSetting(Settings::values.custom_top_x);
ReadBasicSetting(Settings::values.custom_top_y); ReadBasicSetting(Settings::values.custom_top_y);
ReadBasicSetting(Settings::values.custom_top_width); ReadBasicSetting(Settings::values.custom_top_width);
@ -1087,8 +1085,6 @@ void Config::SaveLayoutValues() {
if (global) { if (global) {
WriteBasicSetting(Settings::values.mono_render_option); WriteBasicSetting(Settings::values.mono_render_option);
WriteBasicSetting(Settings::values.custom_layout);
WriteBasicSetting(Settings::values.custom_top_x); WriteBasicSetting(Settings::values.custom_top_x);
WriteBasicSetting(Settings::values.custom_top_y); WriteBasicSetting(Settings::values.custom_top_y);
WriteBasicSetting(Settings::values.custom_top_width); WriteBasicSetting(Settings::values.custom_top_width);

View File

@ -495,9 +495,6 @@ struct Values {
SwitchableSetting<bool> upright_screen{false, "upright_screen"}; SwitchableSetting<bool> upright_screen{false, "upright_screen"};
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f, SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
"large_screen_proportion"}; "large_screen_proportion"};
// I think the custom_layout setting below is no longer needed
// since custom layout is now just part of the layout option above?
Setting<bool> custom_layout{false, "custom_layout"};
Setting<u16> custom_top_x{0, "custom_top_x"}; Setting<u16> custom_top_x{0, "custom_top_x"};
Setting<u16> custom_top_y{0, "custom_top_y"}; Setting<u16> custom_top_y{0, "custom_top_y"};
Setting<u16> custom_top_width{800, "custom_top_width"}; Setting<u16> custom_top_width{800, "custom_top_width"};

View File

@ -121,7 +121,7 @@ FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool swapped
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}}; FramebufferLayout res{width, height, true, true, {}, {}, true, true};
// Default layout gives equal screen sizes to the top and bottom screen // Default layout gives equal screen sizes to the top and bottom screen
Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2}; Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2};
Common::Rectangle<u32> top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO); Common::Rectangle<u32> top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
@ -305,7 +305,7 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}, !upright, true, {}}; FramebufferLayout res{width, height, true, true, {}, {}, !upright, false, true, {}};
// Split the window into two parts. Give 2.25x width to the main screen, // Split the window into two parts. Give 2.25x width to the main screen,
// and make a bar on the right side with 1x width top screen and 1.25x width bottom screen // and make a bar on the right side with 1x width top screen and 1.25x width bottom screen
@ -386,7 +386,8 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}, !Settings::values.upright_screen}; FramebufferLayout res{
width, height, true, true, {}, {}, !Settings::values.upright_screen, is_portrait_mode};
u16 top_x = is_portrait_mode ? Settings::values.custom_portrait_top_x.GetValue() u16 top_x = is_portrait_mode ? Settings::values.custom_portrait_top_x.GetValue()
: Settings::values.custom_top_x.GetValue(); : Settings::values.custom_top_x.GetValue();
u16 top_width = is_portrait_mode ? Settings::values.custom_portrait_top_width.GetValue() u16 top_width = is_portrait_mode ? Settings::values.custom_portrait_top_width.GetValue()
@ -629,9 +630,7 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
return new_layout; return new_layout;
} }
/*f
* TODO: remove this?
*/
std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout() { std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout() {
u32 min_width, min_height; u32 min_width, min_height;
min_width = Core::kScreenTopWidth; min_width = Core::kScreenTopWidth;

View File

@ -61,7 +61,7 @@ struct FramebufferLayout {
Common::Rectangle<u32> top_screen; Common::Rectangle<u32> top_screen;
Common::Rectangle<u32> bottom_screen; Common::Rectangle<u32> bottom_screen;
bool is_rotated = true; bool is_rotated = true;
bool is_portrait = false;
bool additional_screen_enabled; bool additional_screen_enabled;
Common::Rectangle<u32> additional_screen; Common::Rectangle<u32> additional_screen;

View File

@ -635,10 +635,6 @@ void RendererOpenGL::DrawSingleScreenStereo(const ScreenInfo& screen_info_l,
* Draws the emulated screens to the emulator window. * Draws the emulated screens to the emulator window.
*/ */
void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool flipped) { void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool flipped) {
bool isPortrait = false;
#ifdef ANDROID
isPortrait = layout.height > layout.width;
#endif
if (settings.bg_color_update_requested.exchange(false)) { if (settings.bg_color_update_requested.exchange(false)) {
// Update background color before drawing // Update background color before drawing
glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(), glClearColor(Settings::values.bg_red.GetValue(), Settings::values.bg_green.GetValue(),
@ -680,12 +676,12 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
if (!Settings::values.swap_screen.GetValue()) { if (!Settings::values.swap_screen.GetValue()) {
DrawTopScreen(layout, top_screen); DrawTopScreen(layout, top_screen);
glUniform1i(uniform_layer, 0); glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity(isPortrait); ApplySecondLayerOpacity(layout.is_portrait);
DrawBottomScreen(layout, bottom_screen); DrawBottomScreen(layout, bottom_screen);
} else { } else {
DrawBottomScreen(layout, bottom_screen); DrawBottomScreen(layout, bottom_screen);
glUniform1i(uniform_layer, 0); glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity(isPortrait); ApplySecondLayerOpacity(layout.is_portrait);
DrawTopScreen(layout, top_screen); DrawTopScreen(layout, top_screen);
} }
@ -697,19 +693,14 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
DrawBottomScreen(layout, additional_screen); DrawBottomScreen(layout, additional_screen);
} }
} }
ResetSecondLayerOpacity(isPortrait); ResetSecondLayerOpacity(layout.is_portrait);
} }
void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) { void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) {
#ifdef ANDROID
// TODO: Allow for second layer opacity in portrait mode android // TODO: Allow for second layer opacity in portrait mode android
if (isPortrait) {
return;
}
#endif
if ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout || if (!isPortrait &&
Settings::values.custom_layout) && (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) { Settings::values.custom_second_layer_opacity.GetValue() < 100) {
state.blend.src_rgb_func = GL_CONSTANT_ALPHA; state.blend.src_rgb_func = GL_CONSTANT_ALPHA;
state.blend.src_a_func = GL_CONSTANT_ALPHA; state.blend.src_a_func = GL_CONSTANT_ALPHA;
@ -720,15 +711,8 @@ void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) {
} }
void RendererOpenGL::ResetSecondLayerOpacity(bool isPortrait) { void RendererOpenGL::ResetSecondLayerOpacity(bool isPortrait) {
#ifdef ANDROID if (!isPortrait &&
// TODO: Allow for second layer opacity in portrait mode android (Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout) &&
if (isPortrait) {
return;
}
#endif
if ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout ||
Settings::values.custom_layout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) { Settings::values.custom_second_layer_opacity.GetValue() < 100) {
state.blend.src_rgb_func = GL_ONE; state.blend.src_rgb_func = GL_ONE;
state.blend.dst_rgb_func = GL_ZERO; state.blend.dst_rgb_func = GL_ZERO;