Added "Small Screen Position" feature

* error checking for layout value from older config

* rename enum and update aspect ratio code

* rewrite LargeFrameLayout to support multiple positions

* add settings for smallscreenposition, fix minsize function

* fixed framebuffer from res scale (screenshots)

* add desktop UI for small screen position

* small screen position submenu on desktop

* fix int-float conversion warning

* rename Above and Below to hopefully fix linux issue

* Add Small Screen Position Setting to android settings menu

* fix sliders to work with floats, mostly

* fix android slider textinput ui

* change None enums in settings and cam_params

* Apply clang-format-18

* SettingsAdapter.kt: Make more null pointer exception resistant

* Updated license headers

* Code formatting nitpicks

* fix bug in main.ui that was hiding menu

* replace default layout with a special call to LargeFrame (like SideBySide does)

* fix bug when "large screen" is actually narrower

* edit documentation for LargeScreenLayout

* update PortraitTopFullFrameLayout to use LargeFrameLayout

* fix unary minus on unsigned int bug

* Applied formatting correction

* Added `const`s where appropriate

* android: Add mention of the bottom-right small screen position being the default

* review fixes + more constants

* refactor all Upright calculations to a reverseLayout method, simplifying code and reducing bugs

* Removed stray extra newline

* SettingsAdapter.kt: Fixed some strange indentation

* Removed unnecessary `if` in favour of direct value usage

---------

Co-authored-by: Reg Tiangha <rtiangha@users.noreply.github.com>
Co-authored-by: OpenSauce04 <opensauce04@gmail.com>
This commit is contained in:
David Griswold 2024-10-29 14:22:51 -07:00 committed by GitHub
parent 21aec70f67
commit ff98896782
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 780 additions and 454 deletions

View file

@ -21,6 +21,23 @@ enum class ScreenLayout(val int: Int) {
}
}
enum class SmallScreenPosition(val int: Int) {
TOP_RIGHT(0),
MIDDLE_RIGHT(1),
BOTTOM_RIGHT(2),
TOP_LEFT(3),
MIDDLE_LEFT(4),
BOTTOM_LEFT(5),
ABOVE(6),
BELOW(7);
companion object {
fun from(int: Int): SmallScreenPosition {
return entries.firstOrNull { it.int == int } ?: TOP_RIGHT
}
}
}
enum class PortraitScreenLayout(val int: Int) {
// These must match what is defined in src/common/settings.h
TOP_FULL_WIDTH(0),
@ -28,7 +45,7 @@ enum class PortraitScreenLayout(val int: Int) {
companion object {
fun from(int: Int): PortraitScreenLayout {
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH;
return entries.firstOrNull { it.int == int } ?: TOP_FULL_WIDTH
}
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -9,7 +9,7 @@ enum class FloatSetting(
override val section: String,
override val defaultValue: Float
) : AbstractFloatSetting {
// There are no float settings currently
LARGE_SCREEN_PROPORTION("large_screen_proportion",Settings.SECTION_LAYOUT,2.25f),
EMPTY_SETTING("", "", 0.0f);
override var float: Float = defaultValue

View file

@ -23,6 +23,7 @@ enum class IntSetting(
CARDBOARD_X_SHIFT("cardboard_x_shift", Settings.SECTION_LAYOUT, 0),
CARDBOARD_Y_SHIFT("cardboard_y_shift", Settings.SECTION_LAYOUT, 0),
SCREEN_LAYOUT("layout_option", Settings.SECTION_LAYOUT, 0),
SMALL_SCREEN_POSITION("small_screen_position",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_X("custom_top_x",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_Y("custom_top_y",Settings.SECTION_LAYOUT,0),
LANDSCAPE_TOP_WIDTH("custom_top_width",Settings.SECTION_LAYOUT,800),

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -39,5 +39,6 @@ abstract class SettingsItem(
const val TYPE_RUNNABLE = 7
const val TYPE_INPUT_BINDING = 8
const val TYPE_STRING_INPUT = 9
const val TYPE_FLOAT_INPUT = 10
}
}

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -23,17 +23,16 @@ class SliderSetting(
val defaultValue: Float? = null
) : SettingsItem(setting, titleId, descriptionId) {
override val type = TYPE_SLIDER
val selectedValue: Int
val selectedFloat: Float
get() {
val setting = setting ?: return defaultValue!!.toInt()
val setting = setting ?: return defaultValue!!.toFloat()
return when (setting) {
is AbstractIntSetting -> setting.int
is FloatSetting -> setting.float.roundToInt()
is ScaledFloatSetting -> setting.float.roundToInt()
is AbstractIntSetting -> setting.int.toFloat()
is FloatSetting -> setting.float
is ScaledFloatSetting -> setting.float
else -> {
Log.error("[SliderSetting] Error casting setting type.")
-1
-1f
}
}
}

View file

@ -12,6 +12,7 @@ import android.icu.util.Calendar
import android.icu.util.TimeZone
import android.text.Editable
import android.text.InputFilter
import android.text.InputType
import android.text.TextWatcher
import android.text.format.DateFormat
import android.view.LayoutInflater
@ -68,6 +69,7 @@ import io.github.lime3ds.android.utils.SystemSaveGame
import java.lang.IllegalStateException
import java.lang.NumberFormatException
import java.text.SimpleDateFormat
import kotlin.math.roundToInt
class SettingsAdapter(
private val fragmentView: SettingsFragmentView,
@ -77,7 +79,7 @@ class SettingsAdapter(
private var clickedItem: SettingsItem? = null
private var clickedPosition: Int
private var dialog: AlertDialog? = null
private var sliderProgress = 0
private var sliderProgress = 0f
private var textSliderValue: TextInputEditText? = null
private var textInputLayout: TextInputLayout? = null
private var textInputValue: String = ""
@ -136,27 +138,23 @@ class SettingsAdapter(
}
override fun onBindViewHolder(holder: SettingViewHolder, position: Int) {
holder.bind(getItem(position))
getItem(position)?.let { holder.bind(it) }
}
private fun getItem(position: Int): SettingsItem {
return settings!![position]
private fun getItem(position: Int): SettingsItem? {
return settings?.get(position)
}
override fun getItemCount(): Int {
return if (settings != null) {
settings!!.size
} else {
0
}
return settings?.size ?: 0
}
override fun getItemViewType(position: Int): Int {
return getItem(position).type
return getItem(position)?.type ?: -1
}
fun setSettingsList(settings: ArrayList<SettingsItem>?) {
this.settings = settings
this.settings = settings ?: arrayListOf()
notifyDataSetChanged()
}
@ -182,10 +180,12 @@ class SettingsAdapter(
private fun onStringSingleChoiceClick(item: StringSingleChoiceSetting) {
clickedItem = item
dialog = MaterialAlertDialogBuilder(context)
.setTitle(item.nameId)
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
.show()
dialog = context?.let {
MaterialAlertDialogBuilder(it)
.setTitle(item.nameId)
.setSingleChoiceItems(item.choices, item.selectValueIndex, this)
.show()
}
}
fun onStringSingleChoiceClick(item: StringSingleChoiceSetting, position: Int) {
@ -231,10 +231,10 @@ class SettingsAdapter(
.build()
datePicker.addOnPositiveButtonClickListener {
timePicker.show(
(fragmentView.activityView as AppCompatActivity).supportFragmentManager,
"TimePicker"
)
val activity = fragmentView.activityView as? AppCompatActivity
activity?.supportFragmentManager?.let { fragmentManager ->
timePicker.show(fragmentManager, "TimePicker")
}
}
timePicker.addOnPositiveButtonClickListener {
var epochTime: Long = datePicker.selection!! / 1000
@ -258,38 +258,62 @@ class SettingsAdapter(
fun onSliderClick(item: SliderSetting, position: Int) {
clickedItem = item
clickedPosition = position
sliderProgress = item.selectedValue
sliderProgress = (item.selectedFloat * 100f).roundToInt() / 100f
val inflater = LayoutInflater.from(context)
val sliderBinding = DialogSliderBinding.inflate(inflater)
textInputLayout = sliderBinding.textInput
textSliderValue = sliderBinding.textValue
textSliderValue!!.setText(sliderProgress.toString())
textInputLayout!!.suffixText = item.units
if (item.setting is FloatSetting) {
textSliderValue?.let {
it.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
it.setText(sliderProgress.toString())
}
} else {
textSliderValue?.setText(sliderProgress.roundToInt().toString())
}
textInputLayout?.suffixText = item.units
sliderBinding.slider.apply {
valueFrom = item.min.toFloat()
valueTo = item.max.toFloat()
value = sliderProgress.toFloat()
textSliderValue!!.addTextChangedListener( object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val textValue = s.toString().toIntOrNull();
if (textValue == null || textValue < valueFrom || textValue > valueTo) {
textInputLayout!!.error ="Inappropriate value"
} else {
textInputLayout!!.error = null
value = textValue.toFloat();
}
value = sliderProgress
textSliderValue?.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
var textValue = s.toString().toFloatOrNull();
if (item.setting !is FloatSetting) {
textValue = textValue?.roundToInt()?.toFloat();
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
if (textValue == null || textValue < valueFrom || textValue > valueTo) {
textInputLayout?.error = "Inappropriate value"
} else {
textInputLayout?.error = null
value = textValue
}
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
})
addOnChangeListener { _: Slider, value: Float, _: Boolean ->
sliderProgress = value.toInt()
if (textSliderValue!!.text.toString() != value.toInt().toString()) {
textSliderValue!!.setText(value.toInt().toString())
textSliderValue!!.setSelection(textSliderValue!!.length())
sliderProgress = (value * 100).roundToInt().toFloat() / 100f
var sliderString = sliderProgress.toString()
if (item.setting !is FloatSetting) {
sliderString = sliderProgress.roundToInt().toString()
if (textSliderValue?.text.toString() != sliderString) {
textSliderValue?.setText(sliderString)
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
}
} else {
val currentText = textSliderValue?.text.toString()
val currentTextValue = currentText.toFloat()
if (currentTextValue != sliderProgress) {
textSliderValue?.setText(sliderString)
textSliderValue?.setSelection(textSliderValue?.length() ?: 0 )
}
}
}
}
@ -300,14 +324,14 @@ class SettingsAdapter(
.setPositiveButton(android.R.string.ok, this)
.setNegativeButton(android.R.string.cancel, defaultCancelListener)
.setNeutralButton(R.string.slider_default) { dialog: DialogInterface, which: Int ->
sliderBinding.slider.value = when (item.setting) {
sliderBinding.slider?.value = when (item.setting) {
is ScaledFloatSetting -> {
val scaledSetting = item.setting as ScaledFloatSetting
scaledSetting.defaultValue * scaledSetting.scale
}
is FloatSetting -> (item.setting as FloatSetting).defaultValue
else -> item.defaultValue!!
else -> item.defaultValue ?: 0f
}
onClick(dialog, which)
}
@ -358,85 +382,89 @@ class SettingsAdapter(
override fun onClick(dialog: DialogInterface, which: Int) {
when (clickedItem) {
is SingleChoiceSetting -> {
val scSetting = clickedItem as SingleChoiceSetting
val setting = when (scSetting.setting) {
is AbstractIntSetting -> {
val value = getValueForSingleChoiceSelection(scSetting, which)
if (scSetting.selectedValue != value) {
fragmentView.onSettingChanged()
val scSetting = clickedItem as? SingleChoiceSetting
scSetting?.let {
val setting = when (it.setting) {
is AbstractIntSetting -> {
val value = getValueForSingleChoiceSelection(it, which)
if (it.selectedValue != value) {
fragmentView?.onSettingChanged()
}
it.setSelectedValue(value)
}
scSetting.setSelectedValue(value)
}
is AbstractShortSetting -> {
val value = getValueForSingleChoiceSelection(scSetting, which).toShort()
if (scSetting.selectedValue.toShort() != value) {
fragmentView.onSettingChanged()
is AbstractShortSetting -> {
val value = getValueForSingleChoiceSelection(it, which).toShort()
if (it.selectedValue.toShort() != value) {
fragmentView?.onSettingChanged()
}
it.setSelectedValue(value)
}
scSetting.setSelectedValue(value)
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
}
else -> throw IllegalStateException("Unrecognized type used for SingleChoiceSetting!")
fragmentView?.putSetting(setting)
closeDialog()
}
fragmentView.putSetting(setting)
closeDialog()
}
is StringSingleChoiceSetting -> {
val scSetting = clickedItem as StringSingleChoiceSetting
val setting = when (scSetting.setting) {
is AbstractStringSetting -> {
val value = scSetting.getValueAt(which)
if (scSetting.selectedValue != value) fragmentView.onSettingChanged()
scSetting.setSelectedValue(value!!)
val scSetting = clickedItem as? StringSingleChoiceSetting
scSetting?.let {
val setting = when (it.setting) {
is AbstractStringSetting -> {
val value = it.getValueAt(which)
if (it.selectedValue != value) fragmentView?.onSettingChanged()
it.setSelectedValue(value ?: "")
}
is AbstractShortSetting -> {
if (it.selectValueIndex != which) fragmentView?.onSettingChanged()
it.setSelectedValue(it.getValueAt(which)?.toShort() ?: 1)
}
else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!")
}
is AbstractShortSetting -> {
if (scSetting.selectValueIndex != which) fragmentView.onSettingChanged()
scSetting.setSelectedValue(scSetting.getValueAt(which)?.toShort() ?: 1)
}
else -> throw IllegalStateException("Unrecognized type used for StringSingleChoiceSetting!")
fragmentView?.putSetting(setting)
closeDialog()
}
fragmentView.putSetting(setting)
closeDialog()
}
is SliderSetting -> {
val sliderSetting = clickedItem as SliderSetting
if (sliderSetting.selectedValue != sliderProgress) {
fragmentView.onSettingChanged()
}
when (sliderSetting.setting) {
is FloatSetting,
is ScaledFloatSetting -> {
val value = sliderProgress.toFloat()
val setting = sliderSetting.setSelectedValue(value)
fragmentView.putSetting(setting)
}
else -> {
val setting = sliderSetting.setSelectedValue(sliderProgress)
fragmentView.putSetting(setting)
val sliderSetting = clickedItem as? SliderSetting
sliderSetting?.let {
val sliderval = (it.selectedFloat * 100).roundToInt().toFloat() / 100
if (sliderval != sliderProgress) {
fragmentView?.onSettingChanged()
}
when (it.setting) {
is AbstractIntSetting -> {
val value = sliderProgress.roundToInt()
val setting = it.setSelectedValue(value)
fragmentView?.putSetting(setting)
}
else -> {
val setting = it.setSelectedValue(sliderProgress)
fragmentView?.putSetting(setting)
}
}
closeDialog()
}
closeDialog()
}
is StringInputSetting -> {
val inputSetting = clickedItem as StringInputSetting
if (inputSetting.selectedValue != textInputValue) {
fragmentView.onSettingChanged()
}
val setting = inputSetting.setSelectedValue(textInputValue)
fragmentView.putSetting(setting)
closeDialog()
val inputSetting = clickedItem as? StringInputSetting
inputSetting?.let {
if (it.selectedValue != textInputValue) {
fragmentView?.onSettingChanged()
}
val setting = it.setSelectedValue(textInputValue ?: "")
fragmentView?.putSetting(setting)
closeDialog()
}
}
}
clickedItem = null
sliderProgress = -1
sliderProgress = -1f
textInputValue = ""
}
@ -473,7 +501,7 @@ class SettingsAdapter(
R.string.setting_not_editable_description
).show((fragmentView as SettingsFragment).childFragmentManager, MessageDialogFragment.TAG)
}
fun onClickRegenerateConsoleId() {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.regenerate_console_id)

View file

@ -15,6 +15,8 @@ import android.text.TextUtils
import androidx.preference.PreferenceManager
import io.github.lime3ds.android.LimeApplication
import io.github.lime3ds.android.R
import io.github.lime3ds.android.display.PortraitScreenLayout
import io.github.lime3ds.android.display.ScreenLayout
import io.github.lime3ds.android.features.settings.model.AbstractBooleanSetting
import io.github.lime3ds.android.features.settings.model.AbstractIntSetting
import io.github.lime3ds.android.features.settings.model.AbstractSetting
@ -25,6 +27,7 @@ import io.github.lime3ds.android.features.settings.model.ScaledFloatSetting
import io.github.lime3ds.android.features.settings.model.Settings
import io.github.lime3ds.android.features.settings.model.StringSetting
import io.github.lime3ds.android.features.settings.model.AbstractShortSetting
import io.github.lime3ds.android.features.settings.model.FloatSetting
import io.github.lime3ds.android.features.settings.model.view.DateTimeSetting
import io.github.lime3ds.android.features.settings.model.view.HeaderSetting
import io.github.lime3ds.android.features.settings.model.view.InputBindingSetting
@ -938,6 +941,29 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
IntSetting.PORTRAIT_SCREEN_LAYOUT.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.SMALL_SCREEN_POSITION,
R.string.emulation_small_screen_position,
R.string.small_screen_position_description,
R.array.smallScreenPositions,
R.array.smallScreenPositionValues,
IntSetting.SMALL_SCREEN_POSITION.key,
IntSetting.SMALL_SCREEN_POSITION.defaultValue
)
)
add(
SliderSetting(
FloatSetting.LARGE_SCREEN_PROPORTION,
R.string.large_screen_proportion,
R.string.large_screen_proportion_description,
1,
5,
"",
FloatSetting.LARGE_SCREEN_PROPORTION.key,
FloatSetting.LARGE_SCREEN_PROPORTION.defaultValue
)
)
add(
SubmenuSetting(
R.string.emulation_landscape_custom_layout,
@ -954,8 +980,6 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
Settings.SECTION_CUSTOM_PORTRAIT
)
)
}
}

View file

@ -182,10 +182,11 @@ void Config::ReadValues() {
layoutInt = static_cast<int>(Settings::LayoutOption::LargeScreen);
}
Settings::values.layout_option = static_cast<Settings::LayoutOption>(layoutInt);
Settings::values.large_screen_proportion =
static_cast<float>(sdl2_config->GetReal("Layout", "large_screen_proportion", 2.25));
Settings::values.small_screen_position = static_cast<Settings::SmallScreenPosition>(
sdl2_config->GetInteger("Layout", "small_screen_position",
static_cast<int>(Settings::SmallScreenPosition::TopRight)));
ReadSetting("Layout", Settings::values.custom_top_x);
ReadSetting("Layout", Settings::values.custom_top_y);
ReadSetting("Layout", Settings::values.custom_top_width);

View file

@ -180,15 +180,26 @@ filter_mode =
[Layout]
# Layout for the screen inside the render window, landscape mode
# 0: Top/Bottom *currently unsupported on android*
# 0: Original (screens vertically aligned)
# 1: Single Screen Only,
# 2: *currently unsupported on android*
# 2: Large Screen (Default on android)
# 3: Side by Side
# 4: Hybrid
# 5: Custom Layout
# 6: (default) Large screen / small screen
layout_option =
# Large Screen Proportion - Relative size of large:small in large screen mode
# Default value is 2.25
large_screen_proportion =
# Small Screen Position - where is the small screen relative to the large
# Default value is 0
# 0: Top Right 1: Middle Right 2: Bottom Right
# 3: Top Left 4: Middle left 5: Bottom Left
# 6: Above the large screen 7: Below the large screen
small_screen_position =
# Screen placement when using Custom layout option
# 0x, 0y is the top left corner of the render window.
# suggested aspect ratio for top screen is 5:3

View file

@ -39,6 +39,28 @@
<item>1</item>
</integer-array>
<string-array name="smallScreenPositions">
<item>@string/small_screen_position_top_right</item>
<item>@string/small_screen_position_middle_right</item>
<item>@string/small_screen_position_bottom_right</item>
<item>@string/small_screen_position_top_left</item>
<item>@string/small_screen_position_middle_left</item>
<item>@string/small_screen_position_bottom_left</item>
<item>@string/small_screen_position_above</item>
<item>@string/small_screen_position_below</item>
</string-array>
<integer-array name="smallScreenPositionValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
</integer-array>
<string-array name="regionNames">
<item>@string/auto_select</item>
<item>@string/system_region_jpn</item>

View file

@ -382,6 +382,18 @@
<string name="emulation_screen_layout_original">Original</string>
<string name="emulation_portrait_layout_top_full">Default</string>
<string name="emulation_screen_layout_custom">Custom Layout</string>
<string name="emulation_small_screen_position">Small Screen Position</string>
<string name="small_screen_position_description">Where should the small screen appear relative to the large one in Large Screen Layout?</string>
<string name="small_screen_position_top_right">Top Right</string>
<string name="small_screen_position_middle_right">Middle Right</string>
<string name="small_screen_position_bottom_right">Bottom Right (Default)</string>
<string name="small_screen_position_top_left">Top Left</string>
<string name="small_screen_position_middle_left">Middle Left</string>
<string name="small_screen_position_bottom_left">Bottom Left</string>
<string name="small_screen_position_above">Above</string>
<string name="small_screen_position_below">Below</string>
<string name="large_screen_proportion">Large Screen Proportion</string>
<string name="large_screen_proportion_description">How many times larger is the large screen than the small screen in Large Screen layout?</string>
<string name="emulation_adjust_custom_layout">Adjust Custom Layout in Settings</string>
<string name="emulation_landscape_custom_layout">Landscape Custom Layout</string>
<string name="emulation_portrait_custom_layout">Portrait Custom Layout</string>

View file

@ -40,7 +40,7 @@ std::string_view GetGraphicsAPIName(GraphicsAPI api) {
std::string_view GetTextureFilterName(TextureFilter filter) {
switch (filter) {
case TextureFilter::None:
case TextureFilter::NoFilter:
return "None";
case TextureFilter::Anime4K:
return "Anime4K";
@ -112,6 +112,7 @@ void LogSettings() {
log_setting("Layout_SwapScreen", values.swap_screen.GetValue());
log_setting("Layout_UprightScreen", values.upright_screen.GetValue());
log_setting("Layout_LargeScreenProportion", values.large_screen_proportion.GetValue());
log_setting("Layout_SmallScreenPosition", values.small_screen_position.GetValue());
log_setting("Utility_DumpTextures", values.dump_textures.GetValue());
log_setting("Utility_CustomTextures", values.custom_textures.GetValue());
log_setting("Utility_PreloadTextures", values.preload_textures.GetValue());
@ -201,6 +202,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.swap_screen.SetGlobal(true);
values.upright_screen.SetGlobal(true);
values.large_screen_proportion.SetGlobal(true);
values.small_screen_position.SetGlobal(true);
values.bg_red.SetGlobal(true);
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);

View file

@ -53,6 +53,20 @@ enum class PortraitLayoutOption : u32 {
PortraitCustomLayout,
};
/** Defines where the small screen will appear relative to the large screen
* when in Large Screen mode
*/
enum class SmallScreenPosition : u32 {
TopRight,
MiddleRight,
BottomRight,
TopLeft,
MiddleLeft,
BottomLeft,
AboveLarge,
BelowLarge
};
enum class StereoRenderOption : u32 {
Off = 0,
SideBySide = 1,
@ -77,7 +91,7 @@ enum class AudioEmulation : u32 {
};
enum class TextureFilter : u32 {
None = 0,
NoFilter = 0,
Anime4K = 1,
Bicubic = 2,
ScaleForce = 3,
@ -479,7 +493,7 @@ struct Values {
Setting<bool> use_shader_jit{true, "use_shader_jit"};
SwitchableSetting<u32, true> resolution_factor{1, 0, 10, "resolution_factor"};
SwitchableSetting<double, true> frame_limit{100, 0, 1000, "frame_limit"};
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::None, "texture_filter"};
SwitchableSetting<TextureFilter> texture_filter{TextureFilter::NoFilter, "texture_filter"};
SwitchableSetting<TextureSampling> texture_sampling{TextureSampling::GameControlled,
"texture_sampling"};
SwitchableSetting<LayoutOption> layout_option{LayoutOption::Default, "layout_option"};
@ -487,6 +501,8 @@ struct Values {
SwitchableSetting<bool> upright_screen{false, "upright_screen"};
SwitchableSetting<float, true> large_screen_proportion{4.f, 1.f, 16.f,
"large_screen_proportion"};
SwitchableSetting<SmallScreenPosition> small_screen_position{SmallScreenPosition::BottomRight,
"small_screen_position"};
Setting<u16> custom_top_x{0, "custom_top_x"};
Setting<u16> custom_top_y{0, "custom_top_y"};
Setting<u16> custom_top_width{800, "custom_top_width"};

View file

@ -224,7 +224,7 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue(),
Layout::VerticalAlignment::Bottom);
Settings::values.small_screen_position.GetValue());
break;
case Settings::LayoutOption::HybridScreen:
layout =
@ -235,7 +235,7 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
layout =
Layout::LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(), 1.0f,
Layout::VerticalAlignment::Bottom);
Settings::SmallScreenPosition::MiddleRight);
break;
#ifndef ANDROID
case Settings::LayoutOption::SeparateWindows:

View file

@ -15,10 +15,6 @@ static constexpr float TOP_SCREEN_ASPECT_RATIO =
static_cast<float>(Core::kScreenTopHeight) / Core::kScreenTopWidth;
static constexpr float BOT_SCREEN_ASPECT_RATIO =
static_cast<float>(Core::kScreenBottomHeight) / Core::kScreenBottomWidth;
static constexpr float TOP_SCREEN_UPRIGHT_ASPECT_RATIO =
static_cast<float>(Core::kScreenTopWidth) / Core::kScreenTopHeight;
static constexpr float BOT_SCREEN_UPRIGHT_ASPECT_RATIO =
static_cast<float>(Core::kScreenBottomWidth) / Core::kScreenBottomHeight;
u32 FramebufferLayout::GetScalingRatio() const {
if (is_rotated) {
@ -39,115 +35,19 @@ static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
}
FramebufferLayout DefaultFrameLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0);
ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
Common::Rectangle<u32> screen_window_area;
Common::Rectangle<u32> top_screen;
Common::Rectangle<u32> bot_screen;
float emulation_aspect_ratio;
if (upright) {
// Default layout gives equal screen sizes to the top and bottom screen
screen_window_area = {0, 0, width / 2, height};
top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_UPRIGHT_ASPECT_RATIO);
bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
// both screens width are taken into account by dividing by 2
emulation_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO / 2;
} else {
// Default layout gives equal screen sizes to the top and bottom screen
screen_window_area = {0, 0, width, height / 2};
top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
// both screens height are taken into account by multiplying by 2
emulation_aspect_ratio = TOP_SCREEN_ASPECT_RATIO * 2;
}
float window_aspect_ratio = static_cast<float>(height) / width;
if (window_aspect_ratio < emulation_aspect_ratio) {
// Window is wider than the emulation content => apply borders to the right and left sides
if (upright) {
// Recalculate the bottom screen to account for the height difference between right and
// left
screen_window_area = {0, 0, top_screen.GetWidth(), height};
bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
bot_screen =
bot_screen.TranslateY((top_screen.GetHeight() - bot_screen.GetHeight()) / 2);
if (swapped) {
bot_screen = bot_screen.TranslateX(width / 2 - bot_screen.GetWidth());
} else {
top_screen = top_screen.TranslateX(width / 2 - top_screen.GetWidth());
}
} else {
top_screen =
top_screen.TranslateX((screen_window_area.GetWidth() - top_screen.GetWidth()) / 2);
bot_screen =
bot_screen.TranslateX((screen_window_area.GetWidth() - bot_screen.GetWidth()) / 2);
}
} else {
// Window is narrower than the emulation content => apply borders to the top and bottom
if (upright) {
top_screen = top_screen.TranslateY(
(screen_window_area.GetHeight() - top_screen.GetHeight()) / 2);
bot_screen = bot_screen.TranslateY(
(screen_window_area.GetHeight() - bot_screen.GetHeight()) / 2);
} else {
// Recalculate the bottom screen to account for the width difference between top and
// bottom
screen_window_area = {0, 0, width, top_screen.GetHeight()};
bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
bot_screen = bot_screen.TranslateX((top_screen.GetWidth() - bot_screen.GetWidth()) / 2);
if (swapped) {
bot_screen = bot_screen.TranslateY(height / 2 - bot_screen.GetHeight());
} else {
top_screen = top_screen.TranslateY(height / 2 - top_screen.GetHeight());
}
}
}
if (upright) {
// Move the top screen to the right if we are swapped.
res.top_screen = swapped ? top_screen.TranslateX(width / 2) : top_screen;
res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateX(width / 2);
} else {
// Move the top screen to the bottom if we are swapped.
res.top_screen = swapped ? top_screen.TranslateY(height / 2) : top_screen;
res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateY(height / 2);
}
return res;
return LargeFrameLayout(width, height, swapped, upright, 1.0f,
Settings::SmallScreenPosition::BelowLarge);
}
FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool swapped) {
ASSERT(width > 0);
ASSERT(height > 0);
FramebufferLayout res{width, height, true, true, {}, {}, true, true};
// Default layout gives equal screen sizes to the top and bottom screen
Common::Rectangle<u32> screen_window_area{0, 0, width, height / 2};
Common::Rectangle<u32> top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
Common::Rectangle<u32> bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
float window_aspect_ratio = static_cast<float>(height) / width;
// both screens height are taken into account by multiplying by 2
float emulation_aspect_ratio = TOP_SCREEN_ASPECT_RATIO * 2;
if (window_aspect_ratio < emulation_aspect_ratio) {
// Apply borders to the left and right sides of the window.
top_screen =
top_screen.TranslateX((screen_window_area.GetWidth() - top_screen.GetWidth()) / 2);
bot_screen =
bot_screen.TranslateX((screen_window_area.GetWidth() - bot_screen.GetWidth()) / 2);
} else {
// Window is narrower than the emulation content
// Recalculate the bottom screen to account for the width difference between top and bottom
bot_screen = bot_screen.TranslateX((top_screen.GetWidth() - bot_screen.GetWidth()) / 2);
}
// Move the top screen to the bottom if we are swapped.
res.top_screen = swapped ? top_screen.TranslateY(bot_screen.GetHeight()) : top_screen;
res.bottom_screen = swapped ? bot_screen : bot_screen.TranslateY(top_screen.GetHeight());
const float scale_factor = swapped ? 1.25f : 0.8f;
FramebufferLayout res = LargeFrameLayout(width, height, swapped, false, scale_factor,
Settings::SmallScreenPosition::BelowLarge);
const int shiftY = -(int)(swapped ? res.bottom_screen.top : res.top_screen.top);
res.top_screen = res.top_screen.TranslateY(shiftY);
res.bottom_screen = res.bottom_screen.TranslateY(shiftY);
return res;
}
@ -156,26 +56,22 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up
ASSERT(height > 0);
// The drawing code needs at least somewhat valid values for both screens
// so just calculate them both even if the other isn't showing.
if (upright) {
std::swap(width, height);
}
FramebufferLayout res{width, height, !swapped, swapped, {}, {}, !upright};
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
Common::Rectangle<u32> top_screen;
Common::Rectangle<u32> bot_screen;
float emulation_aspect_ratio;
if (upright) {
top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_UPRIGHT_ASPECT_RATIO);
bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_UPRIGHT_ASPECT_RATIO);
emulation_aspect_ratio =
(swapped) ? BOT_SCREEN_UPRIGHT_ASPECT_RATIO : TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
} else {
top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
}
top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO);
bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO);
emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
const bool stretched = (Settings::values.screen_top_stretch.GetValue() && !swapped) ||
(Settings::values.screen_bottom_stretch.GetValue() && swapped);
float window_aspect_ratio = static_cast<float>(height) / width;
const float window_aspect_ratio = static_cast<float>(height) / width;
if (stretched) {
top_screen = {Settings::values.screen_top_leftright_padding.GetValue(),
@ -197,116 +93,148 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up
}
res.top_screen = top_screen;
res.bottom_screen = bot_screen;
return res;
if (upright) {
return reverseLayout(res);
} else {
return res;
}
}
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upright,
float scale_factor, VerticalAlignment vertical_alignment) {
float scale_factor,
Settings::SmallScreenPosition small_screen_position) {
ASSERT(width > 0);
ASSERT(height > 0);
#ifdef ANDROID
vertical_alignment = VerticalAlignment::Top;
#endif
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
// Split the window into two parts. Give 4x width to the main screen and 1x width to the small
// To do that, find the total emulation box and maximize that based on window size
float window_aspect_ratio = static_cast<float>(height) / width;
float emulation_aspect_ratio;
float large_screen_aspect_ratio;
float small_screen_aspect_ratio;
if (upright) {
if (swapped) {
emulation_aspect_ratio =
(Core::kScreenBottomWidth * scale_factor + Core::kScreenTopWidth) /
(Core::kScreenBottomHeight * scale_factor);
large_screen_aspect_ratio = BOT_SCREEN_UPRIGHT_ASPECT_RATIO;
small_screen_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
} else {
emulation_aspect_ratio =
(Core::kScreenTopWidth * scale_factor + Core::kScreenBottomWidth) /
(Core::kScreenTopHeight * scale_factor);
large_screen_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
small_screen_aspect_ratio = BOT_SCREEN_UPRIGHT_ASPECT_RATIO;
}
} else {
if (swapped) {
emulation_aspect_ratio =
Core::kScreenBottomHeight * scale_factor /
(Core::kScreenBottomWidth * scale_factor + Core::kScreenTopWidth);
large_screen_aspect_ratio = BOT_SCREEN_ASPECT_RATIO;
small_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO;
} else {
emulation_aspect_ratio =
Core::kScreenTopHeight * scale_factor /
(Core::kScreenTopWidth * scale_factor + Core::kScreenBottomWidth);
large_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO;
small_screen_aspect_ratio = BOT_SCREEN_ASPECT_RATIO;
}
std::swap(width, height);
}
const bool vertical = (small_screen_position == Settings::SmallScreenPosition::AboveLarge ||
small_screen_position == Settings::SmallScreenPosition::BelowLarge);
FramebufferLayout res{width, height, true, true, {}, {}, !upright};
// Split the window into two parts. Give proportional width to the smaller screen
// To do that, find the total emulation box and maximize that based on window size
const float window_aspect_ratio = static_cast<float>(height) / width;
float emulation_aspect_ratio;
float large_height =
swapped ? Core::kScreenBottomHeight * scale_factor : Core::kScreenTopHeight * scale_factor;
float small_height =
static_cast<float>(swapped ? Core::kScreenTopHeight : Core::kScreenBottomHeight);
float large_width =
swapped ? Core::kScreenBottomWidth * scale_factor : Core::kScreenTopWidth * scale_factor;
float small_width =
static_cast<float>(swapped ? Core::kScreenTopWidth : Core::kScreenBottomWidth);
float emulation_width, emulation_height;
if (vertical) {
// width is just the larger size at this point
emulation_width = std::max(large_width, small_width);
emulation_height = large_height + small_height;
} else {
emulation_width = large_width + small_width;
emulation_height = std::max(large_height, small_height);
}
emulation_aspect_ratio = emulation_height / emulation_width;
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
Common::Rectangle<u32> total_rect = MaxRectangle(screen_window_area, emulation_aspect_ratio);
Common::Rectangle<u32> large_screen = MaxRectangle(total_rect, large_screen_aspect_ratio);
Common::Rectangle<u32> scaled_rect = total_rect.Scale(1.f / scale_factor);
Common::Rectangle<u32> small_screen = MaxRectangle(scaled_rect, small_screen_aspect_ratio);
const float scale_amount = total_rect.GetHeight() * 1.f / emulation_height * 1.f;
Common::Rectangle<u32> large_screen =
Common::Rectangle<u32>{total_rect.left, total_rect.top,
static_cast<u32>(large_width * scale_amount + total_rect.left),
static_cast<u32>(large_height * scale_amount + total_rect.top)};
Common::Rectangle<u32> small_screen =
Common::Rectangle<u32>{total_rect.left, total_rect.top,
static_cast<u32>(small_width * scale_amount + total_rect.left),
static_cast<u32>(small_height * scale_amount + total_rect.top)};
if (window_aspect_ratio < emulation_aspect_ratio) {
// shift the large screen so it is at the left position of the bounding rectangle
large_screen = large_screen.TranslateX((width - total_rect.GetWidth()) / 2);
} else {
// shift the large screen so it is at the top position of the bounding rectangle
large_screen = large_screen.TranslateY((height - total_rect.GetHeight()) / 2);
}
if (upright) {
large_screen = large_screen.TranslateY(small_screen.GetHeight());
small_screen = small_screen.TranslateY(large_screen.top - small_screen.GetHeight());
switch (vertical_alignment) {
case VerticalAlignment::Top:
// Shift the small screen to the top right corner
small_screen = small_screen.TranslateX(large_screen.left);
break;
case VerticalAlignment::Middle:
// Shift the small screen to the center right
small_screen = small_screen.TranslateX(
((large_screen.GetWidth() - small_screen.GetWidth()) / 2) + large_screen.left);
break;
case VerticalAlignment::Bottom:
// Shift the small screen to the bottom right corner
small_screen = small_screen.TranslateX(large_screen.right - small_screen.GetWidth());
break;
default:
UNREACHABLE();
break;
}
} else {
switch (small_screen_position) {
case Settings::SmallScreenPosition::TopRight:
// Shift the small screen to the top right corner
small_screen = small_screen.TranslateX(large_screen.right);
switch (vertical_alignment) {
case VerticalAlignment::Top:
// Shift the small screen to the top right corner
small_screen = small_screen.TranslateY(large_screen.top);
break;
case VerticalAlignment::Middle:
// Shift the small screen to the center right
small_screen = small_screen.TranslateY(
((large_screen.GetHeight() - small_screen.GetHeight()) / 2) + large_screen.top);
break;
case VerticalAlignment::Bottom:
// Shift the small screen to the bottom right corner
small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.GetHeight());
break;
default:
UNREACHABLE();
break;
small_screen = small_screen.TranslateY(large_screen.top);
break;
case Settings::SmallScreenPosition::MiddleRight:
// Shift the small screen to the center right
small_screen = small_screen.TranslateX(large_screen.right);
small_screen = small_screen.TranslateY(
((large_screen.GetHeight() - small_screen.GetHeight()) / 2) + large_screen.top);
break;
case Settings::SmallScreenPosition::BottomRight:
// Shift the small screen to the bottom right corner
small_screen = small_screen.TranslateX(large_screen.right);
small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.GetHeight());
break;
case Settings::SmallScreenPosition::TopLeft:
// shift the small screen to the upper left then shift the large screen to its right
small_screen = small_screen.TranslateX(large_screen.left);
large_screen = large_screen.TranslateX(small_screen.GetWidth());
small_screen = small_screen.TranslateY(large_screen.top);
break;
case Settings::SmallScreenPosition::MiddleLeft:
// shift the small screen to the middle left and shift the large screen to its right
small_screen = small_screen.TranslateX(large_screen.left);
large_screen = large_screen.TranslateX(small_screen.GetWidth());
small_screen = small_screen.TranslateY(
((large_screen.GetHeight() - small_screen.GetHeight()) / 2) + large_screen.top);
break;
case Settings::SmallScreenPosition::BottomLeft:
// shift the small screen to the bottom left and shift the large screen to its right
small_screen = small_screen.TranslateX(large_screen.left);
large_screen = large_screen.TranslateX(small_screen.GetWidth());
small_screen = small_screen.TranslateY(large_screen.bottom - small_screen.GetHeight());
break;
case Settings::SmallScreenPosition::AboveLarge:
// shift the large screen down and the bottom screen above it
small_screen = small_screen.TranslateY(large_screen.top);
large_screen = large_screen.TranslateY(small_screen.GetHeight());
// If the "large screen" is actually smaller, center it
if (large_screen.GetWidth() < total_rect.GetWidth()) {
large_screen =
large_screen.TranslateX((total_rect.GetWidth() - large_screen.GetWidth()) / 2);
}
small_screen = small_screen.TranslateX(large_screen.left + large_screen.GetWidth() / 2 -
small_screen.GetWidth() / 2);
break;
case Settings::SmallScreenPosition::BelowLarge:
// shift the bottom_screen down and then over to the center
// If the "large screen" is actually smaller, center it
if (large_screen.GetWidth() < total_rect.GetWidth()) {
large_screen =
large_screen.TranslateX((total_rect.GetWidth() - large_screen.GetWidth()) / 2);
}
small_screen = small_screen.TranslateY(large_screen.bottom);
small_screen = small_screen.TranslateX(large_screen.left + large_screen.GetWidth() / 2 -
small_screen.GetWidth() / 2);
break;
default:
UNREACHABLE();
break;
}
res.top_screen = swapped ? small_screen : large_screen;
res.bottom_screen = swapped ? large_screen : small_screen;
return res;
if (upright) {
return reverseLayout(res);
} else {
return res;
}
}
FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool upright) {
ASSERT(width > 0);
ASSERT(height > 0);
if (upright) {
std::swap(width, height);
}
FramebufferLayout res{width, height, true, true, {}, {}, !upright, false, true, {}};
// Split the window into two parts. Give 2.25x width to the main screen,
@ -327,13 +255,6 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
(Core::kScreenBottomWidth * scale_factor + Core::kScreenTopWidth);
}
if (upright) {
hybrid_area_aspect_ratio = 1.f / hybrid_area_aspect_ratio;
main_screen_aspect_ratio = 1.f / main_screen_aspect_ratio;
top_screen_aspect_ratio = TOP_SCREEN_UPRIGHT_ASPECT_RATIO;
bot_screen_aspect_ratio = BOT_SCREEN_UPRIGHT_ASPECT_RATIO;
}
Common::Rectangle<u32> screen_window_area{0, 0, width, height};
Common::Rectangle<u32> total_rect = MaxRectangle(screen_window_area, hybrid_area_aspect_ratio);
Common::Rectangle<u32> large_main_screen = MaxRectangle(total_rect, main_screen_aspect_ratio);
@ -349,32 +270,24 @@ FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool u
// Scale the bottom screen so it's width is the same as top screen
small_bottom_screen = small_bottom_screen.Scale(1.25f);
if (upright) {
large_main_screen = large_main_screen.TranslateY(small_bottom_screen.GetHeight());
// Shift small bottom screen to upper right corner
small_bottom_screen =
small_bottom_screen.TranslateX(large_main_screen.right - small_bottom_screen.GetWidth())
.TranslateY(large_main_screen.top - small_bottom_screen.GetHeight());
// Shift small top screen to upper left corner
small_top_screen = small_top_screen.TranslateX(large_main_screen.left)
.TranslateY(large_main_screen.top - small_bottom_screen.GetHeight());
} else {
// Shift the small bottom screen to the bottom right corner
small_bottom_screen =
small_bottom_screen.TranslateX(large_main_screen.right)
.TranslateY(large_main_screen.GetHeight() + large_main_screen.top -
small_bottom_screen.GetHeight());
// Shift the small bottom screen to the bottom right corner
small_bottom_screen = small_bottom_screen.TranslateX(large_main_screen.right)
.TranslateY(large_main_screen.GetHeight() + large_main_screen.top -
small_bottom_screen.GetHeight());
// Shift small top screen to upper right corner
small_top_screen =
small_top_screen.TranslateX(large_main_screen.right).TranslateY(large_main_screen.top);
}
// Shift small top screen to upper right corner
small_top_screen =
small_top_screen.TranslateX(large_main_screen.right).TranslateY(large_main_screen.top);
res.top_screen = small_top_screen;
res.additional_screen = swapped ? small_bottom_screen : large_main_screen;
res.bottom_screen = swapped ? large_main_screen : small_bottom_screen;
return res;
if (upright) {
return reverseLayout(res);
} else {
return res;
}
}
FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary, bool upright) {
@ -387,25 +300,30 @@ FramebufferLayout SeparateWindowsLayout(u32 width, u32 height, bool is_secondary
FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool is_portrait_mode) {
ASSERT(width > 0);
ASSERT(height > 0);
const bool upright = Settings::values.upright_screen.GetValue();
if (upright) {
std::swap(width, height);
}
FramebufferLayout res{
width, height, true, true, {}, {}, !Settings::values.upright_screen, is_portrait_mode};
u16 top_x = is_portrait_mode ? Settings::values.custom_portrait_top_x.GetValue()
: Settings::values.custom_top_x.GetValue();
u16 top_width = is_portrait_mode ? Settings::values.custom_portrait_top_width.GetValue()
: Settings::values.custom_top_width.GetValue();
u16 top_y = is_portrait_mode ? Settings::values.custom_portrait_top_y.GetValue()
: Settings::values.custom_top_y.GetValue();
u16 top_height = is_portrait_mode ? Settings::values.custom_portrait_top_height.GetValue()
: Settings::values.custom_top_height.GetValue();
u16 bottom_x = is_portrait_mode ? Settings::values.custom_portrait_bottom_x.GetValue()
: Settings::values.custom_bottom_x.GetValue();
u16 bottom_width = is_portrait_mode ? Settings::values.custom_portrait_bottom_width.GetValue()
: Settings::values.custom_bottom_width.GetValue();
u16 bottom_y = is_portrait_mode ? Settings::values.custom_portrait_bottom_y.GetValue()
: Settings::values.custom_bottom_y.GetValue();
u16 bottom_height = is_portrait_mode ? Settings::values.custom_portrait_bottom_height.GetValue()
: Settings::values.custom_bottom_height.GetValue();
const u16 top_x = is_portrait_mode ? Settings::values.custom_portrait_top_x.GetValue()
: Settings::values.custom_top_x.GetValue();
const u16 top_width = is_portrait_mode ? Settings::values.custom_portrait_top_width.GetValue()
: Settings::values.custom_top_width.GetValue();
const u16 top_y = is_portrait_mode ? Settings::values.custom_portrait_top_y.GetValue()
: Settings::values.custom_top_y.GetValue();
const u16 top_height = is_portrait_mode ? Settings::values.custom_portrait_top_height.GetValue()
: Settings::values.custom_top_height.GetValue();
const u16 bottom_x = is_portrait_mode ? Settings::values.custom_portrait_bottom_x.GetValue()
: Settings::values.custom_bottom_x.GetValue();
const u16 bottom_width = is_portrait_mode
? Settings::values.custom_portrait_bottom_width.GetValue()
: Settings::values.custom_bottom_width.GetValue();
const u16 bottom_y = is_portrait_mode ? Settings::values.custom_portrait_bottom_y.GetValue()
: Settings::values.custom_bottom_y.GetValue();
const u16 bottom_height = is_portrait_mode
? Settings::values.custom_portrait_bottom_height.GetValue()
: Settings::values.custom_bottom_height.GetValue();
Common::Rectangle<u32> top_screen{top_x, top_y, (u32)(top_x + top_width),
(u32)(top_y + top_height)};
@ -419,7 +337,11 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height, bool is_swapped, bool
res.top_screen = top_screen;
res.bottom_screen = bot_screen;
}
return res;
if (upright) {
return reverseLayout(res);
} else {
return res;
}
}
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondary,
@ -479,30 +401,37 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
Settings::values.upright_screen.GetValue());
}
case Settings::LayoutOption::LargeScreen:
if (Settings::values.swap_screen.GetValue()) {
width =
(Core::kScreenBottomWidth +
Core::kScreenTopWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale;
height = Core::kScreenBottomHeight * res_scale;
case Settings::LayoutOption::LargeScreen: {
const bool swapped = Settings::values.swap_screen.GetValue();
const int largeWidth = swapped ? Core::kScreenBottomWidth : Core::kScreenTopWidth;
const int largeHeight = swapped ? Core::kScreenBottomHeight : Core::kScreenTopHeight;
const int smallWidth =
static_cast<int>((swapped ? Core::kScreenTopWidth : Core::kScreenBottomWidth) /
Settings::values.large_screen_proportion.GetValue());
const int smallHeight =
static_cast<int>((swapped ? Core::kScreenTopHeight : Core::kScreenBottomHeight) /
Settings::values.large_screen_proportion.GetValue());
if (Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::AboveLarge ||
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BelowLarge) {
// vertical, so height is sum of heights, width is larger of widths
width = std::max(largeWidth, smallWidth) * res_scale;
height = (largeHeight + smallHeight) * res_scale;
} else {
width =
(Core::kScreenTopWidth +
Core::kScreenBottomWidth /
static_cast<int>(Settings::values.large_screen_proportion.GetValue())) *
res_scale;
height = Core::kScreenTopHeight * res_scale;
width = (largeWidth + smallWidth) * res_scale;
height = std::max(largeHeight, smallHeight) * res_scale;
}
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue(),
VerticalAlignment::Bottom);
Settings::values.small_screen_position.GetValue());
}
case Settings::LayoutOption::SideScreen:
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
height = Core::kScreenTopHeight * res_scale;
@ -512,7 +441,7 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(), 1,
VerticalAlignment::Middle);
Settings::SmallScreenPosition::MiddleRight);
case Settings::LayoutOption::Default:
default:
@ -613,10 +542,44 @@ FramebufferLayout GetCardboardSettings(const FramebufferLayout& layout) {
return new_layout;
}
FramebufferLayout reverseLayout(FramebufferLayout layout) {
std::swap(layout.height, layout.width);
u32 oldLeft, oldRight, oldTop, oldBottom;
oldLeft = layout.top_screen.left;
oldRight = layout.top_screen.right;
oldTop = layout.top_screen.top;
oldBottom = layout.top_screen.bottom;
layout.top_screen.left = oldTop;
layout.top_screen.right = oldBottom;
layout.top_screen.top = layout.height - oldRight;
layout.top_screen.bottom = layout.height - oldLeft;
oldLeft = layout.bottom_screen.left;
oldRight = layout.bottom_screen.right;
oldTop = layout.bottom_screen.top;
oldBottom = layout.bottom_screen.bottom;
layout.bottom_screen.left = oldTop;
layout.bottom_screen.right = oldBottom;
layout.bottom_screen.top = layout.height - oldRight;
layout.bottom_screen.bottom = layout.height - oldLeft;
if (layout.additional_screen_enabled) {
oldLeft = layout.additional_screen.left;
oldRight = layout.additional_screen.right;
oldTop = layout.additional_screen.top;
oldBottom = layout.additional_screen.bottom;
layout.additional_screen.left = oldTop;
layout.additional_screen.right = oldBottom;
layout.additional_screen.top = layout.height - oldRight;
layout.additional_screen.bottom = layout.height - oldLeft;
}
return layout;
}
std::pair<unsigned, unsigned> GetMinimumSizeFromPortraitLayout() {
u32 min_width, min_height;
min_width = Core::kScreenTopWidth;
min_height = Core::kScreenTopHeight + Core::kScreenBottomHeight;
const u32 min_width = Core::kScreenTopWidth;
const u32 min_height = Core::kScreenTopHeight + Core::kScreenBottomHeight;
return std::make_pair(min_width, min_height);
}
@ -632,15 +595,30 @@ std::pair<unsigned, unsigned> GetMinimumSizeFromLayout(Settings::LayoutOption la
min_width = Settings::values.swap_screen ? Core::kScreenBottomWidth : Core::kScreenTopWidth;
min_height = Core::kScreenBottomHeight;
break;
case Settings::LayoutOption::LargeScreen:
min_width = static_cast<u32>(
Settings::values.swap_screen
? Core::kScreenTopWidth / Settings::values.large_screen_proportion.GetValue() +
Core::kScreenBottomWidth
: Core::kScreenTopWidth + Core::kScreenBottomWidth /
Settings::values.large_screen_proportion.GetValue());
min_height = Core::kScreenBottomHeight;
case Settings::LayoutOption::LargeScreen: {
const bool swapped = Settings::values.swap_screen.GetValue();
const int largeWidth = swapped ? Core::kScreenBottomWidth : Core::kScreenTopWidth;
const int largeHeight = swapped ? Core::kScreenBottomHeight : Core::kScreenTopHeight;
int smallWidth = swapped ? Core::kScreenTopWidth : Core::kScreenBottomWidth;
int smallHeight = swapped ? Core::kScreenTopHeight : Core::kScreenBottomHeight;
smallWidth =
static_cast<int>(smallWidth / Settings::values.large_screen_proportion.GetValue());
smallHeight =
static_cast<int>(smallHeight / Settings::values.large_screen_proportion.GetValue());
min_width = static_cast<u32>(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::AboveLarge ||
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BelowLarge
? std::max(largeWidth, smallWidth)
: largeWidth + smallWidth);
min_height = static_cast<u32>(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::AboveLarge ||
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BelowLarge
? largeHeight + smallHeight
: std::max(largeHeight, smallHeight));
break;
}
case Settings::LayoutOption::SideScreen:
min_width = Core::kScreenTopWidth + Core::kScreenBottomWidth;
min_height = Core::kScreenBottomHeight;

View file

@ -5,10 +5,7 @@
#pragma once
#include "common/math_util.h"
namespace Settings {
enum class LayoutOption : u32;
}
#include "common/settings.h"
namespace Layout {
@ -20,31 +17,6 @@ enum class DisplayOrientation {
PortraitFlipped, // 3DS rotated 270 degrees counter-clockwise
};
/// Describes the vertical alignment of the top and bottom screens in LargeFrameLayout
/// Top
/// +-------------+-----+
/// | | |
/// | +-----+
/// | |
/// +-------------+
/// Middle
/// +-------------+
/// | +-----+
/// | | |
/// | +-----+
/// +-------------+
/// Bottom
/// +-------------+
/// | |
/// | +-----+
/// | | |
/// +-------------+-----+
enum class VerticalAlignment {
Top,
Middle,
Bottom,
};
/// Describes the horizontal coordinates for the right eye screen when using Cardboard VR
struct CardboardSettings {
u32 top_screen_right_eye;
@ -75,7 +47,12 @@ struct FramebufferLayout {
};
/**
* Factory method for constructing a default FramebufferLayout
* Method to create a rotated copy of a framebuffer layout, used to rotate to upright mode
*/
FramebufferLayout reverseLayout(FramebufferLayout layout);
/**
* Factory method for constructing a default FramebufferLayout with screens on top of one another
* @param width Window framebuffer width in pixels
* @param height Window framebuffer height in pixels
* @param is_swapped if true, the bottom screen will be displayed above the top screen
@ -105,9 +82,7 @@ FramebufferLayout PortraitTopFullFrameLayout(u32 width, u32 height, bool is_swap
FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped, bool upright);
/**
* Factory method for constructing a Frame with the a 4x size Top screen with a 1x size bottom
* screen on the right
* This is useful in particular because it matches well with a 1920x1080 resolution monitor
* Factory method for constructing a Frame with differently sized top and bottom windows
* @param width Window framebuffer width in pixels
* @param height Window framebuffer height in pixels
* @param is_swapped if true, the bottom screen will be the large display
@ -118,7 +93,8 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped, bool
* @return Newly created FramebufferLayout object with default screen regions initialized
*/
FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped, bool upright,
float scale_factor, VerticalAlignment vertical_alignment);
float scale_factor,
Settings::SmallScreenPosition small_screen_position);
/**
* Factory method for constructing a frame with 2.5 times bigger top screen on the right,
* and 1x top and bottom screen on the left
@ -126,6 +102,9 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped, bool
* @param height Window framebuffer height in pixels
* @param is_swapped if true, the bottom screen will be the large display
* @param upright if true, the screens will be rotated 90 degrees anti-clockwise
* @param scale_factor determines the proportion of large to small. Must be >= 1
* @param small_screen_position determines where the small screen appears relative to the large
* screen
* @return Newly created FramebufferLayout object with default screen regions initialized
*/
FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool upright);

View file

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -1099,8 +1099,8 @@ void Module::Interface::DriverInitialize(Kernel::HLERequestContext& ctx) {
for (int context_id = 0; context_id < 2; ++context_id) {
// Note: the following default values are verified against real 3DS
ContextConfig& context = camera.contexts[context_id];
context.flip = camera_id == 1 ? Flip::Horizontal : Flip::None;
context.effect = Effect::None;
context.flip = camera_id == 1 ? Flip::Horizontal : Flip::NoFlip;
context.effect = Effect::NoEffect;
context.format = OutputFormat::YUV422;
context.resolution =
context_id == 0 ? PRESET_RESOLUTION[5 /*DS_LCD*/] : PRESET_RESOLUTION[0 /*VGA*/];

View file

@ -1,4 +1,4 @@
// Copyright 2015 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -656,8 +656,8 @@ private:
Result SetPackageParameter(const PackageParameterType& package);
struct ContextConfig {
Flip flip{Flip::None};
Effect effect{Effect::None};
Flip flip{Flip::NoFlip};
Effect effect{Effect::NoEffect};
OutputFormat format{OutputFormat::YUV422};
Resolution resolution = {0, 0, 0, 0, 0, 0};

View file

@ -1,4 +1,4 @@
// Copyright 2020 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -17,7 +17,7 @@ enum CameraIndex {
};
enum class Effect : u8 {
None = 0,
NoEffect = 0,
Mono = 1,
Sepia = 2,
Negative = 3,
@ -26,7 +26,7 @@ enum class Effect : u8 {
};
enum class Flip : u8 {
None = 0,
NoFlip = 0,
Horizontal = 1,
Vertical = 2,
Reverse = 3,

View file

@ -1,4 +1,4 @@
// Copyright 2018 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -32,7 +32,7 @@ void QtCameraInterface::SetFlip(Service::CAM::Flip flip) {
}
void QtCameraInterface::SetEffect(Service::CAM::Effect effect) {
if (effect != Service::CAM::Effect::None) {
if (effect != Service::CAM::Effect::NoEffect) {
LOG_ERROR(Service_CAM, "Unimplemented effect {}", static_cast<int>(effect));
}
}

View file

@ -518,6 +518,7 @@ void QtConfig::ReadLayoutValues() {
ReadGlobalSetting(Settings::values.swap_screen);
ReadGlobalSetting(Settings::values.upright_screen);
ReadGlobalSetting(Settings::values.large_screen_proportion);
ReadGlobalSetting(Settings::values.small_screen_position);
if (global) {
ReadBasicSetting(Settings::values.mono_render_option);
@ -1077,7 +1078,7 @@ void QtConfig::SaveLayoutValues() {
WriteGlobalSetting(Settings::values.swap_screen);
WriteGlobalSetting(Settings::values.upright_screen);
WriteGlobalSetting(Settings::values.large_screen_proportion);
WriteGlobalSetting(Settings::values.small_screen_position);
if (global) {
WriteBasicSetting(Settings::values.mono_render_option);
WriteBasicSetting(Settings::values.custom_top_x);

View file

@ -206,8 +206,8 @@ void ConfigureCamera::StartPreviewing() {
}
previewing_camera->SetResolution(
{static_cast<u16>(preview_width), static_cast<u16>(preview_height)});
previewing_camera->SetEffect(Service::CAM::Effect::None);
previewing_camera->SetFlip(Service::CAM::Flip::None);
previewing_camera->SetEffect(Service::CAM::Effect::NoEffect);
previewing_camera->SetFlip(Service::CAM::Flip::NoFlip);
previewing_camera->SetFormat(Service::CAM::OutputFormat::RGB565);
previewing_camera->SetFrameRate(Service::CAM::FrameRate::Rate_30);
previewing_camera->StartCapture();

View file

@ -28,6 +28,15 @@ ConfigureLayout::ConfigureLayout(QWidget* parent)
currentIndex == (uint)(Settings::LayoutOption::LargeScreen));
});
ui->small_screen_position_combobox->setEnabled(
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::LargeScreen));
connect(ui->layout_combobox,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[this](int currentIndex) {
ui->small_screen_position_combobox->setEnabled(
currentIndex == (uint)(Settings::LayoutOption::LargeScreen));
});
ui->single_screen_layout_config_group->setEnabled(
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SingleScreen) ||
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows));
@ -116,7 +125,8 @@ void ConfigureLayout::SetConfiguration() {
ui->toggle_swap_screen->setChecked(Settings::values.swap_screen.GetValue());
ui->toggle_upright_screen->setChecked(Settings::values.upright_screen.GetValue());
ui->large_screen_proportion->setValue(Settings::values.large_screen_proportion.GetValue());
ui->small_screen_position_combobox->setCurrentIndex(
static_cast<int>(Settings::values.small_screen_position.GetValue()));
ui->custom_top_x->setValue(Settings::values.custom_top_x.GetValue());
ui->custom_top_y->setValue(Settings::values.custom_top_y.GetValue());
ui->custom_top_width->setValue(Settings::values.custom_top_width.GetValue());
@ -153,7 +163,8 @@ void ConfigureLayout::RetranslateUI() {
void ConfigureLayout::ApplyConfiguration() {
Settings::values.large_screen_proportion = ui->large_screen_proportion->value();
Settings::values.small_screen_position = static_cast<Settings::SmallScreenPosition>(
ui->small_screen_position_combobox->currentIndex());
Settings::values.custom_top_x = ui->custom_top_x->value();
Settings::values.custom_top_y = ui->custom_top_y->value();
Settings::values.custom_top_width = ui->custom_top_width->value();

View file

@ -145,6 +145,75 @@
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="small_pos_widget" native="true">
<layout class="QHBoxLayout" name="small_pos_layout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="small_pos_label">
<property name="text">
<string>Small Screen Position</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="small_screen_position_combobox">
<item>
<property name="text">
<string>Upper Right</string>
</property>
</item>
<item>
<property name="text">
<string>Middle Right</string>
</property>
</item>
<item>
<property name="text">
<string>Bottom Right (default)</string>
</property>
</item>
<item>
<property name="text">
<string>Upper Left</string>
</property>
</item>
<item>
<property name="text">
<string>Middle Left</string>
</property>
</item>
<item>
<property name="text">
<string>Bottom Left</string>
</property>
</item>
<item>
<property name="text">
<string>Above large screen</string>
</property>
</item>
<item>
<property name="text">
<string>Below large screen</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="bg_color_group" native="true">
<layout class="QHBoxLayout" name="bg_color_group_2">
@ -592,6 +661,7 @@
<tabstop>toggle_swap_screen</tabstop>
<tabstop>toggle_upright_screen</tabstop>
<tabstop>large_screen_proportion</tabstop>
<tabstop>small_screen_position_combobox</tabstop>
<tabstop>bg_button</tabstop>
</tabstops>
<resources/>

View file

@ -580,6 +580,16 @@ void GMainWindow::InitializeWidgets() {
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Separate_Windows);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Hybrid_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Custom_Layout);
QActionGroup* actionGroup_SmallPositions = new QActionGroup(this);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_TopRight);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_MiddleRight);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_BottomRight);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_TopLeft);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_MiddleLeft);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_BottomLeft);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_Above);
actionGroup_SmallPositions->addAction(ui->action_Small_Screen_Below);
}
void GMainWindow::InitializeDebugWidgets() {
@ -1032,6 +1042,14 @@ void GMainWindow::ConnectMenuEvents() {
connect_menu(ui->action_Screen_Layout_Custom_Layout, &GMainWindow::ChangeScreenLayout);
connect_menu(ui->action_Screen_Layout_Swap_Screens, &GMainWindow::OnSwapScreens);
connect_menu(ui->action_Screen_Layout_Upright_Screens, &GMainWindow::OnRotateScreens);
connect_menu(ui->action_Small_Screen_TopRight, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_MiddleRight, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_BottomRight, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_TopLeft, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_MiddleLeft, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_BottomLeft, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_Above, &GMainWindow::ChangeSmallScreenPosition);
connect_menu(ui->action_Small_Screen_Below, &GMainWindow::ChangeSmallScreenPosition);
// Movie
connect_menu(ui->action_Record_Movie, &GMainWindow::OnRecordMovie);
@ -2548,13 +2566,13 @@ void GMainWindow::UpdateSecondaryWindowVisibility() {
void GMainWindow::ChangeScreenLayout() {
Settings::LayoutOption new_layout = Settings::LayoutOption::Default;
if (ui->action_Screen_Layout_Default->isChecked()) {
new_layout = Settings::LayoutOption::Default;
} else if (ui->action_Screen_Layout_Single_Screen->isChecked()) {
new_layout = Settings::LayoutOption::SingleScreen;
} else if (ui->action_Screen_Layout_Large_Screen->isChecked()) {
new_layout = Settings::LayoutOption::LargeScreen;
ui->menu_Small_Screen_Position->setEnabled(true);
} else if (ui->action_Screen_Layout_Hybrid_Screen->isChecked()) {
new_layout = Settings::LayoutOption::HybridScreen;
} else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) {
@ -2566,6 +2584,34 @@ void GMainWindow::ChangeScreenLayout() {
}
Settings::values.layout_option = new_layout;
SyncMenuUISettings();
system.ApplySettings();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::ChangeSmallScreenPosition() {
Settings::SmallScreenPosition new_position = Settings::SmallScreenPosition::BottomRight;
if (ui->action_Small_Screen_TopRight->isChecked()) {
new_position = Settings::SmallScreenPosition::TopRight;
} else if (ui->action_Small_Screen_MiddleRight->isChecked()) {
new_position = Settings::SmallScreenPosition::MiddleRight;
} else if (ui->action_Small_Screen_BottomRight->isChecked()) {
new_position = Settings::SmallScreenPosition::BottomRight;
} else if (ui->action_Small_Screen_TopLeft->isChecked()) {
new_position = Settings::SmallScreenPosition::TopLeft;
} else if (ui->action_Small_Screen_MiddleLeft->isChecked()) {
new_position = Settings::SmallScreenPosition::MiddleLeft;
} else if (ui->action_Small_Screen_BottomLeft->isChecked()) {
new_position = Settings::SmallScreenPosition::BottomLeft;
} else if (ui->action_Small_Screen_Above->isChecked()) {
new_position = Settings::SmallScreenPosition::AboveLarge;
} else if (ui->action_Small_Screen_Below->isChecked()) {
new_position = Settings::SmallScreenPosition::BelowLarge;
}
Settings::values.small_screen_position = new_position;
SyncMenuUISettings();
system.ApplySettings();
UpdateSecondaryWindowVisibility();
}
@ -3637,6 +3683,31 @@ void GMainWindow::SyncMenuUISettings() {
ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen.GetValue());
ui->action_Screen_Layout_Upright_Screens->setChecked(
Settings::values.upright_screen.GetValue());
ui->menu_Small_Screen_Position->setEnabled(Settings::values.layout_option.GetValue() ==
Settings::LayoutOption::LargeScreen);
ui->action_Small_Screen_TopRight->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::TopRight);
ui->action_Small_Screen_MiddleRight->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::MiddleRight);
ui->action_Small_Screen_BottomRight->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BottomRight);
ui->action_Small_Screen_TopLeft->setChecked(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::TopLeft);
ui->action_Small_Screen_MiddleLeft->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::MiddleLeft);
ui->action_Small_Screen_BottomLeft->setChecked(
Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BottomLeft);
ui->action_Small_Screen_Above->setChecked(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::AboveLarge);
ui->action_Small_Screen_Below->setChecked(Settings::values.small_screen_position.GetValue() ==
Settings::SmallScreenPosition::BelowLarge);
}
void GMainWindow::RetranslateStatusBar() {

View file

@ -265,6 +265,7 @@ private slots:
void ToggleFullscreen();
void ToggleSecondaryFullscreen();
void ChangeScreenLayout();
void ChangeSmallScreenPosition();
void UpdateSecondaryWindowVisibility();
void ToggleScreenLayout();
void OnSwapScreens();

View file

@ -142,6 +142,23 @@
<addaction name="separator"/>
<addaction name="action_Screen_Layout_Upright_Screens"/>
<addaction name="action_Screen_Layout_Swap_Screens"/>
<widget class="QMenu" name="menu_Small_Screen_Position">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>Small Screen Position</string>
</property>
<addaction name="action_Small_Screen_TopRight"/>
<addaction name="action_Small_Screen_MiddleRight"/>
<addaction name="action_Small_Screen_BottomRight"/>
<addaction name="action_Small_Screen_TopLeft"/>
<addaction name="action_Small_Screen_MiddleLeft"/>
<addaction name="action_Small_Screen_BottomLeft"/>
<addaction name="action_Small_Screen_Above"/>
<addaction name="action_Small_Screen_Below"/>
</widget>
<addaction name="menu_Small_Screen_Position"/>
</widget>
<addaction name="action_Fullscreen"/>
<addaction name="action_Single_Window_Mode"/>
@ -541,6 +558,70 @@
<string>Custom Layout</string>
</property>
</action>
<action name="action_Small_Screen_TopRight">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Top Right</string>
</property>
</action>
<action name="action_Small_Screen_MiddleRight">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Middle Right</string>
</property>
</action>
<action name="action_Small_Screen_BottomRight">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Bottom Right</string>
</property>
</action>
<action name="action_Small_Screen_TopLeft">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Top Left</string>
</property>
</action>
<action name="action_Small_Screen_MiddleLeft">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Middle Left</string>
</property>
</action>
<action name="action_Small_Screen_BottomLeft">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Bottom Left</string>
</property>
</action>
<action name="action_Small_Screen_Above">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Above</string>
</property>
</action>
<action name="action_Small_Screen_Below">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Below</string>
</property>
</action>
<action name="action_Screen_Layout_Swap_Screens">
<property name="checkable">
<bool>true</bool>

View file

@ -1,4 +1,4 @@
// Copyright 2022 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -553,7 +553,7 @@ SurfaceId RasterizerCache<T>::GetTextureSurface(const Pica::Texture::TextureInfo
params.levels = max_level + 1;
params.is_tiled = true;
params.pixel_format = PixelFormatFromTextureFormat(info.format);
params.res_scale = filter != Settings::TextureFilter::None ? resolution_scale_factor : 1;
params.res_scale = filter != Settings::TextureFilter::NoFilter ? resolution_scale_factor : 1;
params.UpdateParams();
const u32 min_width = info.width >> max_level;

View file

@ -1,4 +1,4 @@
// Copyright 2023 Citra Emulator Project
// Copyright Citra Emulator Project / Lime3DS Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -159,7 +159,7 @@ bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) {
const auto filter = Settings::values.texture_filter.GetValue();
const bool is_depth =
surface.type == SurfaceType::Depth || surface.type == SurfaceType::DepthStencil;
if (filter == Settings::TextureFilter::None || is_depth) {
if (filter == Settings::TextureFilter::NoFilter || is_depth) {
return false;
}
if (blit.src_level != 0) {