Unravel Engine C++ Reference
Loading...
Searching...
No Matches
imgui_messagebox.cpp
Go to the documentation of this file.
1#include "imgui_messagebox.h"
3#include "imgui/imgui.h"
4#include <algorithm>
5
6namespace ImBox
7{
8
9// Static member initialization
10int MsgBox::next_id_counter_ = 1;
11
12MsgBox::MsgBox(const std::string& title, const std::string& message, int buttons, const MsgBoxConfig& config)
13 : title_(title)
14 , message_(message)
15 , buttons_(buttons)
16 , config_(config)
17 , id_counter_(next_id_counter_++)
18 , animation_state_(AnimationState::Closed)
19 , animation_progress_(0.0f)
20 , custom_icon_(nullptr)
21{
22 CalculateButtonLayout();
23}
24
25MsgBox::~MsgBox() = default;
26
27auto MsgBox::OpenPopup(std::function<void(ModalResult)> callback) -> void
28{
29 callback_ = std::move(callback);
30 animation_state_ = AnimationState::Opening;
31 animation_start_ = std::chrono::steady_clock::now();
32 animation_progress_ = 0.0f;
33 open_requested_ = true;
34}
35
36auto MsgBox::Draw() -> bool
37{
38 UpdateAnimation();
39
40 if (animation_state_ == AnimationState::Closed)
41 {
42 return false;
43 }
44
45 // Apply animation alpha
46 float alpha = GetAnimationAlpha();
47 ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
48
49 // Set up window positioning and styling
50 if (config_.center_on_screen)
51 {
52 ImVec2 center = ImGui::GetMainViewport()->GetCenter();
53 ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, {0.5f, 0.5f});
54 }
55
56 // Apply custom background color if specified
57 if (config_.background_color.w > 0.0f)
58 {
59 ImGui::PushStyleColor(ImGuiCol_PopupBg, config_.background_color);
60 }
61
62 // Enhanced window padding and rounding
63 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {20, 20});
64 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f);
65 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, {12, 8});
66
67 // Window flags
68 ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings;
69 if (!config_.allow_resize)
70 {
71 flags |= ImGuiWindowFlags_NoResize;
72 }
73 if (!config_.show_close_button)
74 {
75 flags |= ImGuiWindowFlags_NoTitleBar;
76 }
77
78 bool is_open = true;
79 // Create unique window title using the ID counter to avoid conflicts with same titles
80 std::string window_title = config_.show_close_button ?
81 (title_ + "###MsgBox" + std::to_string(id_counter_)) :
82 ("###MsgBox" + std::to_string(id_counter_));
83
84 if (open_requested_)
85 {
86 ImGui::OpenPopup(window_title.c_str());
87 open_requested_ = false;
88 }
89
90 if (ImGui::BeginPopupModal(window_title.c_str(), &is_open, flags))
91 {
92 // Constrain window size
93 ImVec2 window_size = ImGui::GetWindowSize();
94 if (window_size.x < config_.min_size.x || window_size.y < config_.min_size.y ||
95 window_size.x > config_.max_size.x || window_size.y > config_.max_size.y)
96 {
97 ImVec2 new_size = {
98 std::max(config_.min_size.x, std::min(config_.max_size.x, window_size.x)),
99 std::max(config_.min_size.y, std::min(config_.max_size.y, window_size.y))
100 };
101 ImGui::SetWindowSize(new_size);
102 }
103
104 // Draw content
105 DrawIcon();
106 ImGui::SameLine();
107 DrawMessage();
108
109 ImGui::Spacing();
110 ImGui::Separator();
111 ImGui::Spacing();
112
113 DrawButtons();
114
115 ImGui::EndPopup();
116 }
117 else if (!is_open && animation_state_ != AnimationState::Closing)
118 {
119 // User closed via X button or Escape
120 animation_state_ = AnimationState::Closing;
121 animation_start_ = std::chrono::steady_clock::now();
122 if (callback_)
123 {
124 callback_(ModalResult::Cancel);
125 }
126 }
127
128 // Clean up style variables
129 ImGui::PopStyleVar(3); // WindowPadding, WindowRounding, ItemSpacing
130
131 if (config_.background_color.w > 0.0f)
132 {
133 ImGui::PopStyleColor();
134 }
135
136 ImGui::PopStyleVar(); // Alpha
137
138 return animation_state_ != AnimationState::Closed;
139}
140
141auto MsgBox::CalculateButtonLayout() -> void
142{
143 button_layout_.clear();
144
145 // Define button order and labels
146 std::vector<std::pair<ModalResult, std::string>> button_definitions = {
147 {ModalResult::Save, "Save"},
148 {ModalResult::DontSave, "Don't Save"},
149 {ModalResult::Delete, "Delete"},
150 {ModalResult::CancelDelete, "Cancel"},
151 {ModalResult::Yes, "Yes"},
152 {ModalResult::YesToAll, "Yes to All"},
153 {ModalResult::Ok, "OK"},
154 {ModalResult::Apply, "Apply"},
155 {ModalResult::Retry, "Retry"},
156 {ModalResult::Ignore, "Ignore"},
157 {ModalResult::No, "No"},
158 {ModalResult::NoToAll, "No to All"},
159 {ModalResult::Abort, "Abort"},
160 {ModalResult::Cancel, "Cancel"},
161 {ModalResult::Close, "Close"},
162 {ModalResult::Discard, "Discard"},
163 {ModalResult::Help, "Help"},
164 {ModalResult::Reset, "Reset"}
165 };
166
167 for (const auto& [result, label] : button_definitions)
168 {
169 if (buttons_ & result)
170 {
171 button_layout_.push_back({result, label});
172 }
173 }
174}
175
176auto MsgBox::DrawIcon() -> void
177{
178 const char* icon = custom_icon_ ? custom_icon_ : GetTypeIcon();
179 if (!icon) return;
180
181 ImVec4 icon_color = GetTypeColor();
182 ImGui::PushStyleColor(ImGuiCol_Text, icon_color);
183
184 // Large icon
185 ImGui::PushFont(nullptr, ImGui::GetFontSize() * 2.0f);
186 ImGui::Text("%s", icon);
187 ImGui::PopFont();
188
189 ImGui::PopStyleColor();
190}
191
192auto MsgBox::DrawMessage() -> void
193{
194 ImGui::BeginGroup();
195
196 // Title (if not shown in window title bar)
197 if (!config_.show_close_button && !title_.empty())
198 {
199 ImGui::Text("%s", title_.c_str());
200 ImGui::Spacing();
201 }
202
203 // Message content with proper wrapping
204 float available_width = std::min(config_.max_size.x - 100.0f, 400.0f); // Reserve space for icon and padding
205 ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + available_width);
206 ImGui::TextWrapped("%s", message_.c_str());
207 ImGui::PopTextWrapPos();
208
209 ImGui::EndGroup();
210}
211
212auto MsgBox::DrawButtons() -> void
213{
214 if (button_layout_.empty()) return;
215
216 const float button_width = 100.0f;
217 const float button_height = 30.0f;
218 const float spacing = ImGui::GetStyle().ItemSpacing.x;
219
220 // Calculate total width needed
221 float total_width = button_layout_.size() * button_width + (button_layout_.size() - 1) * spacing;
222 float available_width = ImGui::GetContentRegionAvail().x;
223
224 // Center the buttons
225 if (total_width < available_width)
226 {
227 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (available_width - total_width) * 0.5f);
228 }
229
230 // Draw buttons
231 for (size_t i = 0; i < button_layout_.size(); ++i)
232 {
233 const auto& [result, label] = button_layout_[i];
234
235 if (i > 0) ImGui::SameLine();
236
237 // Set focus to first button when appearing
238 if (i == 0 && ImGui::IsWindowAppearing())
239 {
240 ImGui::SetKeyboardFocusHere();
241 }
242
243 // Apply button styling based on type
244 bool is_primary = (result == ModalResult::Ok || result == ModalResult::Yes || result == ModalResult::Save);
245 bool is_destructive = (result == ModalResult::No || result == ModalResult::Cancel || result == ModalResult::Delete ||
246 result == ModalResult::Abort || result == ModalResult::Discard);
247
248
249 if (is_primary)
250 {
251 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.6f, 0.2f, 1.0f));
252 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.3f, 0.7f, 0.3f, 1.0f));
253 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.1f, 0.5f, 0.1f, 1.0f));
254 }
255 else if (is_destructive)
256 {
257 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.6f, 0.2f, 0.2f, 1.0f));
258 ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.3f, 0.3f, 1.0f));
259 ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.1f, 0.1f, 1.0f));
260 }
261
262 bool clicked = ImGui::Button(label.c_str(), {button_width, button_height});
263
264 if (is_primary || is_destructive)
265 {
266 ImGui::PopStyleColor(3);
267 }
268
269 // Handle button click
270 if (clicked)
271 {
272 animation_state_ = AnimationState::Closing;
273 animation_start_ = std::chrono::steady_clock::now();
274 if (callback_)
275 {
276 callback_(result);
277 }
278 }
279
280 // Handle keyboard shortcuts
281 if ((result == ModalResult::Ok && buttons_ == ModalResult::Ok && ImGui::IsKeyPressed(ImGuiKey_Escape)) ||
282 (result == ModalResult::Cancel && ImGui::IsKeyPressed(ImGuiKey_Escape)) ||
283 (result == ModalResult::Cancel && ImGui::IsKeyPressed(ImGuiKey_C)) ||
284 (result == ModalResult::CancelDelete && ImGui::IsKeyPressed(ImGuiKey_Escape)) ||
285 (result == ModalResult::CancelDelete && ImGui::IsKeyPressed(ImGuiKey_C)) ||
286 (result == ModalResult::Ok && ImGui::IsKeyPressed(ImGuiKey_Enter)) ||
287 (result == ModalResult::Save && ImGui::IsKeyPressed(ImGuiKey_Enter)) ||
288 (result == ModalResult::Delete && ImGui::IsKeyPressed(ImGuiKey_Enter)) ||
289 (result == ModalResult::Yes && ImGui::IsKeyPressed(ImGuiKey_Y)) ||
290 (result == ModalResult::No && ImGui::IsKeyPressed(ImGuiKey_N)))
291 {
292 animation_state_ = AnimationState::Closing;
293 animation_start_ = std::chrono::steady_clock::now();
294 if (callback_)
295 {
296 callback_(result);
297 }
298 }
299 }
300}
301
302auto MsgBox::UpdateAnimation() -> void
303{
304 if (animation_state_ == AnimationState::Open || animation_state_ == AnimationState::Closed)
305 {
306 return;
307 }
308
309 auto now = std::chrono::steady_clock::now();
310 float elapsed = std::chrono::duration<float>(now - animation_start_).count();
311 animation_progress_ = std::min(1.0f, elapsed / config_.animation_duration);
312
313 if (animation_progress_ >= 1.0f)
314 {
315 if (animation_state_ == AnimationState::Opening)
316 {
317 animation_state_ = AnimationState::Open;
318 }
319 else if (animation_state_ == AnimationState::Closing)
320 {
321 animation_state_ = AnimationState::Closed;
322 ImGui::CloseCurrentPopup();
323 }
324 }
325}
326
327auto MsgBox::GetTypeIcon() const -> const char*
328{
329 switch (config_.type)
330 {
336 case MessageType::Custom: return nullptr;
337 default: return ICON_MDI_INFORMATION;
338 }
339}
340
341auto MsgBox::GetTypeColor() const -> ImVec4
342{
343 switch (config_.type)
344 {
345 case MessageType::Info: return ImVec4(0.3f, 0.7f, 1.0f, 1.0f); // Blue
346 case MessageType::Warning: return ImVec4(1.0f, 0.8f, 0.0f, 1.0f); // Orange
347 case MessageType::Error: return ImVec4(1.0f, 0.3f, 0.3f, 1.0f); // Red
348 case MessageType::Success: return ImVec4(0.3f, 0.8f, 0.3f, 1.0f); // Green
349 case MessageType::Question: return ImVec4(0.7f, 0.5f, 1.0f, 1.0f); // Purple
350 case MessageType::Custom: return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White
351 default: return ImVec4(0.3f, 0.7f, 1.0f, 1.0f); // Blue
352 }
353}
354
355auto MsgBox::GetAnimationAlpha() const -> float
356{
357 if (animation_state_ == AnimationState::Open)
358 {
359 return 1.0f;
360 }
361 else if (animation_state_ == AnimationState::Closed)
362 {
363 return 0.0f;
364 }
365 else if (animation_state_ == AnimationState::Opening)
366 {
367 // Smooth ease-out animation
368 float t = animation_progress_;
369 return t * t * (3.0f - 2.0f * t); // Smoothstep
370 }
371 else if (animation_state_ == AnimationState::Closing)
372 {
373 // Smooth ease-in animation
374 float t = 1.0f - animation_progress_;
375 return t * t * (3.0f - 2.0f * t); // Smoothstep
376 }
377
378 return 1.0f;
379}
380
381// MsgBoxManager implementation
382
384{
385 static MsgBoxManager instance;
386 return instance;
387}
388
390 const std::string& title,
391 const std::string& message,
392 int buttons,
393 const MsgBoxConfig& config,
394 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
395{
396 auto message_box = std::make_shared<MsgBox>(title, message, buttons, config);
397 message_box->OpenPopup(callback);
398 active_boxes_.push_back(message_box);
399 return message_box;
400}
401
403{
404 CleanupClosedBoxes();
405
406 // Render in reverse order so newer popups appear on top
407 for (auto it = active_boxes_.rbegin(); it != active_boxes_.rend(); ++it)
408 {
409 (*it)->Draw();
410 break;
411 }
412}
413
415{
416 active_boxes_.clear();
417}
418
419auto MsgBoxManager::CleanupClosedBoxes() -> void
420{
421 active_boxes_.erase(
422 std::remove_if(active_boxes_.begin(), active_boxes_.end(),
423 [](const std::shared_ptr<MsgBox>& box) {
424 return !box || !box->IsOpen();
425 }),
426 active_boxes_.end());
427}
428
429// Convenience functions
430
431auto ShowInfo(const std::string& title, const std::string& message,
432 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
433{
434 MsgBoxConfig config;
435 config.type = MessageType::Info;
436 return MsgBoxManager::GetInstance().ShowMessageBox(title, message, ModalResult::Ok, config, callback);
437}
438
439auto ShowWarning(const std::string& title, const std::string& message,
440 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
441{
442 MsgBoxConfig config;
443 config.type = MessageType::Warning;
444 return MsgBoxManager::GetInstance().ShowMessageBox(title, message, ModalResult::Ok, config, callback);
445}
446
447auto ShowError(const std::string& title, const std::string& message,
448 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
449{
450 MsgBoxConfig config;
451 config.type = MessageType::Error;
452 return MsgBoxManager::GetInstance().ShowMessageBox(title, message, ModalResult::Ok, config, callback);
453}
454
455auto ShowSuccess(const std::string& title, const std::string& message,
456 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
457{
458 MsgBoxConfig config;
459 config.type = MessageType::Success;
460 return MsgBoxManager::GetInstance().ShowMessageBox(title, message, ModalResult::Ok, config, callback);
461}
462
463auto ShowQuestion(const std::string& title, const std::string& message,
464 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
465{
466 MsgBoxConfig config;
468 return MsgBoxManager::GetInstance().ShowMessageBox(title, message,
469 ModalResult::Yes | ModalResult::No, config, callback);
470}
471
472auto ShowConfirmation(const std::string& title, const std::string& message,
473 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
474{
475 MsgBoxConfig config;
477 return MsgBoxManager::GetInstance().ShowMessageBox(title, message,
478 ModalResult::Ok | ModalResult::Cancel, config, callback);
479}
480
481auto ShowSaveConfirmation(const std::string& title, const std::string& message,
482 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
483{
484 MsgBoxConfig config;
486 return MsgBoxManager::GetInstance().ShowMessageBox(title, message,
488 config, callback);
489}
490
491auto ShowDeleteConfirmation(const std::string& title, const std::string& message,
492 std::function<void(ModalResult)> callback) -> std::shared_ptr<MsgBox>
493{
494 MsgBoxConfig config;
496 return MsgBoxManager::GetInstance().ShowMessageBox(title, message,
498}
499
500auto RenderMessageBoxes() -> void
501{
502 MsgBoxManager::GetInstance().RenderAll();
503}
504
505
506
507} // namespace ImBox
float elapsed
auto OpenPopup(std::function< void(ModalResult)> callback) -> void
Open the popup with callback.
MsgBox(const std::string &title, const std::string &message, int buttons, const MsgBoxConfig &config=MsgBoxConfig{})
Constructor with configuration.
auto Draw() -> bool
Draw the message box (returns true if still open)
~MsgBox()
Destructor.
Message box manager for handling multiple popups.
auto RenderAll() -> void
Render all active message boxes.
auto ShowMessageBox(const std::string &title, const std::string &message, int buttons=ModalResult::Ok, const MsgBoxConfig &config=MsgBoxConfig{}, std::function< void(ModalResult)> callback=nullptr) -> std::shared_ptr< MsgBox >
Create and show a message box.
static auto GetInstance() -> MsgBoxManager &
Get the singleton instance.
auto CloseAll() -> void
Close all message boxes.
#define ICON_MDI_ALERT
#define ICON_MDI_ALERT_CIRCLE
#define ICON_MDI_CHECK_CIRCLE
#define ICON_MDI_INFORMATION
#define ICON_MDI_HELP_CIRCLE
const char * icon
ModalResult
Modal result flags for message box buttons.
auto ShowDeleteConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a delete confirmation dialog with Delete/Cancel buttons.
auto ShowInfo(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show an information message box.
auto ShowSaveConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a save confirmation dialog with Save/Don't Save/Cancel buttons.
auto ShowSuccess(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a success message box.
auto ShowWarning(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a warning message box.
auto ShowQuestion(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a question message box with Yes/No buttons.
auto ShowConfirmation(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show a confirmation dialog with OK/Cancel buttons.
auto ShowError(const std::string &title, const std::string &message, std::function< void(ModalResult)> callback) -> std::shared_ptr< MsgBox >
Show an error message box.
AnimationState
Animation state for smooth transitions.
auto RenderMessageBoxes() -> void
Render all message boxes (call this in your main render loop)
void PushFont(Font::Enum _font)
Definition imgui.cpp:617
float x
Configuration for message box appearance and behavior.