// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #pragma once #include "common/types.h" #include "IconsFontAwesome5.h" #include "imgui.h" #include "imgui_internal.h" #include #include #include #include #include #include #include #include class Image; class GPUTexture; class SmallStringBase; namespace ImGuiFullscreen { #define HEX_TO_IMVEC4(hex, alpha) \ ImVec4(static_cast((hex >> 16) & 0xFFu) / 255.0f, static_cast((hex >> 8) & 0xFFu) / 255.0f, \ static_cast(hex & 0xFFu) / 255.0f, static_cast(alpha) / 255.0f) 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; static constexpr float LAYOUT_MEDIUM_FONT_SIZE = 16.0f; static constexpr float LAYOUT_SMALL_FONT_SIZE = 10.0f; static constexpr float LAYOUT_MENU_BUTTON_HEIGHT = 50.0f; static constexpr float LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY = 26.0f; static constexpr float LAYOUT_MENU_BUTTON_X_PADDING = 15.0f; static constexpr float LAYOUT_MENU_BUTTON_Y_PADDING = 10.0f; static constexpr float LAYOUT_MENU_WINDOW_X_PADDING = 12.0f; static constexpr float LAYOUT_FOOTER_PADDING = 10.0f; static constexpr float LAYOUT_FOOTER_HEIGHT = LAYOUT_MEDIUM_FONT_SIZE + LAYOUT_FOOTER_PADDING * 2.0f; 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; static constexpr float LAYOUT_SMALL_POPUP_PADDING = 20.0f; static constexpr float LAYOUT_LARGE_POPUP_PADDING = 30.0f; static constexpr float LAYOUT_LARGE_POPUP_ROUNDING = 40.0f; struct ALIGN_TO_CACHE_LINE UIStyles { ImVec4 BackgroundColor; ImVec4 BackgroundTextColor; ImVec4 BackgroundLineColor; ImVec4 BackgroundHighlight; ImVec4 PopupBackgroundColor; ImVec4 PopupFrameBackgroundColor; ImVec4 DisabledColor; ImVec4 PrimaryColor; ImVec4 PrimaryLightColor; ImVec4 PrimaryDarkColor; ImVec4 PrimaryTextColor; ImVec4 TextHighlightColor; ImVec4 PrimaryLineColor; ImVec4 SecondaryColor; ImVec4 SecondaryWeakColor; // Not currently used. ImVec4 SecondaryStrongColor; ImVec4 SecondaryTextColor; ImVec4 ToastBackgroundColor; ImVec4 ToastTextColor; ImFont* MediumFont; ImFont* LargeFont; u32 ShadowColor; float LayoutScale; float RcpLayoutScale; float LayoutPaddingLeft; float LayoutPaddingTop; bool Animations; bool SmoothScrolling; }; extern UIStyles UIStyle; ALWAYS_INLINE static float LayoutScale(float v) { return ImCeil(UIStyle.LayoutScale * v); } ALWAYS_INLINE static ImVec2 LayoutScale(const ImVec2& v) { return ImVec2(ImCeil(v.x * UIStyle.LayoutScale), ImCeil(v.y * UIStyle.LayoutScale)); } ALWAYS_INLINE static ImVec2 LayoutScale(float x, float y) { return ImVec2(ImCeil(x * UIStyle.LayoutScale), ImCeil(y * UIStyle.LayoutScale)); } ALWAYS_INLINE static float LayoutUnscale(float v) { return ImCeil(UIStyle.RcpLayoutScale * v); } ALWAYS_INLINE static ImVec2 LayoutUnscale(const ImVec2& v) { return ImVec2(ImCeil(v.x * UIStyle.RcpLayoutScale), ImCeil(v.y * UIStyle.RcpLayoutScale)); } ALWAYS_INLINE static ImVec2 LayoutUnscale(float x, float y) { return ImVec2(ImCeil(x * UIStyle.RcpLayoutScale), ImCeil(y * UIStyle.RcpLayoutScale)); } ALWAYS_INLINE static ImVec4 ModAlpha(const ImVec4& v, float a) { return ImVec4(v.x, v.y, v.z, a); } ALWAYS_INLINE static u32 ModAlpha(u32 col32, float a) { return (col32 & ~IM_COL32_A_MASK) | (static_cast(a * 255.0f) << IM_COL32_A_SHIFT); } // lighter in light themes ALWAYS_INLINE static ImVec4 DarkerColor(const ImVec4& v, float f = 0.8f) { // light theme f = (UIStyle.PrimaryTextColor.x < UIStyle.PrimaryColor.x) ? (1.0f / f) : f; return ImVec4(std::max(v.x, 1.0f / 255.0f) * f, std::max(v.y, 1.0f / 255.0f) * f, std::max(v.z, 1.0f / 255.0f) * f, v.w); } ALWAYS_INLINE static ImVec4 MulAlpha(const ImVec4& v, float a) { return ImVec4(v.x, v.y, v.z, v.w * a); } ALWAYS_INLINE static u32 MulAlpha(u32 col32, float a) { return (col32 & ~IM_COL32_A_MASK) | (static_cast(static_cast((col32 /*& IM_COL32_A_MASK*/) >> IM_COL32_A_SHIFT) * a) << IM_COL32_A_SHIFT); } ALWAYS_INLINE static u32 MulAlpha(u32 col32, u32 a) { return (col32 & ~IM_COL32_A_MASK) | (((((col32 /*& IM_COL32_A_MASK*/) >> IM_COL32_A_SHIFT) * a) / 255u) << IM_COL32_A_SHIFT); } ALWAYS_INLINE static std::string_view RemoveHash(std::string_view s) { const std::string_view::size_type pos = s.find('#'); return (pos != std::string_view::npos) ? s.substr(0, pos) : 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); /// Initializes, setting up any state. bool Initialize(const char* placeholder_image_path); void SetTheme(std::string_view theme); void SetAnimations(bool enabled); void SetSmoothScrolling(bool enabled); void SetFonts(ImFont* medium_font, ImFont* large_font); bool UpdateLayoutScale(); /// Shuts down, clearing all state. 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); GPUTexture* GetCachedTextureAsync(std::string_view name, u32 svg_width, u32 svg_height); bool InvalidateCachedTexture(std::string_view path); bool TextureNeedsSVGDimensions(std::string_view path); void UploadAsyncTextures(); void BeginLayout(); void EndLayout(); bool IsAnyFixedPopupDialogOpen(); bool IsFixedPopupDialogOpen(std::string_view name); void OpenFixedPopupDialog(std::string_view name); void CloseFixedPopupDialog(); void CloseFixedPopupDialogImmediately(); bool BeginFixedPopupDialog(float scaled_window_padding = LayoutScale(20.0f), float scaled_window_rounding = LayoutScale(20.0f), const ImVec2& scaled_window_size = ImVec2(0.0f, 0.0f)); void EndFixedPopupDialog(); void RenderOverlays(); void PushResetLayout(); void PopResetLayout(); enum class FocusResetType : u8 { None, PopupOpened, PopupClosed, ViewChanged, Other, }; void QueueResetFocus(FocusResetType type); bool ResetFocusHere(); bool IsFocusResetQueued(); bool IsFocusResetFromWindowChange(); FocusResetType GetQueuedFocusResetType(); void ForceKeyNavEnabled(); bool WantsToCloseMenu(); void ResetCloseMenuIfNeeded(); void CancelPendingMenuClose(); void PushPrimaryColor(); void PopPrimaryColor(); void DrawWindowTitle(const char* title); bool BeginFullscreenColumns(const char* title = nullptr, float pos_y = 0.0f, bool expand_to_screen_width = false, bool footer = false); void EndFullscreenColumns(); bool BeginFullscreenColumnWindow(float start, float end, const char* name, const ImVec4& background = UIStyle.BackgroundColor); void EndFullscreenColumnWindow(); bool BeginFullscreenWindow(float left, float top, float width, float height, const char* name, const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f, const ImVec2& padding = ImVec2(), ImGuiWindowFlags flags = 0); bool BeginFullscreenWindow(const ImVec2& position, const ImVec2& size, const char* name, const ImVec4& background = HEX_TO_IMVEC4(0x212121, 0xFF), float rounding = 0.0f, const ImVec2& padding = ImVec2(), ImGuiWindowFlags flags = 0); void EndFullscreenWindow(bool allow_wrap_x = false, bool allow_wrap_y = true); void SetWindowNavWrapping(bool allow_wrap_x = false, bool allow_wrap_y = true); bool IsGamepadInputSource(); std::string_view GetControllerIconMapping(std::string_view icon); void CreateFooterTextString(SmallStringBase& dest, std::span> items); void SetFullscreenFooterText(std::string_view text, float background_alpha); void SetFullscreenFooterText(std::span> items, float background_alpha); void SetFullscreenFooterTextIconMapping(std::span> mapping); void SetFullscreenStatusText(std::string_view text); void SetFullscreenStatusText(std::span> items); void DrawFullscreenFooter(); void PrerenderMenuButtonBorder(); void BeginMenuButtons(u32 num_items = 0, float y_align = 0.0f, float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING, float item_height = LAYOUT_MENU_BUTTON_HEIGHT); void EndMenuButtons(); void GetMenuButtonFrameBounds(float height, ImVec2* pos, ImVec2* size); bool MenuButtonFrame(const char* str_id, bool enabled, float height, bool* visible, bool* hovered, ImVec2* min, ImVec2* max, ImGuiButtonFlags flags = 0, float hover_alpha = 1.0f); void DrawMenuButtonFrame(const ImVec2& p_min, const ImVec2& p_max, ImU32 fill_col, bool border = true); void ResetMenuButtonFrame(); void RenderShadowedTextClipped(ImFont* font, const ImVec2& pos_min, const ImVec2& pos_max, u32 color, const char* text, const char* text_end, const ImVec2* text_size_if_known = nullptr, const ImVec2& align = ImVec2(0, 0), float wrap_width = 0.0f, const ImRect* clip_rect = nullptr); void RenderShadowedTextClipped(ImDrawList* draw_list, ImFont* font, const ImVec2& pos_min, const ImVec2& pos_max, u32 color, const char* text, const char* text_end, const ImVec2* text_size_if_known = nullptr, const ImVec2& align = ImVec2(0, 0), float wrap_width = 0.0f, const ImRect* clip_rect = nullptr); void RenderShadowedTextClipped(ImDrawList* draw_list, ImFont* font, const ImVec2& pos_min, const ImVec2& pos_max, u32 color, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align, float wrap_width, const ImRect* clip_rect, float shadow_offset); void MenuHeading(const char* title, bool draw_line = true); bool MenuHeadingButton(const char* title, const char* value = nullptr, bool enabled = true, bool draw_line = true); bool MenuButton(const char* title, const char* summary, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont, const ImVec2& text_align = ImVec2(0.0f, 0.0f)); bool MenuButtonWithoutSummary(const char* title, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont, const ImVec2& text_align = ImVec2(0.0f, 0.0f)); bool MenuButtonWithValue(const char* title, const char* summary, const char* value, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont); bool MenuImageButton(const char* title, const char* summary, ImTextureID user_texture_id, const ImVec2& image_size, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, const ImVec2& uv0 = ImVec2(0.0f, 0.0f), const ImVec2& uv1 = ImVec2(1.0f, 1.0f), ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont); bool FloatingButton(const char* text, float x, float y, float width = -1.0f, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, float anchor_x = 0.0f, float anchor_y = 0.0f, bool enabled = true, ImFont* font = UIStyle.LargeFont, ImVec2* out_position = nullptr, bool repeat_button = false); bool ToggleButton(const char* title, const char* summary, bool* v, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont); bool ThreeWayToggleButton(const char* title, const char* summary, std::optional* v, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont); bool RangeButton(const char* title, const char* summary, s32* value, s32 min, s32 max, s32 increment, const char* format = "%d", bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont, const char* ok_text = "OK"); bool RangeButton(const char* title, const char* summary, float* value, float min, float max, float increment, const char* format = "%f", bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont, const char* ok_text = "OK"); bool EnumChoiceButtonImpl(const char* title, const char* summary, s32* value_pointer, const char* (*to_display_name_function)(s32 value, void* opaque), void* opaque, u32 count, bool enabled, float height, ImFont* font, ImFont* summary_font); template ALWAYS_INLINE static bool EnumChoiceButton(const char* title, const char* summary, DataType* value_pointer, const char* (*to_display_name_function)(DataType value), CountType count, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont) { s32 value = static_cast(*value_pointer); auto to_display_name_wrapper = [](s32 value, void* opaque) -> const char* { return (*static_cast(opaque))(static_cast(value)); }; if (EnumChoiceButtonImpl(title, summary, &value, to_display_name_wrapper, &to_display_name_function, static_cast(count), enabled, height, font, summary_font)) { *value_pointer = static_cast(value); return true; } else { return false; } } void BeginNavBar(float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING); void EndNavBar(); void NavTitle(const char* title, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont); void RightAlignNavButtons(u32 num_items = 0, float item_width = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, float item_height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY); bool NavButton(const char* title, bool is_active, bool enabled = true, float width = -1.0f, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont); bool NavTab(const char* title, bool is_active, bool enabled, float width, float height, const ImVec4& background, ImFont* font = UIStyle.LargeFont); 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, u32 color = IM_COL32(255, 255, 255, 255)); using FileSelectorCallback = std::function; using FileSelectorFilters = std::vector; bool IsFileSelectorOpen(); void OpenFileSelector(std::string_view title, bool select_directory, FileSelectorCallback callback, FileSelectorFilters filters = FileSelectorFilters(), std::string initial_directory = std::string()); void CloseFileSelector(); using ChoiceDialogCallback = std::function; using ChoiceDialogOptions = std::vector>; bool IsChoiceDialogOpen(); void OpenChoiceDialog(std::string_view title, bool checkable, ChoiceDialogOptions options, ChoiceDialogCallback callback); void CloseChoiceDialog(); using InputStringDialogCallback = std::function; bool IsInputDialogOpen(); void OpenInputStringDialog(std::string_view title, std::string message, std::string caption, std::string ok_button_text, InputStringDialogCallback callback); void CloseInputDialog(); using ConfirmMessageDialogCallback = std::function; using InfoMessageDialogCallback = std::function; using MessageDialogCallback = std::function; bool IsMessageBoxDialogOpen(); void OpenConfirmMessageDialog(std::string_view title, std::string message, ConfirmMessageDialogCallback callback, std::string yes_button_text = ICON_FA_CHECK " Yes", std::string no_button_text = ICON_FA_TIMES " No"); void OpenInfoMessageDialog(std::string_view title, std::string message, InfoMessageDialogCallback callback = {}, std::string button_text = ICON_FA_WINDOW_CLOSE " Close"); void OpenMessageDialog(std::string_view title, std::string message, MessageDialogCallback callback, std::string first_button_text, std::string second_button_text, std::string third_button_text); void CloseMessageDialog(); float GetNotificationVerticalPosition(); float GetNotificationVerticalDirection(); void SetNotificationVerticalPosition(float position, float direction); void OpenBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value); void UpdateBackgroundProgressDialog(const char* str_id, std::string message, s32 min, s32 max, s32 value); void CloseBackgroundProgressDialog(const char* str_id); bool IsBackgroundProgressDialogOpen(const char* str_id); /// Displays a loading screen with the logo, rendered with ImGui. Use when executing possibly-time-consuming tasks /// such as compiling shaders when starting up. void RenderLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1, s32 progress_value = -1); void OpenOrUpdateLoadingScreen(std::string_view image, std::string_view message, s32 progress_min = -1, s32 progress_max = -1, s32 progress_value = -1); bool IsLoadingScreenOpen(); void CloseLoadingScreen(); /// Renders a previously-configured loading screen. void RenderLoadingScreen(); void AddNotification(std::string key, float duration, std::string title, std::string text, std::string image_path); bool HasAnyNotifications(); void ClearNotifications(); void ShowToast(std::string title, std::string message, float duration = 10.0f); bool HasToast(); void ClearToast(); // Message callbacks. void GetChoiceDialogHelpText(SmallStringBase& dest); void GetFileSelectorHelpText(SmallStringBase& dest); void GetInputDialogHelpText(SmallStringBase& dest); // Wrapper for an animated popup dialog. class PopupDialog { public: PopupDialog(); ~PopupDialog(); ALWAYS_INLINE const std::string& GetTitle() const { return m_title; } ALWAYS_INLINE bool IsOpen() const { return (m_state != State::Inactive); } void StartClose(); void CloseImmediately(); void ClearState(); protected: enum class State { Inactive, ClosingTrigger, Open, OpeningTrigger, Opening, Closing, Reopening, }; static constexpr float OPEN_TIME = 0.2f; static constexpr float CLOSE_TIME = 0.1f; void SetTitleAndOpen(std::string title); bool BeginRender(float scaled_window_padding = LayoutScale(20.0f), float scaled_window_rounding = LayoutScale(20.0f), const ImVec2& scaled_window_size = ImVec2(0.0f, 0.0f)); void EndRender(); std::string m_title; float m_animation_time_remaining = 0.0f; State m_state = State::Inactive; }; } // namespace ImGuiFullscreen // Host UI triggers from Big Picture mode. namespace Host { /// Returns the name of the default Big Picture theme to use based on the host theme. const char* GetDefaultFullscreenUITheme(); /// Returns true if native file dialogs should be preferred over Big Picture. bool ShouldPreferHostFileSelector(); /// Opens a file selector dialog. using FileSelectorCallback = std::function; using FileSelectorFilters = std::vector; void OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback, FileSelectorFilters filters = FileSelectorFilters(), std::string_view initial_directory = std::string_view()); } // namespace Host