diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 1a1aa1758..941e17733 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -187,6 +187,7 @@ filter_mode = # 2: Large Screen Small Screen # 3: Side by Side # 4: Separate Windows +# 5: Hybrid Screen layout_option = # Toggle custom layout (using the settings below) on or off. diff --git a/src/citra_qt/configuration/configure_enhancements.ui b/src/citra_qt/configuration/configure_enhancements.ui index 08465ef29..83b85ba9a 100644 --- a/src/citra_qt/configuration/configure_enhancements.ui +++ b/src/citra_qt/configuration/configure_enhancements.ui @@ -368,6 +368,11 @@ Separate Windows + + + Hybrid Screen + + diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 63d3cc45f..c00d277fd 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -387,6 +387,7 @@ void GMainWindow::InitializeWidgets() { actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Default); actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Single_Screen); actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Large_Screen); + actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Hybrid_Screen); actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side); actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Separate_Windows); } @@ -800,6 +801,7 @@ void GMainWindow::ConnectMenuEvents() { connect_menu(ui->action_Screen_Layout_Default, &GMainWindow::ChangeScreenLayout); connect_menu(ui->action_Screen_Layout_Single_Screen, &GMainWindow::ChangeScreenLayout); connect_menu(ui->action_Screen_Layout_Large_Screen, &GMainWindow::ChangeScreenLayout); + connect_menu(ui->action_Screen_Layout_Hybrid_Screen, &GMainWindow::ChangeScreenLayout); connect_menu(ui->action_Screen_Layout_Side_by_Side, &GMainWindow::ChangeScreenLayout); connect_menu(ui->action_Screen_Layout_Separate_Windows, &GMainWindow::ChangeScreenLayout); connect_menu(ui->action_Screen_Layout_Swap_Screens, &GMainWindow::OnSwapScreens); @@ -1883,6 +1885,8 @@ void GMainWindow::ChangeScreenLayout() { new_layout = Settings::LayoutOption::SingleScreen; } else if (ui->action_Screen_Layout_Large_Screen->isChecked()) { new_layout = Settings::LayoutOption::LargeScreen; + } else if (ui->action_Screen_Layout_Hybrid_Screen->isChecked()) { + new_layout = Settings::LayoutOption::HybridScreen; } else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) { new_layout = Settings::LayoutOption::SideScreen; } else if (ui->action_Screen_Layout_Separate_Windows->isChecked()) { @@ -1902,6 +1906,8 @@ void GMainWindow::ToggleScreenLayout() { case Settings::LayoutOption::SingleScreen: return Settings::LayoutOption::LargeScreen; case Settings::LayoutOption::LargeScreen: + return Settings::LayoutOption::HybridScreen; + case Settings::LayoutOption::HybridScreen: return Settings::LayoutOption::SideScreen; case Settings::LayoutOption::SideScreen: return Settings::LayoutOption::SeparateWindows; @@ -2774,6 +2780,8 @@ void GMainWindow::SyncMenuUISettings() { Settings::LayoutOption::SingleScreen); ui->action_Screen_Layout_Large_Screen->setChecked(Settings::values.layout_option.GetValue() == Settings::LayoutOption::LargeScreen); + ui->action_Screen_Layout_Hybrid_Screen->setChecked(Settings::values.layout_option.GetValue() == + Settings::LayoutOption::HybridScreen); ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SideScreen); ui->action_Screen_Layout_Separate_Windows->setChecked( diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 345139568..557322750 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui @@ -134,6 +134,7 @@ + @@ -504,6 +505,14 @@ Large Screen + + + true + + + Hybrid Screen + + true diff --git a/src/common/settings.h b/src/common/settings.h index 37754b495..67c7b182c 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -36,6 +36,7 @@ enum class LayoutOption : u32 { #ifndef ANDROID SeparateWindows, #endif + HybridScreen, // Similiar to default, but better for mobile devices in portrait mode. Top screen in clamped to // the top of the frame, and the bottom screen is enlarged to match the top screen. MobilePortrait, diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 37038d2d2..2d5dfde54 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp @@ -200,6 +200,11 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height, Settings::values.upright_screen.GetValue(), Settings::values.large_screen_proportion.GetValue()); break; + case Settings::LayoutOption::HybridScreen: + layout = + Layout::HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(), + Settings::values.upright_screen.GetValue()); + break; case Settings::LayoutOption::SideScreen: layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen.GetValue(), Settings::values.upright_screen.GetValue()); diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp index 36a7c5de0..40b86fb68 100644 --- a/src/core/frontend/framebuffer_layout.cpp +++ b/src/core/frontend/framebuffer_layout.cpp @@ -299,6 +299,80 @@ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool swapped, bool upr return res; } +FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool upright) { + ASSERT(width > 0); + ASSERT(height > 0); + + FramebufferLayout res{width, height, true, true, {}, {}, !upright, true, {}}; + + // Split the window into two parts. Give 2.25x width to the main screen, + // and make a bar on the right side with 1x width top screen and 1.25x width bottom screen + // To do that, find the total emulation box and maximize that based on window size + const float window_aspect_ratio = static_cast(height) / width; + const float scale_factor = 2.25f; + + float main_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO; + float hybrid_area_aspect_ratio = 27.f / 65; + float top_screen_aspect_ratio = TOP_SCREEN_ASPECT_RATIO; + float bot_screen_aspect_ratio = BOT_SCREEN_ASPECT_RATIO; + + if (swapped) { + main_screen_aspect_ratio = BOT_SCREEN_ASPECT_RATIO; + hybrid_area_aspect_ratio = + Core::kScreenBottomHeight * scale_factor / + (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); + Common::Rectangle side_rect = total_rect.Scale(1.f / scale_factor); + Common::Rectangle small_top_screen = maxRectangle(side_rect, top_screen_aspect_ratio); + Common::Rectangle small_bottom_screen = maxRectangle(side_rect, bot_screen_aspect_ratio); + + if (window_aspect_ratio < hybrid_area_aspect_ratio) { + large_main_screen = large_main_screen.TranslateX((width - total_rect.GetWidth()) / 2); + } else { + large_main_screen = large_main_screen.TranslateY((height - total_rect.GetHeight()) / 2); + } + + // 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 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; +} + FramebufferLayout SideFrameLayout(u32 width, u32 height, bool swapped, bool upright) { ASSERT(width > 0); ASSERT(height > 0); diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index ce1183361..76549954f 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -37,6 +37,9 @@ struct FramebufferLayout { Common::Rectangle bottom_screen; bool is_rotated = true; + bool additional_screen_enabled; + Common::Rectangle additional_screen; + CardboardSettings cardboard; /** @@ -99,6 +102,15 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool is_swapped, bool */ FramebufferLayout LargeFrameLayout(u32 width, u32 height, bool is_swapped, bool upright, float scale_factor); +/** + * 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 + * @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 + * @return Newly created FramebufferLayout object with default screen regions initialized + */ +FramebufferLayout HybridScreenLayout(u32 width, u32 height, bool swapped, bool upright); /** * Factory method for constructing a Frame with the Top screen and bottom diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index bc897d73f..9d48e23ca 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -942,6 +942,15 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f ApplySecondLayerOpacity(); DrawTopScreen(layout, top_screen); } + + if (layout.additional_screen_enabled) { + const auto& additional_screen = layout.additional_screen; + if (!Settings::values.swap_screen.GetValue()) { + DrawTopScreen(layout, additional_screen); + } else { + DrawBottomScreen(layout, additional_screen); + } + } ResetSecondLayerOpacity(); }