mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-01 03:57:17 +01:00
RenderNotice and MeasureNotice take padding (added inside the background) and the corners to round. The drop shadow moves out of RenderNotice to the floating OSD notices, since views don't have one. Without layout params, a NoticeView is FILL_PARENT wide like a Choice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
6.0 KiB
C++
168 lines
6.0 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 padding) {
|
|
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 + padding * 2.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) + padding * 2.0f;
|
|
}
|
|
|
|
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, float padding, RoundCorners corners) {
|
|
UI::Drawable background = UI::Drawable(colorAlpha(GetNoticeBackgroundColor(level), alpha));
|
|
|
|
dc.SetFontStyle(dc.GetTheme().uiFont);
|
|
|
|
uint32_t foreGround = whiteAlpha(alpha);
|
|
|
|
if (!(flags & OSDMessageFlags::Transparent)) {
|
|
dc.FillRoundRect(background, bounds, dc.GetTheme().choiceRoundRadius, corners);
|
|
}
|
|
bounds = bounds.Expand(-padding);
|
|
|
|
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);
|
|
}
|