diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/display/ScreenLayout.kt b/src/android/app/src/main/java/io/github/lime3ds/android/display/ScreenLayout.kt index e3bc4101f..09e2892aa 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/display/ScreenLayout.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/display/ScreenLayout.kt @@ -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 } } } \ No newline at end of file diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/FloatSetting.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/FloatSetting.kt index 3eb586858..83aac46e4 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/FloatSetting.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/FloatSetting.kt @@ -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 diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt index 9dad3ea3e..5c88ea0fd 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/IntSetting.kt @@ -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), diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SettingsItem.kt index 86cb6368e..dc107076c 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SettingsItem.kt @@ -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 } } diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SliderSetting.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SliderSetting.kt index 2cfbc0d22..f3170a92b 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SliderSetting.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/model/view/SliderSetting.kt @@ -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 } } } diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsAdapter.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsAdapter.kt index 0ecfb5384..9e76057b9 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsAdapter.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsAdapter.kt @@ -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?) { - 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) diff --git a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt index 40abb9b07..b6738ae19 100644 --- a/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/io/github/lime3ds/android/features/settings/ui/SettingsFragmentPresenter.kt @@ -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 ) ) - - } } diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index e18c5a581..101e2dcf4 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -182,10 +182,11 @@ void Config::ReadValues() { layoutInt = static_cast(Settings::LayoutOption::LargeScreen); } Settings::values.layout_option = static_cast(layoutInt); - Settings::values.large_screen_proportion = static_cast(sdl2_config->GetReal("Layout", "large_screen_proportion", 2.25)); - + Settings::values.small_screen_position = static_cast( + sdl2_config->GetInteger("Layout", "small_screen_position", + static_cast(Settings::SmallScreenPosition::TopRight))); ReadSetting("Layout", Settings::values.custom_top_x); ReadSetting("Layout", Settings::values.custom_top_y); ReadSetting("Layout", Settings::values.custom_top_width); diff --git a/src/android/app/src/main/jni/default_ini.h b/src/android/app/src/main/jni/default_ini.h index 15ece6465..e38c606bf 100644 --- a/src/android/app/src/main/jni/default_ini.h +++ b/src/android/app/src/main/jni/default_ini.h @@ -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 diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index a732ca920..4159d9e4e 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -39,6 +39,28 @@ 1 + + @string/small_screen_position_top_right + @string/small_screen_position_middle_right + @string/small_screen_position_bottom_right + @string/small_screen_position_top_left + @string/small_screen_position_middle_left + @string/small_screen_position_bottom_left + @string/small_screen_position_above + @string/small_screen_position_below + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + + @string/auto_select @string/system_region_jpn diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 2e34972ba..a1eefd0c4 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -382,6 +382,18 @@ Original Default Custom Layout + Small Screen Position + Where should the small screen appear relative to the large one in Large Screen Layout? + Top Right + Middle Right + Bottom Right (Default) + Top Left + Middle Left + Bottom Left + Above + Below + Large Screen Proportion + How many times larger is the large screen than the small screen in Large Screen layout? Adjust Custom Layout in Settings Landscape Custom Layout Portrait Custom Layout diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 6431e9301..cc6bb2d9b 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -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); diff --git a/src/common/settings.h b/src/common/settings.h index a7867651b..ac83a0c5b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -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 use_shader_jit{true, "use_shader_jit"}; SwitchableSetting resolution_factor{1, 0, 10, "resolution_factor"}; SwitchableSetting frame_limit{100, 0, 1000, "frame_limit"}; - SwitchableSetting texture_filter{TextureFilter::None, "texture_filter"}; + SwitchableSetting texture_filter{TextureFilter::NoFilter, "texture_filter"}; SwitchableSetting texture_sampling{TextureSampling::GameControlled, "texture_sampling"}; SwitchableSetting layout_option{LayoutOption::Default, "layout_option"}; @@ -487,6 +501,8 @@ struct Values { SwitchableSetting upright_screen{false, "upright_screen"}; SwitchableSetting large_screen_proportion{4.f, 1.f, 16.f, "large_screen_proportion"}; + SwitchableSetting small_screen_position{SmallScreenPosition::BottomRight, + "small_screen_position"}; Setting custom_top_x{0, "custom_top_x"}; Setting custom_top_y{0, "custom_top_y"}; Setting custom_top_width{800, "custom_top_width"}; diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 07c78c12b..64e687af1 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -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: diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index c587cbed3..d834c182d 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -15,10 +15,6 @@ static constexpr float TOP_SCREEN_ASPECT_RATIO = static_cast(Core::kScreenTopHeight) / Core::kScreenTopWidth; static constexpr float BOT_SCREEN_ASPECT_RATIO = static_cast(Core::kScreenBottomHeight) / Core::kScreenBottomWidth; -static constexpr float TOP_SCREEN_UPRIGHT_ASPECT_RATIO = - static_cast(Core::kScreenTopWidth) / Core::kScreenTopHeight; -static constexpr float BOT_SCREEN_UPRIGHT_ASPECT_RATIO = - static_cast(Core::kScreenBottomWidth) / Core::kScreenBottomHeight; u32 FramebufferLayout::GetScalingRatio() const { if (is_rotated) { @@ -39,115 +35,19 @@ static Common::Rectangle MaxRectangle(Common::Rectangle 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 screen_window_area; - Common::Rectangle top_screen; - Common::Rectangle 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(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 screen_window_area{0, 0, width, height / 2}; - Common::Rectangle top_screen = MaxRectangle(screen_window_area, TOP_SCREEN_ASPECT_RATIO); - Common::Rectangle bot_screen = MaxRectangle(screen_window_area, BOT_SCREEN_ASPECT_RATIO); - - float window_aspect_ratio = static_cast(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 screen_window_area{0, 0, width, height}; Common::Rectangle top_screen; Common::Rectangle 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(height) / width; + const float window_aspect_ratio = static_cast(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(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(height) / width; + float emulation_aspect_ratio; + + float large_height = + swapped ? Core::kScreenBottomHeight * scale_factor : Core::kScreenTopHeight * scale_factor; + float small_height = + static_cast(swapped ? Core::kScreenTopHeight : Core::kScreenBottomHeight); + float large_width = + swapped ? Core::kScreenBottomWidth * scale_factor : Core::kScreenTopWidth * scale_factor; + float small_width = + static_cast(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 screen_window_area{0, 0, width, height}; Common::Rectangle total_rect = MaxRectangle(screen_window_area, emulation_aspect_ratio); - Common::Rectangle large_screen = MaxRectangle(total_rect, large_screen_aspect_ratio); - Common::Rectangle scaled_rect = total_rect.Scale(1.f / scale_factor); - Common::Rectangle small_screen = MaxRectangle(scaled_rect, small_screen_aspect_ratio); + const float scale_amount = total_rect.GetHeight() * 1.f / emulation_height * 1.f; + Common::Rectangle large_screen = + Common::Rectangle{total_rect.left, total_rect.top, + static_cast(large_width * scale_amount + total_rect.left), + static_cast(large_height * scale_amount + total_rect.top)}; + Common::Rectangle small_screen = + Common::Rectangle{total_rect.left, total_rect.top, + static_cast(small_width * scale_amount + total_rect.left), + static_cast(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 screen_window_area{0, 0, width, height}; Common::Rectangle total_rect = MaxRectangle(screen_window_area, hybrid_area_aspect_ratio); Common::Rectangle 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 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(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((swapped ? Core::kScreenTopWidth : Core::kScreenBottomWidth) / + Settings::values.large_screen_proportion.GetValue()); + const int smallHeight = + static_cast((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(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 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 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( - 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(smallWidth / Settings::values.large_screen_proportion.GetValue()); + smallHeight = + static_cast(smallHeight / Settings::values.large_screen_proportion.GetValue()); + min_width = static_cast(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(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; diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index 510035dc8..2edcaadfe 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -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); diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index f994c6d5a..cec48add4 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp @@ -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*/]; diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h index 7c2d2f0a9..5e8125040 100644 --- a/src/core/hle/service/cam/cam.h +++ b/src/core/hle/service/cam/cam.h @@ -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}; diff --git a/src/core/hle/service/cam/cam_params.h b/src/core/hle/service/cam/cam_params.h index 5cb1e7c6c..eab5a6503 100644 --- a/src/core/hle/service/cam/cam_params.h +++ b/src/core/hle/service/cam/cam_params.h @@ -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, diff --git a/src/lime_qt/camera/qt_camera_base.cpp b/src/lime_qt/camera/qt_camera_base.cpp index 55ebc44c6..7250c71c7 100644 --- a/src/lime_qt/camera/qt_camera_base.cpp +++ b/src/lime_qt/camera/qt_camera_base.cpp @@ -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(effect)); } } diff --git a/src/lime_qt/configuration/config.cpp b/src/lime_qt/configuration/config.cpp index 01a3828fb..90b630b78 100644 --- a/src/lime_qt/configuration/config.cpp +++ b/src/lime_qt/configuration/config.cpp @@ -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); diff --git a/src/lime_qt/configuration/configure_camera.cpp b/src/lime_qt/configuration/configure_camera.cpp index dcacaccfd..ddbe19ee4 100644 --- a/src/lime_qt/configuration/configure_camera.cpp +++ b/src/lime_qt/configuration/configure_camera.cpp @@ -206,8 +206,8 @@ void ConfigureCamera::StartPreviewing() { } previewing_camera->SetResolution( {static_cast(preview_width), static_cast(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(); diff --git a/src/lime_qt/configuration/configure_layout.cpp b/src/lime_qt/configuration/configure_layout.cpp index c19e53f3d..45f658bfd 100644 --- a/src/lime_qt/configuration/configure_layout.cpp +++ b/src/lime_qt/configuration/configure_layout.cpp @@ -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(&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(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( + 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(); diff --git a/src/lime_qt/configuration/configure_layout.ui b/src/lime_qt/configuration/configure_layout.ui index 0a044badb..3b564515b 100644 --- a/src/lime_qt/configuration/configure_layout.ui +++ b/src/lime_qt/configuration/configure_layout.ui @@ -145,6 +145,75 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Small Screen Position + + + + + + + + Upper Right + + + + + Middle Right + + + + + Bottom Right (default) + + + + + Upper Left + + + + + Middle Left + + + + + Bottom Left + + + + + Above large screen + + + + + Below large screen + + + + + + + @@ -592,6 +661,7 @@ toggle_swap_screen toggle_upright_screen large_screen_proportion + small_screen_position_combobox bg_button diff --git a/src/lime_qt/lime_qt.cpp b/src/lime_qt/lime_qt.cpp index abf40cafc..05a268b6b 100644 --- a/src/lime_qt/lime_qt.cpp +++ b/src/lime_qt/lime_qt.cpp @@ -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() { diff --git a/src/lime_qt/lime_qt.h b/src/lime_qt/lime_qt.h index 7a174b35d..8924377b8 100644 --- a/src/lime_qt/lime_qt.h +++ b/src/lime_qt/lime_qt.h @@ -265,6 +265,7 @@ private slots: void ToggleFullscreen(); void ToggleSecondaryFullscreen(); void ChangeScreenLayout(); + void ChangeSmallScreenPosition(); void UpdateSecondaryWindowVisibility(); void ToggleScreenLayout(); void OnSwapScreens(); diff --git a/src/lime_qt/main.ui b/src/lime_qt/main.ui index 85380a48a..5e49fc90f 100644 --- a/src/lime_qt/main.ui +++ b/src/lime_qt/main.ui @@ -142,6 +142,23 @@ + + + true + + + Small Screen Position + + + + + + + + + + + @@ -541,6 +558,70 @@ Custom Layout + + + true + + + Top Right + + + + + true + + + Middle Right + + + + + true + + + Bottom Right + + + + + true + + + Top Left + + + + + true + + + Middle Left + + + + + true + + + Bottom Left + + + + + true + + + Above + + + + + true + + + Below + + true diff --git a/src/video_core/rasterizer_cache/rasterizer_cache.h b/src/video_core/rasterizer_cache/rasterizer_cache.h index 179097666..0d3f42834 100644 --- a/src/video_core/rasterizer_cache/rasterizer_cache.h +++ b/src/video_core/rasterizer_cache/rasterizer_cache.h @@ -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::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; diff --git a/src/video_core/renderer_opengl/gl_blit_helper.cpp b/src/video_core/renderer_opengl/gl_blit_helper.cpp index 64ae2c1fd..f6ec9be05 100644 --- a/src/video_core/renderer_opengl/gl_blit_helper.cpp +++ b/src/video_core/renderer_opengl/gl_blit_helper.cpp @@ -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) {