WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
ImGuiLayer.cpp
Go to the documentation of this file.
1#include "ImGuiLayer.h"
2
3#ifdef _WIN32
4#include <windows.h>
5#endif
6
7#include <algorithm>
8#include <filesystem>
9#include <set>
10#include <string>
11
12#include <glad/gl.h>
13#include <GLFW/glfw3.h>
14#include "imgui.h"
15#include "imgui_internal.h"
16#include "imgui_impl_glfw.h"
17#include "imgui_impl_opengl3.h"
18
19#include "Logger.h"
20#include "AppSettings.h"
21#include "AppState.h"
22#include "AppWindow.h"
23#include "CustomTitleBar.h"
24#include "ThemeManager.h"
25
26// ---- Panel open/close settings handler ------------------------------------
27// Persists UIState::panelOpen inside imgui_layout.ini as a
28// [UserData][Panels] section so open/close state survives across sessions.
29
30namespace {
31
32void* PanelReadOpen(ImGuiContext*, ImGuiSettingsHandler* handler, const char* name)
33{
34 if (strcmp(name, "Panels") == 0)
35 return handler->UserData;
36 return nullptr;
37}
38
39void PanelReadLine(ImGuiContext*, ImGuiSettingsHandler*, void* entry, const char* line)
40{
41 auto* ui = static_cast<UIState*>(entry);
42 const char* eq = strchr(line, '=');
43 if (!eq) return;
44 std::string key(line, eq);
45 ui->panelOpen[key] = (atoi(eq + 1) != 0);
46}
47
48void PanelWriteAll(ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* buf)
49{
50 const auto* ui = static_cast<const UIState*>(handler->UserData);
51 buf->appendf("[UserData][Panels]\n");
52 for (const auto& [name, open] : ui->panelOpen)
53 buf->appendf("%s=%d\n", name.c_str(), open ? 1 : 0);
54 buf->append("\n");
55}
56
57} // anonymous namespace
58
59// ---- init / shutdown ------------------------------------------------------
60
61bool ImGuiLayer::init(GLFWwindow* window, float dpiScale, UIState* uiState)
62{
63 IMGUI_CHECKVERSION();
64 ImGui::CreateContext();
65 ImGuiIO& io = ImGui::GetIO();
66 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
67 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
68 io.IniFilename = AppSettings::imguiIniPath;
69
70 // Register panel open/close handler before the ini file is loaded.
71 ImGuiSettingsHandler handler;
72 handler.TypeName = "UserData";
73 handler.TypeHash = ImHashStr("UserData");
74 handler.ReadOpenFn = PanelReadOpen;
75 handler.ReadLineFn = PanelReadLine;
76 handler.WriteAllFn = PanelWriteAll;
77 handler.UserData = uiState;
78 ImGui::GetCurrentContext()->SettingsHandlers.push_back(handler);
79
81
82 if (dpiScale > 1.0f)
83 ImGui::GetStyle().ScaleAllSizes(dpiScale);
84
85 ImGui_ImplGlfw_InitForOpenGL(window, true);
86 ImGui_ImplOpenGL3_Init("#version 130");
87
88 return true;
89}
90
92{
93 ImGui_ImplOpenGL3_Shutdown();
94 ImGui_ImplGlfw_Shutdown();
95 ImGui::DestroyContext();
96}
97
98// ---- Font discovery -------------------------------------------------------
99
100void ImGuiLayer::discoverFonts(std::vector<FontEntry>& fonts, int& selectedFont)
101{
102 namespace fs = std::filesystem;
103
104 std::vector<fs::path> searchDirs;
105#ifdef _WIN32
106 {
107 wchar_t exePath[MAX_PATH]{};
108 GetModuleFileNameW(nullptr, exePath, MAX_PATH);
109 searchDirs.push_back(fs::path(exePath).parent_path() / "fonts");
110 }
111#endif
112#ifdef WMV_FONTS_PATH
113 searchDirs.push_back(fs::path(WMV_FONTS_PATH));
114#endif
115 searchDirs.push_back(fs::current_path() / "fonts");
116
117 std::set<std::string> seen;
118 for (const auto& dir : searchDirs)
119 {
120 if (!fs::is_directory(dir))
121 continue;
122 for (const auto& entry : fs::directory_iterator(dir))
123 {
124 if (!entry.is_regular_file())
125 continue;
126 auto ext = entry.path().extension().string();
127 std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
128 if (ext != ".ttf" && ext != ".otf")
129 continue;
130 std::string stemName = entry.path().stem().string();
131 std::string stemLower = stemName;
132 std::transform(stemLower.begin(), stemLower.end(), stemLower.begin(), ::tolower);
133 if (seen.count(stemLower))
134 continue;
135 seen.insert(stemLower);
136 std::string absPath = fs::canonical(entry.path()).string();
137 fonts.push_back({ stemName, absPath });
138 }
139 }
140
141 std::sort(fonts.begin(), fonts.end(),
142 [](const FontEntry& a, const FontEntry& b) { return a.name < b.name; });
143
144 if (selectedFont <= 0)
145 {
146 const char* preferred[] = { "roboto-regular", "arialn" };
147 for (const char* target : preferred)
148 {
149 bool found = false;
150 for (int i = 0; i < static_cast<int>(fonts.size()); ++i)
151 {
152 std::string lower = fonts[i].name;
153 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
154 if (lower == target)
155 {
156 selectedFont = i;
157 found = true;
158 break;
159 }
160 }
161 if (found)
162 break;
163 }
164 }
165}
166
167// ---- Font atlas -----------------------------------------------------------
168
169void ImGuiLayer::buildFontAtlas(const std::vector<FontEntry>& fonts,
170 int selectedFont,
171 float fontSize,
172 float dpiScale)
173{
174 ImGuiIO& io = ImGui::GetIO();
175 const float pixelSize = fontSize * dpiScale;
176 bool loaded = false;
177
178 if (selectedFont >= 0 && selectedFont < static_cast<int>(fonts.size()))
179 {
180 const auto& fe = fonts[selectedFont];
181 if (std::filesystem::exists(fe.path))
182 {
183 io.Fonts->AddFontFromFileTTF(fe.path.c_str(), pixelSize);
184 loaded = true;
185 LOG_INFO << "Loaded font:" << fe.name << "at" << pixelSize << "px";
186 }
187 }
188 if (!loaded)
189 io.Fonts->AddFontDefault();
190
192 io.FontGlobalScale = 1.0f;
193}
194
196 const std::vector<FontEntry>& fonts,
197 int selectedFont,
198 float fontSize,
199 float dpiScale)
200{
201 if (!fontsDirty)
202 return;
203
204 fontsDirty = false;
205 ImGuiIO& io = ImGui::GetIO();
206 io.Fonts->Clear();
207
208 const float pixelSize = fontSize * dpiScale;
209 bool loaded = false;
210
211 if (selectedFont >= 0 && selectedFont < static_cast<int>(fonts.size()))
212 {
213 const auto& fe = fonts[selectedFont];
214 if (std::filesystem::exists(fe.path))
215 {
216 io.Fonts->AddFontFromFileTTF(fe.path.c_str(), pixelSize);
217 loaded = true;
218 }
219 }
220 if (!loaded)
221 io.Fonts->AddFontDefault();
222
224 io.FontGlobalScale = 1.0f;
225 io.Fonts->Build();
226}
227
228// ---- Per-frame helpers ----------------------------------------------------
229
231{
232 ImGui_ImplOpenGL3_NewFrame();
233 ImGui_ImplGlfw_NewFrame();
234 ImGui::NewFrame();
235}
236
238{
239 ImGui::Render();
240
241 int w, h;
242 window.framebufferSize(w, h);
243 glViewport(0, 0, w, h);
244 glClearColor(0.1f, 0.1f, 0.12f, 1.0f);
245 glClear(GL_COLOR_BUFFER_BIT);
246
247 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
248}
#define LOG_INFO
Definition Logger.h:10
void framebufferSize(int &w, int &h) const
Retrieve the framebuffer size in pixels.
void rebuildFontAtlasIfDirty(bool &fontsDirty, const std::vector< FontEntry > &fonts, int selectedFont, float fontSize, float dpiScale)
If the fontsDirty flag is set, rebuild the atlas and clear the flag.
void shutdown()
Shut down ImGui backends and destroy the context.
void discoverFonts(std::vector< FontEntry > &fonts, int &selectedFont)
void endFrame(const AppWindow &window)
bool init(GLFWwindow *window, float dpiScale, UIState *uiState)
void beginFrame()
Start a new ImGui frame (backend NewFrame + ImGui::NewFrame).
void buildFontAtlas(const std::vector< FontEntry > &fonts, int selectedFont, float fontSize, float dpiScale)
Build (or rebuild) the font atlas from the currently selected font.
void mergeIconFont(float pixelSize)
void apply(Theme theme, GLFWwindow *window)
Theme currentTheme() noexcept
Currently active theme index.
static constexpr const char * imguiIniPath
Definition AppSettings.h:26
Font entry for the UI font selector.
Definition AppState.h:47
Mutable state for the UI layer: dialogs, fonts, log viewer, folder picker.
Definition AppState.h:110