Add Big Picture Mode using RSX overlay UI

This commit is contained in:
BeezBumba
2026-08-25 11:11:08 +00:00
committed by GitHub
parent 19b9c2e60b
commit fdcfded8df
28 changed files with 1300 additions and 172 deletions
+5
View File
@@ -518,6 +518,11 @@ target_sources(rpcs3_emu PRIVATE
RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp
RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp
RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp
RSX/Overlays/HomeMenu/overlay_home_menu_sidebar_page.cpp
RSX/Overlays/BigPicture/overlay_big_picture.cpp
RSX/Overlays/BigPicture/overlay_big_picture_game_details.cpp
RSX/Overlays/BigPicture/overlay_big_picture_game_grid.cpp
RSX/Overlays/BigPicture/overlay_big_picture_main_menu.cpp
RSX/Overlays/Network/overlay_recvmessage_dialog.cpp
RSX/Overlays/Network/overlay_sendmessage_dialog.cpp
RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp
@@ -0,0 +1,174 @@
#include "stdafx.h"
#include "overlay_big_picture.h"
#include "../overlay_manager.h"
#include "Emu/System.h"
atomic_t<bool> g_big_picture_mode_active = false;
namespace rsx
{
namespace overlays
{
namespace
{
void boot_game_from_big_picture_mode(std::string path, std::string title_id)
{
rsx_log.notice("Big Picture Mode: starting boot handoff for '%s' (path='%s')", title_id, path);
Emu.CallFromMainThread([path, title_id]()
{
// Keep the game window alive across the handoff instead of closing and reopening it.
Emu.SetContinuousMode(true);
rsx_log.notice("Big Picture Mode: shutting down shell before boot");
Emu.GracefulShutdown(false);
rsx_log.notice("Big Picture Mode: shell shut down, is_stopped=%d", Emu.IsStopped());
// Only mark the session as BPM-launched now, so the transitional shutdown above doesn't
// itself get treated as "the game closed" and re-trigger Big Picture Mode.
g_big_picture_mode_active = true;
const game_boot_result result = Emu.BootGame(path, title_id);
rsx_log.notice("Big Picture Mode: BootGame result=%s", result);
if (is_error(result))
{
g_big_picture_mode_active = false;
}
});
}
}
big_picture_dialog::big_picture_dialog()
: m_main_menu(20, 85, virtual_width - 2 * 20, 540, nullptr, &boot_game_from_big_picture_mode)
{
m_allow_input_on_pause = true;
m_dim_background.set_size(virtual_width, virtual_height);
m_dim_background.back_color.a = 0.85f;
m_description.set_font("Arial", 20);
m_description.set_pos(20, 37);
m_description.set_text(get_localized_string(localized_string_id::BIG_PICTURE_MODE_TITLE));
m_description.auto_resize();
m_description.back_color.a = 0.f;
fade_animation.duration_sec = 0.15f;
return_code = selection_code::canceled;
}
void big_picture_dialog::update(u64 timestamp_us)
{
if (fade_animation.active)
{
fade_animation.update(timestamp_us);
}
m_main_menu.update(timestamp_us);
}
void big_picture_dialog::on_button_pressed(pad_button button_press, bool is_auto_repeat)
{
if (fade_animation.active) return;
switch (button_press)
{
case pad_button::dpad_left:
case pad_button::dpad_right:
case pad_button::ls_left:
case pad_button::ls_right:
m_auto_repeat_ms_interval = 10;
break;
default:
m_auto_repeat_ms_interval = m_auto_repeat_ms_interval_default;
break;
}
const page_navigation navigation = m_main_menu.handle_button_press(button_press, is_auto_repeat, m_auto_repeat_ms_interval);
switch (navigation)
{
case page_navigation::back:
case page_navigation::next:
{
if (home_menu_page* page = m_main_menu.get_current_page(true))
{
std::string path = page->title;
for (home_menu_page* parent = page->parent; parent; parent = parent->parent)
{
if (parent->title.empty())
{
break;
}
path = parent->title + " > " + path;
}
m_description.set_text(path.empty() ? get_localized_string(localized_string_id::BIG_PICTURE_MODE_TITLE) : path);
m_description.auto_resize();
}
break;
}
case page_navigation::exit:
{
// Don't call close() synchronously from the input thread - just like the pause menu's own
// "Exit Game", tearing down the shell on the main thread takes this dialog down as a side effect.
g_big_picture_mode_active = false;
Emu.CallFromMainThread([]()
{
Emu.GracefulShutdown(true, true);
});
break;
}
default:
break;
}
}
compiled_resource big_picture_dialog::get_compiled()
{
if (!visible)
{
return {};
}
compiled_resource result;
result.add(m_dim_background.get_compiled());
result.add(m_main_menu.get_compiled());
result.add(m_description.get_compiled());
fade_animation.apply(result);
return result;
}
void big_picture_dialog::show()
{
visible = false;
fade_animation.current = color4f(0.f);
fade_animation.end = color4f(1.f);
fade_animation.active = true;
visible = true;
auto& overlayman = g_fxo->get<display_manager>();
overlayman.attach_thread_input(uid, "Big Picture Mode");
}
void open_big_picture_mode()
{
auto& overlayman = g_fxo->get<display_manager>();
const auto dialog = overlayman.create<big_picture_dialog>();
dialog->show();
// Both exit paths (starting a game or leaving Big Picture Mode) tear down this shell via
// Emu.GracefulShutdown on the main thread, which destroys the display_manager (and this dialog
// with it) as a side effect. Just wait for that to happen instead of waiting on the dialog itself.
while (!Emu.IsStopped())
{
thread_ctrl::wait_for(50'000);
}
}
} // namespace overlays
} // namespace rsx
@@ -0,0 +1,39 @@
#pragma once
#include "Emu/RSX/Overlays/overlays.h"
#include "Emu/Cell/ErrorCodes.h"
#include "overlay_big_picture_main_menu.h"
namespace rsx
{
namespace overlays
{
struct big_picture_dialog : public user_interface
{
public:
big_picture_dialog();
void update(u64 timestamp_us) override;
void on_button_pressed(pad_button button_press, bool is_auto_repeat) override;
compiled_resource get_compiled() override;
void show();
private:
big_picture_main_menu m_main_menu;
overlay_element m_dim_background{};
label m_description{};
animation_color_interpolate fade_animation{};
};
// Entry point run on the dedicated thread started by Emulator::BootBigPictureMode().
// Blocks until Big Picture Mode's shell is torn down (either the user exited it, or picked a game to start).
void open_big_picture_mode();
}
}
// True while the currently running/booting game was launched from Big Picture Mode.
// Checked by the Qt front-end so exiting the game re-enters Big Picture Mode instead of the bare game list.
extern atomic_t<bool> g_big_picture_mode_active;
@@ -0,0 +1,146 @@
#include "stdafx.h"
#include "overlay_big_picture_game_details.h"
#include "Emu/RSX/Overlays/HomeMenu/overlay_home_menu_components.h"
namespace rsx
{
namespace overlays
{
big_picture_game_details::big_picture_game_details(s16 x, s16 y, u16 width, u16 height)
: m_x(x), m_y(y), m_w(width), m_h(height)
{
m_pic_background.set_pos(0, 0);
m_pic_background.set_size(overlay::virtual_width, overlay::virtual_height);
m_pic_background.back_color.a = 0.f;
m_dim_background.set_pos(0, 0);
m_dim_background.set_size(overlay::virtual_width, overlay::virtual_height);
m_dim_background.back_color.a = 0.75f;
m_icon.set_pos(x + 40, y + 40);
m_icon.set_size(320, 176);
m_icon.back_color.a = 0.f;
m_title.set_font("Arial", 28);
m_title.set_pos(x + 40, y + 240);
m_title.back_color.a = 0.f;
m_title.set_wrap_text(true);
m_title.set_size(width - 80, 80);
m_info_text.set_font("Arial", 16);
m_info_text.set_pos(x + 40, y + 330);
m_info_text.back_color.a = 0.f;
m_info_text.set_wrap_text(true);
m_info_text.set_size(width - 80, 200);
m_start_btn.set_image_resource(resource_config::standard_image_resource::cross);
m_start_btn.set_text(localized_string_id::BIG_PICTURE_GAME_DETAILS_START);
m_start_btn.set_font("Arial", 16);
m_start_btn.set_pos(x + 40, y + height - 80);
m_back_hint.set_image_resource(resource_config::standard_image_resource::circle);
m_back_hint.set_text(localized_string_id::BIG_PICTURE_HINT_BACK);
m_back_hint.set_font("Arial", 16);
m_back_hint.set_pos(x + 40 + 120 + 20, y + height - 80);
}
void big_picture_game_details::show(const GameInfo& info, const image_info* icon_data)
{
m_title.set_text(info.name.empty() ? info.serial : info.name);
m_title.auto_resize(false, m_w - 80);
m_info_text.set_text(fmt::format("%s\n%s\n%s", info.serial, info.category, info.app_ver));
const std::string game_dir = fs::get_parent_dir(info.icon_path);
if (const std::string pic1_path = game_dir + "/PIC1.PNG"; fs::is_file(pic1_path))
{
m_pic_data = std::make_unique<image_info>(pic1_path);
// The renderer's texture cache is keyed by this object's address, which can be reused by an
// unrelated image after the old one is freed - force a re-upload instead of trusting the cache.
m_pic_data->dirty = true;
m_pic_background.set_raw_image(m_pic_data.get());
m_pic_background.back_color.a = 0.35f;
}
else
{
m_pic_data.reset();
m_pic_background.clear_image();
m_pic_background.back_color.a = 0.f;
}
m_pic_background.refresh();
if (const std::string icon1_path = game_dir + "/ICON1.PAM"; fs::is_file(icon1_path))
{
m_video = std::make_unique<video_view>(icon1_path, "", info.icon_path);
m_video->set_pos(m_icon.x, m_icon.y);
m_video->set_size(m_icon.w, m_icon.h);
m_video->set_active(true);
}
else
{
m_video.reset();
if (icon_data)
{
m_icon.set_raw_image(icon_data);
}
else
{
m_icon.set_image_resource(resource_config::standard_image_resource::new_entry);
}
// set_raw_image()/set_image_resource() don't invalidate the compiled vertex cache themselves.
m_icon.refresh();
}
m_visible = true;
}
void big_picture_game_details::hide()
{
m_visible = false;
if (m_video)
{
m_video->set_active(false);
}
}
big_picture_game_details::result big_picture_game_details::handle_button_press(pad_button button_press)
{
switch (button_press)
{
case pad_button::cross:
play_sound(sound_effect::accept);
return result::start;
case pad_button::circle:
play_sound(sound_effect::cancel);
return result::back;
default:
return result::stay;
}
}
compiled_resource& big_picture_game_details::get_compiled()
{
m_compiled = {};
if (!m_visible)
{
return m_compiled;
}
m_compiled.add(m_pic_background.get_compiled());
m_compiled.add(m_dim_background.get_compiled());
m_compiled.add(m_video ? m_video->get_compiled() : m_icon.get_compiled());
m_compiled.add(m_title.get_compiled());
m_compiled.add(m_info_text.get_compiled());
m_compiled.add(m_start_btn.get_compiled());
m_compiled.add(m_back_hint.get_compiled());
return m_compiled;
}
}
}
@@ -0,0 +1,53 @@
#pragma once
#include "Emu/RSX/Overlays/overlays.h"
#include "Emu/RSX/Overlays/overlay_controls.h"
#include "Emu/RSX/Overlays/overlay_video.h"
#include "Emu/GameInfo.h"
namespace rsx
{
namespace overlays
{
// A full-screen panel shown on top of the game grid, describing the highlighted game with a "Start" prompt.
struct big_picture_game_details
{
public:
big_picture_game_details(s16 x, s16 y, u16 width, u16 height);
enum class result
{
stay,
back,
start
};
void show(const GameInfo& info, const image_info* icon_data);
void hide();
bool is_visible() const { return m_visible; }
result handle_button_press(pad_button button_press);
compiled_resource& get_compiled();
private:
s16 m_x = 0;
s16 m_y = 0;
u16 m_w = 0;
u16 m_h = 0;
bool m_visible = false;
compiled_resource m_compiled;
overlay_element m_dim_background{};
image_view m_pic_background{};
std::unique_ptr<image_info> m_pic_data;
image_view m_icon{};
std::unique_ptr<video_view> m_video;
label m_title{};
label m_info_text{};
image_button m_start_btn{ 120, 30 };
image_button m_back_hint{ 120, 30 };
};
}
}
@@ -0,0 +1,358 @@
#include "stdafx.h"
#include "overlay_big_picture_game_grid.h"
#include "overlay_big_picture_game_details.h"
#include "Emu/System.h"
#include "Emu/system_utils.hpp"
#include "Loader/PSF.h"
#include <algorithm>
namespace rsx
{
namespace overlays
{
big_picture_game_tile::big_picture_game_tile(const big_picture_game_entry& entry, u16 tile_width)
{
pack_padding = 8;
back_color.a = 0.f;
const u16 icon_h = icon_height(tile_width);
std::unique_ptr<overlay_element> icon = std::make_unique<image_view>();
icon->set_size(tile_width, icon_h);
icon->back_color = color4f(1.f, 1.f, 1.f, 0.08f);
if (fs::exists(entry.info.icon_path))
{
m_icon_data = std::make_unique<image_info>(entry.info.icon_path);
// The renderer's texture cache is keyed by this object's address, which can be reused by an
// unrelated image after the old one is freed - force a re-upload instead of trusting the cache.
m_icon_data->dirty = true;
static_cast<image_view*>(icon.get())->set_raw_image(m_icon_data.get());
}
else
{
static_cast<image_view*>(icon.get())->set_image_resource(resource_config::standard_image_resource::new_entry);
}
std::unique_ptr<overlay_element> title = std::make_unique<label>(entry.info.name.empty() ? entry.info.serial : entry.info.name);
title->set_font("Arial", 13);
title->set_size(tile_width, 84);
title->set_wrap_text(true);
title->align_text(text_align::center);
title->back_color.a = 0.f;
m_icon_view = add_element(icon);
add_element(title);
}
big_picture_game_grid::big_picture_game_grid(s16 x, s16 y, u16 width, u16 height, home_menu_page* parent, std::function<void(std::string, std::string)> on_game_selected)
: home_menu_page(x, y, width, height, false, parent, get_localized_string(localized_string_id::BIG_PICTURE_MENU_GAMES))
, m_on_game_selected(std::move(on_game_selected))
{
m_no_games_text = std::make_unique<label>(get_localized_string(localized_string_id::BIG_PICTURE_NO_GAMES_FOUND));
m_no_games_text->set_font("Arial", 20);
m_no_games_text->set_pos(x, y + (height / 2) - 20);
m_no_games_text->set_size(width, 40);
m_no_games_text->align_text(text_align::center);
m_no_games_text->back_color.a = 0.f;
// Bezel around game cover art.
m_highlight = std::make_unique<rounded_rect>();
m_highlight->border_radius = 6;
m_highlight->border_size = 4;
m_highlight->border_color = color4f(0.3f, 0.65f, 1.f, 1.f);
m_highlight->back_color = color4f(0.f, 0.f, 0.f, 0.f);
m_highlight->pulse_effect_enabled = true;
m_back_hint.set_image_resource(resource_config::standard_image_resource::circle);
m_back_hint.set_text(localized_string_id::BIG_PICTURE_HINT_BACK);
m_back_hint.set_font("Arial", 16);
m_back_hint.set_pos(x + width - 2 * (30 + 120), y + height + 20);
m_select_hint.set_image_resource(resource_config::standard_image_resource::cross);
m_select_hint.set_text(localized_string_id::BIG_PICTURE_HINT_SELECT);
m_select_hint.set_font("Arial", 16);
m_select_hint.set_pos(x + width - (30 + 120), y + height + 20);
m_details = std::make_unique<big_picture_game_details>(x, y, width, height);
reload();
}
big_picture_game_grid::~big_picture_game_grid() = default;
void big_picture_game_grid::reload()
{
rsx_log.notice("Big Picture Mode: reload() start");
m_games.clear();
m_tiles.clear();
for (const auto& [title_id, raw_path] : Emu.GetGamesConfig().get_games())
{
std::string path = raw_path;
path.resize(path.find_last_not_of('/') + 1);
if (path.empty())
{
continue;
}
const std::string sfo_dir = rpcs3::utils::get_sfo_dir_from_game_path(path, title_id);
const psf::registry psf = psf::load_object(sfo_dir + "/PARAM.SFO");
if (psf.empty())
{
continue;
}
GameInfo info{};
info.path = path;
info.icon_path = sfo_dir + "/ICON0.PNG";
info.serial = title_id;
info.name = std::string(psf::get_string(psf, "TITLE", title_id));
info.category = std::string(psf::get_string(psf, "CATEGORY"));
info.app_ver = std::string(psf::get_string(psf, "APP_VER"));
m_games.push_back({ std::move(info) });
}
std::sort(m_games.begin(), m_games.end(), [](const big_picture_game_entry& a, const big_picture_game_entry& b)
{
return a.info.name < b.info.name;
});
m_grid = std::make_unique<vertical_layout>();
m_grid->set_pos(x, y);
m_grid->pack_padding = 20;
std::unique_ptr<horizontal_layout> row;
for (usz i = 0; i < m_games.size(); i++)
{
if (i % m_columns == 0)
{
if (row)
{
m_grid->add_element(row);
}
row = std::make_unique<horizontal_layout>();
row->pack_padding = 20;
}
auto tile = std::make_unique<big_picture_game_tile>(m_games[i], m_tile_size);
m_tiles.push_back(row->add_element(tile));
}
if (row)
{
m_grid->add_element(row);
}
// Freeze the grid at the page's viewport height and scroll it manually instead of letting it
// auto-size to the full (potentially much taller) content height of every row combined.
m_content_height = m_grid->h;
m_row_stride = m_tiles.empty() ? 0 : static_cast<u16>(m_tiles.front()->h + m_grid->pack_padding);
m_grid->auto_resize = false;
m_grid->set_size(m_grid->w, h);
m_grid->scroll_offset_value = 0;
m_selected_index = m_tiles.empty() ? 0 : std::clamp(m_selected_index, 0, static_cast<s32>(m_tiles.size()) - 1);
if (!m_tiles.empty())
{
select_tile(m_selected_index);
}
rsx_log.notice("Big Picture Mode: reload() finished, games=%u, tiles=%u", m_games.size(), m_tiles.size());
}
void big_picture_game_grid::select_tile(s32 index)
{
rsx_log.notice("Big Picture Mode: select_tile(%d), tile_count=%u", index, m_tiles.size());
if (index < 0 || static_cast<usz>(index) >= m_tiles.size())
{
return;
}
m_selected_index = index;
// Scroll the selected row into view if the grid has more rows than fit on screen.
if (m_row_stride > 0 && m_grid)
{
const s32 viewport_h = h;
const s32 row = m_selected_index / m_columns;
const s32 row_top = row * m_row_stride;
const s32 row_bottom = row_top + m_row_stride;
const s32 max_offset = std::max(0, static_cast<s32>(m_content_height) - viewport_h);
s32 offset = m_grid->scroll_offset_value;
if (row_top < offset)
{
offset = row_top;
}
else if (row_bottom > offset + viewport_h)
{
offset = row_bottom - viewport_h;
}
m_grid->scroll_offset_value = static_cast<u16>(std::clamp(offset, 0, max_offset));
m_grid->refresh();
}
const big_picture_game_tile* tile = m_tiles[m_selected_index];
const overlay_element* icon = tile->get_icon_view();
// Pad the bezel out a few pixels past the cover art so the border doesn't clip its corners.
constexpr s16 bezel_margin = 3;
m_highlight->set_pos(icon->x - bezel_margin, icon->y - m_grid->scroll_offset_value - bezel_margin);
m_highlight->set_size(icon->w + bezel_margin * 2, icon->h + bezel_margin * 2);
m_highlight->set_sinus_offset(1.6f);
m_highlight->refresh();
}
page_navigation big_picture_game_grid::handle_button_press(pad_button button_press, bool is_auto_repeat, u64 auto_repeat_interval_ms)
{
const bool do_play_sound = !is_auto_repeat || auto_repeat_interval_ms >= user_interface::m_auto_repeat_ms_interval_default;
if (m_details && m_details->is_visible())
{
const auto details_result = m_details->handle_button_press(button_press);
rsx_log.notice("Big Picture Mode: details handle_button_press(%d) -> %d", static_cast<int>(button_press), static_cast<int>(details_result));
switch (details_result)
{
case big_picture_game_details::result::back:
m_details->hide();
break;
case big_picture_game_details::result::start:
{
rsx_log.notice("Big Picture Mode: Start pressed for index=%d", m_selected_index);
const GameInfo& info = m_games[m_selected_index].info;
rsx_log.notice("Big Picture Mode: selected game path='%s' serial='%s'", info.path, info.serial);
m_details->hide();
if (m_on_game_selected)
{
rsx_log.notice("Big Picture Mode: invoking on_game_selected callback");
m_on_game_selected(info.path, info.serial);
rsx_log.notice("Big Picture Mode: on_game_selected callback returned");
}
break;
}
default:
break;
}
return page_navigation::stay;
}
if (m_tiles.empty())
{
if (button_press == pad_button::circle)
{
play_sound(sound_effect::cancel);
if (parent)
{
set_current_page(parent);
return page_navigation::back;
}
return page_navigation::exit;
}
return page_navigation::stay;
}
switch (button_press)
{
case pad_button::dpad_left:
case pad_button::ls_left:
if ((m_selected_index % m_columns) > 0)
{
select_tile(m_selected_index - 1);
}
break;
case pad_button::dpad_right:
case pad_button::ls_right:
if (((m_selected_index % m_columns) + 1) < m_columns && (m_selected_index + 1) < static_cast<s32>(m_tiles.size()))
{
select_tile(m_selected_index + 1);
}
break;
case pad_button::dpad_up:
case pad_button::ls_up:
if ((m_selected_index - m_columns) >= 0)
{
select_tile(m_selected_index - m_columns);
}
break;
case pad_button::dpad_down:
case pad_button::ls_down:
if ((m_selected_index + m_columns) < static_cast<s32>(m_tiles.size()))
{
select_tile(m_selected_index + m_columns);
}
break;
case pad_button::cross:
play_sound(sound_effect::accept);
rsx_log.notice("Big Picture Mode: opening details for index=%d", m_selected_index);
m_details->show(m_games[m_selected_index].info, m_tiles[m_selected_index]->get_icon_data());
rsx_log.notice("Big Picture Mode: details shown");
return page_navigation::stay;
case pad_button::circle:
play_sound(sound_effect::cancel);
if (parent)
{
set_current_page(parent);
return page_navigation::back;
}
return page_navigation::exit;
default:
return page_navigation::stay;
}
if (do_play_sound)
{
play_sound(sound_effect::cursor);
}
return page_navigation::stay;
}
compiled_resource& big_picture_game_grid::get_compiled()
{
m_compiled_grid.clear();
if (m_tiles.empty())
{
m_compiled_grid.add(m_no_games_text->get_compiled());
}
else if (m_grid)
{
m_compiled_grid.add(m_grid->get_compiled());
m_compiled_grid.add(m_highlight->get_compiled());
}
const bool details_visible = m_details && m_details->is_visible();
if (!details_visible)
{
m_compiled_grid.add(m_back_hint.get_compiled());
if (!m_tiles.empty())
{
m_compiled_grid.add(m_select_hint.get_compiled());
}
}
if (m_details)
{
m_compiled_grid.add(m_details->get_compiled());
}
return m_compiled_grid;
}
}
}
@@ -0,0 +1,72 @@
#pragma once
#include "Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.h"
#include "Emu/GameInfo.h"
#include <functional>
namespace rsx
{
namespace overlays
{
struct big_picture_game_details;
struct big_picture_game_entry
{
GameInfo info;
};
// A single selectable tile (icon + title) inside the game grid.
struct big_picture_game_tile : public vertical_layout
{
big_picture_game_tile(const big_picture_game_entry& entry, u16 tile_width);
// PS3 ICON0.PNG is always 320x176 - keep tiles at that exact aspect ratio instead of stretching it.
static constexpr u16 icon_height(u16 tile_width) { return static_cast<u16>(tile_width * 176 / 320); }
// Already-decoded icon, reused by the detail panel to avoid re-decoding and stale-texture-cache issues.
const image_info* get_icon_data() const { return m_icon_data.get(); }
// The icon's own live bounding box (it is offset within the tile by the layout's padding,
// so callers must not assume it starts at the tile's own x/y).
const overlay_element* get_icon_view() const { return m_icon_view; }
private:
std::unique_ptr<image_info> m_icon_data;
overlay_element* m_icon_view = nullptr; // non-owning, owned by vertical_layout::m_items
};
// Controller-friendly grid of installed games. Cross opens a detail panel with a "Start" prompt.
struct big_picture_game_grid : public home_menu_page
{
big_picture_game_grid(s16 x, s16 y, u16 width, u16 height, home_menu_page* parent, std::function<void(std::string, std::string)> on_game_selected);
~big_picture_game_grid() override;
page_navigation handle_button_press(pad_button button_press, bool is_auto_repeat, u64 auto_repeat_interval_ms) override;
compiled_resource& get_compiled() override;
private:
void reload();
void select_tile(s32 index);
static constexpr u16 m_columns = 5;
static constexpr u16 m_tile_size = 200;
std::vector<big_picture_game_entry> m_games;
std::vector<big_picture_game_tile*> m_tiles; // non-owning, owned by m_grid
std::unique_ptr<vertical_layout> m_grid;
std::unique_ptr<label> m_no_games_text;
std::unique_ptr<rounded_rect> m_highlight;
std::unique_ptr<big_picture_game_details> m_details;
std::function<void(std::string, std::string)> m_on_game_selected;
image_button m_back_hint{ 120, 30 };
image_button m_select_hint{ 120, 30 };
s32 m_selected_index = 0;
u16 m_row_stride = 0;
u16 m_content_height = 0;
compiled_resource m_compiled_grid;
};
}
}
@@ -0,0 +1,28 @@
#include "stdafx.h"
#include "overlay_big_picture_main_menu.h"
#include "overlay_big_picture_game_grid.h"
#include "Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.h"
namespace rsx
{
namespace overlays
{
big_picture_main_menu::big_picture_main_menu(s16 x, s16 y, u16 width, u16 height, home_menu_page* parent, std::function<void(std::string, std::string)> on_game_selected)
: home_menu_sidebar_page(x, y, width, height, false, parent)
{
is_current_page = true;
add_page(home_menu::fa_icon::home, std::make_shared<big_picture_game_grid>(x, y, width, height, this, std::move(on_game_selected)));
add_page(home_menu::fa_icon::settings, std::make_shared<home_menu_settings>(x, y, width, height, false, this));
add_item(home_menu::fa_icon::poweroff, get_localized_string(localized_string_id::BIG_PICTURE_MENU_EXIT), [](pad_button btn) -> page_navigation
{
if (btn != pad_button::cross) return page_navigation::stay;
return page_navigation::exit;
});
apply_layout();
}
}
}
@@ -0,0 +1,18 @@
#pragma once
#include "Emu/RSX/Overlays/HomeMenu/overlay_home_menu_sidebar_page.h"
#include <functional>
#include <string>
namespace rsx
{
namespace overlays
{
struct big_picture_main_menu : public home_menu_sidebar_page
{
// on_game_selected is invoked once the user confirms "Start" on a game (path, title_id).
big_picture_main_menu(s16 x, s16 y, u16 width, u16 height, home_menu_page* parent, std::function<void(std::string, std::string)> on_game_selected);
};
}
}
@@ -19,21 +19,13 @@ namespace rsx
namespace overlays
{
home_menu_main_menu::home_menu_main_menu(s16 x, s16 y, u16 width, u16 height, bool use_separators, home_menu_page* parent)
: home_menu_page(x, y, width, height, use_separators, parent, "")
: home_menu_sidebar_page(x, y, width, height, use_separators, parent)
{
is_current_page = true;
m_message_box = std::make_shared<home_menu_message_box>(x, y, width, height);
m_message_box->visible = false;
m_sidebar = std::make_unique<list_view>(350, overlay::virtual_height, false);
m_sidebar->set_pos(0, 0);
m_sidebar->hide_prompt_buttons();
m_sidebar->back_color = color4f(0.05f, 0.05f, 0.05f, 0.95f);
m_sliding_animation.duration_sec = 0.5f;
m_sliding_animation.type = animation_type::ease_in_out_cubic;
add_item(home_menu::fa_icon::back, get_localized_string(localized_string_id::HOME_MENU_RESUME), [](pad_button btn) -> page_navigation
{
if (btn != pad_button::cross) return page_navigation::stay;
@@ -155,143 +147,5 @@ namespace rsx
apply_layout();
}
void home_menu_main_menu::apply_layout(bool center_vertically)
{
home_menu_page::apply_layout(center_vertically);
if (m_sidebar->get_elements_count() == 0)
{
return;
}
auto sidebar_items = std::move(m_sidebar->m_items);
m_sidebar->clear_items();
u16 combined_height = 0;
std::for_each(
sidebar_items.begin(),
sidebar_items.end(),
[&](auto& entry)
{
combined_height += entry->h + m_sidebar->pack_padding;
entry->set_pos(0, 0);
});
if (combined_height < overlay::virtual_height)
{
m_sidebar->advance_pos = (overlay::virtual_height - combined_height) / 2;
}
for (auto& entry : sidebar_items)
{
m_sidebar->add_entry(entry);
}
}
void home_menu_main_menu::add_sidebar_entry(home_menu::fa_icon icon, std::string_view title)
{
auto label_widget = std::make_unique<label>(title.data());
label_widget->set_size(m_sidebar->w, 60);
label_widget->set_font("Arial", 16);
label_widget->back_color.a = 0.f;
label_widget->set_margin(8, 0);
label_widget->set_padding(16, 4, 16, 4);
label_widget->auto_resize();
label_widget->set_size(label_widget->w, 60);
if (icon == home_menu::fa_icon::none)
{
const u16 packed_width = label_widget->w + 18; // rpad
if (packed_width > m_sidebar->w)
{
m_sidebar->set_size(std::min(packed_width, this->w), m_sidebar->h);
}
m_sidebar->add_entry(label_widget);
return;
}
auto icon_info = ensure(home_menu::get_icon(icon));
auto icon_view = std::make_unique<image_view>();
icon_view->set_raw_image(icon_info);
icon_view->set_size(42, 60);
icon_view->set_margin(8, 0);
icon_view->set_padding(18, 0, 18, 18);
const u16 packed_width = icon_view->padding_left + icon_view->w + label_widget->w + 18; // rpad
if (packed_width > m_sidebar->w)
{
m_sidebar->set_size(std::min(packed_width, this->w), m_sidebar->h);
}
auto box = std::make_unique<horizontal_layout>();
box->set_size(0, 16);
box->set_padding(1);
box->add_element(icon_view);
box->add_element(label_widget);
m_sidebar->add_entry(box);
}
void home_menu_main_menu::add_item(home_menu::fa_icon icon, std::string_view title, std::function<page_navigation(pad_button)> callback)
{
add_sidebar_entry(icon, title);
home_menu_page::add_item(home_menu::fa_icon::none, title, callback);
}
void home_menu_main_menu::add_page(home_menu::fa_icon icon, std::shared_ptr<home_menu_page> page)
{
add_sidebar_entry(icon, page->title);
home_menu_page::add_page(home_menu::fa_icon::none, page);
}
void home_menu_main_menu::select_entry(s32 entry)
{
m_sidebar->select_entry(entry);
list_view::select_entry(entry);
}
void home_menu_main_menu::select_next(u16 count)
{
m_sidebar->select_next(count);
list_view::select_next(count);
}
void home_menu_main_menu::select_previous(u16 count)
{
m_sidebar->select_previous(count);
list_view::select_previous(count);
}
void home_menu_main_menu::update(u64 timestamp_us)
{
if (m_animation_timer == 0)
{
m_animation_timer = timestamp_us;
m_sliding_animation.current = { -f32(m_sidebar->x + m_sidebar->w), 0, 0 };
m_sliding_animation.end = {};
m_sliding_animation.active = true;
m_sliding_animation.update(0);
return;
}
if (m_sliding_animation.active)
{
m_sliding_animation.update(timestamp_us);
}
}
compiled_resource& home_menu_main_menu::get_compiled()
{
m_is_compiled = true;
if (home_menu_page* page = get_current_page(false))
{
return page->get_compiled();
}
compiled_resources = m_sidebar->get_compiled();
m_sliding_animation.apply(compiled_resources);
return compiled_resources;
}
}
}
@@ -1,35 +1,14 @@
#pragma once
#include "overlay_home_menu_page.h"
#include "../overlay_animation.h"
#include "overlay_home_menu_sidebar_page.h"
namespace rsx
{
namespace overlays
{
struct home_menu_main_menu : public home_menu_page
struct home_menu_main_menu : public home_menu_sidebar_page
{
home_menu_main_menu(s16 x, s16 y, u16 width, u16 height, bool use_separators, home_menu_page* parent);
void select_entry(s32 entry) override;
void select_next(u16 count = 1) override;
void select_previous(u16 count = 1) override;
compiled_resource& get_compiled() override;
void update(u64 timestamp_us) override;
private:
void apply_layout(bool center_vertically = false) override;
using home_menu_page::add_item;
void add_item(home_menu::fa_icon icon, std::string_view title, std::function<page_navigation(pad_button)> callback) override;
void add_page(home_menu::fa_icon icon, std::shared_ptr<home_menu_page> page) override;
void add_sidebar_entry(home_menu::fa_icon icon, std::string_view title);
u64 m_animation_timer = 0;
animation_translate m_sliding_animation;
std::unique_ptr<list_view> m_sidebar; // Render proxy
};
}
}
@@ -0,0 +1,159 @@
#include "stdafx.h"
#include "overlay_home_menu_sidebar_page.h"
namespace rsx
{
namespace overlays
{
home_menu_sidebar_page::home_menu_sidebar_page(s16 x, s16 y, u16 width, u16 height, bool use_separators, home_menu_page* parent)
: home_menu_page(x, y, width, height, use_separators, parent, "")
{
m_sidebar = std::make_unique<list_view>(350, overlay::virtual_height, false);
m_sidebar->set_pos(0, 0);
m_sidebar->hide_prompt_buttons();
m_sidebar->back_color = color4f(0.05f, 0.05f, 0.05f, 0.95f);
m_sliding_animation.duration_sec = 0.5f;
m_sliding_animation.type = animation_type::ease_in_out_cubic;
}
void home_menu_sidebar_page::apply_layout(bool center_vertically)
{
home_menu_page::apply_layout(center_vertically);
if (m_sidebar->get_elements_count() == 0)
{
return;
}
auto sidebar_items = std::move(m_sidebar->m_items);
m_sidebar->clear_items();
u16 combined_height = 0;
std::for_each(
sidebar_items.begin(),
sidebar_items.end(),
[&](auto& entry)
{
combined_height += entry->h + m_sidebar->pack_padding;
entry->set_pos(0, 0);
});
if (combined_height < overlay::virtual_height)
{
m_sidebar->advance_pos = (overlay::virtual_height - combined_height) / 2;
}
for (auto& entry : sidebar_items)
{
m_sidebar->add_entry(entry);
}
}
void home_menu_sidebar_page::add_sidebar_entry(home_menu::fa_icon icon, std::string_view title)
{
auto label_widget = std::make_unique<label>(title.data());
label_widget->set_size(m_sidebar->w, 60);
label_widget->set_font("Arial", 16);
label_widget->back_color.a = 0.f;
label_widget->set_margin(8, 0);
label_widget->set_padding(16, 4, 16, 4);
label_widget->auto_resize();
label_widget->set_size(label_widget->w, 60);
if (icon == home_menu::fa_icon::none)
{
const u16 packed_width = label_widget->w + 18; // rpad
if (packed_width > m_sidebar->w)
{
m_sidebar->set_size(std::min(packed_width, this->w), m_sidebar->h);
}
m_sidebar->add_entry(label_widget);
return;
}
auto icon_info = ensure(home_menu::get_icon(icon));
auto icon_view = std::make_unique<image_view>();
icon_view->set_raw_image(icon_info);
icon_view->set_size(42, 60);
icon_view->set_margin(8, 0);
icon_view->set_padding(18, 0, 18, 18);
const u16 packed_width = icon_view->padding_left + icon_view->w + label_widget->w + 18; // rpad
if (packed_width > m_sidebar->w)
{
m_sidebar->set_size(std::min(packed_width, this->w), m_sidebar->h);
}
auto box = std::make_unique<horizontal_layout>();
box->set_size(0, 16);
box->set_padding(1);
box->add_element(icon_view);
box->add_element(label_widget);
m_sidebar->add_entry(box);
}
void home_menu_sidebar_page::add_item(home_menu::fa_icon icon, std::string_view title, std::function<page_navigation(pad_button)> callback)
{
add_sidebar_entry(icon, title);
home_menu_page::add_item(home_menu::fa_icon::none, title, callback);
}
void home_menu_sidebar_page::add_page(home_menu::fa_icon icon, std::shared_ptr<home_menu_page> page)
{
add_sidebar_entry(icon, page->title);
home_menu_page::add_page(home_menu::fa_icon::none, page);
}
void home_menu_sidebar_page::select_entry(s32 entry)
{
m_sidebar->select_entry(entry);
list_view::select_entry(entry);
}
void home_menu_sidebar_page::select_next(u16 count)
{
m_sidebar->select_next(count);
list_view::select_next(count);
}
void home_menu_sidebar_page::select_previous(u16 count)
{
m_sidebar->select_previous(count);
list_view::select_previous(count);
}
void home_menu_sidebar_page::update(u64 timestamp_us)
{
if (m_animation_timer == 0)
{
m_animation_timer = timestamp_us;
m_sliding_animation.current = { -f32(m_sidebar->x + m_sidebar->w), 0, 0 };
m_sliding_animation.end = {};
m_sliding_animation.active = true;
m_sliding_animation.update(0);
return;
}
if (m_sliding_animation.active)
{
m_sliding_animation.update(timestamp_us);
}
}
compiled_resource& home_menu_sidebar_page::get_compiled()
{
m_is_compiled = true;
if (home_menu_page* page = get_current_page(false))
{
return page->get_compiled();
}
compiled_resources = m_sidebar->get_compiled();
m_sliding_animation.apply(compiled_resources);
return compiled_resources;
}
}
}
@@ -0,0 +1,37 @@
#pragma once
#include "overlay_home_menu_page.h"
#include "../overlay_animation.h"
namespace rsx
{
namespace overlays
{
// A home_menu_page that renders a sliding icon+label sidebar (left navigation bar) next to its item list.
// Used by the in-game pause menu (home_menu_main_menu) and Big Picture Mode's main menu.
struct home_menu_sidebar_page : public home_menu_page
{
home_menu_sidebar_page(s16 x, s16 y, u16 width, u16 height, bool use_separators, home_menu_page* parent);
void select_entry(s32 entry) override;
void select_next(u16 count = 1) override;
void select_previous(u16 count = 1) override;
compiled_resource& get_compiled() override;
void update(u64 timestamp_us) override;
protected:
void apply_layout(bool center_vertically = false) override;
using home_menu_page::add_item;
void add_item(home_menu::fa_icon icon, std::string_view title, std::function<page_navigation(pad_button)> callback) override;
void add_page(home_menu::fa_icon icon, std::shared_ptr<home_menu_page> page) override;
void add_sidebar_entry(home_menu::fa_icon icon, std::string_view title);
u64 m_animation_timer = 0;
animation_translate m_sliding_animation;
std::unique_ptr<list_view> m_sidebar; // Render proxy
};
}
}
+90 -2
View File
@@ -31,6 +31,7 @@
#include "Emu/IdManager.h"
#include "Emu/RSX/Capture/rsx_replay.h"
#include "Emu/RSX/Overlays/overlay_message.h"
#include "Emu/RSX/Overlays/BigPicture/overlay_big_picture.h"
#include "Loader/PSF.h"
#include "Loader/TAR.h"
@@ -95,6 +96,8 @@ fs::file make_file_view(const fs::file& file, u64 offset, u64 size);
extern std::string get_syscache_state_corruption_indicator_file_path(std::string_view dir_path);
extern atomic_t<bool> g_big_picture_mode_active;
fs::file g_tty;
atomic_t<s64> g_tty_size{0};
std::array<std::deque<std::string>, 16> g_tty_input;
@@ -951,6 +954,70 @@ bool Emulator::BootRsxCapture(const std::string& path)
return true;
}
bool Emulator::BootBigPictureMode()
{
if (m_state != system_state::stopped || m_restrict_emu_state_change)
{
sys_log.error("Big Picture Mode: cannot boot, state=%d, restricted=%d", static_cast<u32>(m_state.load()), +m_restrict_emu_state_change);
return false;
}
sys_log.notice("Big Picture Mode: booting window");
m_state = system_state::loading;
m_path.clear();
m_path_old.clear();
m_path_original.clear();
m_path_real.clear();
m_title_id.clear();
m_title.clear();
m_localized_title.clear();
m_app_version.clear();
m_hash.clear();
m_cat.clear();
m_dir.clear();
m_sfo_dir.clear();
m_ar.reset();
Init();
g_cfg.video.disable_on_disk_shader_cache.set(true);
vm::init();
g_fxo->init(false);
// Initialize progress dialog
g_fxo->init<named_thread<progress_dialog_server>>();
// Initialize performance monitor
g_fxo->init<named_thread<perf_monitor>>();
// No PS3 executable is loaded. GSRender/pad_thread only exist to host the Big Picture Mode overlay.
m_state = system_state::ready;
GetCallbacks().on_ready();
GetCallbacks().init_gs_render(nullptr);
GetCallbacks().init_pad_handler("");
GetCallbacks().on_run(false);
m_state = system_state::starting;
m_state.notify_all();
ensure(g_fxo->init<named_thread>("Big Picture Mode"sv, []()
{
rsx::overlays::open_big_picture_mode();
}));
sys_log.notice("Big Picture Mode: booted successfully");
return true;
}
void Emulator::DeactivateBigPictureMode() const
{
g_big_picture_mode_active = false;
}
game_boot_result Emulator::GetElfPathFromDir(std::string& elf_path, const std::string& path)
{
if (!fs::is_dir(path))
@@ -3530,6 +3597,15 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
const bool continuous_savestate_mode = savestate && !g_cfg.savestate.suspend_emu;
// Decide upfront (before the renderer is torn down) whether this stop should return to Big Picture
// Mode, so SetContinuousMode() takes effect before GSRender::~GSRender() runs and closes the window.
const bool return_to_big_picture_mode = !after_kill_callback && g_big_picture_mode_active.exchange(false);
if (return_to_big_picture_mode)
{
SetContinuousMode(true);
}
// Show visual feedback to the user in case that stopping takes a while.
// This needs to be done before actually stopping, because otherwise the necessary threads will be terminated before we can show an image.
if (g_fxo->try_get<named_thread<progress_dialog_server>>() && (continuous_savestate_mode || g_progr_text.operator bool()))
@@ -3564,7 +3640,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
// There is no race condition because it is only accessed by the same thread
std::shared_ptr<std::shared_ptr<void>> join_thread = std::make_shared<std::shared_ptr<void>>();
*join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, reset_emu_state, savestate, allow_autoexit, save_stage = save_stage ? *save_stage : savestate_stage{}, this]() mutable
*join_thread = make_ptr(new named_thread("Emulation Join Thread"sv, [join_thread, reset_emu_state, savestate, allow_autoexit, save_stage = save_stage ? *save_stage : savestate_stage{}, return_to_big_picture_mode, this]() mutable
{
fs::pending_file file;
@@ -4074,7 +4150,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
set_progress_message("Resetting Objects");
// Final termination from main thread (move the last ownership of join thread in order to destroy it)
CallFromMainThread([join_thread = std::move(join_thread), reset_emu_state, verbose_message, stop_watchdog, init_mtx, allow_autoexit, this]()
CallFromMainThread([join_thread = std::move(join_thread), reset_emu_state, verbose_message, stop_watchdog, init_mtx, allow_autoexit, return_to_big_picture_mode, this]()
{
cpu_thread::cleanup();
@@ -4131,6 +4207,18 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
// Complete the operation
m_state = system_state::stopped;
if (return_to_big_picture_mode)
{
ensure(!after_kill_callback);
after_kill_callback = [this]()
{
sys_log.notice("Big Picture Mode: game stopped, returning to Big Picture Mode.");
const bool result = BootBigPictureMode();
sys_log.notice("Big Picture Mode: BootBigPictureMode() returned %d", result);
};
}
GetCallbacks().on_stop();
// Always Enable display sleep, not only if it was prevented.
+5
View File
@@ -433,6 +433,11 @@ public:
game_boot_result BootGame(const std::string& path, const std::string& title_id = "", bool direct = false, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "", const std::optional<std::string>& db_config = std::nullopt);
bool BootRsxCapture(const std::string& path);
// Boots a minimal shell (no PS3 executable) that hosts the RSX-overlay-based Big Picture Mode game grid.
bool BootBigPictureMode();
// Cancel any pending return to Big Picture Mode, e.g. when a game is booted manually and bypasses the shell.
void DeactivateBigPictureMode() const;
void SetForceBoot(bool force_boot);
void SetContinuousMode(bool continuous_mode);
+9
View File
@@ -285,6 +285,7 @@ enum class localized_string_id
HOME_MENU_SETTINGS_MOUSE_DEBUG_INPUT_OVERLAY,
HOME_MENU_SETTINGS_DEBUG_DISABLE_VIDEO_OUTPUT,
HOME_MENU_SETTINGS_DEBUG_TEXTURE_LOD_BIAS,
HOME_MENU_SETTINGS_SYSTEM_START_BIG_PICTURE_MODE,
HOME_MENU_SCREENSHOT,
HOME_MENU_SAVESTATE,
HOME_MENU_SAVESTATE_SAVE,
@@ -347,4 +348,12 @@ enum class localized_string_id
SAVESTATE_FAILED_DUE_TO_SAVEDATA,
SAVESTATE_FAILED_DUE_TO_SPU,
SAVESTATE_FAILED_DUE_TO_MISSING_SPU_SETTING,
BIG_PICTURE_MODE_TITLE,
BIG_PICTURE_MENU_GAMES,
BIG_PICTURE_MENU_EXIT,
BIG_PICTURE_NO_GAMES_FOUND,
BIG_PICTURE_GAME_DETAILS_START,
BIG_PICTURE_HINT_BACK,
BIG_PICTURE_HINT_SELECT,
};
+1
View File
@@ -360,6 +360,7 @@ struct cfg_root : cfg::node
cfg::_bool autoexit{ this, "Exit RPCS3 when process finishes", false, true };
cfg::_bool autopause{ this, "Pause emulation on RPCS3 focus loss", false, true };
cfg::_bool start_fullscreen{ this, "Start games in fullscreen mode", true, true };
cfg::_bool start_big_picture_mode{ this, "Start Big Picture Mode on boot", false, true };
cfg::_bool prevent_display_sleep{ this, "Prevent display sleep while running games", true, true };
cfg::_bool show_trophy_popups{ this, "Show trophy popups", true, true };
cfg::_bool show_rpcn_popups{ this, "Show RPCN popups", true, true };
+10
View File
@@ -146,6 +146,11 @@
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_page.cpp" />
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_settings.cpp" />
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_savestate.cpp" />
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_sidebar_page.cpp" />
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture.cpp" />
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_details.cpp" />
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_grid.cpp" />
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_main_menu.cpp" />
<ClCompile Include="Emu\RSX\Overlays\Network\overlay_recvmessage_dialog.cpp" />
<ClCompile Include="Emu\RSX\Overlays\Network\overlay_sendmessage_dialog.cpp" />
<ClCompile Include="Emu\RSX\Overlays\overlay_animated_icon.cpp" />
@@ -724,6 +729,11 @@
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_page.h" />
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_settings.h" />
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_savestate.h" />
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_sidebar_page.h" />
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture.h" />
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_details.h" />
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_grid.h" />
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_main_menu.h" />
<ClInclude Include="Emu\RSX\Overlays\Network\overlay_recvmessage_dialog.h" />
<ClInclude Include="Emu\RSX\Overlays\Network\overlay_sendmessage_dialog.h" />
<ClInclude Include="Emu\RSX\Overlays\overlay_animated_icon.h" />
+30
View File
@@ -1216,6 +1216,21 @@
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_savestate.cpp">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_sidebar_page.cpp">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture.cpp">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_details.cpp">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_grid.cpp">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_main_menu.cpp">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\Overlays\overlay_animated_icon.cpp">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClCompile>
@@ -2593,6 +2608,21 @@
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_savestate.h">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_sidebar_page.h">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture.h">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_details.h">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_game_grid.h">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Overlays\BigPicture\overlay_big_picture_main_menu.h">
<Filter>Emu\GPU\RSX\Overlays\HomeMenu\Pages</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\Overlays\overlay_loading_icon.hpp">
<Filter>Emu\GPU\RSX\Overlays</Filter>
</ClInclude>
+4
View File
@@ -1379,6 +1379,10 @@ int run_rpcs3(int argc, char** argv)
Emu.Quit(true);
return 0;
}
else if (!g_headless && g_cfg.misc.start_big_picture_mode)
{
Emu.BootBigPictureMode();
}
// run event loop (maybe only needed for the gui application)
if (gui_application* gui_app = qobject_cast<gui_application*>(app.data()))
+1
View File
@@ -195,6 +195,7 @@ const std::map<emu_settings_type, cfg_location> settings_location =
{ emu_settings_type::StartOnBoot, get_cfg_location(local_cfg.misc.autostart) },
{ emu_settings_type::PauseOnFocusLoss, get_cfg_location(local_cfg.misc.autopause) },
{ emu_settings_type::StartGameFullscreen, get_cfg_location(local_cfg.misc.start_fullscreen) },
{ emu_settings_type::StartBigPictureModeOnBoot, get_cfg_location(local_cfg.misc.start_big_picture_mode) },
{ emu_settings_type::PreventDisplaySleep, get_cfg_location(local_cfg.misc.prevent_display_sleep) },
{ emu_settings_type::ShowTrophyPopups, get_cfg_location(local_cfg.misc.show_trophy_popups) },
{ emu_settings_type::ShowRpcnPopups, get_cfg_location(local_cfg.misc.show_rpcn_popups) },
+1
View File
@@ -187,6 +187,7 @@ enum class emu_settings_type
StartOnBoot,
PauseOnFocusLoss,
StartGameFullscreen,
StartBigPictureModeOnBoot,
PreventDisplaySleep,
ShowTrophyPopups,
ShowRpcnPopups,
+8
View File
@@ -305,6 +305,7 @@ private:
case localized_string_id::HOME_MENU_SETTINGS_MOUSE_DEBUG_INPUT_OVERLAY: return tr("Mouse Debug Overlay", "Debug");
case localized_string_id::HOME_MENU_SETTINGS_DEBUG_DISABLE_VIDEO_OUTPUT: return tr("Disable Video Output", "Debug");
case localized_string_id::HOME_MENU_SETTINGS_DEBUG_TEXTURE_LOD_BIAS: return tr("Texture LOD Bias Addend", "Debug");
case localized_string_id::HOME_MENU_SETTINGS_SYSTEM_START_BIG_PICTURE_MODE: return tr("Open Big Picture Mode On Boot", "System");
case localized_string_id::HOME_MENU_SCREENSHOT: return tr("Take Screenshot");
case localized_string_id::HOME_MENU_SAVESTATE: return tr("SaveState");
case localized_string_id::HOME_MENU_SAVESTATE_SAVE: return tr("Save Emulation State");
@@ -363,6 +364,13 @@ private:
case localized_string_id::SAVESTATE_FAILED_DUE_TO_VDEC: return tr("SaveState failed: VDEC-based video/cutscenes are in order, wait for them to end or enable libvdec.sprx.");
case localized_string_id::SAVESTATE_FAILED_DUE_TO_MISSING_SPU_SETTING: return tr("SaveState failed: Failed to lock SPU state, enabling SPU-Compatible mode may fix it.");
case localized_string_id::SAVESTATE_FAILED_DUE_TO_SPU: return tr("SaveState failed: Failed to lock SPU state, using SPU ASMJIT will fix it.");
case localized_string_id::BIG_PICTURE_MODE_TITLE: return tr("Big Picture Mode");
case localized_string_id::BIG_PICTURE_MENU_GAMES: return tr("Games");
case localized_string_id::BIG_PICTURE_MENU_EXIT: return tr("Exit Big Picture Mode");
case localized_string_id::BIG_PICTURE_NO_GAMES_FOUND: return tr("No games found.\nAdd games in the main RPCS3 window.");
case localized_string_id::BIG_PICTURE_GAME_DETAILS_START: return tr("Start");
case localized_string_id::BIG_PICTURE_HINT_BACK: return tr("Back");
case localized_string_id::BIG_PICTURE_HINT_SELECT: return tr("Select");
case localized_string_id::INVALID: return tr("Invalid");
default: return tr("Unknown");
}
+28
View File
@@ -564,6 +564,9 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo
return;
}
// A manual boot from the game list bypasses Big Picture Mode, so exiting it shouldn't relaunch Big Picture Mode.
Emu.DeactivateBigPictureMode();
Emu.GracefulShutdown(false);
m_app_icon = gui::utils::get_app_icon_from_path(path, title_id);
@@ -2141,6 +2144,7 @@ void main_window::OnEmuStop()
ui->actionManage_Users->setEnabled(true);
ui->confCamerasAct->setEnabled(true);
ui->actionPS_Move_Tracker->setEnabled(true);
ui->bigPictureModeAct->setEnabled(true);
// Refresh game list in order to update time played
if (m_game_list_frame && m_is_list_mode)
@@ -2183,6 +2187,7 @@ void main_window::OnEmuReady()
ui->actionManage_Users->setEnabled(false);
ui->confCamerasAct->setEnabled(false);
ui->actionPS_Move_Tracker->setEnabled(false);
ui->bigPictureModeAct->setEnabled(false);
ui->batchRemoveShaderCachesAct->setEnabled(false);
ui->batchRemovePPUCachesAct->setEnabled(false);
@@ -3468,6 +3473,29 @@ void main_window::CreateConnects()
m_gui_settings->SetValue(gui::mw_debugger, checked);
});
connect(ui->bigPictureModeAct, &QAction::triggered, this, [this]()
{
if (Emu.IsBootingRestricted())
{
gui_log.notice("Big Picture Mode: boot request ignored, booting is currently restricted.");
return;
}
if (!m_gui_settings->GetBootConfirmation(this, gui::ib_confirm_boot))
{
gui_log.notice("Big Picture Mode: boot cancelled by user at confirmation dialog.");
return;
}
gui_log.notice("Booting Big Picture Mode from main window");
Emu.GracefulShutdown(false);
if (!Emu.BootBigPictureMode())
{
gui_log.error("Failed to start Big Picture Mode.");
}
});
connect(ui->showLogAct, &QAction::triggered, this, [this](bool checked)
{
checked ? m_log_frame->show() : m_log_frame->hide();
+10
View File
@@ -442,6 +442,8 @@
<addaction name="showCatUnknownAct"/>
<addaction name="showCatOtherAct"/>
</widget>
<addaction name="bigPictureModeAct"/>
<addaction name="separator"/>
<widget class="QMenu" name="menuView_Game_Collections">
<property name="title">
<string>Game Collections</string>
@@ -839,6 +841,14 @@
<string>Show Debugger</string>
</property>
</action>
<action name="bigPictureModeAct">
<property name="text">
<string>Big Picture Mode</string>
</property>
<property name="toolTip">
<string>Browse and launch your installed games in a controller-friendly full screen grid</string>
</property>
</action>
<action name="showLogAct">
<property name="checkable">
<bool>true</bool>
+3
View File
@@ -1800,6 +1800,8 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
SubscribeTooltip(ui->gs_showMouseInFullscreen, tooltips.settings.show_mouse_in_fullscreen);
SubscribeTooltip(ui->gs_lockMouseInFullscreen, tooltips.settings.lock_mouse_in_fullscreen);
SubscribeTooltip(ui->gs_hideMouseOnIdle_widget, tooltips.settings.hide_mouse_on_idle);
EnhanceCheckBox(emu_settings_type::StartBigPictureModeOnBoot, ui->startBigPictureModeOnBoot, tooltips.settings.start_big_picture_mode_on_boot);
ui->gs_disableMouse->setChecked(m_gui_settings->GetValue(gui::gs_disableMouse).toBool());
connect(ui->gs_disableMouse, &QCheckBox::toggled, [this](bool checked)
@@ -1882,6 +1884,7 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> gui_settings, std
{
ui->gb_viewport->setEnabled(false);
ui->gb_viewport->setVisible(false);
ui->startBigPictureModeOnBoot->setDisabled(true);
}
// Game window title builder
+7
View File
@@ -3101,6 +3101,13 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="startBigPictureModeOnBoot">
<property name="text">
<string>Open Big Picture Mode on boot</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="useNativeInterface">
<property name="text">
+1
View File
@@ -139,6 +139,7 @@ public:
const QString exit_on_stop = tr("Automatically close RPCS3 when closing a game, or when a game closes itself.");
const QString pause_on_focus_loss = tr("Automatically pause emulation when RPCS3 loses its focus or the application is inactive in order to save power and reduce CPU usage.\nDo note that emulation pausing in general is not perfect and may not be compatible with all games.\nAlthough it currently also pauses gameplay, it is not recommended to rely on it as this behavior may be changed in the future and it is not the purpose of this setting.");
const QString start_game_fullscreen = tr("Automatically puts the game window in fullscreen.\nDouble click on the game window or press Alt+Enter to toggle fullscreen and windowed mode.");
const QString start_big_picture_mode_on_boot = tr("Automatically opens Big Picture Mode's controller-friendly game grid when RPCS3 starts.");
const QString prevent_display_sleep = tr("Prevent the display from sleeping while a game is running.\nThis requires the org.freedesktop.ScreenSaver D-Bus service on Linux.\nThis option will be disabled if the current platform does not support display sleep control.");
const QString game_window_title_format = tr("Configure the game window title.\nChanging this and/or adding the framerate may cause buggy or outdated recording software to not notice RPCS3.");
const QString resize_on_boot = tr("Automatically resizes the game window on boot.\nThis does not change the internal game resolution.");