Refactored layout code in preparation for Android custom layout GUI

This commit is contained in:
David Griswold 2024-08-11 15:59:41 +01:00 committed by OpenSauce
parent c5ac6e84da
commit 7e5b83f126
29 changed files with 453 additions and 245 deletions

View File

@ -158,8 +158,9 @@ object NativeLibrary {
/** /**
* Notifies the core emulation that the orientation has changed. * Notifies the core emulation that the orientation has changed.
*/ */
external fun notifyOrientationChange(layoutOption: Int, rotation: Int) external fun notifyOrientationChange(layoutOption: Int, rotation: Int, isPortrait: Boolean)
external fun notifyPortraitLayoutChange(layoutOption: Int, rotation: Int, isPortrait: Boolean)
/** /**
* Swaps the top and bottom screens. * Swaps the top and bottom screens.
*/ */
@ -266,6 +267,10 @@ object NativeLibrary {
@JvmStatic @JvmStatic
fun landscapeScreenLayout(): Int = EmulationMenuSettings.landscapeScreenLayout 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

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

@ -12,8 +12,10 @@ import io.github.lime3ds.android.features.settings.model.Settings
import io.github.lime3ds.android.features.settings.utils.SettingsFile import io.github.lime3ds.android.features.settings.utils.SettingsFile
import io.github.lime3ds.android.utils.EmulationMenuSettings import io.github.lime3ds.android.utils.EmulationMenuSettings
class ScreenAdjustmentUtil(private val windowManager: WindowManager, class ScreenAdjustmentUtil(
private val settings: Settings) { private val windowManager: WindowManager,
private val settings: Settings
) {
fun swapScreen() { fun swapScreen() {
val isEnabled = !EmulationMenuSettings.swapScreens val isEnabled = !EmulationMenuSettings.swapScreens
EmulationMenuSettings.swapScreens = isEnabled EmulationMenuSettings.swapScreens = isEnabled
@ -25,18 +27,39 @@ class ScreenAdjustmentUtil(private val windowManager: WindowManager,
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 = (EmulationMenuSettings.landscapeScreenLayout + 1) % ScreenLayout.entries.size val nextLayout = if (NativeLibrary.isPortraitMode) {
changeScreenOrientation(ScreenLayout.from(nextLayout)) (EmulationMenuSettings.portraitScreenLayout + 1) % (PortraitScreenLayout.entries.size - 1)
} else {
(EmulationMenuSettings.landscapeScreenLayout + 1) % (ScreenLayout.entries.size - 1)
}
settings.loadSettings()
changeScreenOrientation(nextLayout)
} }
fun changeScreenOrientation(layoutOption: ScreenLayout) { fun changePortraitOrientation(layoutOption: Int) {
EmulationMenuSettings.landscapeScreenLayout = layoutOption.int EmulationMenuSettings.portraitScreenLayout = layoutOption
NativeLibrary.notifyPortraitLayoutChange(
EmulationMenuSettings.portraitScreenLayout,
windowManager.defaultDisplay.rotation,
NativeLibrary::isPortraitMode.get()
)
IntSetting.PORTRAIT_SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.PORTRAIT_SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
}
fun changeScreenOrientation(layoutOption: Int) {
EmulationMenuSettings.landscapeScreenLayout = layoutOption
NativeLibrary.notifyOrientationChange( NativeLibrary.notifyOrientationChange(
EmulationMenuSettings.landscapeScreenLayout, EmulationMenuSettings.landscapeScreenLayout,
windowManager.defaultDisplay.rotation windowManager.defaultDisplay.rotation,
NativeLibrary::isPortraitMode.get()
) )
IntSetting.SCREEN_LAYOUT.int = layoutOption.int IntSetting.SCREEN_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG) settings.saveSetting(IntSetting.SCREEN_LAYOUT, SettingsFile.FILE_NAME_CONFIG)
} }
} }

View File

@ -6,17 +6,18 @@ package io.github.lime3ds.android.display
enum class ScreenLayout(val int: Int) { enum class ScreenLayout(val int: Int) {
// These must match what is defined in src/common/settings.h // These must match what is defined in src/common/settings.h
DEFAULT(0), TOP_BOTTOM(0),
SINGLE_SCREEN(1), SINGLE_SCREEN(1),
LARGE_SCREEN(2), LARGE_SCREEN(2),
SIDE_SCREEN(3), SIDE_SCREEN(3),
HYBRID_SCREEN(4), HYBRID_SCREEN(4),
MOBILE_PORTRAIT(5), CUSTOM_LAYOUT(5),
MOBILE_LANDSCAPE(6); MOBILE_LANDSCAPE(6);
companion object { companion object {
fun from(int: Int): ScreenLayout { fun from(int: Int): ScreenLayout {
return entries.firstOrNull { it.int == int } ?: DEFAULT return entries.firstOrNull { it.int == int } ?: MOBILE_LANDSCAPE
} }
} }
} }

View File

@ -14,6 +14,7 @@ enum class BooleanSetting(
PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false), PLUGIN_LOADER("plugin_loader", Settings.SECTION_SYSTEM, false),
ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true), ALLOW_PLUGIN_LOADER("allow_plugin_loader", Settings.SECTION_SYSTEM, true),
SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false), SWAP_SCREEN("swap_screen", Settings.SECTION_LAYOUT, false),
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
ADRENO_GPU_BOOST("adreno_gpu_boost", Settings.SECTION_RENDERER, false); ADRENO_GPU_BOOST("adreno_gpu_boost", Settings.SECTION_RENDERER, false);
override var boolean: Boolean = defaultValue override var boolean: Boolean = defaultValue

View File

@ -23,6 +23,7 @@ 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),
PORTRAIT_SCREEN_LAYOUT("portrait_layout_option",Settings.SECTION_LAYOUT,0),
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

@ -131,7 +131,6 @@ class Settings {
const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal" const val KEY_CSTICK_AXIS_HORIZONTAL = "cstick_axis_horizontal"
const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical" const val KEY_DPAD_AXIS_VERTICAL = "dpad_axis_vertical"
const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal" const val KEY_DPAD_AXIS_HORIZONTAL = "dpad_axis_horizontal"
const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap" const val HOTKEY_SCREEN_SWAP = "hotkey_screen_swap"
const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout" const val HOTKEY_CYCLE_LAYOUT = "hotkey_toggle_layout"
const val HOTKEY_CLOSE_GAME = "hotkey_close_game" const val HOTKEY_CLOSE_GAME = "hotkey_close_game"

View File

@ -51,6 +51,7 @@ import io.github.lime3ds.android.activities.EmulationActivity
import io.github.lime3ds.android.databinding.DialogCheckboxBinding import io.github.lime3ds.android.databinding.DialogCheckboxBinding
import io.github.lime3ds.android.databinding.DialogSliderBinding import io.github.lime3ds.android.databinding.DialogSliderBinding
import io.github.lime3ds.android.databinding.FragmentEmulationBinding import io.github.lime3ds.android.databinding.FragmentEmulationBinding
import io.github.lime3ds.android.display.PortraitScreenLayout
import io.github.lime3ds.android.display.ScreenAdjustmentUtil import io.github.lime3ds.android.display.ScreenAdjustmentUtil
import io.github.lime3ds.android.display.ScreenLayout import io.github.lime3ds.android.display.ScreenLayout
import io.github.lime3ds.android.features.settings.model.SettingsViewModel import io.github.lime3ds.android.features.settings.model.SettingsViewModel
@ -142,7 +143,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
retainInstance = true retainInstance = true
emulationState = EmulationState(game.path) emulationState = EmulationState(game.path)
emulationActivity = requireActivity() as EmulationActivity emulationActivity = requireActivity() as EmulationActivity
screenAdjustmentUtil = ScreenAdjustmentUtil(emulationActivity.windowManager, settingsViewModel.settings) screenAdjustmentUtil =
ScreenAdjustmentUtil(emulationActivity.windowManager, settingsViewModel.settings)
EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() }) EmulationLifecycleUtil.addShutdownHook(hook = { emulationState.stop() })
EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() }) EmulationLifecycleUtil.addPauseResumeHook(hook = { togglePause() })
} }
@ -207,16 +209,18 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
}) })
binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply { binding.inGameMenu.menu.findItem(R.id.menu_lock_drawer).apply {
val titleId = if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) { val titleId =
R.string.unlock_drawer if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_LOCKED_CLOSED) {
} else { R.string.unlock_drawer
R.string.lock_drawer } else {
} R.string.lock_drawer
val iconId = if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_UNLOCKED) { }
R.drawable.ic_unlocked val iconId =
} else { if (EmulationMenuSettings.drawerLockMode == DrawerLayout.LOCK_MODE_UNLOCKED) {
R.drawable.ic_lock R.drawable.ic_unlocked
} } else {
R.drawable.ic_lock
}
title = getString(titleId) title = getString(titleId)
icon = ResourcesCompat.getDrawable( icon = ResourcesCompat.getDrawable(
@ -267,7 +271,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
R.id.menu_landscape_screen_layout -> { R.id.menu_landscape_screen_layout -> {
showScreenLayoutMenu() showLandscapeScreenLayoutMenu()
true
}
R.id.menu_portrait_screen_layout -> {
showPortraitScreenLayoutMenu()
true true
} }
@ -423,7 +432,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
} }
private fun togglePause() { private fun togglePause() {
if(emulationState.isPaused) { if (emulationState.isPaused) {
emulationState.unpause() emulationState.unpause()
} else { } else {
emulationState.pause() emulationState.pause()
@ -769,7 +778,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.show() popupMenu.show()
} }
private fun showScreenLayoutMenu() { private fun showLandscapeScreenLayoutMenu() {
val popupMenu = PopupMenu( val popupMenu = PopupMenu(
requireContext(), requireContext(),
binding.inGameMenu.findViewById(R.id.menu_landscape_screen_layout) binding.inGameMenu.findViewById(R.id.menu_landscape_screen_layout)
@ -784,12 +793,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
ScreenLayout.SIDE_SCREEN.int -> ScreenLayout.SIDE_SCREEN.int ->
R.id.menu_screen_layout_sidebyside R.id.menu_screen_layout_sidebyside
ScreenLayout.MOBILE_PORTRAIT.int ->
R.id.menu_screen_layout_portrait
ScreenLayout.HYBRID_SCREEN.int -> ScreenLayout.HYBRID_SCREEN.int ->
R.id.menu_screen_layout_hybrid R.id.menu_screen_layout_hybrid
ScreenLayout.CUSTOM_LAYOUT.int ->
R.id.menu_screen_layout_custom
else -> R.id.menu_screen_layout_landscape else -> R.id.menu_screen_layout_landscape
} }
popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true) popupMenu.menu.findItem(layoutOptionMenuItem).setChecked(true)
@ -797,27 +806,68 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.setOnMenuItemClickListener { popupMenu.setOnMenuItemClickListener {
when (it.itemId) { when (it.itemId) {
R.id.menu_screen_layout_landscape -> { R.id.menu_screen_layout_landscape -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_LANDSCAPE) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_LANDSCAPE.int)
true
}
R.id.menu_screen_layout_portrait -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.MOBILE_PORTRAIT)
true true
} }
R.id.menu_screen_layout_single -> { R.id.menu_screen_layout_single -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SINGLE_SCREEN) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SINGLE_SCREEN.int)
true true
} }
R.id.menu_screen_layout_sidebyside -> { R.id.menu_screen_layout_sidebyside -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SIDE_SCREEN) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.SIDE_SCREEN.int)
true true
} }
R.id.menu_screen_layout_hybrid -> { R.id.menu_screen_layout_hybrid -> {
screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.HYBRID_SCREEN) screenAdjustmentUtil.changeScreenOrientation(ScreenLayout.HYBRID_SCREEN.int)
true
}
R.id.menu_screen_layout_custom -> {
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 (EmulationMenuSettings.portraitScreenLayout) {
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 -> {
screenAdjustmentUtil.changePortraitOrientation(PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int)
true true
} }
@ -959,7 +1009,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_SWAP) resetScale("controlScale-" + NativeLibrary.ButtonType.BUTTON_SWAP)
binding.surfaceInputOverlay.refreshControls() binding.surfaceInputOverlay.refreshControls()
} }
private fun setControlOpacity(opacity: Int) { private fun setControlOpacity(opacity: Int) {
preferences.edit() preferences.edit()
.putInt("controlOpacity", opacity) .putInt("controlOpacity", opacity)

View File

@ -7,6 +7,7 @@ package io.github.lime3ds.android.utils
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import io.github.lime3ds.android.LimeApplication import io.github.lime3ds.android.LimeApplication
import io.github.lime3ds.android.display.PortraitScreenLayout
import io.github.lime3ds.android.display.ScreenLayout import io.github.lime3ds.android.display.ScreenLayout
object EmulationMenuSettings { object EmulationMenuSettings {
@ -30,13 +31,23 @@ object EmulationMenuSettings {
var landscapeScreenLayout: Int var landscapeScreenLayout: Int
get() = preferences.getInt( get() = preferences.getInt(
"EmulationMenuSettings_LandscapeScreenLayout", "EmulationMenuSettings_LandscapeScreenLayout",
ScreenLayout.MOBILE_LANDSCAPE.int ScreenLayout.LARGE_SCREEN.int
) )
set(value) { set(value) {
preferences.edit() preferences.edit()
.putInt("EmulationMenuSettings_LandscapeScreenLayout", value) .putInt("EmulationMenuSettings_LandscapeScreenLayout", value)
.apply() .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

@ -174,7 +174,7 @@ void Config::ReadValues() {
// Layout // Layout
Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger( Settings::values.layout_option = static_cast<Settings::LayoutOption>(sdl2_config->GetInteger(
"Layout", "layout_option", static_cast<int>(Settings::LayoutOption::MobileLandscape))); "Layout", "layout_option", static_cast<int>(Settings::LayoutOption::LargeScreen)));
ReadSetting("Layout", Settings::values.custom_layout); ReadSetting("Layout", Settings::values.custom_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);
@ -188,7 +188,10 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.cardboard_x_shift); ReadSetting("Layout", Settings::values.cardboard_x_shift);
ReadSetting("Layout", Settings::values.cardboard_y_shift); ReadSetting("Layout", Settings::values.cardboard_y_shift);
ReadSetting("Layout", Settings::values.custom_portrait_layout); 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_x);
ReadSetting("Layout", Settings::values.custom_portrait_top_y); ReadSetting("Layout", Settings::values.custom_portrait_top_y);
ReadSetting("Layout", Settings::values.custom_portrait_top_width); ReadSetting("Layout", Settings::values.custom_portrait_top_width);

View File

@ -179,15 +179,15 @@ anaglyph_shader_name =
filter_mode = filter_mode =
[Layout] [Layout]
# Layout for the screen inside the render window. # Layout for the screen inside the render window, landscape mode
# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen, 3: Side by Side # 0 (default): Default Top Bottom Screen,
# 1: Single Screen Only,
# 2: Large Screen Small Screen
# 3: Side by Side
# 4: Hybrid
# 5: Custom Layout
layout_option = layout_option =
# Toggle custom layout (using the settings below) on or off.
# Only applies to landscape on Android
# 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 =
@ -199,12 +199,12 @@ custom_bottom_y =
custom_bottom_width = custom_bottom_width =
custom_bottom_height = custom_bottom_height =
# Custom Layout Options for Android Portrait Mode # Layout for the portrait mode
# Toggle custom layout (using the settings below) on or off. # 0 (default): Top and bottom screens at top, full width
# 0 (default): Off, 1: On # 1: Custom Layout
custom_portrait_layout = portrait_layout_option =
# Screen placement when using Custom layout option # Screen placement when using Portrait 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_portrait_top_x = custom_portrait_top_x =
custom_portrait_top_y = custom_portrait_top_y =

View File

@ -27,6 +27,12 @@ static void UpdateLandscapeScreenLayout() {
IDCache::GetNativeLibraryClass(), IDCache::GetLandscapeScreenLayout())); 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;
@ -58,6 +64,7 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) {
void EmuWindow_Android::OnFramebufferSizeChanged() { void EmuWindow_Android::OnFramebufferSizeChanged() {
UpdateLandscapeScreenLayout(); 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

@ -27,6 +27,7 @@ static jclass s_native_library_class;
static jmethodID s_on_core_error; static jmethodID s_on_core_error;
static jmethodID s_is_portrait_mode; static jmethodID s_is_portrait_mode;
static jmethodID s_landscape_screen_layout; static jmethodID s_landscape_screen_layout;
static jmethodID s_portrait_screen_layout;
static jmethodID s_exit_emulation_activity; static jmethodID s_exit_emulation_activity;
static jmethodID s_request_camera_permission; static jmethodID s_request_camera_permission;
static jmethodID s_request_mic_permission; static jmethodID s_request_mic_permission;
@ -90,6 +91,10 @@ jmethodID GetLandscapeScreenLayout() {
return s_landscape_screen_layout; return s_landscape_screen_layout;
} }
jmethodID GetPortraitScreenLayout() {
return s_portrait_screen_layout;
}
jmethodID GetExitEmulationActivity() { jmethodID GetExitEmulationActivity() {
return s_exit_emulation_activity; return s_exit_emulation_activity;
} }
@ -174,6 +179,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
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 = s_landscape_screen_layout =
env->GetStaticMethodID(s_native_library_class, "landscapeScreenLayout", "()I"); 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

@ -27,6 +27,7 @@ jmethodID GetDisplayAlertPrompt();
jmethodID GetAlertPromptButton(); jmethodID GetAlertPromptButton();
jmethodID GetIsPortraitMode(); jmethodID GetIsPortraitMode();
jmethodID GetLandscapeScreenLayout(); jmethodID GetLandscapeScreenLayout();
jmethodID GetPortraitScreenLayout();
jmethodID GetExitEmulationActivity(); jmethodID GetExitEmulationActivity();
jmethodID GetRequestCameraPermission(); jmethodID GetRequestCameraPermission();
jmethodID GetRequestMicPermission(); jmethodID GetRequestMicPermission();

View File

@ -347,9 +347,23 @@ void JNICALL Java_io_github_lime3ds_android_NativeLibrary_enableAdrenoTurboMode(
} }
void Java_io_github_lime3ds_android_NativeLibrary_notifyOrientationChange( void Java_io_github_lime3ds_android_NativeLibrary_notifyOrientationChange(
[[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, jint layout_option, jint rotation) { [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj, jint layout_option, jint rotation,
jboolean portrait) {
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option); Settings::values.layout_option = static_cast<Settings::LayoutOption>(layout_option);
auto& system = Core::System::GetInstance(); auto& system = Core::System::GetInstance();
if (system.IsPoweredOn()) {
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()) { if (system.IsPoweredOn()) {
system.GPU().Renderer().UpdateCurrentFramebufferLayout(!(rotation % 2)); system.GPU().Renderer().UpdateCurrentFramebufferLayout(!(rotation % 2));
} }

View File

@ -69,7 +69,7 @@
<menu> <menu>
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item <item
android:id="@+id/menu_screen_layout_landscape" android:id="@+id/menu_portrait_layout_top_full"
android:title="@string/emulation_screen_layout_landscape" /> android:title="@string/emulation_screen_layout_landscape" />
<item <item

View File

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

View File

@ -7,10 +7,6 @@
android:id="@+id/menu_screen_layout_landscape" android:id="@+id/menu_screen_layout_landscape"
android:title="@string/emulation_screen_layout_landscape" /> android:title="@string/emulation_screen_layout_landscape" />
<item
android:id="@+id/menu_screen_layout_portrait"
android:title="@string/emulation_screen_layout_portrait" />
<item <item
android:id="@+id/menu_screen_layout_single" android:id="@+id/menu_screen_layout_single"
android:title="@string/emulation_screen_layout_single" /> android:title="@string/emulation_screen_layout_single" />
@ -23,6 +19,9 @@
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_custom"
android:title="@string/emulation_screen_layout_custom" />
</group> </group>
</menu> </menu>

View File

@ -0,0 +1,15 @@
<?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

@ -359,12 +359,15 @@
<string name="emulation_open_settings">Open Settings</string> <string name="emulation_open_settings">Open Settings</string>
<string name="emulation_open_cheats">Open Cheats</string> <string name="emulation_open_cheats">Open Cheats</string>
<string name="emulation_switch_screen_layout">Landscape Screen Layout</string> <string name="emulation_switch_screen_layout">Landscape Screen Layout</string>
<string name="emulation_switch_portrait_layout">Portrait Screen Layout</string>
<string name="emulation_screen_layout_landscape">Default</string> <string name="emulation_screen_layout_landscape">Default</string>
<string name="emulation_screen_layout_portrait">Portrait</string> <string name="emulation_screen_layout_portrait">Portrait</string>
<string name="emulation_screen_layout_single">Single Screen</string> <string name="emulation_screen_layout_single">Single Screen</string>
<string name="emulation_screen_layout_sidebyside">Side by Side Screens</string> <string name="emulation_screen_layout_sidebyside">Side by Side Screens</string>
<string name="emulation_screen_layout_hybrid">Hybrid Screens</string> <string name="emulation_screen_layout_hybrid">Hybrid Screens</string>
<string name="emulation_cycle_landscape_layouts">Cycle Landscape Layouts</string> <string name="emulation_portrait_layout_top_full">Default</string>
<string name="emulation_screen_layout_custom">Custom Layout</string>
<string name="emulation_cycle_landscape_layouts">Cycle Layouts</string>
<string name="emulation_swap_screens">Swap Screens</string> <string name="emulation_swap_screens">Swap Screens</string>
<string name="emulation_touch_overlay_reset">Reset Overlay</string> <string name="emulation_touch_overlay_reset">Reset Overlay</string>
<string name="emulation_show_overlay">Show Overlay</string> <string name="emulation_show_overlay">Show Overlay</string>

View File

@ -108,6 +108,7 @@ void LogSettings() {
log_setting("Renderer_AnaglyphShader", values.anaglyph_shader_name.GetValue()); log_setting("Renderer_AnaglyphShader", values.anaglyph_shader_name.GetValue());
} }
log_setting("Layout_LayoutOption", values.layout_option.GetValue()); log_setting("Layout_LayoutOption", values.layout_option.GetValue());
log_setting("Layout_PortraitLayoutOption", values.portrait_layout_option.GetValue());
log_setting("Layout_SwapScreen", values.swap_screen.GetValue()); log_setting("Layout_SwapScreen", values.swap_screen.GetValue());
log_setting("Layout_UprightScreen", values.upright_screen.GetValue()); log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue()); log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue());
@ -196,6 +197,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.texture_filter.SetGlobal(true); values.texture_filter.SetGlobal(true);
values.texture_sampling.SetGlobal(true); values.texture_sampling.SetGlobal(true);
values.layout_option.SetGlobal(true); values.layout_option.SetGlobal(true);
values.portrait_layout_option.SetGlobal(true);
values.swap_screen.SetGlobal(true); values.swap_screen.SetGlobal(true);
values.upright_screen.SetGlobal(true); values.upright_screen.SetGlobal(true);
values.large_screen_proportion.SetGlobal(true); values.large_screen_proportion.SetGlobal(true);

View File

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

View File

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

View File

@ -117,7 +117,7 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool u
return res; return res;
} }
FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool swapped) { FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool swapped) {
ASSERT(width > 0); ASSERT(width > 0);
ASSERT(height > 0); ASSERT(height > 0);
@ -419,119 +419,126 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool
return res; return res;
} }
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary) { FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary,
bool is_portrait_mode = bool is_portrait) {
Settings::values.layout_option.GetValue() == Settings::LayoutOption::MobilePortrait;
if (Settings::values.custom_layout.GetValue() == true && !is_portrait_mode) {
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_mode);
} else if (Settings::values.custom_portrait_layout.GetValue() == true && is_portrait_mode) {
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_mode);
}
int width, height; int width, height;
switch (Settings::values.layout_option.GetValue()) { if (is_portrait) {
case Settings::LayoutOption::SingleScreen: auto layout_option = Settings::values.portrait_layout_option.GetValue();
#ifndef ANDROID switch (layout_option) {
case Settings::LayoutOption::SeparateWindows: case Settings::PortraitLayoutOption::PortraitCustomLayout:
#endif return CustomFrameLayout(
{ std::max(Settings::values.custom_portrait_top_x.GetValue() +
const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue(); Settings::values.custom_portrait_top_width.GetValue(),
if (swap_screens) { Settings::values.custom_portrait_bottom_x.GetValue() +
width = Core::kScreenBottomWidth * res_scale; Settings::values.custom_portrait_bottom_width.GetValue()),
height = Core::kScreenBottomHeight * res_scale; std::max(Settings::values.custom_portrait_top_y.GetValue() +
} else { 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; 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()) { } else {
std::swap(width, height); auto layout_option = Settings::values.layout_option.GetValue();
} switch (layout_option) {
return SingleFrameLayout(width, height, swap_screens, case Settings::LayoutOption::CustomLayout:
Settings::values.upright_screen.GetValue()); 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: case Settings::LayoutOption::SingleScreen:
if (Settings::values.swap_screen.GetValue()) { #ifndef ANDROID
width = (Core::kScreenBottomWidth + 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 / Core::kScreenTopWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) * static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale; res_scale;
height = Core::kScreenBottomHeight * res_scale; height = Core::kScreenBottomHeight * res_scale;
} else { } else {
width = (Core::kScreenTopWidth + width =
(Core::kScreenTopWidth +
Core::kScreenBottomWidth / Core::kScreenBottomWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) * static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale; res_scale;
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; 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(); UNREACHABLE();
} }
@ -554,11 +561,27 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
u32 cardboard_screen_width; u32 cardboard_screen_width;
u32 cardboard_screen_height; u32 cardboard_screen_height;
switch (Settings::values.layout_option.GetValue()) { if (is_portrait) {
case Settings::LayoutOption::MobileLandscape: switch (Settings::values.portrait_layout_option.GetValue()) {
case Settings::LayoutOption::SideScreen: case Settings::PortraitLayoutOption::PortraitTopFullWidth:
// If orientation is portrait, only use MobilePortrait cardboard_screen_width = top_screen_width;
if (!is_portrait) { 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_width = top_screen_width + bottom_screen_width;
cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height; cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height;
if (is_swapped) if (is_swapped)
@ -566,28 +589,14 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
else else
bottom_screen_left += top_screen_width; bottom_screen_left += top_screen_width;
break; break;
} else {
[[fallthrough]]; case Settings::LayoutOption::SingleScreen:
} default:
case Settings::LayoutOption::SingleScreen:
default:
if (!is_portrait) {
// Default values when using LayoutOption::SingleScreen
cardboard_screen_width = is_swapped ? bottom_screen_width : top_screen_width; cardboard_screen_width = is_swapped ? bottom_screen_width : top_screen_width;
cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height; cardboard_screen_height = is_swapped ? bottom_screen_height : top_screen_height;
break; 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_max_x_shift = (layout.width / 2 - cardboard_screen_width) / 2;
s32 cardboard_user_x_shift = s32 cardboard_user_x_shift =
@ -620,6 +629,15 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
return new_layout; return new_layout;
} }
/*f
* TODO: remove this?
*/
std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout() {
u32 min_width, min_height;
min_width = Core::kScreenTopWidth;
min_height = Core::kScreenTopHeight + Core::kScreenBottomHeight;
return std::make_pair(min_width, min_height);
}
std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout, std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout,
bool upright_screen) { bool upright_screen) {

View File

@ -85,13 +85,14 @@ struct FramebufferLayout {
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, bool upright); FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
/** /**
* Factory method for constructing a mobile portrait FramebufferLayout * Factory method for constructing the mobile Full Width Top layout
* Two screens at top, full width, no gap between them
* @param width Window framebuffer width in pixels * @param width Window framebuffer width in pixels
* @param height Window framebuffer height in pixels * @param height Window framebuffer height in pixels
* @param is_swapped if true, the bottom screen will be displayed above the top screen * @param is_swapped if true, the bottom screen will be displayed above the top screen
* @return Newly created FramebufferLayout object with mobile portrait screen regions initialized * @return Newly created FramebufferLayout object with mobile portrait screen regions initialized
*/ */
FramebufferLayout MobilePortraitFrameLayout(u32 width, u32 height, bool is_swapped); FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool is_swapped);
/** /**
* Factory method for constructing a FramebufferLayout with only the top or bottom screen * Factory method for constructing a FramebufferLayout with only the top or bottom screen
@ -152,8 +153,10 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped,
* Convenience method to get frame layout by resolution scale * Convenience method to get frame layout by resolution scale
* Read from the current settings to determine which layout to use. * Read from the current settings to determine which layout to use.
* @param res_scale resolution scale factor * @param res_scale resolution scale factor
* @param is_portrait_mode defaults to false
*/ */
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary = false); FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary = false,
bool is_portrait_mode = false);
/** /**
* Convenience method for transforming a frame layout when using Cardboard VR * Convenience method for transforming a frame layout when using Cardboard VR
@ -165,4 +168,6 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout);
std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout, std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption layout,
bool upright_screen); bool upright_screen);
std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout();
} // namespace Layout } // namespace Layout

View File

@ -183,7 +183,7 @@ void Config::ReadValues() {
ReadSetting("Layout", Settings::values.screen_bottom_leftright_padding); ReadSetting("Layout", Settings::values.screen_bottom_leftright_padding);
ReadSetting("Layout", Settings::values.screen_bottom_topbottom_padding); ReadSetting("Layout", Settings::values.screen_bottom_topbottom_padding);
ReadSetting("Layout", Settings::values.custom_portrait_layout); ReadSetting("Layout", Settings::values.portrait_layout_option);
ReadSetting("Layout", Settings::values.custom_portrait_top_x); ReadSetting("Layout", Settings::values.custom_portrait_top_x);
ReadSetting("Layout", Settings::values.custom_portrait_top_y); ReadSetting("Layout", Settings::values.custom_portrait_top_y);
ReadSetting("Layout", Settings::values.custom_portrait_top_width); ReadSetting("Layout", Settings::values.custom_portrait_top_width);

View File

@ -538,7 +538,7 @@ void Config::ReadLayoutValues() {
ReadBasicSetting(Settings::values.screen_bottom_stretch); ReadBasicSetting(Settings::values.screen_bottom_stretch);
ReadBasicSetting(Settings::values.screen_bottom_leftright_padding); ReadBasicSetting(Settings::values.screen_bottom_leftright_padding);
ReadBasicSetting(Settings::values.screen_bottom_topbottom_padding); ReadBasicSetting(Settings::values.screen_bottom_topbottom_padding);
ReadBasicSetting(Settings::values.custom_portrait_layout); ReadBasicSetting(Settings::values.portrait_layout_option);
ReadBasicSetting(Settings::values.custom_portrait_top_x); ReadBasicSetting(Settings::values.custom_portrait_top_x);
ReadBasicSetting(Settings::values.custom_portrait_top_y); ReadBasicSetting(Settings::values.custom_portrait_top_y);
ReadBasicSetting(Settings::values.custom_portrait_top_width); ReadBasicSetting(Settings::values.custom_portrait_top_width);
@ -1099,7 +1099,6 @@ void Config::SaveLayoutValues() {
WriteBasicSetting(Settings::values.screen_bottom_stretch); WriteBasicSetting(Settings::values.screen_bottom_stretch);
WriteBasicSetting(Settings::values.screen_bottom_leftright_padding); WriteBasicSetting(Settings::values.screen_bottom_leftright_padding);
WriteBasicSetting(Settings::values.screen_bottom_topbottom_padding); WriteBasicSetting(Settings::values.screen_bottom_topbottom_padding);
WriteBasicSetting(Settings::values.custom_portrait_layout);
WriteBasicSetting(Settings::values.custom_portrait_top_x); WriteBasicSetting(Settings::values.custom_portrait_top_x);
WriteBasicSetting(Settings::values.custom_portrait_top_y); WriteBasicSetting(Settings::values.custom_portrait_top_y);
WriteBasicSetting(Settings::values.custom_portrait_top_width); WriteBasicSetting(Settings::values.custom_portrait_top_width);

View File

@ -634,6 +634,10 @@ 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(),
@ -675,12 +679,12 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
if (!Settings::values.swap_screen.GetValue()) { if (!Settings::values.swap_screen.GetValue()) {
DrawTopScreen(layout, top_screen); DrawTopScreen(layout, top_screen);
glUniform1i(uniform_layer, 0); glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity(); ApplySecondLayerOpacity(isPortrait);
DrawBottomScreen(layout, bottom_screen); DrawBottomScreen(layout, bottom_screen);
} else { } else {
DrawBottomScreen(layout, bottom_screen); DrawBottomScreen(layout, bottom_screen);
glUniform1i(uniform_layer, 0); glUniform1i(uniform_layer, 0);
ApplySecondLayerOpacity(); ApplySecondLayerOpacity(isPortrait);
DrawTopScreen(layout, top_screen); DrawTopScreen(layout, top_screen);
} }
@ -692,11 +696,17 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
DrawBottomScreen(layout, additional_screen); DrawBottomScreen(layout, additional_screen);
} }
} }
ResetSecondLayerOpacity(); ResetSecondLayerOpacity(isPortrait);
} }
void RendererOpenGL::ApplySecondLayerOpacity() { void RendererOpenGL::ApplySecondLayerOpacity(bool isPortrait) {
#ifndef ANDROID // TODO: Implement custom layouts on Android #ifdef 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 ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout ||
Settings::values.custom_layout) && Settings::values.custom_layout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) { Settings::values.custom_second_layer_opacity.GetValue() < 100) {
@ -706,11 +716,16 @@ void RendererOpenGL::ApplySecondLayerOpacity() {
state.blend.dst_rgb_func = GL_ONE_MINUS_CONSTANT_ALPHA; state.blend.dst_rgb_func = GL_ONE_MINUS_CONSTANT_ALPHA;
state.blend.color.alpha = Settings::values.custom_second_layer_opacity.GetValue() / 100.0f; state.blend.color.alpha = Settings::values.custom_second_layer_opacity.GetValue() / 100.0f;
} }
#endif
} }
void RendererOpenGL::ResetSecondLayerOpacity() { void RendererOpenGL::ResetSecondLayerOpacity(bool isPortrait) {
#ifndef ANDROID // TODO: Implement custom layouts on Android #ifdef 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 ((Settings::values.layout_option.GetValue() == Settings::LayoutOption::CustomLayout ||
Settings::values.custom_layout) && Settings::values.custom_layout) &&
Settings::values.custom_second_layer_opacity.GetValue() < 100) { Settings::values.custom_second_layer_opacity.GetValue() < 100) {
@ -720,7 +735,6 @@ void RendererOpenGL::ResetSecondLayerOpacity() {
state.blend.dst_a_func = GL_ZERO; state.blend.dst_a_func = GL_ZERO;
state.blend.color.alpha = 0.0f; state.blend.color.alpha = 0.0f;
} }
#endif
} }
void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout, void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,

View File

@ -65,8 +65,8 @@ private:
void ConfigureFramebufferTexture(TextureInfo& texture, void ConfigureFramebufferTexture(TextureInfo& texture,
const Pica::FramebufferConfig& framebuffer); const Pica::FramebufferConfig& framebuffer);
void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped); void DrawScreens(const Layout::FramebufferLayout& layout, bool flipped);
void ApplySecondLayerOpacity(); void ApplySecondLayerOpacity(bool isPortrait = false);
void ResetSecondLayerOpacity(); void ResetSecondLayerOpacity(bool isPortrait = false);
void DrawBottomScreen(const Layout::FramebufferLayout& layout, void DrawBottomScreen(const Layout::FramebufferLayout& layout,
const Common::Rectangle<u32>& bottom_screen); const Common::Rectangle<u32>& bottom_screen);
void DrawTopScreen(const Layout::FramebufferLayout& layout, void DrawTopScreen(const Layout::FramebufferLayout& layout,