mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-15 23:49:36 -04:00
android: Secondary Layout and Menu Improvements (#1385)
* Add a new Secondary Display Layout option on android that makes the secondary display honor swap button * add quick menu option for secondary layout * fix icon * added other secondary layouts * Add a new Secondary Display Layout option on android that makes the secondary display honor swap button # Conflicts: # src/core/frontend/framebuffer_layout.cpp * add quick menu option for secondary layout * fix icon * added other secondary layouts * updated secondary menu with functionality to switch external displays Conflicts: src/android/app/src/main/java/org/citra/citra_emu/display/SecondaryDisplay.kt * safety checks for crash prevention * make secondary menu only appear if a secondary display is available * update default displayid behavior to exclude "Built" * update odin 2 bugfix to handle other languages * Apply clang-format * Rename "Opposite of Primary Display" option for brevity * Rename menu_secondary_layout_reverse_primary for consistency * Rename SecondaryDisplayLayout::ReversePrimary for consistency * first round of code fixes based on review * Added android-side enable_secondary_display boolean setting, replacing secondary_display_layout = none on the android side. Exposed it in Layout settings, and secondary display layout is now only selectable if it is enabled. Support for the old option is still in the code, but should no longer be selectable. Also renamed opposite to reverse_primary in a few other places. * Update framebuffer layout and renderers to correctly render hybrid mode on both primary and secondary displays * Apply clang format * Re-apply terminology changes which were undone by a recent commit * Removed unused string emulation_secondary_display_default --------- Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
parent
3dc357a8c9
commit
8a6d597dec
23 changed files with 429 additions and 90 deletions
|
|
@ -238,6 +238,7 @@ if (ANDROID)
|
|||
"android_hide_images"
|
||||
"screen_orientation"
|
||||
"performance_overlay_position"
|
||||
"enable_secondary_display"
|
||||
)
|
||||
string(REPLACE "_" "_1" KEY_JNI_ESCAPED ${KEY})
|
||||
set(SETTING_KEY_LIST "${SETTING_KEY_LIST}\n\"${KEY}\",")
|
||||
|
|
|
|||
|
|
@ -64,7 +64,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 secondaryDisplayManager: SecondaryDisplay
|
||||
|
||||
private val onShutdown = Runnable {
|
||||
if (intent.getBooleanExtra("launched_from_shortcut", false)) {
|
||||
|
|
@ -102,8 +102,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
secondaryDisplay = SecondaryDisplay(this)
|
||||
secondaryDisplay.updateDisplay()
|
||||
secondaryDisplayManager = SecondaryDisplay(this)
|
||||
secondaryDisplayManager.updateDisplay()
|
||||
|
||||
binding = ActivityEmulationBinding.inflate(layoutInflater)
|
||||
hotkeyUtility = HotkeyUtility(screenAdjustmentUtil, this)
|
||||
|
|
@ -188,7 +188,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
override fun onStop() {
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplayManager.releasePresentation()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
|
|
@ -199,7 +199,7 @@ class EmulationActivity : AppCompatActivity() {
|
|||
|
||||
public override fun onRestart() {
|
||||
super.onRestart()
|
||||
secondaryDisplay.updateDisplay()
|
||||
secondaryDisplayManager.updateDisplay()
|
||||
NativeLibrary.reloadCameraDevices()
|
||||
}
|
||||
|
||||
|
|
@ -222,8 +222,8 @@ class EmulationActivity : AppCompatActivity() {
|
|||
NativeLibrary.playTimeManagerStop()
|
||||
isEmulationRunning = false
|
||||
instance = null
|
||||
secondaryDisplay.releasePresentation()
|
||||
secondaryDisplay.releaseVD()
|
||||
secondaryDisplayManager.releasePresentation()
|
||||
secondaryDisplayManager.releaseVD()
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,24 @@ 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 enableSecondaryDisplay(layoutOption: Int) {
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = true
|
||||
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
|
||||
changeSecondaryOrientation(layoutOption)
|
||||
}
|
||||
|
||||
fun disableSecondaryDisplay() {
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = false
|
||||
settings.saveSetting(BooleanSetting.ENABLE_SECONDARY_DISPLAY, SettingsFile.FILE_NAME_CONFIG)
|
||||
}
|
||||
|
||||
fun changeActivityOrientation(orientationOption: Int) {
|
||||
val activity = context as? Activity ?: return
|
||||
IntSetting.ORIENTATION_OPTION.int = orientationOption
|
||||
|
|
|
|||
|
|
@ -53,10 +53,17 @@ enum class PortraitScreenLayout(val int: Int) {
|
|||
|
||||
enum class SecondaryDisplayLayout(val int: Int) {
|
||||
// These must match what is defined in src/common/settings.h
|
||||
// NONE is no longer selectable in the interface, having been replaced with
|
||||
// the boolean ENABLE_SECONDARY_DISPLAY setting, but is left here for backwards compatibility
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import android.app.Presentation
|
|||
import android.content.Context
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.hardware.display.VirtualDisplay
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.MotionEvent
|
||||
|
|
@ -16,11 +17,18 @@ import android.view.SurfaceView
|
|||
import android.view.WindowManager
|
||||
import org.citra.citra_emu.features.settings.model.IntSetting
|
||||
import org.citra.citra_emu.NativeLibrary
|
||||
import org.citra.citra_emu.features.settings.model.BooleanSetting
|
||||
import org.citra.citra_emu.utils.Log
|
||||
|
||||
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
|
||||
|
||||
val availableDisplays: List<Display>
|
||||
get() = getSecondaryDisplays()
|
||||
|
||||
init {
|
||||
vd = displayManager.createVirtualDisplay(
|
||||
|
|
@ -35,31 +43,39 @@ 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.warning("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 currentDisplayId = context.display.displayId
|
||||
private fun getSecondaryDisplays(): 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);
|
||||
val extDisplays = displays.filter {
|
||||
return displays.filter {
|
||||
val isPresentable = presDisplays.any { pd -> pd.displayId == it.displayId }
|
||||
val isNotDefaultOrPresentable = it.displayId != Display.DEFAULT_DISPLAY || isPresentable
|
||||
val isNotDefaultOrPresentable = (it != null && it.displayId != Display.DEFAULT_DISPLAY) || isPresentable
|
||||
|
||||
isNotDefaultOrPresentable &&
|
||||
it.displayId != currentDisplayId &&
|
||||
it.name != "HiddenDisplay" &&
|
||||
it.state != Display.STATE_OFF &&
|
||||
it.isValid
|
||||
it.displayId != currentDisplayId &&
|
||||
it.name != "HiddenDisplay" &&
|
||||
it.state != Display.STATE_OFF &&
|
||||
it.isValid
|
||||
}
|
||||
// if there is a display called Built-In Display or Built-In Screen, prioritize the OTHER screen
|
||||
val selected = extDisplays.firstOrNull { ! it.name.contains("Built",true) }
|
||||
?: extDisplays.firstOrNull()
|
||||
return selected
|
||||
}
|
||||
|
||||
fun updateDisplay() {
|
||||
|
|
@ -68,21 +84,37 @@ class SecondaryDisplay(val context: Context) : DisplayManager.DisplayListener {
|
|||
return
|
||||
}
|
||||
|
||||
// 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 displayToUse = if (availableDisplays.isEmpty() ||
|
||||
// Theoretically, the NONE option is no longer selectable, but
|
||||
// I am leaving this in for backwards compatibility
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.int == SecondaryDisplayLayout.NONE.int ||
|
||||
! BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
|
||||
) {
|
||||
currentDisplayId = -1
|
||||
vd.display
|
||||
} else if (preferredDisplayId >=0 && availableDisplays.any { it.displayId == preferredDisplayId }) {
|
||||
currentDisplayId = preferredDisplayId
|
||||
availableDisplays.first { it.displayId == preferredDisplayId }
|
||||
} else {
|
||||
val dm = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||
val default = dm.displays.first {it.displayId == Display.DEFAULT_DISPLAY}
|
||||
// prioritize displays that have a different name from the default display, as
|
||||
// some devices such as the Odin 2 create a permanent virtual display with the same
|
||||
// name as the default display that should be skipped in most cases
|
||||
currentDisplayId = availableDisplays.firstOrNull{
|
||||
it.name != default.name && !it.name.contains("Built",true)}?.displayId ?:
|
||||
availableDisplays[0].displayId
|
||||
availableDisplays.first{ it.displayId == currentDisplayId }
|
||||
}
|
||||
|
||||
// if our presentation is already on the right display, ignore
|
||||
if (pres?.display == display) return
|
||||
if (pres?.display == displayToUse) return
|
||||
|
||||
// otherwise, make a new presentation
|
||||
releasePresentation()
|
||||
|
||||
try {
|
||||
pres = SecondaryDisplayPresentation(context, display!!, this)
|
||||
pres = SecondaryDisplayPresentation(context, displayToUse!!, this)
|
||||
pres?.show()
|
||||
}
|
||||
// catch BadTokenException and InvalidDisplayException,
|
||||
|
|
@ -137,16 +169,18 @@ class SecondaryDisplayPresentation(
|
|||
surfaceView = SurfaceView(context)
|
||||
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
|
||||
override fun surfaceCreated(holder: SurfaceHolder) {
|
||||
|
||||
Log.debug("SecondaryDisplay Surface created")
|
||||
}
|
||||
|
||||
override fun surfaceChanged(
|
||||
holder: SurfaceHolder, format: Int, width: Int, height: Int
|
||||
) {
|
||||
Log.debug("SecondaryDisplay Surface changed: ${width}x${height}")
|
||||
parent.updateSurface()
|
||||
}
|
||||
|
||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||
Log.debug("SecondaryDisplay Surface destroyed")
|
||||
parent.destroySurface()
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -141,4 +141,5 @@ object SettingKeys {
|
|||
external fun android_hide_images(): String
|
||||
external fun screen_orientation(): String
|
||||
external fun performance_overlay_position(): String
|
||||
external fun enable_secondary_display(): String
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ enum class BooleanSetting(
|
|||
ANDROID_HIDE_IMAGES(SettingKeys.android_hide_images(), Settings.SECTION_MISC, false),
|
||||
APPLY_REGION_FREE_PATCH(SettingKeys.apply_region_free_patch(), Settings.SECTION_SYSTEM, true),
|
||||
USE_INTEGER_SCALING(SettingKeys.use_integer_scaling(), Settings.SECTION_RENDERER, false),
|
||||
ENABLE_SECONDARY_DISPLAY(SettingKeys.enable_secondary_display(), Settings.SECTION_LAYOUT, true),
|
||||
SIMULATE_3DS_GPU_TIMINGS(SettingKeys.simulate_3ds_gpu_timings(), Settings.SECTION_RENDERER, true);
|
||||
|
||||
override var boolean: Boolean = defaultValue
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ enum class IntSetting(
|
|||
LANDSCAPE_BOTTOM_HEIGHT(SettingKeys.custom_bottom_height(),Settings.SECTION_LAYOUT,480),
|
||||
SCREEN_GAP(SettingKeys.screen_gap(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_SCREEN_LAYOUT(SettingKeys.portrait_layout_option(),Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,0),
|
||||
SECONDARY_DISPLAY_LAYOUT(SettingKeys.secondary_display_layout(),Settings.SECTION_LAYOUT,4),
|
||||
PORTRAIT_TOP_X(SettingKeys.custom_portrait_top_x(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_Y(SettingKeys.custom_portrait_top_y(),Settings.SECTION_LAYOUT,0),
|
||||
PORTRAIT_TOP_WIDTH(SettingKeys.custom_portrait_top_width(),Settings.SECTION_LAYOUT,800),
|
||||
|
|
|
|||
|
|
@ -1201,6 +1201,15 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
|
||||
)
|
||||
)
|
||||
add (
|
||||
SwitchSetting(
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY,
|
||||
R.string.emulation_secondary_display_enable,
|
||||
R.string.emulation_secondary_display_enable_description,
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.key,
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.defaultValue
|
||||
)
|
||||
)
|
||||
add(
|
||||
SingleChoiceSetting(
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT,
|
||||
|
|
@ -1209,7 +1218,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
|
|||
R.array.secondaryLayouts,
|
||||
R.array.secondaryLayoutValues,
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.key,
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.defaultValue
|
||||
IntSetting.SECONDARY_DISPLAY_LAYOUT.defaultValue,
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean
|
||||
)
|
||||
)
|
||||
add(
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ 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
|
||||
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.utils.SettingsFile
|
||||
|
|
@ -78,7 +78,6 @@ import org.citra.citra_emu.utils.BuildUtil
|
|||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization.DirectoryInitializationState
|
||||
import org.citra.citra_emu.utils.EmulationMenuSettings
|
||||
import org.citra.citra_emu.utils.FileUtil
|
||||
import org.citra.citra_emu.utils.GameHelper
|
||||
import org.citra.citra_emu.utils.GameIconUtils
|
||||
import org.citra.citra_emu.utils.EmulationLifecycleUtil
|
||||
|
|
@ -93,7 +92,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
private lateinit var emulationState: EmulationState
|
||||
private var perfStatsUpdater: Runnable? = null
|
||||
|
||||
private lateinit var emulationActivity: EmulationActivity
|
||||
private val emulationActivity: EmulationActivity
|
||||
get() = (requireActivity() as EmulationActivity)
|
||||
|
||||
private var _binding: FragmentEmulationBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
|
@ -107,8 +107,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() }
|
||||
|
||||
// Only used if a game is passed through intent on google play variant
|
||||
private var gameFd: Int? = null
|
||||
|
|
@ -116,8 +116,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context is EmulationActivity) {
|
||||
emulationActivity = context
|
||||
NativeLibrary.setEmulationActivity(context)
|
||||
NativeLibrary.setEmulationActivity(context)
|
||||
} else {
|
||||
throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
|
||||
}
|
||||
|
|
@ -183,8 +182,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback, Choreographer.Fram
|
|||
// So this fragment doesn't restart on configuration changes; i.e. rotation.
|
||||
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)
|
||||
}
|
||||
|
|
@ -195,6 +194,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.secondaryDisplayManager.availableDisplays.isNotEmpty()
|
||||
binding.inGameMenu.menu.findItem(R.id.menu_landscape_screen_layout).isVisible =
|
||||
CitraApplication.appContext.resources.configuration.orientation !=
|
||||
Configuration.ORIENTATION_PORTRAIT
|
||||
|
|
@ -336,6 +337,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
|
||||
|
|
@ -624,17 +630,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
|
||||
}
|
||||
|
|
@ -643,9 +653,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)
|
||||
|
|
@ -727,8 +737,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
|
||||
}
|
||||
|
|
@ -999,10 +1013,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
|
||||
|
||||
|
|
@ -1039,6 +1056,145 @@ 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_enable_secondary_layout)
|
||||
chooserMenu?.subMenu?.removeGroup(R.id.menu_secondary_management_display_group)
|
||||
val displays =
|
||||
emulationActivity.secondaryDisplayManager.availableDisplays
|
||||
|
||||
if (selectedLayout == SecondaryDisplayLayout.NONE.int || !BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean) {
|
||||
BooleanSetting.ENABLE_SECONDARY_DISPLAY.boolean = false
|
||||
enableSecondaryCheckbox.isChecked = false
|
||||
chooserMenu.isVisible = false
|
||||
popupMenu.menu.setGroupEnabled(R.id.menu_secondary_layout_group, false)
|
||||
|
||||
} 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_opposite
|
||||
|
||||
SecondaryDisplayLayout.REVERSE_PRIMARY.int ->
|
||||
R.id.menu_secondary_layout_opposite
|
||||
|
||||
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
|
||||
// Add the available secondary displays to the display chooser list
|
||||
// Use the display ID as the menu ID - since generated menu IDs are all > 1,000,000 this
|
||||
// *should* result in unique ids
|
||||
if (displays.size > 1 && selectedLayout != SecondaryDisplayLayout.NONE.int) {
|
||||
val current = emulationActivity.secondaryDisplayManager.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_enable_secondary_layout -> {
|
||||
if (!it.isChecked) {
|
||||
screenAdjustmentUtil.enableSecondaryDisplay(selectedLayout)
|
||||
} else {
|
||||
screenAdjustmentUtil.disableSecondaryDisplay()
|
||||
}
|
||||
emulationActivity.secondaryDisplayManager.updateDisplay()
|
||||
showSecondaryScreenLayoutMenu() // reopen menu to get new behaviors
|
||||
true
|
||||
}
|
||||
|
||||
R.id.menu_secondary_layout_opposite -> {
|
||||
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
|
||||
// If we are clicking on a menu item that isn't one of the options above, it must
|
||||
// be one of the dynamically generated menu items added to the secondary display
|
||||
// choice list.
|
||||
emulationActivity.secondaryDisplayManager.preferredDisplayId = it.itemId
|
||||
emulationActivity.secondaryDisplayManager.updateDisplay()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
popupMenu.show()
|
||||
}
|
||||
|
||||
private fun editControlsPlacement() {
|
||||
if (binding.surfaceInputOverlay.isInEditMode) {
|
||||
binding.doneControlConfig.visibility = View.GONE
|
||||
|
|
@ -1095,7 +1251,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) {
|
||||
|
|
@ -1105,6 +1261,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) {}
|
||||
})
|
||||
|
|
@ -1145,7 +1302,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) {
|
||||
|
|
@ -1155,6 +1312,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) {}
|
||||
})
|
||||
|
|
@ -1163,11 +1321,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 = "%"
|
||||
}
|
||||
|
|
@ -1352,7 +1510,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)
|
||||
|
||||
|
|
@ -1387,7 +1546,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
|
||||
|
|
|
|||
|
|
@ -354,6 +354,10 @@ static const char* android_config_default_file_content = (BOOST_HANA_STRING(R"(
|
|||
# 0 (default): Off, 1: On
|
||||
)") DECLARE_KEY(expand_to_cutout_area) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Allows Azahar to use externally connected displays
|
||||
# 0: Off, 1: On (default)
|
||||
)") DECLARE_KEY(enable_secondary_display) BOOST_HANA_STRING(R"(
|
||||
|
||||
# Secondary Display Layout
|
||||
# What the game should do if a secondary display is connected physically or using
|
||||
# Miracast / Chromecast screen mirroring
|
||||
|
|
|
|||
|
|
@ -18,17 +18,16 @@
|
|||
#include "video_core/renderer_base.h"
|
||||
|
||||
bool EmuWindow_Android::OnSurfaceChanged(ANativeWindow* surface) {
|
||||
if (render_window == surface) {
|
||||
int temp_width = surface == nullptr ? 0 : ANativeWindow_getWidth(surface);
|
||||
int temp_height = surface == nullptr ? 0 : ANativeWindow_getHeight(surface);
|
||||
if (render_window == surface && temp_width == window_width && temp_height == window_height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
window_width = temp_width;
|
||||
window_height = temp_height;
|
||||
render_window = surface;
|
||||
window_info.type = Frontend::WindowSystemType::Android;
|
||||
window_info.render_surface = surface;
|
||||
if (surface != nullptr) {
|
||||
window_width = ANativeWindow_getWidth(surface);
|
||||
window_height = ANativeWindow_getHeight(surface);
|
||||
}
|
||||
StopPresenting();
|
||||
OnFramebufferSizeChanged();
|
||||
return true;
|
||||
|
|
@ -48,15 +47,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)
|
||||
|
|
|
|||
|
|
@ -391,6 +391,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.");
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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_enable_secondary_layout"
|
||||
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_opposite"
|
||||
android:title="@string/emulation_secondary_display_opposite" />
|
||||
|
||||
<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>
|
||||
|
|
@ -36,10 +36,14 @@
|
|||
</string-array>
|
||||
|
||||
<string-array name="secondaryLayouts">
|
||||
<item>@string/emulation_secondary_display_default</item>
|
||||
<item>@string/emulation_secondary_display_opposite</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">
|
||||
|
|
@ -49,10 +53,13 @@
|
|||
</integer-array>
|
||||
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -500,7 +500,11 @@
|
|||
<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_secondary_display_enable_description">If disabled, Azahar will let Android manage connected displays. If this is enabled and multiple displays are connected, you can select which one Azahar will use in the Emulation Quick Menu</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>
|
||||
|
|
@ -509,7 +513,7 @@
|
|||
<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_opposite">Opposite Screen</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>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,16 @@ enum class PortraitLayoutOption : u32 {
|
|||
PortraitOriginal
|
||||
};
|
||||
|
||||
enum class SecondaryDisplayLayout : u32 { None, TopScreenOnly, BottomScreenOnly, SideBySide };
|
||||
enum class SecondaryDisplayLayout : u32 {
|
||||
None,
|
||||
TopScreenOnly,
|
||||
BottomScreenOnly,
|
||||
SideBySide,
|
||||
OppositeScreenOnly,
|
||||
Original,
|
||||
Hybrid,
|
||||
LargeScreen
|
||||
};
|
||||
/** Defines where the small screen will appear relative to the large screen
|
||||
* when in Large Screen mode
|
||||
*/
|
||||
|
|
@ -549,7 +558,7 @@ struct Values {
|
|||
SwitchableSetting<bool> swap_screen{false, Keys::swap_screen};
|
||||
SwitchableSetting<bool> upright_screen{false, Keys::upright_screen};
|
||||
SwitchableSetting<SecondaryDisplayLayout> secondary_display_layout{
|
||||
SecondaryDisplayLayout::None, Keys::secondary_display_layout};
|
||||
SecondaryDisplayLayout::OppositeScreenOnly, Keys::secondary_display_layout};
|
||||
SwitchableSetting<std::vector<LayoutOption>> layouts_to_cycle{
|
||||
{LayoutOption::Default, LayoutOption::SingleScreen, LayoutOption::LargeScreen,
|
||||
LayoutOption::SideScreen,
|
||||
|
|
|
|||
|
|
@ -275,18 +275,19 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
|
|||
std::swap(width, height);
|
||||
}
|
||||
|
||||
// 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
|
||||
// To do that, find the total emulation box and maximize that based on window size
|
||||
// use Large Screen layout with these specific ratios to get two of the pieces
|
||||
const float scale_factor = swapped ? 2.25 : 1.8;
|
||||
const Settings::SmallScreenPosition pos = swapped ? Settings::SmallScreenPosition::TopRight
|
||||
: Settings::SmallScreenPosition::BottomRight;
|
||||
FramebufferLayout res = LargeFrameLayout(width, height, swapped, upright, scale_factor, pos);
|
||||
// always pass false as the upright value here, as it is being handled here not there
|
||||
FramebufferLayout res = LargeFrameLayout(width, height, swapped, false, scale_factor, pos);
|
||||
const Common::Rectangle<u32> main = swapped ? res.bottom_screen : res.top_screen;
|
||||
const Common::Rectangle<u32> small = swapped ? res.top_screen : res.bottom_screen;
|
||||
res.additional_screen = Common::Rectangle<u32>{small.left, swapped ? small.bottom : main.top,
|
||||
small.right, swapped ? main.bottom : small.top};
|
||||
res.additional_screen_is_bottom = swapped;
|
||||
res.additional_screen_enabled = true;
|
||||
res.is_rotated = !upright;
|
||||
if (upright) {
|
||||
return reverseLayout(res);
|
||||
} else {
|
||||
|
|
@ -305,17 +306,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 OppositeScreenOnly
|
||||
case Settings::SecondaryDisplayLayout::OppositeScreenOnly:
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,13 +32,14 @@ struct FramebufferLayout {
|
|||
bool bottom_screen_enabled;
|
||||
Common::Rectangle<u32> top_screen;
|
||||
Common::Rectangle<u32> bottom_screen;
|
||||
// is_rotated is true when the screen is in landscape mode - not sure why!
|
||||
bool is_rotated = true;
|
||||
bool is_portrait = false;
|
||||
bool additional_screen_enabled;
|
||||
bool additional_screen_enabled = false;
|
||||
// top_opacity is currently not used but could be used in the future
|
||||
float top_opacity = 1.0f;
|
||||
float bottom_opacity = 1.0f;
|
||||
bool additional_screen_is_bottom = false;
|
||||
Common::Rectangle<u32> additional_screen;
|
||||
|
||||
CardboardSettings cardboard;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -706,7 +706,7 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
|
|||
|
||||
if (layout.additional_screen_enabled) {
|
||||
const auto& additional_screen = layout.additional_screen;
|
||||
if (!Settings::values.swap_screen.GetValue()) {
|
||||
if (!layout.additional_screen_is_bottom) {
|
||||
DrawTopScreen(layout, additional_screen);
|
||||
} else {
|
||||
DrawBottomScreen(layout, additional_screen);
|
||||
|
|
|
|||
|
|
@ -1037,7 +1037,7 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout&
|
|||
|
||||
if (layout.additional_screen_enabled) {
|
||||
const auto& additional_screen = layout.additional_screen;
|
||||
if (!Settings::values.swap_screen.GetValue()) {
|
||||
if (!layout.additional_screen_is_bottom) {
|
||||
DrawTopScreen(layout, additional_screen);
|
||||
} else {
|
||||
DrawBottomScreen(layout, additional_screen);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue