FullscreenUI: Add notifications API

This commit is contained in:
Connor McLaughlin 2021-02-21 16:55:28 +10:00
parent 229ed5a852
commit 7e1fe166ee
4 changed files with 220 additions and 41 deletions

View File

@ -5,6 +5,7 @@
#include "common/byte_stream.h"
#include "common/file_system.h"
#include "common/log.h"
#include "common/lru_cache.h"
#include "common/make_array.h"
#include "common/string.h"
#include "common/string_util.h"
@ -99,15 +100,19 @@ static std::unique_ptr<HostDisplayTexture> LoadTextureResource(const char* name)
static bool LoadResources();
static void DestroyResources();
std::unique_ptr<HostDisplayTexture> s_app_icon_texture;
std::unique_ptr<HostDisplayTexture> s_placeholder_texture;
std::array<std::unique_ptr<HostDisplayTexture>, static_cast<u32>(DiscRegion::Count)> s_disc_region_textures;
std::array<std::unique_ptr<HostDisplayTexture>, static_cast<u32>(GameListCompatibilityRating::Count)>
static HostDisplayTexture* GetCachedTexture(const std::string& name);
static ImTextureID ResolveTextureHandle(const std::string& name);
static std::unique_ptr<HostDisplayTexture> s_app_icon_texture;
static std::unique_ptr<HostDisplayTexture> s_placeholder_texture;
static std::array<std::unique_ptr<HostDisplayTexture>, static_cast<u32>(DiscRegion::Count)> s_disc_region_textures;
static std::array<std::unique_ptr<HostDisplayTexture>, static_cast<u32>(GameListCompatibilityRating::Count)>
s_game_compatibility_textures;
std::unique_ptr<HostDisplayTexture> s_fallback_disc_texture;
std::unique_ptr<HostDisplayTexture> s_fallback_exe_texture;
std::unique_ptr<HostDisplayTexture> s_fallback_psf_texture;
std::unique_ptr<HostDisplayTexture> s_fallback_playlist_texture;
static std::unique_ptr<HostDisplayTexture> s_fallback_disc_texture;
static std::unique_ptr<HostDisplayTexture> s_fallback_exe_texture;
static std::unique_ptr<HostDisplayTexture> s_fallback_psf_texture;
static std::unique_ptr<HostDisplayTexture> s_fallback_playlist_texture;
static LRUCache<std::string, std::unique_ptr<HostDisplayTexture>> s_texture_cache;
//////////////////////////////////////////////////////////////////////////
// Settings
@ -195,6 +200,7 @@ bool Initialize(CommonHostInterface* host_interface, SettingsInterface* settings
ImGuiFullscreen::UpdateLayoutScale();
ImGuiFullscreen::UpdateFonts();
ImGuiFullscreen::SetResolveTextureFunction(ResolveTextureHandle);
return true;
}
@ -388,6 +394,7 @@ bool LoadResources()
void DestroyResources()
{
s_texture_cache.Clear();
s_app_icon_texture.reset();
s_placeholder_texture.reset();
s_fallback_playlist_texture.reset();
@ -400,33 +407,46 @@ void DestroyResources()
tex.reset();
}
std::unique_ptr<HostDisplayTexture> LoadTextureResource(const char* name)
static std::unique_ptr<HostDisplayTexture> LoadTexture(const char* path, bool from_package)
{
std::unique_ptr<HostDisplayTexture> texture;
const std::string path(StringUtil::StdStringFromFormat("resources" FS_OSPATH_SEPARATOR_STR "%s", name));
std::unique_ptr<ByteStream> stream = s_host_interface->OpenPackageFile(path.c_str(), BYTESTREAM_OPEN_READ);
std::unique_ptr<ByteStream> stream;
if (from_package)
stream = s_host_interface->OpenPackageFile(path, BYTESTREAM_OPEN_READ);
else
stream = FileSystem::OpenFile(path, BYTESTREAM_OPEN_READ);
if (!stream)
{
Log_ErrorPrintf("Failed to open texture resource '%s'", path.c_str());
Log_ErrorPrintf("Failed to open texture resource '%s'", path);
return {};
}
Common::RGBA8Image image;
if (Common::LoadImageFromStream(&image, stream.get()) && image.IsValid())
if (!Common::LoadImageFromStream(&image, stream.get()) && image.IsValid())
{
texture = s_host_interface->GetDisplay()->CreateTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1,
HostDisplayPixelFormat::RGBA8, image.GetPixels(),
image.GetByteStride());
if (texture)
{
Log_DevPrintf("Uploaded texture resource '%s' (%ux%u)", name, image.GetWidth(), image.GetHeight());
return texture;
}
Log_ErrorPrintf("failed to create %ux%u texture for resource", image.GetWidth(), image.GetHeight());
Log_ErrorPrintf("Failed to read texture resource '%s'", path);
return {};
}
std::unique_ptr<HostDisplayTexture> texture = s_host_interface->GetDisplay()->CreateTexture(
image.GetWidth(), image.GetHeight(), 1, 1, 1, HostDisplayPixelFormat::RGBA8, image.GetPixels(),
image.GetByteStride());
if (!texture)
{
Log_ErrorPrintf("failed to create %ux%u texture for resource", image.GetWidth(), image.GetHeight());
return {};
}
Log_DevPrintf("Uploaded texture resource '%s' (%ux%u)", path, image.GetWidth(), image.GetHeight());
return texture;
}
std::unique_ptr<HostDisplayTexture> LoadTextureResource(const char* name)
{
const std::string path(StringUtil::StdStringFromFormat("resources" FS_OSPATH_SEPARATOR_STR "%s", name));
std::unique_ptr<HostDisplayTexture> texture = LoadTexture(path.c_str(), true);
if (texture)
return texture;
Log_ErrorPrintf("Missing resource '%s', using fallback", name);
texture = s_host_interface->GetDisplay()->CreateTexture(PLACEHOLDER_ICON_WIDTH, PLACEHOLDER_ICON_HEIGHT, 1, 1, 1,
@ -438,6 +458,29 @@ std::unique_ptr<HostDisplayTexture> LoadTextureResource(const char* name)
return texture;
}
HostDisplayTexture* GetCachedTexture(const std::string& name)
{
std::unique_ptr<HostDisplayTexture>* tex_ptr = s_texture_cache.Lookup(name);
if (!tex_ptr)
{
std::unique_ptr<HostDisplayTexture> tex = LoadTexture(name.c_str(), false);
tex_ptr = s_texture_cache.Insert(name, std::move(tex));
}
return tex_ptr->get();
}
ImTextureID ResolveTextureHandle(const std::string& name)
{
HostDisplayTexture* tex = GetCachedTexture(name);
return tex ? tex->GetHandle() : nullptr;
}
bool InvalidateCachedTexture(const std::string& path)
{
return s_texture_cache.Remove(path);
}
//////////////////////////////////////////////////////////////////////////
// Utility
//////////////////////////////////////////////////////////////////////////

View File

@ -1,5 +1,6 @@
#pragma once
#include "common/types.h"
#include <string>
class CommonHostInterface;
class SettingsInterface;
@ -47,6 +48,8 @@ void CloseQuickMenu();
void Shutdown();
void Render();
bool InvalidateCachedTexture(const std::string& path);
// Returns true if the message has been dismissed.
bool DrawErrorWindow(const char* message);
bool DrawConfirmWindow(const char* message, bool* result);

View File

@ -6,6 +6,7 @@
#include "common/file_system.h"
#include "common/string.h"
#include "common/string_util.h"
#include "common/timer.h"
#include "core/host_display.h"
#include "core/host_interface.h"
#include "imgui_internal.h"
@ -16,7 +17,8 @@
namespace ImGuiFullscreen {
static void DrawFileSelector();
static void DrawChoiceDialog();
static void DrawBackgroundProgressDialogs();
static void DrawBackgroundProgressDialogs(ImVec2& position, float spacing);
static void DrawNotifications(ImVec2& position, float spacing);
ImFont* g_standard_font = nullptr;
ImFont* g_medium_font = nullptr;
@ -33,6 +35,7 @@ static std::string s_icon_font_filename;
static std::vector<u8> s_icon_font_data;
static float s_font_size = 15.0f;
static const ImWchar* s_font_glyph_range = nullptr;
static ResolveTextureHandleCallback s_resolve_texture_handle = nullptr;
static u32 s_menu_button_index = 0;
@ -75,6 +78,11 @@ void SetMenuBarSize(float size)
s_menu_bar_size = size;
}
void SetResolveTextureFunction(ResolveTextureHandleCallback callback)
{
s_resolve_texture_handle = callback;
}
static void AddIconFonts(float size)
{
static const ImWchar range_fa[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
@ -182,7 +190,12 @@ void EndLayout()
{
DrawFileSelector();
DrawChoiceDialog();
DrawBackgroundProgressDialogs();
const float notification_margin = LayoutScale(10.0f);
const float spacing = LayoutScale(10.0f);
ImVec2 position(notification_margin, g_layout_padding_top + LayoutScale(LAYOUT_SCREEN_HEIGHT) - notification_margin);
DrawBackgroundProgressDialogs(position, spacing);
DrawNotifications(position, spacing);
ImGui::PopStyleColor(5);
ImGui::PopStyleVar(2);
@ -321,7 +334,7 @@ static void GetMenuButtonFrameBounds(float height, ImVec2* pos, ImVec2* size)
}
static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImRect* bb,
ImGuiButtonFlags flags = 0)
ImGuiButtonFlags flags = 0, float hover_alpha = 1.0f)
{
ImGuiWindow* window = ImGui::GetCurrentWindow();
if (window->SkipItems)
@ -365,7 +378,7 @@ static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool
pressed = ImGui::ButtonBehavior(*bb, id, hovered, &held, flags);
if (*hovered)
{
const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered);
const ImU32 col = ImGui::GetColorU32(held ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered, hover_alpha);
ImGui::RenderFrame(bb->Min, bb->Max, col, true, 0.0f);
}
}
@ -383,10 +396,10 @@ static bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool
}
bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImVec2* min,
ImVec2* max, ImGuiButtonFlags flags /*= 0*/)
ImVec2* max, ImGuiButtonFlags flags /*= 0*/, float hover_alpha /*= 0*/)
{
ImRect bb;
const bool result = MenuButtonFrame(str_id, enabled, height, visible, hovered, &bb, flags);
const bool result = MenuButtonFrame(str_id, enabled, height, visible, hovered, &bb, flags, hover_alpha);
*min = bb.Min;
*max = bb.Max;
return result;
@ -1214,7 +1227,7 @@ void CloseBackgroundProgressDialog(const char* str_id)
Panic("Closing unknown progress entry.");
}
void DrawBackgroundProgressDialogs()
void DrawBackgroundProgressDialogs(ImVec2& position, float spacing)
{
std::unique_lock<std::mutex> lock(s_background_progress_lock);
if (s_background_progress_dialogs.empty())
@ -1222,10 +1235,6 @@ void DrawBackgroundProgressDialogs()
const float window_width = LayoutScale(500.0f);
const float window_height = LayoutScale(75.0f);
const float window_spacing = LayoutScale(20.0f);
float current_pos_x = LayoutScale(10.0f);
float current_pos_y = (LayoutScale(LAYOUT_SCREEN_HEIGHT) + g_layout_padding_top) - window_height - LayoutScale(10.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, UIPrimaryDarkColor());
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, UISecondaryLightColor());
@ -1239,11 +1248,14 @@ void DrawBackgroundProgressDialogs()
for (const BackgroundProgressDialogData& data : s_background_progress_dialogs)
{
dl->AddRectFilled(ImVec2(current_pos_x, current_pos_y),
ImVec2(current_pos_x + window_width, current_pos_y + window_height),
const float window_pos_x = position.x;
const float window_pos_y = position.y - window_height;
dl->AddRectFilled(ImVec2(window_pos_x, window_pos_y),
ImVec2(window_pos_x + window_width, window_pos_y + window_height),
IM_COL32(0x11, 0x11, 0x11, 200), LayoutScale(10.0f));
ImVec2 pos(current_pos_x + LayoutScale(10.0f), current_pos_y + LayoutScale(10.0f));
ImVec2 pos(window_pos_x + LayoutScale(10.0f), window_pos_y + LayoutScale(10.0f));
dl->AddText(g_medium_font, g_medium_font->FontSize, pos, IM_COL32(255, 255, 255, 255), data.message.c_str(),
nullptr, 0.0f);
pos.y += g_medium_font->FontSize + LayoutScale(10.0f);
@ -1261,7 +1273,7 @@ void DrawBackgroundProgressDialogs()
pos.y + ((bar_end.y - pos.y) / 2.0f) - (text_size.y / 2.0f));
dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos, ImGui::GetColorU32(UIPrimaryTextColor()), text);
current_pos_y -= window_height - window_spacing;
position.y -= window_height + spacing;
}
ImGui::PopFont();
@ -1269,4 +1281,118 @@ void DrawBackgroundProgressDialogs()
ImGui::PopStyleColor(2);
}
//////////////////////////////////////////////////////////////////////////
// Notifications
//////////////////////////////////////////////////////////////////////////
struct Notification
{
std::string title;
std::string text;
std::string badge_path;
Common::Timer::Value start_time;
float duration;
};
static std::vector<Notification> s_notifications;
void AddNotification(float duration, std::string title, std::string text, std::string image_path)
{
Notification notif;
notif.duration = duration;
notif.title = std::move(title);
notif.text = std::move(text);
notif.badge_path = std::move(image_path);
notif.start_time = Common::Timer::GetValue();
s_notifications.push_back(std::move(notif));
}
void DrawNotifications(ImVec2& position, float spacing)
{
if (s_notifications.empty())
return;
const Common::Timer::Value current_time = Common::Timer::GetValue();
const float horizontal_padding = ImGuiFullscreen::LayoutScale(20.0f);
const float vertical_padding = ImGuiFullscreen::LayoutScale(10.0f);
const float horizontal_spacing = ImGuiFullscreen::LayoutScale(10.0f);
const float vertical_spacing = ImGuiFullscreen::LayoutScale(4.0f);
const float badge_size = ImGuiFullscreen::LayoutScale(48.0f);
const float min_width = ImGuiFullscreen::LayoutScale(200.0f);
const float max_width = ImGuiFullscreen::LayoutScale(800.0f);
const float max_text_width = max_width - badge_size - (horizontal_padding * 2.0f) - horizontal_spacing;
const float min_height = (vertical_padding * 2.0f) + badge_size;
const float shadow_size = ImGuiFullscreen::LayoutScale(4.0f);
const float rounding = ImGuiFullscreen::LayoutScale(4.0f);
ImFont* const title_font = ImGuiFullscreen::g_large_font;
ImFont* const text_font = ImGuiFullscreen::g_medium_font;
#if 0
static constexpr u32 toast_background_color = IM_COL32(241, 241, 241, 255);
static constexpr u32 toast_border_color = IM_COL32(0x88, 0x88, 0x88, 255);
static constexpr u32 toast_title_color = IM_COL32(1, 1, 1, 255);
static constexpr u32 toast_text_color = IM_COL32(0, 0, 0, 255);
#else
static constexpr u32 toast_background_color = IM_COL32(0x21, 0x21, 0x21, 255);
static constexpr u32 toast_border_color = IM_COL32(0x48, 0x48, 0x48, 255);
static constexpr u32 toast_title_color = IM_COL32(0xff, 0xff, 0xff, 255);
static constexpr u32 toast_text_color = IM_COL32(0xff, 0xff, 0xff, 255);
#endif
for (u32 index = 0; index < static_cast<u32>(s_notifications.size());)
{
Notification& notif = s_notifications[index];
if (Common::Timer::ConvertValueToSeconds(current_time - notif.start_time) >= notif.duration)
{
s_notifications.erase(s_notifications.begin() + index);
continue;
}
const ImVec2 title_size(text_font->CalcTextSizeA(title_font->FontSize, max_text_width, max_text_width,
notif.title.c_str(), notif.title.c_str() + notif.title.size()));
const ImVec2 text_size(text_font->CalcTextSizeA(text_font->FontSize, max_text_width, max_text_width,
notif.text.c_str(), notif.text.c_str() + notif.text.size()));
const float box_width = std::max(
(horizontal_padding * 2.0f) + badge_size + horizontal_spacing + std::max(title_size.x, text_size.x), min_width);
const float box_height =
std::max((vertical_padding * 2.0f) + title_size.y + vertical_spacing + text_size.y, min_height);
const ImVec2 box_min(position.x, position.y - box_height);
const ImVec2 box_max(box_min.x + box_width, box_min.y + box_height);
ImDrawList* dl = ImGui::GetForegroundDrawList();
dl->AddRectFilled(ImVec2(box_min.x + shadow_size, box_min.y + shadow_size),
ImVec2(box_max.x + shadow_size, box_max.y + shadow_size), IM_COL32(20, 20, 20, 180), rounding,
ImDrawCornerFlags_All);
dl->AddRectFilled(box_min, box_max, toast_background_color, rounding, ImDrawCornerFlags_All);
dl->AddRect(box_min, box_max, toast_border_color, rounding, ImDrawCornerFlags_All,
ImGuiFullscreen::LayoutScale(1.0f));
const ImVec2 badge_min(box_min.x + horizontal_padding, box_min.y + vertical_padding);
const ImVec2 badge_max(badge_min.x + badge_size, badge_min.y + badge_size);
if (!notif.badge_path.empty() && s_resolve_texture_handle)
{
ImTextureID tex = s_resolve_texture_handle(notif.badge_path);
if (tex)
dl->AddImage(tex, badge_min, badge_max);
}
const ImVec2 title_min(badge_max.x + horizontal_spacing, box_min.y + vertical_padding);
const ImVec2 title_max(title_min.x + title_size.x, title_min.y + title_size.y);
dl->AddText(title_font, title_font->FontSize, title_min, toast_title_color, notif.title.c_str(),
notif.title.c_str() + notif.title.size(), max_text_width);
const ImVec2 text_min(badge_max.x + horizontal_spacing, title_max.y + vertical_spacing);
const ImVec2 text_max(text_min.x + text_size.x, text_min.y + text_size.y);
dl->AddText(text_font, text_font->FontSize, text_min, toast_text_color, notif.text.c_str(),
notif.text.c_str() + notif.text.size(), max_text_width);
position.y -= box_height + shadow_size + spacing;
index++;
}
}
} // namespace ImGuiFullscreen

View File

@ -10,6 +10,8 @@ namespace ImGuiFullscreen {
ImVec4(static_cast<float>((hex >> 16) & 0xFFu) / 255.0f, static_cast<float>((hex >> 8) & 0xFFu) / 255.0f, \
static_cast<float>(hex & 0xFFu) / 255.0f, static_cast<float>(alpha) / 255.0f)
using ResolveTextureHandleCallback = ImTextureID(*)(const std::string& path);
static constexpr float LAYOUT_SCREEN_WIDTH = 1280.0f;
static constexpr float LAYOUT_SCREEN_HEIGHT = 720.0f;
static constexpr float LAYOUT_LARGE_FONT_SIZE = 26.0f;
@ -133,6 +135,9 @@ void SetFontGlyphRanges(const ImWchar* glyph_ranges);
/// Changes the menu bar size. Don't forget to call UpdateLayoutScale() and UpdateFonts().
void SetMenuBarSize(float size);
/// Resolves a texture name to a handle.
void SetResolveTextureFunction(ResolveTextureHandleCallback callback);
/// Rebuilds fonts to a new scale if needed. Returns true if fonts have changed and the texture needs updating.
bool UpdateFonts();
@ -159,7 +164,7 @@ void BeginMenuButtons(u32 num_items = 0, float y_align = 0.0f, float x_padding =
float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING, float item_height = LAYOUT_MENU_BUTTON_HEIGHT);
void EndMenuButtons();
bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImVec2* min,
ImVec2* max, ImGuiButtonFlags flags = 0);
ImVec2* max, ImGuiButtonFlags flags = 0, float hover_alpha = 1.0f);
void MenuHeading(const char* title, bool draw_line = true);
bool ActiveButton(const char* title, bool is_active, bool enabled = true,
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
@ -226,4 +231,6 @@ void OpenBackgroundProgressDialog(const char* str_id, std::string message, s32 m
void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value);
void CloseBackgroundProgressDialog(const char* str_id);
void AddNotification(float duration, std::string title, std::string text, std::string image_path);
} // namespace ImGuiFullscreen