#pragma once // HD-2D style game UI (Octopath Traveler / Triangle Strategy look) drawn with // Dear ImGui draw lists: translucent navy panels with a gold double border and // corner gems, serif text with a soft shadow, name plates, menus, progress // bars, a typewriter dialogue box with choices, and toast notifications. // // Everything draws into an ImDrawList (usually ImGui::GetForegroundDrawList()), // so no ImGui windows are needed. Sizes are in pixels at a 1080 px tall // viewport; multiply by scaleFor(viewport) (the widgets taking `scale` or a // viewport do this for you). // // Setup: DebugUI::init(), then ui::loadFonts(dir, ui::scaleFor(viewport)) // before the first ImGui frame (or between frames to rebake after a resize). #include #include #include #include namespace hd2d::ui { // Game fonts. `body` dialogue/menu text (serif), `title` large display face, // `name` speaker / name plates (small caps serif), `small` captions and toasts. // ImGui's default font stays the default so debug panels are unaffected. struct Fonts { ImFont* body = nullptr; ImFont* title = nullptr; ImFont* name = nullptr; ImFont* small = nullptr; }; // Nominal font sizes (px at 1080p). Fonts are baked at size * pixelScale so // text drawn at size * scaleFor(viewport) is pixel-exact. inline constexpr float kBodySize = 34.0f; inline constexpr float kTitleSize = 96.0f; inline constexpr float kNameSize = 28.0f; inline constexpr float kSmallSize = 26.0f; // Loads the fonts from `fontDir` (assets/fonts) baked for `pixelScale` and // rebuilds the ImGui font atlas. Requires an ImGui context (DebugUI::init) // and must be called outside an ImGui frame. Returns false if a font file is // missing (the missing slots fall back to the default font). bool loadFonts(const std::string& fontDir, float pixelScale = 1.0f); const Fonts& fonts(); // Styles standard ImGui widgets (windows, buttons, input text, sliders, combo, // checkboxes, tabs, scrollbars) in the same navy + gold palette. void applyTheme(); // UI scale for a viewport in pixels: 1.0 at 1080 px tall. float scaleFor(ImVec2 viewport); // Shared palette. inline constexpr ImU32 kGold = IM_COL32(222, 189, 120, 255); inline constexpr ImU32 kGoldDim = IM_COL32(150, 122, 74, 255); inline constexpr ImU32 kTextColor = IM_COL32(246, 240, 226, 255); inline constexpr ImU32 kTextMuted = IM_COL32(184, 178, 164, 255); // `alpha` is the background opacity; `tint` modulates the border/ornament // colour and its alpha fades the whole panel (shadow, background, border). // `rounding` is in 1080p pixels; `scale` 0 = scaleFor(ImGui display size). struct PanelStyle { float alpha = 0.88f; bool ornate = true; float rounding = 6.0f; ImU32 tint = IM_COL32(255, 255, 255, 255); float scale = 0.0f; }; void panel(ImDrawList* dl, ImVec2 min, ImVec2 max, const PanelStyle& style = {}); // Text with a 1px dark drop shadow. `font` may be null (current ImGui font). void text(ImDrawList* dl, ImFont* font, float size, ImVec2 pos, ImU32 color, std::string_view s, bool shadow = true); // Word-wrapped variant of text(); wrapWidth <= 0 disables wrapping. void textWrapped(ImDrawList* dl, ImFont* font, float size, ImVec2 pos, ImU32 color, std::string_view s, float wrapWidth, bool shadow = true); ImVec2 measure(ImFont* font, float size, std::string_view s, float wrapWidth = 0.0f); void textCentered(ImDrawList* dl, ImFont* font, float size, ImVec2 center, ImU32 color, std::string_view s, bool shadow = true); // Big glowing gold title with flourish rules underneath (title screens). void titleText(ImDrawList* dl, ImVec2 center, std::string_view s, float size); // Small pill label whose pointer tip sits on `anchorPx` (e.g. a projected // head position). `accent` colours the border and pointer. void nameplate(ImDrawList* dl, ImVec2 anchorPx, std::string_view s, ImU32 accent, float scale = 1.0f); // Vertical option list in a panel with an animated cursor on `selected`. void menu(ImDrawList* dl, ImVec2 center, const std::vector& items, int selected, std::string_view title = {}, float scale = 1.0f); // Same as menu(), placed so that `pivot` (0..1 of the panel size) lands on `pos`. void menuAt(ImDrawList* dl, ImVec2 pos, ImVec2 pivot, const std::vector& items, int selected, std::string_view title = {}, float scale = 1.0f); // Panel size menu()/menuAt() will use. ImVec2 menuSize(const std::vector& items, std::string_view title = {}, float scale = 1.0f); // Recessed bar filled to t (0..1) with a vertical gradient of `color`. void progressBar(ImDrawList* dl, ImVec2 min, ImVec2 max, float t, ImU32 color); // Small blinking/bobbing gem pointing down ("more text" indicator); `time` drives the animation. void advanceIndicator(ImDrawList* dl, ImVec2 center, float scale, float time); struct DialogueLine { std::string speaker; // empty = no name plate std::string text; // UTF-8, word-wrapped to the box ImTextureID portrait = 0; // optional, drawn square on the left inside the box ImVec2 uv0{0, 0}, uv1{1, 1}; }; // Bottom-centre typewriter dialogue box. Typical loop: // if (confirmPressed && box.advance()) handle(box.choice()); // box.update(dt); box.draw(viewport); class DialogueBox { public: void start(std::vector lines, std::vector choices = {}); // choices shown with the last line bool isOpen() const { return open_; } void update(float dt); bool advance(); // reveal the rest of the line, else next line; returns true when the conversation closed void moveChoice(int delta); int choice() const { return chosen_; } // chosen index once closed with choices, else -1 void close(); void draw(ImVec2 viewport); // draws into ImGui::GetForegroundDrawList() bool lineRevealed() const; // current line fully shown bool choicesVisible() const; // last line revealed and choices pending float charsPerSecond = 48.0f; float sentencePause = 0.22f; // extra seconds after . ! ? and an ellipsis float clausePause = 0.09f; // extra seconds after , ; : and dashes private: void stepChar(); std::vector lines_; std::vector choices_; size_t line_ = 0; size_t revealed_ = 0; // bytes of the current line shown (always on a UTF-8 boundary) float budget_ = 0.0f; // seconds banked for the typewriter float time_ = 0.0f; int selected_ = 0; int chosen_ = -1; bool open_ = false; }; // Stacked notification banners at the top centre that slide in and fade out. class Toasts { public: void push(std::string text, ImU32 accent = IM_COL32(230, 190, 90, 255), float seconds = 3.5f); void update(float dt); void draw(ImVec2 viewport); // draws into ImGui::GetForegroundDrawList() void clear() { items_.clear(); } private: struct Item { std::string text; ImU32 accent; float seconds, age = 0.0f; }; std::vector items_; }; } // namespace hd2d::ui