Refactor the highlight handling and background drawing

This commit is contained in:
Henrik Rydgård
2026-03-15 11:19:11 +01:00
parent 4c4081f39e
commit c4b81a37a4
3 changed files with 104 additions and 78 deletions
+50 -45
View File
@@ -455,6 +455,54 @@ enum class BackgroundFillMode {
FitToScreen = 2,
};
void DrawBackgroundTexture(UIContext &dc, Draw::Texture *texture, Lin::Vec3 focus, float alpha) {
dc.GetDrawContext()->BindTexture(0, texture);
uint32_t color = whiteAlpha(std::clamp(alpha, 0.0f, 1.0f)) & 0xFFc0c0c0;
// TODO: Make this configurable?
const BackgroundFillMode mode = BackgroundFillMode::AdaptiveCropToScreen;
const Bounds screenBounds = dc.GetBounds();
float imageW = screenBounds.w;
float imageH = screenBounds.h;
float imageAspect = (float)texture->Width() / (float)texture->Height();
float squash = imageAspect / screenBounds.AspectRatio();
// Allow a lot of leeway for the image aspect - let it stretch a bit, and only then start cropping.
const float aspectLeeway = 0.5f;
if (mode == BackgroundFillMode::AdaptiveCropToScreen && squash >= 0.6f && squash < 1.7f) {
imageAspect = screenBounds.AspectRatio();
}
// Fit the image into the screen bounds according to the fill mode.
if (imageAspect > screenBounds.AspectRatio()) {
// Image is wider than screen.
if (mode == BackgroundFillMode::AdaptiveCropToScreen) {
// Crop width.
imageW = screenBounds.h * imageAspect;
} else if (mode == BackgroundFillMode::FitToScreen) {
// Fit height.
imageH = screenBounds.w / imageAspect;
}
} else {
// Image is taller than screen.
if (mode == BackgroundFillMode::AdaptiveCropToScreen) {
// Crop height.
imageH = screenBounds.w / imageAspect;
} else if (mode == BackgroundFillMode::FitToScreen) {
// Fit width.
imageW = screenBounds.h / imageAspect;
}
}
Bounds finalImageBounds = Bounds::FromCenterWH(screenBounds.centerX(), screenBounds.centerY(), imageW, imageH);
dc.Draw()->DrawTexRect(finalImageBounds, 0, 0, 1, 1, color);
dc.Flush();
dc.RebindTexture();
}
void DrawGameBackground(UIContext &dc, const Path &gamePath, Lin::Vec3 focus, float alpha) {
using namespace Draw;
using namespace UI;
@@ -467,51 +515,8 @@ void DrawGameBackground(UIContext &dc, const Path &gamePath, Lin::Vec3 focus, fl
GameInfoTex *pic = (ginfo && ginfo->Ready(GameInfoFlags::PIC1)) ? ginfo->GetPIC1() : nullptr;
if (pic && pic->texture) {
dc.GetDrawContext()->BindTexture(0, pic->texture);
uint32_t color = whiteAlpha(std::clamp(ease((time_now_d() - pic->timeLoaded) * 3.0f) * alpha, 0.0f, 1.0f)) & 0xFFc0c0c0;
// TODO: Make this configurable?
const BackgroundFillMode mode = BackgroundFillMode::AdaptiveCropToScreen;
const Bounds screenBounds = dc.GetBounds();
float imageW = screenBounds.w;
float imageH = screenBounds.h;
float imageAspect = (float)pic->texture->Width() / (float)pic->texture->Height();
float squash = imageAspect / screenBounds.AspectRatio();
// Allow a lot of leeway for the image aspect - let it stretch a bit, and only then start cropping.
const float aspectLeeway = 0.5f;
if (mode == BackgroundFillMode::AdaptiveCropToScreen && squash >= 0.6f && squash < 1.7f) {
imageAspect = screenBounds.AspectRatio();
}
// Fit the image into the screen bounds according to the fill mode.
if (imageAspect > screenBounds.AspectRatio()) {
// Image is wider than screen.
if (mode == BackgroundFillMode::AdaptiveCropToScreen) {
// Crop width.
imageW = screenBounds.h * imageAspect;
} else if (mode == BackgroundFillMode::FitToScreen) {
// Fit height.
imageH = screenBounds.w / imageAspect;
}
} else {
// Image is taller than screen.
if (mode == BackgroundFillMode::AdaptiveCropToScreen) {
// Crop height.
imageH = screenBounds.w / imageAspect;
} else if (mode == BackgroundFillMode::FitToScreen) {
// Fit width.
imageW = screenBounds.h / imageAspect;
}
}
Bounds finalImageBounds = Bounds::FromCenterWH(screenBounds.centerX(), screenBounds.centerY(), imageW, imageH);
dc.Draw()->DrawTexRect(finalImageBounds, 0, 0, 1, 1, color);
dc.Flush();
dc.RebindTexture();
float alphaMul = ease((time_now_d() - pic->timeLoaded) * 3.0f);
DrawBackgroundTexture(dc, pic->texture, focus, alpha * alphaMul);
} else {
::DrawBackground(dc, 1.0f, focus);
dc.RebindTexture();
+43 -29
View File
@@ -1619,27 +1619,28 @@ void MainScreen::OnLoadFile(UI::EventParams &e) {
}
void MainScreen::DrawBackground(UIContext &dc) {
if (highlightedGamePath_.empty() && prevHighlightedGamePath_.empty()) {
return;
}
constexpr float fadeTime = 0.5f; //
if (DrawBackgroundFor(dc, prevHighlightedGamePath_, 1.0f - prevHighlightProgress_)) {
if (prevHighlightProgress_ < 1.0f) {
prevHighlightProgress_ += 1.0f / 20.0f;
}
}
if (!highlightedGamePath_.empty()) {
if (DrawBackgroundFor(dc, highlightedGamePath_, highlightProgress_)) {
if (highlightProgress_ < 1.0f) {
highlightProgress_ += 1.0f / 20.0f;
double now = time_now_d();
for (auto iter = highlightedBackgrounds_.begin(); iter != highlightedBackgrounds_.end(); ) {
float timeSinceStart = float(now - iter->startTime);
float alpha = std::clamp(timeSinceStart / fadeTime, 0.0f, 1.0f);
if (iter->endTime > 0.0) {
float fadeOutAlpha = std::max(0.0f, float(now - iter->endTime) / fadeTime);
if (fadeOutAlpha > 1.0f) {
iter = highlightedBackgrounds_.erase(iter);
continue;
}
alpha *= 1.0f - fadeOutAlpha;
}
DrawBackgroundFor(dc, iter->gamePath, alpha);
iter++;
}
}
bool MainScreen::DrawBackgroundFor(UIContext &dc, const Path &gamePath, float progress) {
::DrawGameBackground(dc, gamePath, Lin::Vec3(0.f, 0.f, 0.f), progress);
return true;
void MainScreen::DrawBackgroundFor(UIContext &dc, const Path &gamePath, float alpha) {
::DrawGameBackground(dc, gamePath, Lin::Vec3(0.f, 0.f, 0.f), alpha);
}
void MainScreen::OnGameSelected(UI::EventParams &e) {
@@ -1658,26 +1659,40 @@ void MainScreen::OnGameSelected(UI::EventParams &e) {
screenManager()->push(new GameScreen(path, false));
}
void MainScreen::InstantHighlight(const Path &path) {
// Clear the previous highlight immediately, so we don't have multiple at once.
highlightedBackgrounds_.clear();
highlightedBackgrounds_.push_back({path, 0.0f, -1.0});
}
void MainScreen::OnGameHighlight(UI::EventParams &e) {
using namespace UI;
Path path(e.s);
// Don't change when re-highlighting what's already highlighted.
if (path != highlightedGamePath_ || e.a == FF_LOSTFOCUS) {
if (!highlightedGamePath_.empty()) {
if (prevHighlightedGamePath_.empty() || prevHighlightProgress_ >= 0.75f) {
prevHighlightedGamePath_ = highlightedGamePath_;
prevHighlightProgress_ = 1.0 - highlightProgress_;
}
highlightedGamePath_.clear();
}
if (e.a == FF_GOTFOCUS) {
highlightedGamePath_ = path;
highlightProgress_ = 0.0f;
if (path == highlightedGamePath_) {
// Already highlighted, nothing to do.
return;
}
// Trigger fadeouts on any active highlights.
for (auto &iter : highlightedBackgrounds_) {
if (iter.endTime < 0.0) {
iter.endTime = time_now_d();
}
}
highlightedGamePath_ = path;
_dbg_assert_(!path.empty());
if (path.empty()) {
// Nothing highlighed? Exit.
return;
}
// Add a new entry to the highlight list.
highlightedBackgrounds_.push_back({path, time_now_d(), -1.0});
if ((!highlightedGamePath_.empty() || e.a == FF_LOSTFOCUS) && !lockBackgroundAudio_) {
g_BackgroundAudio.SetGame(highlightedGamePath_);
}
@@ -1733,8 +1748,7 @@ void MainScreen::dialogFinished(const Screen *dialog, DialogResult result) {
} else if (tag == "Game") {
if (!restoreFocusGamePath_.empty() && UI::IsFocusMovementEnabled()) {
// Prevent the background from fading, since we just were displaying it.
highlightedGamePath_ = restoreFocusGamePath_;
highlightProgress_ = 1.0f;
InstantHighlight(restoreFocusGamePath_);
// Refocus the game button itself.
int tab = tabHolder_->GetCurrentTab();
+11 -4
View File
@@ -122,6 +122,12 @@ private:
class RemoteISOBrowseScreen;
struct HighlightedBackground {
Path gamePath;
double startTime;
double endTime;
};
class MainScreen : public UIBaseScreen {
public:
MainScreen();
@@ -149,7 +155,7 @@ protected:
void sendMessage(UIMessage message, const char *value) override;
void dialogFinished(const Screen *dialog, DialogResult result) override;
bool DrawBackgroundFor(UIContext &dc, const Path &gamePath, float progress);
void DrawBackgroundFor(UIContext &dc, const Path &gamePath, float alpha);
void OnGameSelected(UI::EventParams &e);
void OnGameSelectedInstant(UI::EventParams &e);
@@ -168,10 +174,9 @@ protected:
Path restoreFocusGamePath_;
std::vector<GameBrowser *> gameBrowsers_;
std::vector<HighlightedBackground> highlightedBackgrounds_;
Path highlightedGamePath_;
Path prevHighlightedGamePath_;
float highlightProgress_ = 0.0f;
float prevHighlightProgress_ = 0.0f;
bool backFromStore_ = false;
bool lockBackgroundAudio_ = false;
bool lastVertical_ = false;
@@ -181,6 +186,8 @@ protected:
std::string searchFilter_;
friend class RemoteISOBrowseScreen;
private:
void InstantHighlight(const Path &path);
};
class UmdReplaceScreen : public UIBaseDialogScreen {