mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-06-06 02:33:44 -04:00
Merge 730af7d453 into 56f738eb06
This commit is contained in:
commit
ff53699354
23 changed files with 430 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_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
|
||||
// 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_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
|
||||
// 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_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>
|
||||
|
|
@ -36,10 +36,14 @@
|
|||
</string-array>
|
||||
|
||||
<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">
|
||||
|
|
@ -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,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 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,
|
||||
ReversePrimaryScreen,
|
||||
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::ReversePrimaryScreen, 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 ReversePrimaryScreen
|
||||
case Settings::SecondaryDisplayLayout::ReversePrimaryScreen:
|
||||
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