MetaImGUI 1.0.0
ImGui Application Template for C++20
Loading...
Searching...
No Matches
DialogManager.cpp
Go to the documentation of this file.
1/*
2 MetaImGUI
3 Copyright (C) 2026 A P Nicholson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17*/
18
19#include "DialogManager.h"
20
21#include "Localization.h"
22
23#include <imgui.h>
24
25#include <algorithm>
26#include <array>
27#include <iterator>
28#include <map>
29
30namespace MetaImGUI {
31
32// Internal dialog state structures
41
43 std::string title;
44 std::string prompt;
45 std::array<char, 256> inputBuffer{};
46 std::function<void(const std::string&)> callback;
47 bool open = true;
48};
49
51 int id = 0;
52 std::string title;
53 std::string message;
54 float progress = 0.0f;
55 bool open = true;
56};
57
59 std::string title;
60 std::vector<std::string> items;
61 int selectedIndex = -1;
62 std::function<void(int)> callback;
63 bool open = true;
64};
65
67 std::unique_ptr<MessageBoxState> messageBox;
68 std::unique_ptr<InputDialogState> inputDialog;
69 std::unique_ptr<ListDialogState> listDialog;
70 std::map<int, ProgressDialogState> progressDialogs;
72};
73
74DialogManager::DialogManager() : m_impl(std::make_unique<Impl>()) {}
75
77
79 if (m_impl->messageBox && m_impl->messageBox->open) {
80 RenderMessageBox();
81 }
82
83 if (m_impl->inputDialog && m_impl->inputDialog->open) {
84 RenderInputDialog();
85 }
86
87 if (!m_impl->progressDialogs.empty()) {
88 RenderProgressDialogs();
89 }
90
91 if (m_impl->listDialog && m_impl->listDialog->open) {
92 RenderListDialog();
93 }
94}
95
96void DialogManager::ShowMessageBox(const std::string& title, const std::string& message, MessageBoxButtons buttons,
97 MessageBoxIcon icon, std::function<void(MessageBoxResult)> callback) {
98 m_impl->messageBox = std::make_unique<MessageBoxState>();
99 m_impl->messageBox->title = title;
100 m_impl->messageBox->message = message;
101 m_impl->messageBox->buttons = buttons;
102 m_impl->messageBox->icon = icon;
103 m_impl->messageBox->callback = callback;
104 m_impl->messageBox->open = true;
105}
106
107void DialogManager::ShowInputDialog(const std::string& title, const std::string& prompt,
108 const std::string& defaultValue, std::function<void(const std::string&)> callback) {
109 m_impl->inputDialog = std::make_unique<InputDialogState>();
110 m_impl->inputDialog->title = title;
111 m_impl->inputDialog->prompt = prompt;
112 m_impl->inputDialog->callback = callback;
113 m_impl->inputDialog->open = true;
114
115 // Copy default value to buffer
116 auto& buffer = m_impl->inputDialog->inputBuffer;
117 strncpy(std::data(buffer), defaultValue.c_str(), std::size(buffer) - 1);
118 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
119 buffer[std::size(buffer) - 1] = '\0';
120}
121
122int DialogManager::ShowProgressDialog(const std::string& title, const std::string& message) {
123 const int id = m_impl->nextProgressId++;
125 state.id = id;
126 state.title = title;
127 state.message = message;
128 state.progress = 0.0f;
129 state.open = true;
130 m_impl->progressDialogs[id] = state;
131 return id;
132}
133
134void DialogManager::UpdateProgress(int dialogId, float progress, const std::string& message) {
135 auto it = m_impl->progressDialogs.find(dialogId);
136 if (it != m_impl->progressDialogs.end()) {
137 it->second.progress = std::clamp(progress, 0.0f, 1.0f);
138 if (!message.empty()) {
139 it->second.message = message;
140 }
141 }
142}
143
145 m_impl->progressDialogs.erase(dialogId);
146}
147
148void DialogManager::ShowListDialog(const std::string& title, const std::vector<std::string>& items,
149 std::function<void(int)> callback) {
150 m_impl->listDialog = std::make_unique<ListDialogState>();
151 m_impl->listDialog->title = title;
152 m_impl->listDialog->items = items;
153 m_impl->listDialog->selectedIndex = -1;
154 m_impl->listDialog->callback = callback;
155 m_impl->listDialog->open = true;
156}
157
158void DialogManager::ShowConfirmation(const std::string& title, const std::string& message,
159 std::function<void(bool)> callback) {
161 [callback](MessageBoxResult result) {
162 if (callback) {
163 callback(result == MessageBoxResult::Yes);
164 }
165 });
166}
167
169 return (m_impl->messageBox && m_impl->messageBox->open) || (m_impl->inputDialog && m_impl->inputDialog->open) ||
170 !m_impl->progressDialogs.empty() || (m_impl->listDialog && m_impl->listDialog->open);
171}
172
174 m_impl->messageBox.reset();
175 m_impl->inputDialog.reset();
176 m_impl->listDialog.reset();
177 m_impl->progressDialogs.clear();
178}
179
180// Rendering implementations
181
182void DialogManager::RenderMessageBox() {
183 auto& mb = m_impl->messageBox;
184 if (!mb || !mb->open) {
185 return;
186 }
187
188 ImGui::OpenPopup(mb->title.c_str());
189 const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
190 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
191
193
194 if (ImGui::BeginPopupModal(mb->title.c_str(), &mb->open,
195 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
196 // Icon and message
197 const char* iconText = nullptr;
198 ImVec4 iconColor = ImVec4(1, 1, 1, 1);
199 switch (mb->icon) {
201 iconText = "[i]";
202 iconColor = ImVec4(0.2f, 0.6f, 1.0f, 1.0f);
203 break;
205 iconText = "[!]";
206 iconColor = ImVec4(1.0f, 0.8f, 0.0f, 1.0f);
207 break;
209 iconText = "[X]";
210 iconColor = ImVec4(1.0f, 0.2f, 0.2f, 1.0f);
211 break;
213 iconText = "[?]";
214 iconColor = ImVec4(0.4f, 0.8f, 0.4f, 1.0f);
215 break;
216 }
217
218 ImGui::TextColored(iconColor, "%s", iconText);
219 ImGui::SameLine();
220 ImGui::TextWrapped("%s", mb->message.c_str());
221 ImGui::Spacing();
222 ImGui::Separator();
223 ImGui::Spacing();
224
225 // Buttons (translated)
226 auto& loc = Localization::Instance();
227 const float buttonWidth = 100.0f;
228 switch (mb->buttons) {
230 if (ImGui::Button(loc.Tr("button.ok").c_str(), ImVec2(buttonWidth, 0))) {
231 result = MessageBoxResult::OK;
232 mb->open = false;
233 }
234 break;
235
237 if (ImGui::Button(loc.Tr("button.ok").c_str(), ImVec2(buttonWidth, 0))) {
238 result = MessageBoxResult::OK;
239 mb->open = false;
240 }
241 ImGui::SameLine();
242 if (ImGui::Button(loc.Tr("button.cancel").c_str(), ImVec2(buttonWidth, 0))) {
244 mb->open = false;
245 }
246 break;
247
249 if (ImGui::Button(loc.Tr("button.yes").c_str(), ImVec2(buttonWidth, 0))) {
250 result = MessageBoxResult::Yes;
251 mb->open = false;
252 }
253 ImGui::SameLine();
254 if (ImGui::Button(loc.Tr("button.no").c_str(), ImVec2(buttonWidth, 0))) {
255 result = MessageBoxResult::No;
256 mb->open = false;
257 }
258 break;
259
261 if (ImGui::Button(loc.Tr("button.yes").c_str(), ImVec2(buttonWidth, 0))) {
262 result = MessageBoxResult::Yes;
263 mb->open = false;
264 }
265 ImGui::SameLine();
266 if (ImGui::Button(loc.Tr("button.no").c_str(), ImVec2(buttonWidth, 0))) {
267 result = MessageBoxResult::No;
268 mb->open = false;
269 }
270 ImGui::SameLine();
271 if (ImGui::Button(loc.Tr("button.cancel").c_str(), ImVec2(buttonWidth, 0))) {
273 mb->open = false;
274 }
275 break;
276
278 if (ImGui::Button(loc.Tr("button.retry").c_str(), ImVec2(buttonWidth, 0))) {
280 mb->open = false;
281 }
282 ImGui::SameLine();
283 if (ImGui::Button(loc.Tr("button.cancel").c_str(), ImVec2(buttonWidth, 0))) {
285 mb->open = false;
286 }
287 break;
288 }
289
290 ImGui::EndPopup();
291 }
292
293 // Handle close and callback
294 if (!mb->open && result != MessageBoxResult::None) {
295 if (mb->callback) {
296 mb->callback(result);
297 }
298 m_impl->messageBox.reset();
299 }
300}
301
302void DialogManager::RenderInputDialog() {
303 auto& id = m_impl->inputDialog;
304 if (!id || !id->open) {
305 return;
306 }
307
308 ImGui::OpenPopup(id->title.c_str());
309 const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
310 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
311
312 std::string result;
313 bool submitted = false;
314
315 if (ImGui::BeginPopupModal(id->title.c_str(), &id->open,
316 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
317 ImGui::Text("%s", id->prompt.c_str());
318 ImGui::Spacing();
319
320 ImGui::SetNextItemWidth(300.0f);
321 if (ImGui::InputText("##input", id->inputBuffer.data(), id->inputBuffer.size(),
322 ImGuiInputTextFlags_EnterReturnsTrue)) {
323 submitted = true;
324 result = id->inputBuffer.data();
325 id->open = false;
326 }
327
328 ImGui::Spacing();
329 ImGui::Separator();
330 ImGui::Spacing();
331
332 auto& loc = Localization::Instance();
333 if (ImGui::Button(loc.Tr("button.ok").c_str(), ImVec2(100, 0))) {
334 submitted = true;
335 result = id->inputBuffer.data();
336 id->open = false;
337 }
338 ImGui::SameLine();
339 if (ImGui::Button(loc.Tr("button.cancel").c_str(), ImVec2(100, 0))) {
340 submitted = false;
341 id->open = false;
342 }
343
344 ImGui::EndPopup();
345 }
346
347 // Handle close and callback
348 if (!id->open) {
349 if (id->callback) {
350 id->callback(submitted ? result : "");
351 }
352 m_impl->inputDialog.reset();
353 }
354}
355
356void DialogManager::RenderProgressDialogs() {
357 for (auto it = m_impl->progressDialogs.begin(); it != m_impl->progressDialogs.end();) {
358 auto& pd = it->second;
359
360 if (!pd.open) {
361 it = m_impl->progressDialogs.erase(it);
362 continue;
363 }
364
365 const std::string popupId = pd.title + "##" + std::to_string(pd.id);
366 ImGui::OpenPopup(popupId.c_str());
367 const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
368 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
369
370 if (ImGui::BeginPopupModal(popupId.c_str(), nullptr,
371 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
372 if (!pd.message.empty()) {
373 ImGui::Text("%s", pd.message.c_str());
374 ImGui::Spacing();
375 }
376
377 ImGui::ProgressBar(pd.progress, ImVec2(300, 0));
378 ImGui::Spacing();
379
380 // Show percentage
381 ImGui::Text("%.1f%%", pd.progress * 100.0f);
382
383 ImGui::EndPopup();
384 }
385
386 ++it;
387 }
388}
389
390void DialogManager::RenderListDialog() {
391 auto& ld = m_impl->listDialog;
392 if (!ld || !ld->open) {
393 return;
394 }
395
396 ImGui::OpenPopup(ld->title.c_str());
397 const ImVec2 center = ImGui::GetMainViewport()->GetCenter();
398 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
399
400 int selectedIndex = -1;
401 bool confirmed = false;
402
403 if (ImGui::BeginPopupModal(ld->title.c_str(), &ld->open,
404 ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
405 ImGui::BeginChild("ListBox", ImVec2(300, 200), ImGuiChildFlags_Border);
406
407 const auto itemCount = static_cast<int>(ld->items.size());
408 for (int i = 0; i < itemCount; ++i) {
409 const bool isSelected = (i == ld->selectedIndex);
410 if (ImGui::Selectable(ld->items[static_cast<size_t>(i)].c_str(), isSelected,
411 ImGuiSelectableFlags_AllowDoubleClick)) {
412 ld->selectedIndex = i;
413 if (ImGui::IsMouseDoubleClicked(0)) {
414 confirmed = true;
415 selectedIndex = ld->selectedIndex;
416 ld->open = false;
417 }
418 }
419 }
420
421 ImGui::EndChild();
422
423 ImGui::Spacing();
424 ImGui::Separator();
425 ImGui::Spacing();
426
427 auto& loc = Localization::Instance();
428 if (ImGui::Button(loc.Tr("button.ok").c_str(), ImVec2(100, 0)) && ld->selectedIndex >= 0) {
429 confirmed = true;
430 selectedIndex = ld->selectedIndex;
431 ld->open = false;
432 }
433 ImGui::SameLine();
434 if (ImGui::Button(loc.Tr("button.cancel").c_str(), ImVec2(100, 0))) {
435 ld->open = false;
436 }
437
438 ImGui::EndPopup();
439 }
440
441 // Handle close and callback
442 if (!ld->open) {
443 if (ld->callback) {
444 ld->callback(confirmed ? selectedIndex : -1);
445 }
446 m_impl->listDialog.reset();
447 }
448}
449
450} // namespace MetaImGUI
void CloseProgress(int dialogId)
Close progress dialog.
bool HasOpenDialog() const
Check if any dialog is currently open.
void ShowInputDialog(const std::string &title, const std::string &prompt, const std::string &defaultValue="", std::function< void(const std::string &)> callback=nullptr)
Show an input dialog.
void CloseAll()
Close all dialogs.
void Render()
Render all active dialogs Call this each frame in your main render loop.
void ShowMessageBox(const std::string &title, const std::string &message, MessageBoxButtons buttons=MessageBoxButtons::OK, MessageBoxIcon icon=MessageBoxIcon::Info, std::function< void(MessageBoxResult)> callback=nullptr)
Show a message box.
void ShowListDialog(const std::string &title, const std::vector< std::string > &items, std::function< void(int)> callback=nullptr)
Show a list selection dialog.
int ShowProgressDialog(const std::string &title, const std::string &message="")
Show a progress dialog.
void UpdateProgress(int dialogId, float progress, const std::string &message="")
Update progress dialog.
void ShowConfirmation(const std::string &title, const std::string &message, std::function< void(bool)> callback=nullptr)
Show a confirmation dialog (Yes/No)
static Localization & Instance()
Get singleton instance.
MessageBoxButtons
Types of message box buttons.
@ OKCancel
OK and Cancel buttons.
@ RetryCancel
Retry and Cancel buttons.
@ YesNoCancel
Yes, No, and Cancel buttons.
@ YesNo
Yes and No buttons.
MessageBoxResult
Result from message box.
MessageBoxIcon
Message box icons/types.
@ Info
Information icon.
@ Question
Question icon.
std::map< int, ProgressDialogState > progressDialogs
std::unique_ptr< ListDialogState > listDialog
std::unique_ptr< MessageBoxState > messageBox
std::unique_ptr< InputDialogState > inputDialog
std::function< void(const std::string &)> callback
std::array< char, 256 > inputBuffer
std::function< void(int)> callback
std::vector< std::string > items
std::function< void(MessageBoxResult)> callback