This commit is contained in:
David Griswold 2025-12-09 09:12:02 +00:00 committed by GitHub
commit 34e5d77255
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 353 additions and 50 deletions

View File

@ -60,7 +60,7 @@ class EmulationActivity : AppCompatActivity() {
private lateinit var binding: ActivityEmulationBinding
private lateinit var screenAdjustmentUtil: ScreenAdjustmentUtil
private lateinit var hotkeyUtility: HotkeyUtility
private lateinit var secondaryDisplay: SecondaryDisplay
lateinit var secondaryDisplay: SecondaryDisplay
private val onShutdown = Runnable {
if (intent.getBooleanExtra("launched_from_shortcut", false)) {

View File

@ -62,6 +62,13 @@ class ScreenAdjustmentUtil(
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
fun changeSecondaryOrientation(layoutOption: Int) {
IntSetting.SECONDARY_DISPLAY_LAYOUT.int = layoutOption
settings.saveSetting(IntSetting.SECONDARY_DISPLAY_LAYOUT,SettingsFile.FILE_NAME_CONFIG)
NativeLibrary.reloadSettings()
NativeLibrary.updateFramebuffer(NativeLibrary.isPortraitMode)
}
fun changeActivityOrientation(orientationOption: Int) {
val activity = context as? Activity ?: return
IntSetting.ORIENTATION_OPTION.int = orientationOption

View File

@ -56,7 +56,12 @@ enum class SecondaryDisplayLayout(val int: Int) {
NONE(0),
TOP_SCREEN(1),
BOTTOM_SCREEN(2),
SIDE_BY_SIDE(3);
SIDE_BY_SIDE(3),
REVERSE_PRIMARY(4),
ORIGINAL(5),
HYBRID(6),
LARGE_SCREEN(7)
;
companion object {
fun from(int: Int): SecondaryDisplayLayout {

View File

@ -9,7 +9,9 @@ import android.content.Context
import android.graphics.SurfaceTexture
import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.Display
import android.view.MotionEvent
import android.view.Surface
@ -24,6 +26,8 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
private var pres: SecondaryDisplayPresentation? = null
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
private val vd: VirtualDisplay
var preferredDisplayId = -1
var currentDisplayId = -1
init {
vd = displayManager.createVirtualDisplay(
@ -38,26 +42,53 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
}
fun updateSurface() {
NativeLibrary.secondarySurfaceChanged(pres!!.getSurfaceHolder().surface)
val surface = pres?.getSurfaceHolder()?.surface
if (surface != null && surface.isValid) {
NativeLibrary.secondarySurfaceChanged(surface)
} else {
Log.w("SecondaryDisplay", "Attempted to update null or invalid surface")
}
}
fun destroySurface() {
NativeLibrary.secondarySurfaceDestroyed()
}
private fun getExternalDisplay(context: Context): Display? {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val internalId = context.display.displayId ?: Display.DEFAULT_DISPLAY
val displays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
return displays.firstOrNull { it.displayId != internalId && it.name != "HiddenDisplay" }
fun getSecondaryDisplays(context: Context): List<Display> {
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val currentDisplayId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.display.displayId
} else {
@Suppress("DEPRECATION")
(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager)
.defaultDisplay.displayId
}
val displays = dm.displays
val presDisplays = dm.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
return displays.filter {
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
val isNotDefaultOrPresentable = it != null && it.displayId != Display.DEFAULT_DISPLAY || isPresentable
isNotDefaultOrPresentable &&
it.displayId != currentDisplayId &&
it.name != "HiddenDisplay" &&
it.state != Display.STATE_OFF &&
it.isValid
}
}
fun updateDisplay() {
// decide if we are going to the external display or the internal one
var display = getExternalDisplay(context)
if (display == null ||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int) {
display = vd.display
val displays = getSecondaryDisplays(context)
val display = if (displays.isEmpty() ||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int
) {
currentDisplayId = -1
vd.display
} else if (preferredDisplayId >=0 && displays.any { it.displayId == preferredDisplayId }) {
currentDisplayId = preferredDisplayId
displays.first { it.displayId == preferredDisplayId }
} else {
currentDisplayId = displays[0].displayId
displays[0]
}
// if our presentation is already on the right display, ignore
@ -109,16 +140,18 @@ class SecondaryDisplayPresentation(
surfaceView = SurfaceView(context)
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
Log.d("SecondaryDisplay", "Surface created")
}
override fun surfaceChanged(
holder: SurfaceHolder, format: Int, width: Int, height: Int
) {
Log.d("SecondaryDisplay", "Surface changed: ${width}x${height}")
parent.updateSurface()
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
Log.d("SecondaryDisplay", "Surface destroyed")
parent.destroySurface()
}
})
@ -166,4 +199,6 @@ class SecondaryDisplayPresentation(
fun getSurfaceHolder(): SurfaceHolder {
return surfaceView.holder
}
}

View File

@ -64,6 +64,7 @@ import org.citra.citra_emu.databinding.FragmentEmulationBinding
import org.citra.citra_emu.display.PortraitScreenLayout
import org.citra.citra_emu.display.ScreenAdjustmentUtil
import org.citra.citra_emu.display.ScreenLayout
import org.citra.citra_emu.display.SecondaryDisplayLayout
import org.citra.citra_emu.features.settings.model.BooleanSetting
import org.citra.citra_emu.features.settings.model.IntSetting
import org.citra.citra_emu.features.settings.model.Settings
@ -103,8 +104,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
private val settingsViewModel: SettingsViewModel by viewModels()
private val settings get() = settingsViewModel.settings
private val onPause = Runnable{ togglePause() }
private val onShutdown = Runnable{ emulationState.stop() }
private val onPause = Runnable { togglePause() }
private val onShutdown = Runnable { emulationState.stop() }
override fun onAttach(context: Context) {
super.onAttach(context)
@ -160,7 +161,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
retainInstance = true
emulationState = EmulationState(game.path)
emulationActivity = requireActivity() as EmulationActivity
screenAdjustmentUtil = ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settings)
screenAdjustmentUtil =
ScreenAdjustmentUtil(requireContext(), requireActivity().windowManager, settings)
EmulationLifecycleUtil.addPauseResumeHook(onPause)
EmulationLifecycleUtil.addShutdownHook(onShutdown)
}
@ -171,6 +173,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
savedInstanceState: Bundle?
): View {
_binding = FragmentEmulationBinding.inflate(inflater)
binding.inGameMenu.menu.findItem(R.id.menu_secondary_screen_layout).isVisible =
emulationActivity.secondaryDisplay.getSecondaryDisplays(emulationActivity).isNotEmpty()
return binding.root
}
@ -306,6 +310,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
true
}
R.id.menu_secondary_screen_layout -> {
showSecondaryScreenLayoutMenu()
true
}
R.id.menu_swap_screens -> {
screenAdjustmentUtil.swapScreen()
true
@ -587,17 +596,21 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
add(text).setEnabled(enableClick).setOnMenuItemClickListener {
if(isSaving) {
if (isSaving) {
NativeLibrary.saveState(slot)
Toast.makeText(context,
Toast.makeText(
context,
getString(R.string.saving),
Toast.LENGTH_SHORT).show()
Toast.LENGTH_SHORT
).show()
} else {
NativeLibrary.loadState(slot)
binding.drawerLayout.close()
Toast.makeText(context,
Toast.makeText(
context,
getString(R.string.loading),
Toast.LENGTH_SHORT).show()
Toast.LENGTH_SHORT
).show()
}
true
}
@ -606,9 +619,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
savestates?.forEach {
var enableClick = true
val text = if(it.slot == NativeLibrary.QUICKSAVE_SLOT) {
val text = if (it.slot == NativeLibrary.QUICKSAVE_SLOT) {
getString(R.string.emulation_occupied_quicksave_slot, it.time)
} else{
} else {
getString(R.string.emulation_occupied_state_slot, it.slot, it.time)
}
popupMenu.menu.getItem(it.slot).setTitle(text).setEnabled(enableClick)
@ -690,8 +703,12 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
R.id.menu_performance_overlay_show -> {
BooleanSetting.PERF_OVERLAY_ENABLE.boolean = !BooleanSetting.PERF_OVERLAY_ENABLE.boolean
settings.saveSetting(BooleanSetting.PERF_OVERLAY_ENABLE, SettingsFile.FILE_NAME_CONFIG)
BooleanSetting.PERF_OVERLAY_ENABLE.boolean =
!BooleanSetting.PERF_OVERLAY_ENABLE.boolean
settings.saveSetting(
BooleanSetting.PERF_OVERLAY_ENABLE,
SettingsFile.FILE_NAME_CONFIG
)
updateShowPerformanceOverlay()
true
}
@ -962,10 +979,13 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
val layoutOptionMenuItem = when (IntSetting.PORTRAIT_SCREEN_LAYOUT.int) {
PortraitScreenLayout.TOP_FULL_WIDTH.int ->
R.id.menu_portrait_layout_top_full
PortraitScreenLayout.ORIGINAL.int ->
R.id.menu_portrait_layout_original
PortraitScreenLayout.CUSTOM_PORTRAIT_LAYOUT.int ->
R.id.menu_portrait_layout_custom
else ->
R.id.menu_portrait_layout_top_full
@ -1002,6 +1022,140 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
popupMenu.show()
}
private fun showSecondaryScreenLayoutMenu() {
val popupMenu = PopupMenu(
requireContext(),
binding.inGameMenu.findViewById(R.id.menu_secondary_screen_layout)
)
popupMenu.menuInflater.inflate(R.menu.menu_secondary_screen_layout, popupMenu.menu)
var selectedLayout = IntSetting.SECONDARY_DISPLAY_LAYOUT.int
val chooserMenu = popupMenu.menu.findItem(R.id.menu_secondary_choose)
val enableSecondaryCheckbox = popupMenu.menu.findItem(R.id.menu_secondary_layout_none)
chooserMenu?.subMenu?.removeGroup(R.id.menu_secondary_management_display_group)
val displays =
emulationActivity.secondaryDisplay.getSecondaryDisplays(emulationActivity)
if (selectedLayout == SecondaryDisplayLayout.NONE.int) {
enableSecondaryCheckbox.isChecked = false
chooserMenu.isVisible = false
popupMenu.menu.setGroupEnabled(R.id.menu_secondary_layout_group, false)
selectedLayout = SecondaryDisplayLayout.REVERSE_PRIMARY.int
} else {
popupMenu.menu.setGroupEnabled(R.id.menu_secondary_layout_group, true)
chooserMenu.isVisible = (displays.size > 1)
}
val layoutOptionMenuItem = when (selectedLayout) {
SecondaryDisplayLayout.NONE.int -> {
R.id.menu_secondary_layout_reverse_primary
}
SecondaryDisplayLayout.REVERSE_PRIMARY.int ->
R.id.menu_secondary_layout_reverse_primary
SecondaryDisplayLayout.TOP_SCREEN.int ->
R.id.menu_secondary_layout_top
SecondaryDisplayLayout.BOTTOM_SCREEN.int ->
R.id.menu_secondary_layout_bottom
SecondaryDisplayLayout.HYBRID.int ->
R.id.menu_secondary_layout_hybrid
SecondaryDisplayLayout.LARGE_SCREEN.int ->
R.id.menu_secondary_layout_largescreen
SecondaryDisplayLayout.ORIGINAL.int ->
R.id.menu_secondary_layout_original
else ->
R.id.menu_secondary_layout_side_by_side
}
popupMenu.menu.findItem(layoutOptionMenuItem).isChecked = true
if (displays.size > 1 && selectedLayout != SecondaryDisplayLayout.NONE.int) {
val current = emulationActivity.secondaryDisplay.currentDisplayId
chooserMenu.isVisible = true
displays.forEachIndexed { index, display ->
chooserMenu?.subMenu?.add(
R.id.menu_secondary_management_display_group,
display.displayId,
index,
"Display ${display.displayId} - ${display.name}"
)?.apply {
isChecked = (display.displayId == current)
}
}
chooserMenu.subMenu?.setGroupCheckable(
R.id.menu_secondary_management_display_group,
true,
true
)
}
popupMenu.setOnMenuItemClickListener {
when (it.itemId) {
R.id.menu_secondary_layout_none -> {
if (!it.isChecked) {
screenAdjustmentUtil.changeSecondaryOrientation(selectedLayout)
} else {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.NONE.int)
}
emulationActivity.secondaryDisplay.updateDisplay()
showSecondaryScreenLayoutMenu() // reopen menu to get new behaviors
true
}
R.id.menu_secondary_layout_reverse_primary -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.REVERSE_PRIMARY.int)
true
}
R.id.menu_secondary_layout_top -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.TOP_SCREEN.int)
true
}
R.id.menu_secondary_layout_bottom -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.BOTTOM_SCREEN.int)
true
}
R.id.menu_secondary_layout_side_by_side -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.SIDE_BY_SIDE.int)
true
}
R.id.menu_secondary_layout_hybrid -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.HYBRID.int)
true
}
R.id.menu_secondary_layout_original -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.ORIGINAL.int)
true
}
R.id.menu_secondary_layout_largescreen -> {
screenAdjustmentUtil.changeSecondaryOrientation(SecondaryDisplayLayout.LARGE_SCREEN.int)
true
}
R.id.menu_secondary_choose -> {
true
}
else -> {
// display ID selection
emulationActivity.secondaryDisplay.preferredDisplayId = it.itemId
emulationActivity.secondaryDisplay.updateDisplay()
true
}
}
}
popupMenu.show()
}
private fun editControlsPlacement() {
if (binding.surfaceInputOverlay.isInEditMode) {
binding.doneControlConfig.visibility = View.GONE
@ -1046,7 +1200,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
slider.valueFrom = 0f
slider.value = preferences.getInt(target, 50).toFloat()
textValue.setText((slider.value + 50).toInt().toString())
textValue.addTextChangedListener( object : TextWatcher {
textValue.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val value = s.toString().toIntOrNull()
if (value == null || value < 50 || value > 150) {
@ -1056,6 +1210,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
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) {}
})
@ -1096,7 +1251,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
slider.value = preferences.getInt("controlOpacity", 50).toFloat()
textValue.setText(slider.value.toInt().toString())
textValue.addTextChangedListener( object : TextWatcher {
textValue.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val value = s.toString().toIntOrNull()
if (value == null || value < slider.valueFrom || value > slider.valueTo) {
@ -1106,6 +1261,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
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) {}
})
@ -1114,11 +1270,11 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
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())
}
textValue.setText(slider.value.toInt().toString())
textValue.setSelection(textValue.length())
setControlOpacity(slider.value.toInt())
}
}
textInput.suffixText = "%"
}
@ -1303,7 +1459,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
}
private fun updateStatsPosition(position: Int) {
val params = binding.performanceOverlayShowText.layoutParams as CoordinatorLayout.LayoutParams
val params =
binding.performanceOverlayShowText.layoutParams as CoordinatorLayout.LayoutParams
val padding = (20 * resources.displayMetrics.density).toInt() // 20dp
params.setMargins(padding, 0, padding, 0)
@ -1338,7 +1495,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
private fun getBatteryTemperature(): Float {
try {
val batteryIntent = requireContext().registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
val batteryIntent =
requireContext().registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
// Temperature in tenths of a degree Celsius
val temperature = batteryIntent?.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0) ?: 0
// Convert to degrees Celsius

View File

@ -18,10 +18,13 @@
#include "video_core/renderer_base.h"
bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
if (render_window == surface) {
int w = surface== NULL ? 0 : ANativeWindow_getWidth(surface);
int h = surface== NULL ? 0 : ANativeWindow_getHeight(surface);
if (render_window == surface && w == window_width && h == window_height) {
return false;
}
window_width = w;
window_height = h;
render_window = surface;
window_info.type = Frontend::WindowSystemType::Android;
window_info.render_surface = surface;
@ -45,15 +48,9 @@ void EmuWindow_Android::OnTouchMoved(int x, int y) {
}
void EmuWindow_Android::OnFramebufferSizeChanged() {
const bool is_portrait_mode{IsPortraitMode()};
const bool is_portrait_mode = IsPortraitMode() && !is_secondary;
const int bigger{window_width > window_height ? window_width : window_height};
const int smaller{window_width < window_height ? window_width : window_height};
if (is_portrait_mode && !is_secondary) {
UpdateCurrentFramebufferLayout(smaller, bigger, is_portrait_mode);
} else {
UpdateCurrentFramebufferLayout(bigger, smaller, is_portrait_mode);
}
UpdateCurrentFramebufferLayout(window_width,window_height,is_portrait_mode);
}
EmuWindow_Android::EmuWindow_Android(ANativeWindow* surface, bool is_secondary)

View File

@ -365,6 +365,11 @@ void Java_org_citra_citra_1emu_NativeLibrary_secondarySurfaceChanged(JNIEnv* env
if (secondary_window) {
// Second window already created, so update it
notify = secondary_window->OnSurfaceChanged(s_secondary_surface);
// Log the dimensions for debugging
int32_t width = ANativeWindow_getWidth(s_secondary_surface);
int32_t height = ANativeWindow_getHeight(s_secondary_surface);
LOG_INFO(Frontend, "Secondary Surface changed to {}x{}", width, height);
} else {
LOG_WARNING(Frontend,
"Second Window does not exist in native.cpp but surface changed. Ignoring.");

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorOnSurface">
<path
android:fillColor="@android:color/black"
android:fillType="evenOdd"
android:pathData="M17 4h3c1.1 0 2 0.9 2 2v2h-2V6h-3ZM4 8V6h3V4H4C2.9 4 2 4.9 2 6v2z m16 8v2h-3v2h3c1.1 0 2-0.9 2-2v-2ZM7 18H4v-2H2v2c0 1.1 0.9 2 2 2h3ZM18 8H6v8h12z M12.6 10.23q-0.07-0.17-0.21-0.28-0.13-0.12-0.26-0.14-0.42 0-0.77 0.23-0.35 0.22-0.64 0.53-0.13 0.1-0.29 0.1-0.16 0-0.27-0.1-0.1-0.12-0.1-0.3 0-0.15 0.1-0.3 0.18-0.19 0.39-0.37 0.21-0.2 0.45-0.34 0.24-0.14 0.5-0.23 0.25-0.09 0.52-0.09 0.31 0 0.58 0.14 0.27 0.12 0.47 0.36 0.2 0.24 0.31 0.56 0.12 0.33 0.12 0.72 0 0.12-0.03 0.26-0.1 0.44-0.33 0.72-0.21 0.28-0.48 0.48-0.26 0.2-0.55 0.36-0.28 0.15-0.53 0.35-0.24 0.2-0.42 0.47-0.19 0.27-0.25 0.7h1.84q0.11 0 0.17-0.03l0.09-0.06 0.1-0.12q0.08-0.1 0.12-0.11l0.1-0.04 0.12-0.01q0.18 0 0.28 0.12 0.1 0.12 0.1 0.28 0 0.11-0.02 0.17l-0.04 0.08q-0.18 0.26-0.43 0.42-0.25 0.15-0.56 0.15h-2.3q-0.38-0.03-0.42-0.45 0.03-0.32 0.1-0.62 0.05-0.3 0.16-0.59 0.1-0.28 0.27-0.53 0.16-0.25 0.41-0.45 0.22-0.17 0.51-0.33 0.3-0.15 0.55-0.34 0.26-0.19 0.45-0.44 0.18-0.25 0.18-0.6 0-0.1-0.04-0.21l-0.05-0.12z"/>
</vector>

View File

@ -32,6 +32,11 @@
android:icon="@drawable/ic_portrait_fit_screen"
android:title="@string/emulation_switch_portrait_layout" />
<item
android:id="@+id/menu_secondary_screen_layout"
android:icon="@drawable/ic_secondary_fit_screen"
android:title="@string/emulation_secondary_display_management" />
<item
android:id="@+id/menu_swap_screens"
android:icon="@drawable/ic_splitscreen"

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_secondary_layout_none"
android:title="@string/emulation_secondary_display_enable"
android:checkable="true"
android:checked="true"/>
<item
android:id="@+id/menu_secondary_choose"
android:title="@string/emulation_select_secondary_display">
<menu>
<group
android:id="@+id/menu_secondary_management_display_group"
android:checkableBehavior="single">
</group>
</menu>
</item>
<item
android:title="@string/preferences_layout"
android:enabled="false"/>
<group
android:checkableBehavior="single"
android:id="@+id/menu_secondary_layout_group">
<item
android:id="@+id/menu_secondary_layout_reverse_primary"
android:title="@string/emulation_secondary_display_reverse_primary" />
<item
android:id="@+id/menu_secondary_layout_top"
android:title="@string/emulation_top_screen" />
<item
android:id="@+id/menu_secondary_layout_bottom"
android:title="@string/emulation_bottom_screen" />
<item
android:id="@+id/menu_secondary_layout_side_by_side"
android:title="@string/emulation_screen_layout_sidebyside" />
<item
android:id="@+id/menu_secondary_layout_original"
android:title="@string/emulation_screen_layout_original" />
<item
android:id="@+id/menu_secondary_layout_hybrid"
android:title="@string/emulation_screen_layout_hybrid" />
<item
android:id="@+id/menu_secondary_layout_largescreen"
android:title="@string/emulation_screen_layout_largescreen" />
</group>
</menu>

View File

@ -37,9 +37,14 @@
<string-array name="secondaryLayouts">
<item>@string/emulation_secondary_display_default</item>
<item>@string/emulation_secondary_display_reverse_primary</item>
<item>@string/emulation_top_screen</item>
<item>@string/emulation_bottom_screen</item>
<item>@string/emulation_screen_layout_sidebyside</item>
<item>@string/emulation_screen_layout_original</item>
<item>@string/emulation_screen_layout_hybrid</item>
<item>@string/emulation_screen_layout_largescreen</item>
</string-array>
<integer-array name="portraitLayoutValues">
@ -50,9 +55,13 @@
<integer-array name="secondaryLayoutValues">
<item>0</item>
<item>4</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>5</item>
<item>6</item>
<item>7</item>
</integer-array>
<string-array name="smallScreenPositions">

View File

@ -439,7 +439,10 @@
<string name="emulation_aspect_ratio">Aspect Ratio</string>
<string name="emulation_switch_screen_layout">Landscape Screen Layout</string>
<string name="emulation_switch_portrait_layout">Portrait Screen Layout</string>
<string name="emulation_switch_secondary_layout">Secondary Display Screen Layout</string>
<string name="emulation_secondary_display_enable">Enable Secondary Display</string>
<string name="emulation_switch_secondary_layout">Secondary Display Layout</string>
<string name="emulation_secondary_display_management">Secondary Display</string>
<string name="emulation_select_secondary_display">Choose Display</string>
<string name="emulation_switch_secondary_layout_description">The layout used by a connected secondary screen, wired or wireless (Chromecast, Miracast)</string>
<string name="emulation_screen_layout_largescreen">Large Screen</string>
<string name="emulation_screen_layout_portrait">Portrait</string>
@ -448,7 +451,8 @@
<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_secondary_display_default">System Default (mirror)</string>
<string name="emulation_secondary_display_default">None (system default)</string>
<string name="emulation_secondary_display_reverse_primary">Opposite of Primary Display</string>
<string name="emulation_screen_layout_custom">Custom Layout</string>
<string name="bg_color">Background Color</string>
<string name="bg_color_description">The color which appears behind the screens during emulation, represented as an RGB value.</string>

View File

@ -54,7 +54,7 @@ enum class PortraitLayoutOption : u32 {
PortraitOriginal
};
enum class SecondaryDisplayLayout : u32 { None, TopScreenOnly, BottomScreenOnly, SideBySide };
enum class SecondaryDisplayLayout : u32 { None, TopScreenOnly, BottomScreenOnly, SideBySide, ReversePrimary, Original, Hybrid, LargeScreen };
/** Defines where the small screen will appear relative to the large screen
* when in Large Screen mode
*/

View File

@ -342,17 +342,30 @@ FramebufferLayout AndroidSecondaryLayout(u32 width, u32 height) {
const Settings::SecondaryDisplayLayout layout =
Settings::values.secondary_display_layout.GetValue();
switch (layout) {
case Settings::SecondaryDisplayLayout::TopScreenOnly:
return SingleFrameLayout(width, height, false, Settings::values.upright_screen.GetValue());
case Settings::SecondaryDisplayLayout::BottomScreenOnly:
return SingleFrameLayout(width, height, true, Settings::values.upright_screen.GetValue());
case Settings::SecondaryDisplayLayout::SideBySide:
return LargeFrameLayout(width, height, false, Settings::values.upright_screen.GetValue(),
1.0f, Settings::SmallScreenPosition::MiddleRight);
case Settings::SecondaryDisplayLayout::LargeScreen:
return LargeFrameLayout(width, height, false, Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue(),
Settings::values.small_screen_position.GetValue());
case Settings::SecondaryDisplayLayout::Original:
return LargeFrameLayout(width, height, false, Settings::values.upright_screen.GetValue(),
1.0f, Settings::SmallScreenPosition::BelowLarge);
case Settings::SecondaryDisplayLayout::Hybrid:
return HybridScreenLayout(width, height, false, Settings::values.upright_screen.GetValue());
case Settings::SecondaryDisplayLayout::None:
// this should never happen, but if it does, somehow, send the top screen
case Settings::SecondaryDisplayLayout::TopScreenOnly:
// this should never happen - if "none" is set this method shouldn't run - but if it does,
// somehow, use ReversePrimary
case Settings::SecondaryDisplayLayout::ReversePrimary:
default:
return SingleFrameLayout(width, height, false, Settings::values.upright_screen.GetValue());
return SingleFrameLayout(width, height, !Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
}
}