mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
GetDimensions only answered once a texture had been created, so the first MeasureNotice for a new icon (and after the texture was released for being unused) measured without it, and the icon was then drawn over the text. Read the size from the PNG header when the icon is inserted or loaded. Also remove a width adjustment in MeasureNotice that was overwritten before use. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
168 lines
5.9 KiB
C++
168 lines
5.9 KiB
C++
#include "Common/UI/UI.h"
|
|
#include "Common/UI/Notice.h"
|
|
#include "Common/UI/IconCache.h"
|
|
#include "Common/UI/Context.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/System/OSD.h"
|
|
|
|
uint32_t GetNoticeBackgroundColor(NoticeLevel type) {
|
|
// Colors from Infima
|
|
switch (type) {
|
|
case NoticeLevel::ERROR: return 0x3530d5; // danger-darker
|
|
case NoticeLevel::WARN: return 0x009ed9; // warning-darker
|
|
case NoticeLevel::INFO: return 0x706760; // gray-700
|
|
case NoticeLevel::SUCCESS: return 0x008b00; // nice green
|
|
default: return 0x606770;
|
|
}
|
|
}
|
|
|
|
ImageID GetOSDIcon(NoticeLevel level) {
|
|
switch (level) {
|
|
case NoticeLevel::INFO: return ImageID("I_INFO");
|
|
case NoticeLevel::ERROR: return ImageID("I_CROSS");
|
|
case NoticeLevel::WARN: return ImageID("I_WARNING");
|
|
case NoticeLevel::SUCCESS: return ImageID("I_CHECKMARK");
|
|
default: return ImageID::invalid();
|
|
}
|
|
}
|
|
|
|
NoticeLevel GetNoticeLevel(OSDType type) {
|
|
switch (type) {
|
|
case OSDType::MESSAGE_INFO:
|
|
return NoticeLevel::INFO;
|
|
case OSDType::MESSAGE_ERROR:
|
|
case OSDType::MESSAGE_ERROR_DUMP:
|
|
case OSDType::MESSAGE_CENTERED_ERROR:
|
|
return NoticeLevel::ERROR;
|
|
case OSDType::MESSAGE_WARNING:
|
|
case OSDType::MESSAGE_CENTERED_WARNING:
|
|
return NoticeLevel::WARN;
|
|
case OSDType::MESSAGE_SUCCESS:
|
|
return NoticeLevel::SUCCESS;
|
|
default:
|
|
return NoticeLevel::SUCCESS;
|
|
}
|
|
}
|
|
|
|
constexpr float m = 4.0f;
|
|
|
|
// Align only matters here for the ASCII-only flag.
|
|
void MeasureNotice(const UIContext &dc, NoticeLevel level, std::string_view text, std::string_view details, std::string_view iconName, int align, float maxWidth, float *width, float *height, float *height1) {
|
|
float iconW = 0.0f;
|
|
float iconH = 0.0f;
|
|
if (!iconName.empty() && !startsWith(iconName, "I_")) { // Check for atlas image. Bit hacky, but we choose prefixes for icon IDs anyway in a way that this is safe.
|
|
// Normal entry but with a cached icon.
|
|
int iconWidth, iconHeight;
|
|
if (g_iconCache.GetDimensions(iconName, &iconWidth, &iconHeight)) {
|
|
iconW = iconWidth;
|
|
iconH = iconHeight;
|
|
}
|
|
} else {
|
|
ImageID iconID = iconName.empty() ? GetOSDIcon(level) : ImageID(iconName);
|
|
if (iconID.isValid()) {
|
|
dc.Draw()->GetAtlas()->measureImage(iconID, &iconW, &iconH);
|
|
}
|
|
}
|
|
|
|
float chromeWidth = iconW + m * 2.0f + 12.0f;
|
|
float availableWidth = maxWidth - chromeWidth;
|
|
|
|
// OK, now that we have figured out how much space we have for the text, we can measure it (with wrapping if needed).
|
|
// We currently don't wrap the title.
|
|
|
|
float titleWidth, titleHeight;
|
|
dc.MeasureTextRect(dc.GetTheme().uiFont, 1.0f, 1.0f, text, availableWidth, &titleWidth, &titleHeight, align);
|
|
|
|
*width = std::min(titleWidth, availableWidth);
|
|
*height1 = titleHeight;
|
|
|
|
float width2 = 0.0f, height2 = 0.0f;
|
|
if (!details.empty()) {
|
|
dc.MeasureTextRect(dc.GetTheme().uiFontSmall, 1.0f, 1.0f, details, availableWidth, &width2, &height2, align | FLAG_WRAP_TEXT);
|
|
*width = std::max(*width, width2);
|
|
}
|
|
|
|
*width += chromeWidth;
|
|
if (height2 == 0.0f && iconH < 2.0f * *height1) {
|
|
// Center vertically using the icon.
|
|
*height1 = std::max(*height1, iconH + m * 2.0f);
|
|
}
|
|
|
|
float heightSum = *height1 + (height2 > 0.0f ? height2 + 8.0f : 0.0f);
|
|
*height = std::max(heightSum, iconH + m);
|
|
}
|
|
|
|
void RenderNotice(UIContext &dc, Bounds bounds, float height1, NoticeLevel level, std::string_view text, std::string_view details, std::string_view iconName, int align, float alpha, OSDMessageFlags flags, float timeVal) {
|
|
UI::Drawable background = UI::Drawable(colorAlpha(GetNoticeBackgroundColor(level), alpha));
|
|
|
|
dc.SetFontStyle(dc.GetTheme().uiFont);
|
|
|
|
uint32_t foreGround = whiteAlpha(alpha);
|
|
|
|
if (!(flags & OSDMessageFlags::Transparent)) {
|
|
dc.DrawRectDropShadow(bounds, 12.0f, 0.7f * alpha);
|
|
dc.FillRect(background, bounds);
|
|
}
|
|
|
|
float iconW = 0.0f;
|
|
float iconH = 0.0f;
|
|
if (!iconName.empty() && !startsWith(iconName, "I_")) {
|
|
dc.Flush();
|
|
// Normal entry but with a cached icon.
|
|
Draw::Texture *texture = g_iconCache.BindIconTexture(&dc, iconName);
|
|
if (texture) {
|
|
iconW = texture->Width();
|
|
iconH = texture->Height();
|
|
dc.Draw()->DrawTexRect(Bounds(bounds.x + m, bounds.y + m, iconW, iconH), 0.0f, 0.0f, 1.0f, 1.0f, foreGround);
|
|
dc.Flush();
|
|
dc.RebindTexture();
|
|
}
|
|
dc.Begin();
|
|
} else {
|
|
ImageID iconID = iconName.empty() ? GetOSDIcon(level) : ImageID(iconName);
|
|
if (iconID.isValid()) {
|
|
// Atlas icon.
|
|
dc.Draw()->GetAtlas()->measureImage(iconID, &iconW, &iconH);
|
|
if (!iconName.empty()) {
|
|
Bounds iconBounds = Bounds(bounds.x + m, bounds.y + m, iconW, iconH);
|
|
// HACK: The RA icon needs some background.
|
|
if (equals(iconName, "I_RETROACHIEVEMENTS_LOGO")) {
|
|
dc.FillRect(UI::Drawable(0x50000000), iconBounds.Expand(2.0f));
|
|
}
|
|
}
|
|
|
|
if (flags & (OSDMessageFlags::SpinLeft | OSDMessageFlags::SpinRight)) {
|
|
const float direction = (flags & OSDMessageFlags::SpinLeft) ? -1.5f : 1.5f;
|
|
dc.DrawImageRotated(iconID, bounds.x + m + iconW * 0.5f, bounds.y + m + iconW * 0.5f, 1.0f, direction * timeVal, foreGround, false);
|
|
} else {
|
|
dc.DrawImageVGradient(iconID, foreGround, foreGround, Bounds(bounds.x + m, bounds.y + m, iconW, iconH));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make room
|
|
bounds.x += iconW + m * 2.0f;
|
|
bounds.w -= iconW + m * 2.0f;
|
|
|
|
Bounds primaryBounds = bounds;
|
|
primaryBounds.h = height1;
|
|
|
|
int titleAlign = (align & (FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT)) | ALIGN_VCENTER | FLAG_ELLIPSIZE_TEXT;
|
|
if (!(titleAlign & FLAG_WRAP_TEXT)) {
|
|
titleAlign |= FLAG_ELLIPSIZE_TEXT;
|
|
}
|
|
|
|
dc.DrawTextShadowRect(text, primaryBounds.Inset(2.0f, 0.0f, 1.0f, 0.0f), foreGround, titleAlign);
|
|
|
|
if (!details.empty()) {
|
|
Bounds bottomTextBounds = bounds.Inset(3.0f, height1 + 5.0f, 3.0f, 3.0f);
|
|
if (!(flags & OSDMessageFlags::Transparent)) {
|
|
UI::Drawable backgroundDark = UI::Drawable(colorAlpha(darkenColor(GetNoticeBackgroundColor(level)), alpha));
|
|
dc.FillRect(backgroundDark, bottomTextBounds);
|
|
}
|
|
dc.SetFontStyle(dc.GetTheme().uiFontSmall);
|
|
dc.DrawTextRect(details, bottomTextBounds.Inset(1.0f, 1.0f), foreGround, (align & FLAG_DYNAMIC_ASCII) | ALIGN_LEFT | FLAG_WRAP_TEXT);
|
|
}
|
|
dc.SetFontStyle(dc.GetTheme().uiFont);
|
|
}
|