WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
SettingsPanel.cpp
Go to the documentation of this file.
1#include "SettingsPanel.h"
2
3#include <cassert>
4
5#ifdef _WIN32
6#include <windows.h>
7#endif
8
9#include <glad/gl.h>
10
11#include "imgui.h"
12#include "imgui_stdlib.h"
13
14#include "AppSettings.h"
15#include "OrbitCamera.h"
16#include "ThemeManager.h"
17#include "string_utils.h"
18
19#include <algorithm>
20#include <cstring>
21#include <filesystem>
22
23// Forward-declare the font entry type (defined in AppState.h)
24#include "AppState.h"
25
26// Game directory access for version / locale display
27#include "Game.h"
28#include "WoWFolder.h"
29
30namespace
31{
32
33// Refresh the folder-picker entry list.
34void folderPickerRefresh(SettingsPanel::DrawContext& ctx)
35{
36 ctx.folderPickerEntries->clear();
37 namespace fs = std::filesystem;
38 std::error_code ec;
39
40 if (ctx.folderPickerCurrent->empty())
41 {
42#ifdef _WIN32
43 DWORD drives = GetLogicalDrives();
44 for (int i = 0; i < 26; ++i)
45 {
46 if (drives & (1u << i))
47 {
48 std::string root = std::string(1, static_cast<char>('A' + i)) + ":\\";
49 ctx.folderPickerEntries->push_back(fs::path(root));
50 }
51 }
52#else
53 ctx.folderPickerEntries->push_back(fs::path("/"));
54#endif
55 }
56 else
57 {
58 for (auto& entry : fs::directory_iterator(*ctx.folderPickerCurrent,
59 fs::directory_options::skip_permission_denied, ec))
60 {
61 if (entry.is_directory(ec))
62 ctx.folderPickerEntries->push_back(entry.path());
63 }
64 std::sort(ctx.folderPickerEntries->begin(), ctx.folderPickerEntries->end(),
65 [](const fs::path& a, const fs::path& b)
66 {
67 return core::toLower(a.filename().string()) < core::toLower(b.filename().string());
68 });
69 }
70
71 *ctx.folderPickerNeedsRefresh = false;
72}
73
74// Open the folder picker, seeding from the current path buffer.
75void openFolderPicker(SettingsPanel::DrawContext& ctx)
76{
77 namespace fs = std::filesystem;
78 std::error_code ec;
79
80 fs::path startDir(*ctx.pathBuf);
81 if (fs::is_directory(startDir, ec))
82 *ctx.folderPickerCurrent = startDir;
83 else if (startDir.has_parent_path() && fs::is_directory(startDir.parent_path(), ec))
84 *ctx.folderPickerCurrent = startDir.parent_path();
85 else
86 ctx.folderPickerCurrent->clear();
87
88 *ctx.folderPickerNeedsRefresh = true;
89 *ctx.showFolderPicker = true;
90}
91
92} // anonymous namespace
93
95{
96 assert(ctx.pathBuf && "DrawContext::pathBuf must not be null");
97 assert(ctx.settings && "DrawContext::settings must not be null");
98 assert(ctx.showFolderPicker && "DrawContext::showFolderPicker must not be null");
99 assert(ctx.folderPickerCurrent && "DrawContext::folderPickerCurrent must not be null");
100 assert(ctx.folderPickerEntries && "DrawContext::folderPickerEntries must not be null");
101
102 // ---- Game loading section ----
103 ImGui::SeparatorText("World of Warcraft");
104
105 ImGui::Text("Game Data Path:");
106 float browseWidth = ImGui::CalcTextSize("Browse...").x + ImGui::GetStyle().FramePadding.x * 2.0f;
107 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - browseWidth - ImGui::GetStyle().ItemSpacing.x);
108 ImGui::InputText("##gamepath", ctx.pathBuf);
109 ImGui::SameLine();
110 if (ImGui::Button("Browse..."))
111 openFolderPicker(ctx);
112
113 // ---- Folder Picker popup ----
114 if (*ctx.showFolderPicker)
115 ImGui::OpenPopup("Select Folder##FolderPicker");
116
117 if (ImGui::BeginPopupModal("Select Folder##FolderPicker", ctx.showFolderPicker,
118 ImGuiWindowFlags_AlwaysAutoResize))
119 {
120 std::string curPathStr = ctx.folderPickerCurrent->empty()
121 ? "My Computer" : ctx.folderPickerCurrent->string();
122 ImGui::TextColored(ImVec4(0.4f, 0.8f, 1.0f, 1.0f), "%s", curPathStr.c_str());
123 ImGui::Separator();
124
126 folderPickerRefresh(ctx);
127
128 // Up / back button
129 {
130 bool canGoUp = !ctx.folderPickerCurrent->empty()
131 && ctx.folderPickerCurrent->has_parent_path()
132 && ctx.folderPickerCurrent->parent_path() != *ctx.folderPickerCurrent;
133 bool canGoRoot = !ctx.folderPickerCurrent->empty();
134 if (!canGoUp && !canGoRoot) ImGui::BeginDisabled();
135 if (ImGui::Button("Up"))
136 {
137 if (canGoUp)
138 *ctx.folderPickerCurrent = ctx.folderPickerCurrent->parent_path();
139 else
140 ctx.folderPickerCurrent->clear();
141 *ctx.folderPickerNeedsRefresh = true;
142 }
143 if (!canGoUp && !canGoRoot) ImGui::EndDisabled();
144 }
145
146 ImGui::SameLine();
147 ImGui::Text("%d folders", static_cast<int>(ctx.folderPickerEntries->size()));
148
149 // Folder list
150 ImGui::BeginChild("##FolderList", ImVec2(500, 400), ImGuiChildFlags_Borders);
151 ImGuiListClipper clipper;
152 clipper.Begin(static_cast<int>(ctx.folderPickerEntries->size()));
153 while (clipper.Step())
154 {
155 for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i)
156 {
157 const auto& p = (*ctx.folderPickerEntries)[i];
158 std::string displayName = ctx.folderPickerCurrent->empty()
159 ? p.string() : p.filename().string();
160 ImGui::PushID(i);
161 if (ImGui::Selectable(displayName.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick))
162 {
163 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left))
164 {
165 *ctx.folderPickerCurrent = p;
166 *ctx.folderPickerNeedsRefresh = true;
167 }
168 }
169 ImGui::PopID();
170 }
171 }
172 ImGui::EndChild();
173
174 ImGui::Separator();
175 if (ImGui::Button("Select This Folder", ImVec2(200, 0)))
176 {
177 if (!ctx.folderPickerCurrent->empty())
178 {
179 *ctx.pathBuf = ctx.folderPickerCurrent->string();
180 *ctx.showFolderPicker = false;
181 ImGui::CloseCurrentPopup();
182 }
183 }
184 ImGui::SameLine();
185 if (ImGui::Button("Cancel", ImVec2(120, 0)))
186 {
187 *ctx.showFolderPicker = false;
188 ImGui::CloseCurrentPopup();
189 }
190 ImGui::EndPopup();
191 }
192
193 if (ctx.isWoWLoaded)
194 {
195 ImGui::TextColored(ImVec4(0.4f, 1.0f, 0.4f, 1.0f), "Loaded: %s (%s)",
196 GAMEDIRECTORY.version().c_str(),
197 GAMEDIRECTORY.locale().c_str());
198 }
199 else if (ctx.loadInProgress)
200 {
201 ImGui::ProgressBar(ctx.loadProgress->load());
202 auto status = ctx.getLoadStatus();
203 ImGui::TextWrapped("%s", status.c_str());
204 }
205 else
206 {
207 auto status = ctx.getLoadStatus();
208 if (!status.empty())
209 ImGui::TextWrapped("%s", status.c_str());
210 else
211 ImGui::TextDisabled("Use File > Load WoW to load game data.");
212 }
213
214 ImGui::Checkbox("Enable Database Cache", &ctx.settings->enableDbCache);
215 ImGui::TextDisabled("Speeds up loading by caching the database. Takes effect on next load.");
216
217 // ---- General section ----
218 ImGui::SeparatorText("General");
219 if (ImGui::Checkbox("Show Console Window", &ctx.settings->showConsole))
220 {
221#ifdef _WIN32
222 if (HWND hConsole = GetConsoleWindow())
223 ShowWindow(hConsole, ctx.settings->showConsole ? SW_SHOW : SW_HIDE);
224#endif
225 }
226 ImGui::TextDisabled("Shows/hides the debug console. Useful for diagnostics.");
227
228 // ---- Theme selector ----
229 ImGui::SeparatorText("Appearance");
230 ImGui::Text("Theme:");
231 ImGui::SetNextItemWidth(-1);
232 if (ImGui::Combo("##Theme", &ThemeManager::currentThemeRef(),
235
236 // ---- Font selector ----
237 ImGui::Text("Font:");
238 ImGui::SetNextItemWidth(-1);
239 if (!ctx.availableFonts->empty())
240 {
241 if (ImGui::BeginCombo("##Font",
242 (ctx.settings->currentFont >= 0 &&
243 ctx.settings->currentFont < static_cast<int>(ctx.availableFonts->size()))
244 ? (*ctx.availableFonts)[ctx.settings->currentFont].name.c_str() : "Default"))
245 {
246 for (int i = 0; i < static_cast<int>(ctx.availableFonts->size()); ++i)
247 {
248 ImGui::PushID(i);
249 const bool selected = (i == ctx.settings->currentFont);
250 if (ImGui::Selectable((*ctx.availableFonts)[i].name.c_str(), selected))
251 {
252 ctx.settings->currentFont = i;
253 *ctx.fontsDirty = true;
254 }
255 if (selected)
256 ImGui::SetItemDefaultFocus();
257 ImGui::PopID();
258 }
259 ImGui::EndCombo();
260 }
261 }
262 else
263 {
264 ImGui::TextDisabled("No .ttf/.otf files found in fonts/ directory.");
265 }
266
267 ImGui::Text("Font Size:");
268 ImGui::SetNextItemWidth(-1);
269 if (ImGui::SliderFloat("##FontSize", &ctx.settings->fontSize, 10.0f, 40.0f, "%.0f px"))
270 *ctx.fontsDirty = true;
271 ImGui::TextDisabled("Drop .ttf or .otf files into the fonts/ folder to add more.");
272
273 ImGui::Separator();
274 ImGui::Checkbox("ImGui Demo Window", ctx.showDemoWindow);
275 ImGui::Separator();
276 ImGui::Text("Camera yaw=%.1f pitch=%.1f radius=%.2f",
277 ctx.camera->yaw(), ctx.camera->pitch(), ctx.camera->radius());
278 ImGui::Text("GL_RENDERER: %s", glGetString(GL_RENDERER));
279
280 // ---- Save ----
281 ImGui::SeparatorText("Save");
282 if (ImGui::Button("Save Settings", ImVec2(-1, 0)))
283 {
284 ctx.settings->gamePath = *ctx.pathBuf;
285 ctx.settings->save();
286 }
287 ImGui::TextDisabled("Saves preferences and UI layout.");
288}
#define GAMEDIRECTORY
Definition Game.h:9
float radius() const
Current orbit radius (distance to target).
Definition OrbitCamera.h:60
float yaw() const
Current yaw angle in radians.
Definition OrbitCamera.h:45
float pitch() const
Current pitch angle in radians.
Definition OrbitCamera.h:48
void draw(DrawContext &ctx)
void apply(Theme theme, GLFWwindow *window)
constexpr int themeCount() noexcept
Number of available themes.
const char *const * themeNames() noexcept
Pointer to the contiguous name array (for ImGui::Combo items).
int & currentThemeRef() noexcept
Writable reference so ImGui::Combo can bind directly.
Theme currentTheme() noexcept
Currently active theme index.
bool enableDbCache
Definition AppSettings.h:13
std::string gamePath
Definition AppSettings.h:10
bool showConsole
Definition AppSettings.h:14
float fontSize
Definition AppSettings.h:22
void save() const
Persist to Config.ini and save the ImGui layout to disk.
Per-frame context for the settings panel.
std::filesystem::path * folderPickerCurrent
std::vector< std::filesystem::path > * folderPickerEntries
std::function< std::string()> getLoadStatus
std::vector< FontEntry > * availableFonts
std::atomic< float > * loadProgress