diff --git a/data/resources/fullscreenui/back-icon.png b/data/resources/fullscreenui/back-icon.png deleted file mode 100644 index 438aaa39b..000000000 Binary files a/data/resources/fullscreenui/back-icon.png and /dev/null differ diff --git a/data/resources/fullscreenui/back-icon.svg b/data/resources/fullscreenui/back-icon.svg new file mode 100644 index 000000000..b93eacaa9 --- /dev/null +++ b/data/resources/fullscreenui/back-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/resources/fullscreenui/desktop-mode.png b/data/resources/fullscreenui/desktop-mode.png deleted file mode 100644 index 081e031b2..000000000 Binary files a/data/resources/fullscreenui/desktop-mode.png and /dev/null differ diff --git a/data/resources/fullscreenui/desktop-mode.svg b/data/resources/fullscreenui/desktop-mode.svg new file mode 100644 index 000000000..a3951508c --- /dev/null +++ b/data/resources/fullscreenui/desktop-mode.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/resources/fullscreenui/settings.png b/data/resources/fullscreenui/exe-file.png similarity index 100% rename from data/resources/fullscreenui/settings.png rename to data/resources/fullscreenui/exe-file.png diff --git a/data/resources/fullscreenui/exit.png b/data/resources/fullscreenui/exit.png deleted file mode 100644 index d0cec9db7..000000000 Binary files a/data/resources/fullscreenui/exit.png and /dev/null differ diff --git a/data/resources/fullscreenui/exit.svg b/data/resources/fullscreenui/exit.svg new file mode 100644 index 000000000..036e983e0 --- /dev/null +++ b/data/resources/fullscreenui/exit.svg @@ -0,0 +1,38 @@ + + + + + + + diff --git a/data/resources/fullscreenui/game-list.png b/data/resources/fullscreenui/game-list.png deleted file mode 100644 index bb6bffc16..000000000 Binary files a/data/resources/fullscreenui/game-list.png and /dev/null differ diff --git a/data/resources/fullscreenui/game-list.svg b/data/resources/fullscreenui/game-list.svg new file mode 100644 index 000000000..b7f38e0fe --- /dev/null +++ b/data/resources/fullscreenui/game-list.svg @@ -0,0 +1,46 @@ + + + + + + + + diff --git a/data/resources/fullscreenui/start-file.png b/data/resources/fullscreenui/playlist-file.png similarity index 100% rename from data/resources/fullscreenui/start-file.png rename to data/resources/fullscreenui/playlist-file.png diff --git a/data/resources/fullscreenui/settings.svg b/data/resources/fullscreenui/settings.svg new file mode 100644 index 000000000..89ef7c0e9 --- /dev/null +++ b/data/resources/fullscreenui/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/data/resources/fullscreenui/start-bios.png b/data/resources/fullscreenui/start-bios.png deleted file mode 100644 index bd7405269..000000000 Binary files a/data/resources/fullscreenui/start-bios.png and /dev/null differ diff --git a/data/resources/fullscreenui/start-bios.svg b/data/resources/fullscreenui/start-bios.svg new file mode 100644 index 000000000..084341ea8 --- /dev/null +++ b/data/resources/fullscreenui/start-bios.svg @@ -0,0 +1,38 @@ + + + + + + + diff --git a/data/resources/fullscreenui/start-disc.png b/data/resources/fullscreenui/start-disc.png deleted file mode 100644 index 5fa911ccd..000000000 Binary files a/data/resources/fullscreenui/start-disc.png and /dev/null differ diff --git a/data/resources/fullscreenui/start-disc.svg b/data/resources/fullscreenui/start-disc.svg new file mode 100644 index 000000000..9f2191a49 --- /dev/null +++ b/data/resources/fullscreenui/start-disc.svg @@ -0,0 +1,38 @@ + + + + + + + diff --git a/data/resources/fullscreenui/start-file.svg b/data/resources/fullscreenui/start-file.svg new file mode 100644 index 000000000..67bf89f2d --- /dev/null +++ b/data/resources/fullscreenui/start-file.svg @@ -0,0 +1,40 @@ + + + + + + + diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 706e2addf..02e0519e0 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -80,6 +80,7 @@ using ImGuiFullscreen::ChoiceDialogOptions; using ImGuiFullscreen::FocusResetType; using ImGuiFullscreen::LAYOUT_FOOTER_HEIGHT; +using ImGuiFullscreen::LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE; using ImGuiFullscreen::LAYOUT_LARGE_FONT_SIZE; using ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE; using ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT; @@ -249,6 +250,11 @@ static ChoiceDialogOptions GetBackgroundOptions(const TinyString& current_value) ////////////////////////////////////////////////////////////////////////// static bool LoadResources(); static void DestroyResources(); +static GPUTexture* GetUserThemeableTexture( + const std::string_view png_name, const std::string_view svg_name, bool* is_colorable = nullptr, + const ImVec2& svg_size = LayoutScale(LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE, LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE)); +static bool UserThemeableHorizontalButton(const std::string_view png_name, const std::string_view svg_name, + const char* title, const char* description); ////////////////////////////////////////////////////////////////////////// // Landing @@ -478,12 +484,13 @@ static constexpr const std::array s_ps_button_mapping{ std::make_pair(ICON_PF_RIGHT_TRIGGER_RT, ICON_PF_RIGHT_TRIGGER_R2), }; -static constexpr std::array s_theme_names = {FSUI_NSTR("Automatic"), FSUI_NSTR("Dark"), FSUI_NSTR("Light"), - FSUI_NSTR("AMOLED"), FSUI_NSTR("Cobalt Sky"), FSUI_NSTR("Grey Matter"), - FSUI_NSTR("Pinky Pals"), FSUI_NSTR("Dark Ruby"), FSUI_NSTR("Purple Rain")}; +static constexpr std::array s_theme_names = { + FSUI_NSTR("Automatic"), FSUI_NSTR("Dark"), FSUI_NSTR("Light"), + FSUI_NSTR("AMOLED"), FSUI_NSTR("Cobalt Sky"), FSUI_NSTR("Grey Matter"), + FSUI_NSTR("Pinky Pals"), FSUI_NSTR("Dark Ruby"), FSUI_NSTR("Purple Rain")}; -static constexpr std::array s_theme_values = {"", "Dark", "Light", "AMOLED", - "CobaltSky", "GreyMatter", "PinkyPals", "DarkRuby", "PurpleRain"}; +static constexpr std::array s_theme_values = {"", "Dark", "Light", "AMOLED", "CobaltSky", + "GreyMatter", "PinkyPals", "DarkRuby", "PurpleRain"}; ////////////////////////////////////////////////////////////////////////// // State @@ -1121,9 +1128,9 @@ bool FullscreenUI::LoadResources() { s_state.app_icon_texture = LoadTexture("images/duck.png"); s_state.fallback_disc_texture = LoadTexture("fullscreenui/cdrom.png"); - s_state.fallback_exe_texture = LoadTexture("fullscreenui/settings.png"); + s_state.fallback_exe_texture = LoadTexture("fullscreenui/exe-file.png"); s_state.fallback_psf_texture = LoadTexture("fullscreenui/psf-file.png"); - s_state.fallback_playlist_texture = LoadTexture("fullscreenui/game-list.png"); + s_state.fallback_playlist_texture = LoadTexture("fullscreenui/playlist-file.png"); return true; } @@ -1138,6 +1145,53 @@ void FullscreenUI::DestroyResources() s_state.app_icon_texture.reset(); } +GPUTexture* FullscreenUI::GetUserThemeableTexture(const std::string_view png_name, const std::string_view svg_name, + bool* is_colorable, const ImVec2& svg_size) +{ + GPUTexture* tex = ImGuiFullscreen::FindCachedTexture(png_name); + if (tex) + { + if (is_colorable) + *is_colorable = false; + + return tex; + } + + const u32 svg_width = static_cast(svg_size.x); + const u32 svg_height = static_cast(svg_size.y); + tex = ImGuiFullscreen::FindCachedTexture(svg_name, svg_width, svg_height); + if (tex) + return tex; + + // slow path, check filesystem for override + if (EmuFolders::Resources != EmuFolders::UserResources && + FileSystem::FileExists(Path::Combine(EmuFolders::UserResources, png_name).c_str())) + { + // use the user's png + if (is_colorable) + *is_colorable = false; + + return ImGuiFullscreen::GetCachedTexture(png_name); + } + + // otherwise use the system/user svg + if (is_colorable) + *is_colorable = true; + + return ImGuiFullscreen::GetCachedTexture(svg_name, svg_width, svg_height); +} + +bool FullscreenUI::UserThemeableHorizontalButton(const std::string_view png_name, const std::string_view svg_name, + const char* title, const char* description) +{ + bool is_colorable; + GPUTexture* icon = GetUserThemeableTexture( + png_name, svg_name, &is_colorable, + LayoutScale(LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE, LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE)); + return HorizontalMenuItem(icon, title, description, + is_colorable ? ImGui::GetColorU32(ImGuiCol_Text) : IM_COL32(255, 255, 255, 255)); +} + ////////////////////////////////////////////////////////////////////////// // Utility ////////////////////////////////////////////////////////////////////////// @@ -1873,28 +1927,29 @@ void FullscreenUI::DrawLandingWindow() { ResetFocusHere(); - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/game-list.png"), FSUI_CSTR("Game List"), - FSUI_CSTR("Launch a game from images scanned from your game directories."))) + if (UserThemeableHorizontalButton("fullscreenui/game-list.png", "fullscreenui/game-list.svg", + FSUI_CSTR("Game List"), + FSUI_CSTR("Launch a game from images scanned from your game directories."))) { SwitchToGameList(); } - if (HorizontalMenuItem( - GetCachedTexture("fullscreenui/cdrom.png"), FSUI_CSTR("Start Game"), + if (UserThemeableHorizontalButton( + "fullscreenui/cdrom.png", "fullscreenui/start-disc.svg", FSUI_CSTR("Start Game"), FSUI_CSTR("Launch a game from a file, disc, or starts the console without any disc inserted."))) { s_state.current_main_window = MainWindowType::StartGame; QueueResetFocus(FocusResetType::ViewChanged); } - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/settings.png"), FSUI_CSTR("Settings"), - FSUI_CSTR("Changes settings for the application."))) + if (UserThemeableHorizontalButton("fullscreenui/settings.png", "fullscreenui/settings.svg", FSUI_CSTR("Settings"), + FSUI_CSTR("Changes settings for the application."))) { SwitchToSettings(); } - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit"), - FSUI_CSTR("Return to desktop mode, or exit the application.")) || + if (UserThemeableHorizontalButton("fullscreenui/exit.png", "fullscreenui/exit.svg", FSUI_CSTR("Exit"), + FSUI_CSTR("Return to desktop mode, or exit the application.")) || (!AreAnyDialogsOpen() && WantsToCloseMenu())) { s_state.current_main_window = MainWindowType::Exit; @@ -1949,27 +2004,26 @@ void FullscreenUI::DrawStartGameWindow() { ResetFocusHere(); - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-file.png"), FSUI_CSTR("Start File"), - FSUI_CSTR("Launch a game by selecting a file/disc image."))) + if (HorizontalMenuItem(GetUserThemeableTexture("fullscreenui/start-file.png", "fullscreenui/start-file.svg"), + FSUI_CSTR("Start File"), FSUI_CSTR("Launch a game by selecting a file/disc image."))) { DoStartFile(); } - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-disc.png"), FSUI_CSTR("Start Disc"), - FSUI_CSTR("Start a game from a disc in your PC's DVD drive."))) + if (HorizontalMenuItem(GetUserThemeableTexture("fullscreenui/start-disc.png", "fullscreenui/start-disc.svg"), + FSUI_CSTR("Start Disc"), FSUI_CSTR("Start a game from a disc in your PC's DVD drive."))) { DoStartDisc(); } - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/start-bios.png"), FSUI_CSTR("Start BIOS"), - FSUI_CSTR("Start the console without any disc inserted."))) + if (HorizontalMenuItem(GetUserThemeableTexture("fullscreenui/start-bios.png", "fullscreenui/start-bios.svg"), + FSUI_CSTR("Start BIOS"), FSUI_CSTR("Start the console without any disc inserted."))) { DoStartBIOS(); } - // https://www.iconpacks.net/free-icon/arrow-back-3783.html - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"), - FSUI_CSTR("Return to the previous menu.")) || + if (HorizontalMenuItem(GetUserThemeableTexture("fullscreenui/back-icon.png", "fullscreenui/back-icon.svg"), + FSUI_CSTR("Back"), FSUI_CSTR("Return to the previous menu.")) || (!AreAnyDialogsOpen() && WantsToCloseMenu())) { s_state.current_main_window = MainWindowType::Landing; @@ -2016,22 +2070,23 @@ void FullscreenUI::DrawExitWindow() { ResetFocusHere(); - // https://www.iconpacks.net/free-icon/arrow-back-3783.html - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/back-icon.png"), FSUI_CSTR("Back"), - FSUI_CSTR("Return to the previous menu.")) || + if (HorizontalMenuItem(GetUserThemeableTexture("fullscreenui/back-icon.png", "fullscreenui/back-icon.svg"), + FSUI_CSTR("Back"), FSUI_CSTR("Return to the previous menu.")) || WantsToCloseMenu()) { s_state.current_main_window = MainWindowType::Landing; QueueResetFocus(FocusResetType::ViewChanged); } - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/exit.png"), FSUI_CSTR("Exit DuckStation"), + if (HorizontalMenuItem(GetUserThemeableTexture("fullscreenui/exit.png", "fullscreenui/exit.svg"), + FSUI_CSTR("Exit DuckStation"), FSUI_CSTR("Completely exits the application, returning you to your desktop."))) { DoRequestExit(); } - if (HorizontalMenuItem(GetCachedTexture("fullscreenui/desktop-mode.png"), FSUI_CSTR("Desktop Mode"), + if (HorizontalMenuItem(GetUserThemeableTexture("fullscreenui/desktop-mode.png", "fullscreenui/desktop-mode.svg"), + FSUI_CSTR("Desktop Mode"), FSUI_CSTR("Exits Big Picture mode, returning to the desktop interface."))) { DoDesktopMode(); diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index b945abe55..b0ade1986 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -372,6 +372,26 @@ std::shared_ptr ImGuiFullscreen::LoadTexture(std::string_view path, return s_state.placeholder_texture; } +GPUTexture* ImGuiFullscreen::FindCachedTexture(std::string_view name) +{ + std::shared_ptr* tex_ptr = s_state.texture_cache.Lookup(name); + return tex_ptr ? tex_ptr->get() : nullptr; +} + +GPUTexture* ImGuiFullscreen::FindCachedTexture(std::string_view name, u32 svg_width, u32 svg_height) +{ + // ignore size hints if it's not needed, don't duplicate + if (!TextureNeedsSVGDimensions(name)) + return FindCachedTexture(name); + + svg_width = static_cast(std::ceil(LayoutScale(static_cast(svg_width)))); + svg_height = static_cast(std::ceil(LayoutScale(static_cast(svg_height)))); + + const SmallString wh_name = SmallString::from_format("{}#{}x{}", name, svg_width, svg_height); + std::shared_ptr* tex_ptr = s_state.texture_cache.Lookup(wh_name.view()); + return tex_ptr ? tex_ptr->get() : nullptr; +} + GPUTexture* ImGuiFullscreen::GetCachedTexture(std::string_view name) { std::shared_ptr* tex_ptr = s_state.texture_cache.Lookup(name); @@ -561,6 +581,12 @@ ImRect ImGuiFullscreen::CenterImage(const ImVec2& fit_size, const ImVec2& image_ return ret; } +ImRect ImGuiFullscreen::CenterImage(const ImVec2& fit_rect, const GPUTexture* texture) +{ + const GSVector2 texture_size = GSVector2(texture->GetSizeVec()); + return CenterImage(fit_rect, ImVec2(texture_size.x, texture_size.y)); +} + ImRect ImGuiFullscreen::CenterImage(const ImRect& fit_rect, const ImVec2& image_size) { ImRect ret(CenterImage(fit_rect.Max - fit_rect.Min, image_size)); @@ -568,6 +594,12 @@ ImRect ImGuiFullscreen::CenterImage(const ImRect& fit_rect, const ImVec2& image_ return ret; } +ImRect ImGuiFullscreen::CenterImage(const ImRect& fit_rect, const GPUTexture* texture) +{ + const GSVector2 texture_size = GSVector2(texture->GetSizeVec()); + return CenterImage(fit_rect, ImVec2(texture_size.x, texture_size.y)); +} + ImRect ImGuiFullscreen::FitImage(const ImVec2& fit_size, const ImVec2& image_size) { ImRect rect; @@ -2209,7 +2241,7 @@ void ImGuiFullscreen::EndHorizontalMenu() EndFullscreenWindow(); } -bool ImGuiFullscreen::HorizontalMenuItem(GPUTexture* icon, const char* title, const char* description) +bool ImGuiFullscreen::HorizontalMenuItem(GPUTexture* icon, const char* title, const char* description, u32 color) { ImGuiWindow* window = ImGui::GetCurrentWindow(); if (window->SkipItems) @@ -2244,11 +2276,13 @@ bool ImGuiFullscreen::HorizontalMenuItem(GPUTexture* icon, const char* title, co bb.Max -= style.FramePadding; const float avail_width = bb.Max.x - bb.Min.x; - const float icon_size = LayoutScale(150.0f); + const float icon_size = LayoutScale(LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE); const ImVec2 icon_pos = bb.Min + ImVec2((avail_width - icon_size) * 0.5f, 0.0f); + const ImRect icon_box = CenterImage(ImRect(icon_pos, icon_pos + ImVec2(icon_size, icon_size)), icon); ImDrawList* dl = ImGui::GetWindowDrawList(); - dl->AddImage(reinterpret_cast(icon), icon_pos, icon_pos + ImVec2(icon_size, icon_size)); + dl->AddImage(reinterpret_cast(icon), icon_box.Min, icon_box.Max, ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f), + color); ImFont* title_font = UIStyle.LargeFont; const ImVec2 title_size = diff --git a/src/util/imgui_fullscreen.h b/src/util/imgui_fullscreen.h index cc98cdd08..b7a4ce616 100644 --- a/src/util/imgui_fullscreen.h +++ b/src/util/imgui_fullscreen.h @@ -42,6 +42,7 @@ static constexpr float LAYOUT_FOOTER_HEIGHT = LAYOUT_MEDIUM_FONT_SIZE + LAYOUT_F static constexpr float LAYOUT_HORIZONTAL_MENU_HEIGHT = 320.0f; static constexpr float LAYOUT_HORIZONTAL_MENU_PADDING = 30.0f; static constexpr float LAYOUT_HORIZONTAL_MENU_ITEM_WIDTH = 250.0f; +static constexpr float LAYOUT_HORIZONTAL_MENU_ITEM_IMAGE_SIZE = 150.0f; static constexpr float LAYOUT_SHADOW_OFFSET = 1.0f; struct ALIGN_TO_CACHE_LINE UIStyles @@ -151,7 +152,9 @@ ALWAYS_INLINE static std::string_view RemoveHash(std::string_view s) /// Centers an image within the specified bounds, scaling up or down as needed. ImRect CenterImage(const ImVec2& fit_size, const ImVec2& image_size); +ImRect CenterImage(const ImVec2& fit_rect, const GPUTexture* texture); ImRect CenterImage(const ImRect& fit_rect, const ImVec2& image_size); +ImRect CenterImage(const ImRect& fit_rect, const GPUTexture* texture); /// Fits an image to the specified bounds, cropping if needed. Returns UV coordinates. ImRect FitImage(const ImVec2& fit_size, const ImVec2& image_size); @@ -170,6 +173,8 @@ void Shutdown(bool clear_state); /// Texture cache. const std::shared_ptr& GetPlaceholderTexture(); std::shared_ptr LoadTexture(std::string_view path, u32 svg_width = 0, u32 svg_height = 0); +GPUTexture* FindCachedTexture(std::string_view name); +GPUTexture* FindCachedTexture(std::string_view name, u32 svg_width, u32 svg_height); GPUTexture* GetCachedTexture(std::string_view name); GPUTexture* GetCachedTexture(std::string_view name, u32 svg_width, u32 svg_height); GPUTexture* GetCachedTextureAsync(std::string_view name); @@ -335,7 +340,8 @@ bool NavTab(const char* title, bool is_active, bool enabled, float width, float bool BeginHorizontalMenu(const char* name, const ImVec2& position, const ImVec2& size, const ImVec4& bg_color, u32 num_items); void EndHorizontalMenu(); -bool HorizontalMenuItem(GPUTexture* icon, const char* title, const char* description); +bool HorizontalMenuItem(GPUTexture* icon, const char* title, const char* description, + u32 color = IM_COL32(255, 255, 255, 255)); using FileSelectorCallback = std::function; using FileSelectorFilters = std::vector;