FSUI/Qt: Add Center/Tile background modes and remove redundant Qt null checks (#13564)

Signed-off-by: SternXD <stern@sidestore.io>
Signed-off-by: SternXD <stern@sidestore.io
Co-authored-by: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
This commit is contained in:
Stern 2025-12-14 09:08:34 -05:00 committed by GitHub
parent cf4412ecbe
commit 0180ec060b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 181 additions and 81 deletions

View File

@ -1148,7 +1148,7 @@ bool BMPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
return false; return false;
} }
if (width > 65536 || height > 65536) if (width >= 65536 || height >= 65536)
{ {
Console.Error("BMP dimensions too large: %ux%u", width, height); Console.Error("BMP dimensions too large: %ux%u", width, height);
return false; return false;

View File

@ -220,6 +220,8 @@ void GameListWidget::initialize()
m_sort_model->setSourceModel(m_model); m_sort_model->setSourceModel(m_model);
m_ui.setupUi(this); m_ui.setupUi(this);
m_ui.stack->installEventFilter(this);
m_ui.stack->setAutoFillBackground(false);
for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++) for (u32 type = 0; type < static_cast<u32>(GameList::EntryType::Count); type++)
{ {
@ -353,6 +355,7 @@ void GameListWidget::setCustomBackground()
m_background_movie = nullptr; m_background_movie = nullptr;
} }
// Get the path to the custom background
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath"); std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath");
if (!Path::IsAbsolute(path)) if (!Path::IsAbsolute(path))
path = Path::Combine(EmuFolders::DataRoot, path); path = Path::Combine(EmuFolders::DataRoot, path);
@ -360,27 +363,26 @@ void GameListWidget::setCustomBackground()
// Only try to create background if path are valid // Only try to create background if path are valid
if (!path.empty() && FileSystem::FileExists(path.c_str())) if (!path.empty() && FileSystem::FileExists(path.c_str()))
{ {
QMovie* new_movie;
QString img_path = QString::fromStdString(path); QString img_path = QString::fromStdString(path);
if (img_path.endsWith(".png", Qt::CaseInsensitive)) const QByteArray format = (img_path.endsWith(".png", Qt::CaseInsensitive)) ? QByteArray("apng") : QByteArray();
// Use apng plugin m_background_movie = new QMovie(img_path, format, this);
new_movie = new QMovie(img_path, "apng", this); if (!m_background_movie->isValid())
else
new_movie = new QMovie(img_path, QByteArray(), this);
if (new_movie->isValid())
m_background_movie = new_movie;
else
{ {
Console.Warning("Failed to load background movie from: %s", path.c_str()); Console.Warning("Failed to load background movie from: %s", path.c_str());
delete new_movie; delete m_background_movie;
m_background_movie = nullptr;
} }
} }
// If there is no valid background then reset fallback to default UI state // If there is no valid background then reset fallback to default UI state
if (!m_background_movie) if (!m_background_movie)
{ {
m_ui.stack->setPalette(QApplication::palette()); m_background_pixmap = QPixmap();
m_ui.stack->setAutoFillBackground(true);
m_table_view->viewport()->setAutoFillBackground(true);
m_list_view->viewport()->setAutoFillBackground(true);
m_ui.stack->update();
m_table_view->setAlternatingRowColors(true); m_table_view->setAlternatingRowColors(true);
return; return;
} }
@ -390,7 +392,7 @@ void GameListWidget::setCustomBackground()
const std::string ar_value = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[static_cast<u8>(QtUtils::ScalingMode::Fit)]); const std::string ar_value = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[static_cast<u8>(QtUtils::ScalingMode::Fit)]);
for (u8 i = 0; i < static_cast<u8>(QtUtils::ScalingMode::MaxCount); i++) for (u8 i = 0; i < static_cast<u8>(QtUtils::ScalingMode::MaxCount); i++)
{ {
if (!(InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] == nullptr)) if (InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i] != nullptr)
{ {
if (ar_value == InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i]) if (ar_value == InterfaceSettingsWidget::BACKGROUND_SCALE_NAMES[i])
{ {
@ -405,8 +407,13 @@ void GameListWidget::setCustomBackground()
// Selected Custom background is valid, connect the signals and start animation in gamelist // Selected Custom background is valid, connect the signals and start animation in gamelist
connect(m_background_movie, &QMovie::frameChanged, this, &GameListWidget::processBackgroundFrames, Qt::UniqueConnection); connect(m_background_movie, &QMovie::frameChanged, this, &GameListWidget::processBackgroundFrames, Qt::UniqueConnection);
m_ui.stack->setAutoFillBackground(false);
m_table_view->viewport()->setAutoFillBackground(false);
m_list_view->viewport()->setAutoFillBackground(false);
updateCustomBackgroundState(true); updateCustomBackgroundState(true);
m_table_view->setAlternatingRowColors(false); m_table_view->setAlternatingRowColors(false);
processBackgroundFrames();
} }
void GameListWidget::updateCustomBackgroundState(const bool force_start) void GameListWidget::updateCustomBackgroundState(const bool force_start)
@ -422,7 +429,7 @@ void GameListWidget::updateCustomBackgroundState(const bool force_start)
void GameListWidget::processBackgroundFrames() void GameListWidget::processBackgroundFrames()
{ {
if (m_background_movie && m_background_movie->isValid()) if (m_background_movie && m_background_movie->isValid() && isVisible())
{ {
const int widget_width = m_ui.stack->width(); const int widget_width = m_ui.stack->width();
const int widget_height = m_ui.stack->height(); const int widget_height = m_ui.stack->height();
@ -435,9 +442,8 @@ void GameListWidget::processBackgroundFrames()
QtUtils::resizeAndScalePixmap(&pm, widget_width, widget_height, dpr, m_background_scaling, m_background_opacity); QtUtils::resizeAndScalePixmap(&pm, widget_width, widget_height, dpr, m_background_scaling, m_background_opacity);
QPalette bg_palette(m_ui.stack->palette()); m_background_pixmap = std::move(pm);
bg_palette.setBrush(QPalette::Base, pm); m_ui.stack->update();
m_ui.stack->setPalette(bg_palette);
} }
} }
@ -725,6 +731,7 @@ bool GameListWidget::event(QEvent* event)
if (event->type() == QEvent::DevicePixelRatioChange) if (event->type() == QEvent::DevicePixelRatioChange)
{ {
m_model->setDevicePixelRatio(devicePixelRatioF()); m_model->setDevicePixelRatio(devicePixelRatioF());
processBackgroundFrames();
QWidget::event(event); QWidget::event(event);
return true; return true;
} }
@ -732,6 +739,25 @@ bool GameListWidget::event(QEvent* event)
return QWidget::event(event); return QWidget::event(event);
} }
bool GameListWidget::eventFilter(QObject* watched, QEvent* event)
{
if (watched == m_ui.stack && event->type() == QEvent::Paint)
{
if (!m_background_pixmap.isNull())
{
QPainter painter(m_ui.stack);
const auto* paint_event = static_cast<QPaintEvent*>(event);
painter.save();
painter.setClipRect(paint_event->rect());
painter.drawTiledPixmap(m_ui.stack->rect(), m_background_pixmap);
painter.restore();
return true;
}
}
return QWidget::eventFilter(watched, event);
}
void GameListWidget::resizeTableViewColumnsToFit() void GameListWidget::resizeTableViewColumnsToFit()
{ {
QtUtils::ResizeColumnsForTableView(m_table_view, { QtUtils::ResizeColumnsForTableView(m_table_view, {

View File

@ -10,7 +10,9 @@
#include "pcsx2/GameList.h" #include "pcsx2/GameList.h"
#include <QtGui/QMovie> #include <QtGui/QMovie>
#include <QtGui/QPixmap>
#include <QtWidgets/QListView> #include <QtWidgets/QListView>
#include <QtWidgets/QStackedWidget>
#include <QtWidgets/QTableView> #include <QtWidgets/QTableView>
Q_DECLARE_METATYPE(const GameList::Entry*); Q_DECLARE_METATYPE(const GameList::Entry*);
@ -101,6 +103,7 @@ protected:
void hideEvent(QHideEvent* event) override; void hideEvent(QHideEvent* event) override;
void resizeEvent(QResizeEvent* event) override; void resizeEvent(QResizeEvent* event) override;
bool event(QEvent* event) override; bool event(QEvent* event) override;
bool eventFilter(QObject* watched, QEvent* event) override;
private: private:
void loadTableHeaderState(); void loadTableHeaderState();
@ -123,6 +126,7 @@ private:
GameListRefreshThread* m_refresh_thread = nullptr; GameListRefreshThread* m_refresh_thread = nullptr;
QMovie* m_background_movie = nullptr; QMovie* m_background_movie = nullptr;
QPixmap m_background_pixmap;
QtUtils::ScalingMode m_background_scaling = QtUtils::ScalingMode::Fit; QtUtils::ScalingMode m_background_scaling = QtUtils::ScalingMode::Fit;
float m_background_opacity = 100.0f; float m_background_opacity = 100.0f;
}; };

View File

@ -1193,6 +1193,12 @@ void MainWindow::cancelGameListRefresh()
m_game_list_widget->cancelRefresh(); m_game_list_widget->cancelRefresh();
} }
void MainWindow::updateGameListBackground()
{
if (m_game_list_widget)
m_game_list_widget->setCustomBackground();
}
void MainWindow::reportInfo(const QString& title, const QString& message) void MainWindow::reportInfo(const QString& title, const QString& message)
{ {
QMessageBox::information(this, title, message); QMessageBox::information(this, title, message);

View File

@ -116,6 +116,7 @@ public Q_SLOTS:
void checkForUpdates(bool display_message, bool force_check); void checkForUpdates(bool display_message, bool force_check);
void refreshGameList(bool invalidate_cache, bool popup_on_error); void refreshGameList(bool invalidate_cache, bool popup_on_error);
void cancelGameListRefresh(); void cancelGameListRefresh();
void updateGameListBackground();
void reportInfo(const QString& title, const QString& message); void reportInfo(const QString& title, const QString& message);
void reportError(const QString& title, const QString& message); void reportError(const QString& title, const QString& message);
bool confirmMessage(const QString& title, const QString& message); bool confirmMessage(const QString& title, const QString& message);

View File

@ -194,6 +194,9 @@ void EmuThread::stopFullscreenUI()
{ {
m_run_fullscreen_ui.store(false, std::memory_order_release); m_run_fullscreen_ui.store(false, std::memory_order_release);
emit onFullscreenUIStateChange(false); emit onFullscreenUIStateChange(false);
// Resume and refresh background when FullscreenUI exits
QMetaObject::invokeMethod(g_main_window, "updateGameListBackground", Qt::QueuedConnection);
} }
} }

View File

@ -141,9 +141,16 @@ namespace QtUtils
void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity) void resizeAndScalePixmap(QPixmap* pm, const int expected_width, const int expected_height, const qreal dpr, const ScalingMode scaling_mode, const float opacity)
{ {
if (!pm || pm->isNull() || pm->width() <= 0 || pm->height() <= 0) if (!pm || pm->width() <= 0 || pm->height() <= 0)
return; return;
if (dpr <= 0.0)
{
Console.ErrorFmt("resizeAndScalePixmap: Invalid device pixel ratio ({}) - pixmap will be null", dpr);
*pm = QPixmap();
return;
}
const int dpr_expected_width = qRound(expected_width * dpr); const int dpr_expected_width = qRound(expected_width * dpr);
const int dpr_expected_height = qRound(expected_height * dpr); const int dpr_expected_height = qRound(expected_height * dpr);
@ -196,8 +203,7 @@ namespace QtUtils
qRound(scaledSize.width()), qRound(scaledSize.width()),
qRound(scaledSize.height()), qRound(scaledSize.height()),
Qt::IgnoreAspectRatio, Qt::IgnoreAspectRatio,
Qt::SmoothTransformation Qt::SmoothTransformation);
);
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height()); const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
@ -211,16 +217,7 @@ namespace QtUtils
} }
case ScalingMode::Stretch: case ScalingMode::Stretch:
{ {
*pm = pm->scaled( painter.drawPixmap(painterRect, *pm, srcRect);
dpr_expected_width,
dpr_expected_height,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation
);
const QRectF scaledSrcRect(0, 0, pm->width(), pm->height());
painter.drawPixmap(painterRect, *pm, scaledSrcRect);
break; break;
} }
case ScalingMode::Center: case ScalingMode::Center:
@ -229,7 +226,6 @@ namespace QtUtils
const qreal pmHeight = pm->height() / dpr; const qreal pmHeight = pm->height() / dpr;
QRectF destRect(0, 0, pmWidth, pmHeight); QRectF destRect(0, 0, pmWidth, pmHeight);
destRect.moveCenter(painterRect.center()); destRect.moveCenter(painterRect.center());
painter.drawPixmap(destRect, *pm, srcRect); painter.drawPixmap(destRect, *pm, srcRect);
@ -243,13 +239,19 @@ namespace QtUtils
if (tileWidth <= 0 || tileHeight <= 0) if (tileWidth <= 0 || tileHeight <= 0)
break; break;
QPixmap tileSource = pm->scaled(tileWidth, tileHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); if (pm->devicePixelRatio() == dpr)
tileSource.setDevicePixelRatio(dpr); {
QBrush tileBrush(*pm);
QBrush tileBrush(tileSource); painter.fillRect(painterRect, tileBrush);
tileBrush.setTextureImage(tileSource.toImage()); }
else
painter.fillRect(painterRect, tileBrush); {
QPixmap tileSource = pm->scaled(tileWidth, tileHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
tileSource.setDevicePixelRatio(dpr);
QBrush tileBrush(tileSource);
tileBrush.setTextureImage(tileSource.toImage());
painter.fillRect(painterRect, tileBrush);
}
break; break;
} }
default: default:

View File

@ -39,6 +39,8 @@ BIOSSettingsWidget::BIOSSettingsWidget(SettingsWindow* settings_dialog, QWidget*
connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList); connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList);
connect(m_ui.fileList, &QTreeWidget::currentItemChanged, this, &BIOSSettingsWidget::listItemChanged); connect(m_ui.fileList, &QTreeWidget::currentItemChanged, this, &BIOSSettingsWidget::listItemChanged);
connect(m_ui.fastBoot, &QCheckBox::checkStateChanged, this, &BIOSSettingsWidget::fastBootChanged); connect(m_ui.fastBoot, &QCheckBox::checkStateChanged, this, &BIOSSettingsWidget::fastBootChanged);
fastBootChanged();
} }
BIOSSettingsWidget::~BIOSSettingsWidget() = default; BIOSSettingsWidget::~BIOSSettingsWidget() = default;

View File

@ -275,7 +275,7 @@ void InterfaceSettingsWidget::onSetGameListBackgroundTriggered()
if (path.isEmpty()) if (path.isEmpty())
return; return;
std::string relative_path = Path::MakeRelative(QDir::toNativeSeparators(path).toStdString(), EmuFolders::DataRoot); std::string relative_path = Path::MakeRelative(path.toStdString(), EmuFolders::DataRoot);
Host::SetBaseStringSettingValue("UI", "GameListBackgroundPath", relative_path.c_str()); Host::SetBaseStringSettingValue("UI", "GameListBackgroundPath", relative_path.c_str());
Host::CommitBaseSettingChanges(); Host::CommitBaseSettingChanges();

View File

@ -1221,7 +1221,7 @@ void FullscreenUI::Render()
// see if background setting changed // see if background setting changed
static std::string s_last_background_path; static std::string s_last_background_path;
std::string current_path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath"); std::string current_path = Host::GetBaseStringSettingValue("UI", "FSUIBackgroundPath");
if (s_last_background_path != current_path) if (s_last_background_path != current_path)
{ {
s_last_background_path = current_path; s_last_background_path = current_path;
@ -1240,7 +1240,8 @@ void FullscreenUI::Render()
s_current_main_window == MainWindowType::Exit || s_current_main_window == MainWindowType::Exit ||
s_current_main_window == MainWindowType::GameList || s_current_main_window == MainWindowType::GameList ||
s_current_main_window == MainWindowType::GameListSettings || s_current_main_window == MainWindowType::GameListSettings ||
s_current_main_window == MainWindowType::Settings) && s_custom_background_enabled && s_custom_background_texture; s_current_main_window == MainWindowType::Settings) &&
!VMManager::HasValidVM() && s_custom_background_enabled && s_custom_background_texture;
ImVec4 original_background_color; ImVec4 original_background_color;
if (should_draw_background) if (should_draw_background)
@ -1691,7 +1692,7 @@ bool FullscreenUI::ShouldDefaultToGameList()
void FullscreenUI::LoadCustomBackground() void FullscreenUI::LoadCustomBackground()
{ {
std::string path = Host::GetBaseStringSettingValue("UI", "GameListBackgroundPath"); std::string path = Host::GetBaseStringSettingValue("UI", "FSUIBackgroundPath");
if (path.empty()) if (path.empty())
{ {
@ -1759,19 +1760,25 @@ void FullscreenUI::DrawCustomBackground()
const ImGuiIO& io = ImGui::GetIO(); const ImGuiIO& io = ImGui::GetIO();
const ImVec2 display_size = io.DisplaySize; const ImVec2 display_size = io.DisplaySize;
const float opacity = Host::GetBaseFloatSettingValue("UI", "GameListBackgroundOpacity", 100.0f) / 100.0f; const u8 alpha = static_cast<u8>(Host::GetBaseFloatSettingValue("UI", "FSUIBackgroundOpacity", 100.0f) * 2.55f);
const std::string mode = Host::GetBaseStringSettingValue("UI", "GameListBackgroundMode", "fit"); const std::string mode = Host::GetBaseStringSettingValue("UI", "FSUIBackgroundMode", "fit");
const float tex_width = static_cast<float>(s_custom_background_texture->GetWidth()); const float tex_width = static_cast<float>(s_custom_background_texture->GetWidth());
const float tex_height = static_cast<float>(s_custom_background_texture->GetHeight()); const float tex_height = static_cast<float>(s_custom_background_texture->GetHeight());
ImVec2 img_min, img_max; // Override the UIBackgroundColor that windows use
// We need to make windows transparent so our background image shows through
const ImVec4 transparent_bg = ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, 0.0f);
ImGuiFullscreen::UIBackgroundColor = transparent_bg;
ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList();
const ImU32 col = IM_COL32(255, 255, 255, alpha);
const ImTextureID tex_id = reinterpret_cast<ImTextureID>(s_custom_background_texture->GetNativeHandle());
if (mode == "stretch") if (mode == "stretch")
{ {
// stretch to fill entire display (ignores aspect ratio) // stretch to fill entire display (ignores aspect ratio)
img_min = ImVec2(0.0f, 0.0f); bg_draw_list->AddImage(tex_id, ImVec2(0.0f, 0.0f), display_size, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
img_max = display_size;
} }
else if (mode == "fill") else if (mode == "fill")
{ {
@ -1796,8 +1803,64 @@ void FullscreenUI::DrawCustomBackground()
const float offset_x = (display_size.x - scaled_width) * 0.5f; const float offset_x = (display_size.x - scaled_width) * 0.5f;
const float offset_y = (display_size.y - scaled_height) * 0.5f; const float offset_y = (display_size.y - scaled_height) * 0.5f;
img_min = ImVec2(offset_x, offset_y); bg_draw_list->AddImage(tex_id,
img_max = ImVec2(offset_x + scaled_width, offset_y + scaled_height); ImVec2(offset_x, offset_y),
ImVec2(offset_x + scaled_width, offset_y + scaled_height),
ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
else if (mode == "center")
{
// Center image at original size
const float offset_x = (display_size.x - tex_width) * 0.5f;
const float offset_y = (display_size.y - tex_height) * 0.5f;
bg_draw_list->AddImage(tex_id,
ImVec2(offset_x, offset_y),
ImVec2(offset_x + tex_width, offset_y + tex_height),
ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
}
else if (mode == "tile")
{
// Tile image across entire display
// If the image is extremely small, this approach can generate millions of quads
// and overflow the backend stream buffer (e.g. Vulkan assertion in VKStreamBuffer).
// Since we cannot switch ImGui's sampler to wrap (yet), clamp the maximum number of quads
constexpr int MAX_TILE_QUADS = 16384;
float tile_width = tex_width;
float tile_height = tex_height;
int tiles_x = static_cast<int>(std::ceil(display_size.x / tile_width));
int tiles_y = static_cast<int>(std::ceil(display_size.y / tile_height));
const int total_tiles = tiles_x * tiles_y;
if (total_tiles > MAX_TILE_QUADS)
{
const float scale = std::sqrt(static_cast<float>(total_tiles) / static_cast<float>(MAX_TILE_QUADS));
tile_width *= scale;
tile_height *= scale;
tiles_x = static_cast<int>(std::ceil(display_size.x / tile_width));
tiles_y = static_cast<int>(std::ceil(display_size.y / tile_height));
}
for (int y = 0; y < tiles_y; y++)
{
for (int x = 0; x < tiles_x; x++)
{
const float tile_x = static_cast<float>(x) * tile_width;
const float tile_y = static_cast<float>(y) * tile_height;
const float tile_max_x = std::min(tile_x + tile_width, display_size.x);
const float tile_max_y = std::min(tile_y + tile_height, display_size.y);
// get uvs for partial tiles at edges
const float uv_max_x = (tile_max_x - tile_x) / tile_width;
const float uv_max_y = (tile_max_y - tile_y) / tile_height;
bg_draw_list->AddImage(tex_id,
ImVec2(tile_x, tile_y),
ImVec2(tile_max_x, tile_max_y),
ImVec2(0.0f, 0.0f), ImVec2(uv_max_x, uv_max_y), col);
}
}
} }
else // "fit" or default else // "fit" or default
{ {
@ -1822,19 +1885,11 @@ void FullscreenUI::DrawCustomBackground()
const float offset_x = (display_size.x - scaled_width) * 0.5f; const float offset_x = (display_size.x - scaled_width) * 0.5f;
const float offset_y = (display_size.y - scaled_height) * 0.5f; const float offset_y = (display_size.y - scaled_height) * 0.5f;
img_min = ImVec2(offset_x, offset_y); bg_draw_list->AddImage(tex_id,
img_max = ImVec2(offset_x + scaled_width, offset_y + scaled_height); ImVec2(offset_x, offset_y),
ImVec2(offset_x + scaled_width, offset_y + scaled_height),
ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
} }
// Override the UIBackgroundColor that windows use
// We need to make windows transparent so our background image shows through
const ImVec4 transparent_bg = ImVec4(UIBackgroundColor.x, UIBackgroundColor.y, UIBackgroundColor.z, 0.0f);
ImGuiFullscreen::UIBackgroundColor = transparent_bg;
ImDrawList* bg_draw_list = ImGui::GetBackgroundDrawList();
const ImU32 col = IM_COL32(255, 255, 255, static_cast<u8>(opacity * 255.0f));
bg_draw_list->AddImage(reinterpret_cast<ImTextureID>(s_custom_background_texture->GetNativeHandle()),
img_min, img_max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), col);
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -3630,7 +3685,7 @@ void FullscreenUI::DrawSettingsWindow()
ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) + ImVec2(io.DisplaySize.x, LayoutScale(LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY) +
(LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f)); (LayoutScale(LAYOUT_MENU_BUTTON_Y_PADDING) * 2.0f) + LayoutScale(2.0f));
const bool using_custom_bg = s_custom_background_enabled && s_custom_background_texture; const bool using_custom_bg = !VMManager::HasValidVM() && s_custom_background_enabled && s_custom_background_texture;
const float header_bg_alpha = VMManager::HasValidVM() ? 0.90f : 1.0f; const float header_bg_alpha = VMManager::HasValidVM() ? 0.90f : 1.0f;
const float content_bg_alpha = using_custom_bg ? 0.0f : (VMManager::HasValidVM() ? 0.90f : 1.0f); const float content_bg_alpha = using_custom_bg ? 0.0f : (VMManager::HasValidVM() ? 0.90f : 1.0f);
SettingsInterface* bsi = GetEditingSettingsInterface(); SettingsInterface* bsi = GetEditingSettingsInterface();
@ -4053,21 +4108,19 @@ void FullscreenUI::DrawInterfaceSettingsPage()
MenuHeading(FSUI_CSTR("Background")); MenuHeading(FSUI_CSTR("Background"));
std::string background_path = bsi->GetStringValue("UI", "GameListBackgroundPath", ""); std::string background_path = bsi->GetStringValue("UI", "FSUIBackgroundPath", "");
const bool background_enabled = bsi->GetBoolValue("UI", "GameListBackgroundEnabled", false);
std::string background_display = FSUI_STR("None"); std::string background_display = FSUI_STR("None");
if (!background_path.empty() && background_enabled) if (!background_path.empty())
{ {
background_display = Path::GetFileName(background_path); background_display = Path::GetFileName(background_path);
} }
if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_IMAGE, "Background Image"), if (MenuButtonWithValue(FSUI_ICONSTR(ICON_FA_IMAGE, "Background Image"),
FSUI_CSTR("Select a custom background image to use in Big Picture Mode menus."), FSUI_CSTR("Select a custom background image to use in Big Picture Mode menus.\n\nSupported formats: PNG, JPG, JPEG, BMP."),
background_display.c_str())) background_display.c_str()))
{ {
OpenFileSelector(FSUI_ICONSTR(ICON_FA_IMAGE, "Select Background Image"), false, OpenFileSelector(FSUI_ICONSTR(ICON_FA_IMAGE, "Select Background Image"), false, [](const std::string& path) {
[](const std::string& path) {
if (!path.empty()) if (!path.empty())
{ {
{ {
@ -4075,23 +4128,20 @@ void FullscreenUI::DrawInterfaceSettingsPage()
SettingsInterface* bsi = GetEditingSettingsInterface(false); SettingsInterface* bsi = GetEditingSettingsInterface(false);
std::string relative_path = Path::MakeRelative(path, EmuFolders::DataRoot); std::string relative_path = Path::MakeRelative(path, EmuFolders::DataRoot);
bsi->SetStringValue("UI", "GameListBackgroundPath", relative_path.c_str()); bsi->SetStringValue("UI", "FSUIBackgroundPath", relative_path.c_str());
bsi->SetBoolValue("UI", "GameListBackgroundEnabled", true); bsi->SetBoolValue("UI", "FSUIBackgroundEnabled", true);
SetSettingsChanged(bsi); SetSettingsChanged(bsi);
} }
LoadCustomBackground(); LoadCustomBackground();
} }
CloseFileSelector(); CloseFileSelector(); }, GetImageFileFilters());
},
GetImageFileFilters());
} }
if (MenuButton(FSUI_ICONSTR(ICON_FA_XMARK, "Clear Background Image"), if (MenuButton(FSUI_ICONSTR(ICON_FA_XMARK, "Clear Background Image"),
FSUI_CSTR("Removes the custom background image."))) FSUI_CSTR("Removes the custom background image.")))
{ {
bsi->DeleteValue("UI", "GameListBackgroundPath"); bsi->DeleteValue("UI", "FSUIBackgroundPath");
bsi->SetBoolValue("UI", "GameListBackgroundEnabled", false);
SetSettingsChanged(bsi); SetSettingsChanged(bsi);
s_custom_background_texture.reset(); s_custom_background_texture.reset();
@ -4101,21 +4151,25 @@ void FullscreenUI::DrawInterfaceSettingsPage()
DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_DROPLET, "Background Opacity"), DrawIntRangeSetting(bsi, FSUI_ICONSTR(ICON_FA_DROPLET, "Background Opacity"),
FSUI_CSTR("Sets the transparency of the custom background image."), FSUI_CSTR("Sets the transparency of the custom background image."),
"UI", "GameListBackgroundOpacity", 100, 0, 100, "%d%%"); "UI", "FSUIBackgroundOpacity", 100, 0, 100, "%d%%");
static constexpr const char* s_background_mode_names[] = { static constexpr const char* s_background_mode_names[] = {
FSUI_NSTR("Fit"), FSUI_NSTR("Fit"),
FSUI_NSTR("Fill"), FSUI_NSTR("Fill"),
FSUI_NSTR("Stretch"), FSUI_NSTR("Stretch"),
FSUI_NSTR("Center"),
FSUI_NSTR("Tile"),
}; };
static constexpr const char* s_background_mode_values[] = { static constexpr const char* s_background_mode_values[] = {
"fit", "fit",
"fill", "fill",
"stretch", "stretch",
"center",
"tile",
}; };
DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_EXPAND, "Background Mode"), DrawStringListSetting(bsi, FSUI_ICONSTR(ICON_FA_EXPAND, "Background Mode"),
FSUI_CSTR("Select how to display the background image."), FSUI_CSTR("Select how to display the background image."),
"UI", "GameListBackgroundMode", "fit", s_background_mode_names, s_background_mode_values, std::size(s_background_mode_names), true); "UI", "FSUIBackgroundMode", "fit", s_background_mode_names, s_background_mode_values, std::size(s_background_mode_names), true);
MenuHeading(FSUI_CSTR("Behaviour")); MenuHeading(FSUI_CSTR("Behaviour"));
DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_SNOOZE, "Inhibit Screensaver"), DrawToggleSetting(bsi, FSUI_ICONSTR(ICON_PF_SNOOZE, "Inhibit Screensaver"),

View File

@ -2396,6 +2396,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor); ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
const float width = LayoutScale(600.0f); const float width = LayoutScale(600.0f);
const float title_height = g_large_font.second + ImGui::GetStyle().FramePadding.y * 2.0f + ImGui::GetStyle().WindowPadding.y * 2.0f; const float title_height = g_large_font.second + ImGui::GetStyle().FramePadding.y * 2.0f + ImGui::GetStyle().WindowPadding.y * 2.0f;
@ -2464,7 +2465,7 @@ void ImGuiFullscreen::DrawChoiceDialog()
is_open = false; is_open = false;
} }
ImGui::PopStyleColor(3); ImGui::PopStyleColor(4);
ImGui::PopStyleVar(3); ImGui::PopStyleVar(3);
ImGui::PopFont(); ImGui::PopFont();
@ -2523,7 +2524,7 @@ void ImGuiFullscreen::DrawInputDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor); ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIBackgroundColor); ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
bool is_open = true; bool is_open = true;
if (ImGui::BeginPopupModal(s_input_dialog_title.c_str(), &is_open, if (ImGui::BeginPopupModal(s_input_dialog_title.c_str(), &is_open,
@ -2715,6 +2716,7 @@ void ImGuiFullscreen::DrawMessageDialog()
ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor); ImGui::PushStyleColor(ImGuiCol_Text, UIPrimaryTextColor);
ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor); ImGui::PushStyleColor(ImGuiCol_TitleBg, UIPrimaryDarkColor);
ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor); ImGui::PushStyleColor(ImGuiCol_TitleBgActive, UIPrimaryColor);
ImGui::PushStyleColor(ImGuiCol_PopupBg, UIPopupBackgroundColor);
bool is_open = true; bool is_open = true;
const u32 flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | const u32 flags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
@ -2745,7 +2747,7 @@ void ImGuiFullscreen::DrawMessageDialog()
ImGui::EndPopup(); ImGui::EndPopup();
} }
ImGui::PopStyleColor(3); ImGui::PopStyleColor(4);
ImGui::PopStyleVar(4); ImGui::PopStyleVar(4);
ImGui::PopFont(); ImGui::PopFont();