Unravel Engine C++ Reference
Loading...
Searching...
No Matches
imgui_notify.h
Go to the documentation of this file.
1// imgui-notify by patrickcjk
2// https://github.com/patrickcjk/imgui-notify
3//
4// Enhanced with custom draw callback support:
5// - Set custom draw callbacks for notifications to add additional rendering AFTER text content
6// - Callbacks receive toast reference, opacity, and text color for theming
7// - Standard text content is rendered first, then callback is called for additional content
8//
9// Usage examples:
10//
11// 1. Basic text notification (existing functionality):
12// ImGui::PushNotification(ImGuiToast(ImGuiToastType_Info, "Hello World"));
13//
14// 2. Text + custom draw callback notification:
15// auto callback = [](const ImGuiToast& toast, float opacity, const ImVec4& text_color) {
16// ImGui::TextColored(text_color, "Additional content with opacity: %.2f", opacity);
17// ImGui::Button("Click me!");
18// };
19// ImGuiToast toast(ImGuiToastType_Success, callback);
20// toast.set_content("Main message");
21// ImGui::PushNotification(toast);
22//
23// 3. Title + text + custom draw:
24// ImGuiToast toast(ImGuiToastType_Warning, callback);
25// toast.set_title("Custom Title");
26// toast.set_content("Main content");
27// ImGui::PushNotification(toast);
28
29#ifndef IMGUI_NOTIFY
30#define IMGUI_NOTIFY
31
32#include "imgui/imgui.h"
33#include "imgui/imgui_internal.h"
34#pragma once
35#include <vector>
36#include <chrono>
37#include <functional>
39#include <imgui_includes.h>
40
41#define NOTIFY_MAX_TOASTS 10
42#define NOTIFY_MAX_MSG_LENGTH 4096 // Max message content length
43#define NOTIFY_PADDING_X 20.f // Bottom-left X padding
44#define NOTIFY_PADDING_Y 20.f // Bottom-left Y padding
45#define NOTIFY_PADDING_MESSAGE_Y 10.f // Padding Y between each message
46#define NOTIFY_FADE_IN_OUT_TIME 150 // Fade in and out duration
47#define NOTIFY_DEFAULT_DISMISS 3000 // Auto dismiss after X ms (default, applied only of no data provided in constructors)
48#define NOTIFY_OPACITY 1.0f // 0-1 Toast opacity
49#define NOTIFY_TOAST_FLAGS ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs
50// Comment out if you don't want any separator between title and content
51#define NOTIFY_USE_SEPARATOR
52#define NOTIFY_USE_DISMISS_BUTTON false // If true, a dismiss button will be rendered in the top right corner of the toast
53#define NOTIFY_RENDER_LIMIT 10
54
55#define NOTIFY_INLINE inline
56#define NOTIFY_NULL_OR_EMPTY(str) (!str ||! strlen(str))
57#define NOTIFY_FORMAT(fn, format, ...) if (format) { va_list args; va_start(args, format); fn(format, args, ##__VA_ARGS__); va_end(args); }
58
59typedef int ImGuiToastType;
60typedef int ImGuiToastPhase;
61typedef int ImGuiToastPos;
63
64// Forward declaration
65class ImGuiToast;
66
67// Draw callback function signature
68// Parameters: toast reference, opacity for fading, text_color for theming
69typedef std::function<void(const ImGuiToast&, float, const ImVec4&)> ImGuiToastDrawCallback;
70
80
89
101
108
110{
111private:
113 char title[NOTIFY_MAX_MSG_LENGTH];
114 char content[NOTIFY_MAX_MSG_LENGTH];
115 int dismiss_time = NOTIFY_DEFAULT_DISMISS;
116 uint64_t creation_time = 0;
117 ImGuiToastDrawCallback draw_callback = nullptr;
118 ImGuiWindowFlags window_flags = NOTIFY_TOAST_FLAGS;
119 bool show_dismiss_button = NOTIFY_USE_DISMISS_BUTTON;
120 std::function<void()> on_dismiss = nullptr;
122 ImVec2 frame_padding = ImVec2(20.0f, 20.0f);
123
124
125private:
126 // Setters
127
128 NOTIFY_INLINE auto set_title(const char* format, va_list args) { vsnprintf(this->title, sizeof(this->title), format, args); }
129
130 NOTIFY_INLINE auto set_content(const char* format, va_list args) { vsnprintf(this->content, sizeof(this->content), format, args); }
131
132
133public:
134 uint64_t unique_id = 0;
135
136 NOTIFY_INLINE auto set_title(const char* format, ...) -> void { NOTIFY_FORMAT(this->set_title, format); }
137
138 NOTIFY_INLINE auto set_content(const char* format, ...) -> void { NOTIFY_FORMAT(this->set_content, format); }
139
140 NOTIFY_INLINE auto set_type(const ImGuiToastType& type) -> void { IM_ASSERT(type < ImGuiToastType_COUNT); this->type = type; };
141
142 NOTIFY_INLINE auto set_draw_callback(const ImGuiToastDrawCallback& callback) -> void { this->draw_callback = callback; };
143
144 NOTIFY_INLINE auto set_show_dismiss_button(const bool& show) -> void { this->show_dismiss_button = show; }
145
146 NOTIFY_INLINE auto set_on_dismiss(const std::function<void()>& callback) -> void { this->on_dismiss = callback; }
153 NOTIFY_INLINE auto set_button_label(const char* format, ...) -> void
154 {
155 NOTIFY_FORMAT(this->set_button_label, format);
156 }
157
158 NOTIFY_INLINE auto set_window_flags(ImGuiWindowFlags flags) -> void { this->window_flags = flags; }
159
160 NOTIFY_INLINE auto set_horizontal_pos(const ImGuiToastHorizontalPos& pos) -> void { IM_ASSERT(pos < ImGuiToastHorizontalPos_COUNT); this->horizontal_pos = pos; }
161
162 NOTIFY_INLINE auto set_frame_padding(const ImVec2& frame_padding) -> void { this->frame_padding = frame_padding; }
163
164 NOTIFY_INLINE auto set_frame_padding(float x, float y) -> void { this->frame_padding = ImVec2(x, y); }
165public:
166 // Getters
167
168 NOTIFY_INLINE auto get_on_dismiss() -> const std::function<void()>& { return this->on_dismiss; }
169
170 NOTIFY_INLINE auto get_window_flags() -> const ImGuiWindowFlags& { return this->window_flags; }
171
172 NOTIFY_INLINE auto get_show_dismiss_button() -> const bool { return this->show_dismiss_button; }
173
174 NOTIFY_INLINE auto get_title() -> const char* { return this->title; };
175
176 NOTIFY_INLINE auto get_default_title() -> const char*
177 {
178 if (!NOTIFY_NULL_OR_EMPTY(this->title))
179 {
180 switch (this->type)
181 {
183 return nullptr;
185 return "Success";
187 return "Warning";
189 return "Error";
191 return "Info";
192 default:
193 return nullptr;
194 }
195 }
196
197 return this->title;
198 };
199
200 NOTIFY_INLINE auto get_type() -> const ImGuiToastType& { return this->type; };
201
202
203 static NOTIFY_INLINE auto get_color(ImGuiToastType type) -> const ImVec4
204 {
205 switch (type)
206 {
208 return { 255, 255, 255, 255 }; // White
210 return { 0, 255, 0, 255 }; // Green
212 return { 255, 255, 0, 255 }; // Yellow
214 return { 255, 0, 0, 255 }; // Error
216 return { 255, 255, 255, 255 }; // Blue
217 default:
218 return { 255, 255, 255, 255 }; // White
219 }
220 }
221
222 NOTIFY_INLINE auto get_color() -> const ImVec4
223 {
224 return get_color(this->type);
225 }
226
227 static NOTIFY_INLINE auto get_icon(ImGuiToastType type) -> const char*
228 {
229 switch (type)
230 {
232 return nullptr;
236 return ICON_MDI_ALERT_BOX;
241 default:
242 return nullptr;
243 }
244 }
245
246 NOTIFY_INLINE auto get_icon() -> const char*
247 {
248 return get_icon(this->type);
249 }
250
251 NOTIFY_INLINE auto get_content() -> char* { return this->content; };
252
253 NOTIFY_INLINE auto get_draw_callback() -> const ImGuiToastDrawCallback& { return this->draw_callback; };
254
255 NOTIFY_INLINE auto has_draw_callback() -> bool { return this->draw_callback != nullptr; };
256
257 NOTIFY_INLINE auto get_horizontal_pos() -> const ImGuiToastHorizontalPos& { return this->horizontal_pos; };
258
259 NOTIFY_INLINE auto get_frame_padding() -> const ImVec2& { return this->frame_padding; };
260
261 NOTIFY_INLINE auto get_elapsed_time() { return get_tick_count() - this->creation_time; }
262
264 {
265 const auto elapsed = get_elapsed_time();
266
267 if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismiss_time + NOTIFY_FADE_IN_OUT_TIME)
268 {
270 }
271 else if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismiss_time)
272 {
274 }
276 {
278 }
279 else
280 {
282 }
283 }
284
285 NOTIFY_INLINE auto get_fade_percent() -> const float
286 {
287 const auto phase = get_phase();
288 const auto elapsed = get_elapsed_time();
289
291 {
292 return ((float)elapsed / (float)NOTIFY_FADE_IN_OUT_TIME) * NOTIFY_OPACITY;
293 }
294 else if (phase == ImGuiToastPhase_FadeOut)
295 {
296 return (1.f - (((float)elapsed - (float)NOTIFY_FADE_IN_OUT_TIME - (float)this->dismiss_time) / (float)NOTIFY_FADE_IN_OUT_TIME)) * NOTIFY_OPACITY;
297 }
298
299 return 1.f * NOTIFY_OPACITY;
300 }
301
302 NOTIFY_INLINE static auto get_tick_count() -> const unsigned long long
303 {
304 using namespace std::chrono;
305 return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
306 }
307
308 NOTIFY_INLINE auto set_creation_time(uint64_t offset) -> const uint64_t { return this->creation_time = get_tick_count() + offset; }
309
310public:
311 // Constructors
312
314 {
315 IM_ASSERT(type < ImGuiToastType_COUNT);
316
317 this->type = type;
318 this->dismiss_time = dismiss_time;
319 this->set_creation_time(0);
320
321 memset(this->title, 0, sizeof(this->title));
322 memset(this->content, 0, sizeof(this->content));
323 }
324
325 ImGuiToast(ImGuiToastType type, const char* format, ...) : ImGuiToast(type) { NOTIFY_FORMAT(this->set_title, format); }
326
327 ImGuiToast(ImGuiToastType type, int dismiss_time, const char* format, ...) : ImGuiToast(type, dismiss_time) { NOTIFY_FORMAT(this->set_title, format); }
328
329 // Constructor with draw callback
331 : ImGuiToast(type, dismiss_time)
332 {
333 this->draw_callback = callback;
334 }
335};
336
337namespace ImGui
338{
339 NOTIFY_INLINE std::vector<ImGuiToast> notifications;
340
341
343 {
344 for(auto& toast : notifications)
345 {
346 if(toast.unique_id == unique_id)
347 {
348 return &toast;
349 }
350 }
351 return nullptr;
352 }
353
358 {
359 notifications.push_back(toast);
360 }
361
369 NOTIFY_INLINE void PushNotification(uint64_t unique_id, const ImGuiToast& toast)
370 {
371 auto notification = GetNotification(unique_id);
372 if(notification)
373 {
374 // Check current phase before updating
375 auto current_phase = notification->get_phase();
376 auto current_elapsed = notification->get_elapsed_time();
377
378 // Update the notification contents
379 *notification = toast;
380 notification->unique_id = unique_id;
381
382 // Refresh timing based on current phase
383 if(current_phase == ImGuiToastPhase_FadeIn)
384 {
385 // Still fading in - preserve current fade-in progress
386 notification->set_creation_time(static_cast<uint64_t>(-static_cast<int64_t>(current_elapsed)));
387 }
388 else if(current_phase == ImGuiToastPhase_Wait || current_phase == ImGuiToastPhase_FadeOut)
389 {
390 // Already fully visible or fading out - reset to start of wait phase
391 notification->set_creation_time(static_cast<uint64_t>(-static_cast<int64_t>(NOTIFY_FADE_IN_OUT_TIME)));
392 }
393 else
394 {
395 // Expired - start fresh
396 notification->set_creation_time(0);
397 }
398 }
399 else
400 {
401 notifications.push_back(toast);
402 notifications.back().unique_id = unique_id;
403 }
404 }
405
411 {
412 notifications.erase(notifications.begin() + index);
413 }
414
419 {
420 ImGuiViewport* viewport = GetMainViewport();
421 const auto vp_pos = viewport->WorkPos;
422 const auto vp_size = viewport->WorkSize;
423
424 float height_left = 0.f;
425 float height_right = 0.f;
426
427 for (auto i = 0; i < std::min<size_t>(notifications.size(), NOTIFY_MAX_TOASTS); i++)
428 {
429 auto* current_toast = &notifications[i];
430
431 // Remove toast if expired
432 if (current_toast->get_phase() == ImGuiToastPhase_Expired)
433 {
435 continue;
436 }
437
438 #if NOTIFY_RENDER_LIMIT > 0
439 if (i > NOTIFY_RENDER_LIMIT)
440 {
441 continue;
442 }
443 #endif
444
445 // Get icon, title and other data
446 const auto icon = current_toast->get_icon();
447 const auto title = current_toast->get_title();
448 const auto content = current_toast->get_content();
449 const auto default_title = current_toast->get_default_title();
450 const auto opacity = current_toast->get_fade_percent(); // Get opacity based of the current phase
451 const auto horizontal_pos = current_toast->get_horizontal_pos();
452
453 // Window rendering
454 auto text_color = current_toast->get_color();
455 text_color.w = opacity;
456
457 // Generate new unique name for this toast
458 char window_name[50]{};
459 snprintf(window_name, sizeof(window_name), "##TOAST%d", i);
460
461 //PushStyleColor(ImGuiCol_Text, text_color);
462 SetNextWindowBgAlpha(opacity);
463 //SetNextWindowSizeConstraints(ImVec2(180, 0), ImVec2(FLT_MAX, FLT_MAX ));
464
465 // Calculate window position based on horizontal position
466 ImVec2 window_pos;
467 ImVec2 pivot;
468 float* current_height;
469 const ImVec2& toast_frame_padding = current_toast->get_frame_padding();
470
471 if (horizontal_pos == ImGuiToastHorizontalPos_Left)
472 {
473 window_pos = vp_pos + ImVec2(NOTIFY_PADDING_X, vp_size.y - NOTIFY_PADDING_Y - height_left);
474 pivot = ImVec2(0.0f, 1.0f);
475 current_height = &height_left;
476 }
477 else
478 {
479 window_pos = vp_pos + ImVec2(vp_size.x - NOTIFY_PADDING_X, vp_size.y - NOTIFY_PADDING_Y - height_right);
480 pivot = ImVec2(1.0f, 1.0f);
481 current_height = &height_right;
482 }
483
484 SetNextWindowPos(window_pos, ImGuiCond_Always, pivot);
485
486 auto flags = current_toast->get_window_flags();
487 // Set notification window flags
488 if (current_toast->get_show_dismiss_button())
489 {
490 flags &= ~ImGuiWindowFlags_NoInputs;
491 flags &= ~ImGuiWindowFlags_NoBringToFrontOnFocus;
492 }
493
494 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, toast_frame_padding);
495 Begin(window_name, nullptr, flags);
496 PopStyleVar();
497 // Here we render the toast content
498 {
499 ImGui::BeginGroup();
500 PushTextWrapPos(vp_size.x / 3.f); // We want to support multi-line text, this will wrap the text after 1/3 of the screen width
501
502 bool was_title_rendered = false;
503
504 // If an icon is set
506 {
507 //Text(icon); // Render icon text
508 ImGui::AlignTextToFramePadding();
509 TextColored(text_color, "%s", icon);
510 // was_title_rendered = true;
511 }
512
513 // If a title is set
515 {
516 // If a title and an icon is set, we want to render on same line
518 SameLine();
519
520 ImGui::AlignTextToFramePadding();
521 Text("%s",title); // Render title text
522 was_title_rendered = true;
523 }
524 else if (!NOTIFY_NULL_OR_EMPTY(default_title))
525 {
527 SameLine();
528
529 ImGui::AlignTextToFramePadding();
530 Text("%s", default_title); // Render default title text (ImGuiToastType_Success -> "Success", etc...)
531 was_title_rendered = true;
532 }
533
534
535 // In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically
536 if (was_title_rendered && !NOTIFY_NULL_OR_EMPTY(content))
537 {
538 SetCursorPosY(GetCursorPosY() + 5.f); // Must be a better way to do this!!!!
539 }
540
541 // If a content is set, render default text
542 if (!NOTIFY_NULL_OR_EMPTY(content))
543 {
544 if (was_title_rendered)
545 {
546#ifdef NOTIFY_USE_SEPARATOR
547 Separator();
548#endif
549 }
550
551 ImGui::AlignTextToFramePadding();
552 Text("%s", content); // Render content text
553 }
554
555 // If a draw callback is set, call it after the text content
556 if (current_toast->has_draw_callback())
557 {
558 if(!was_title_rendered)
559 {
560 SameLine();
561 }
562
563 ImGui::BeginGroup();
564 // Call the custom draw callback for additional rendering
565 current_toast->get_draw_callback()(*current_toast, opacity, text_color);
566 ImGui::EndGroup();
567 }
568
569 PopTextWrapPos();
570 ImGui::EndGroup();
571
572
573 // If a dismiss button is enabled
574 if (current_toast->get_show_dismiss_button())
575 {
576 // If a title or content is set, we want to render the button on the same line
577 if (was_title_rendered || !NOTIFY_NULL_OR_EMPTY(content) || current_toast->has_draw_callback())
578 {
579 SameLine();
580 }
581 // If the button is pressed, we want to remove the notification
582 if (Button(ICON_MDI_CLOSE))
583 {
584 if(current_toast->get_on_dismiss())
585 {
586 current_toast->get_on_dismiss()();
587 }
589 }
590 }
591 }
592
593 // Save height for next toasts in the same stack
594 *current_height += GetWindowHeight() + NOTIFY_PADDING_MESSAGE_Y;
595
596 // End
597 End();
598 }
599 }
600
601}
602
603#endif
gfx::texture_format format
float elapsed
NOTIFY_INLINE auto get_title() -> const char *
NOTIFY_INLINE auto get_phase() -> const ImGuiToastPhase
NOTIFY_INLINE auto set_button_label(const char *format,...) -> void
Set the label for the button in the notification.
NOTIFY_INLINE auto get_show_dismiss_button() -> const bool
NOTIFY_INLINE auto get_color() -> const ImVec4
NOTIFY_INLINE auto get_on_dismiss() -> const std::function< void()> &
NOTIFY_INLINE auto get_elapsed_time()
NOTIFY_INLINE auto get_window_flags() -> const ImGuiWindowFlags &
ImGuiToast(ImGuiToastType type, const char *format,...)
static NOTIFY_INLINE auto get_tick_count() -> const unsigned long long
NOTIFY_INLINE auto set_creation_time(uint64_t offset) -> const uint64_t
NOTIFY_INLINE auto get_horizontal_pos() -> const ImGuiToastHorizontalPos &
NOTIFY_INLINE auto get_type() -> const ImGuiToastType &
NOTIFY_INLINE auto get_content() -> char *
static NOTIFY_INLINE auto get_icon(ImGuiToastType type) -> const char *
NOTIFY_INLINE auto get_draw_callback() -> const ImGuiToastDrawCallback &
NOTIFY_INLINE auto set_show_dismiss_button(const bool &show) -> void
NOTIFY_INLINE auto get_fade_percent() -> const float
NOTIFY_INLINE auto set_on_dismiss(const std::function< void()> &callback) -> void
uint64_t unique_id
NOTIFY_INLINE auto set_content(const char *format,...) -> void
NOTIFY_INLINE auto get_icon() -> const char *
NOTIFY_INLINE auto get_frame_padding() -> const ImVec2 &
NOTIFY_INLINE auto get_default_title() -> const char *
static NOTIFY_INLINE auto get_color(ImGuiToastType type) -> const ImVec4
NOTIFY_INLINE auto set_frame_padding(float x, float y) -> void
NOTIFY_INLINE auto set_title(const char *format,...) -> void
ImGuiToast(ImGuiToastType type, int dismiss_time, const char *format,...)
NOTIFY_INLINE auto set_horizontal_pos(const ImGuiToastHorizontalPos &pos) -> void
NOTIFY_INLINE auto set_window_flags(ImGuiWindowFlags flags) -> void
NOTIFY_INLINE auto set_type(const ImGuiToastType &type) -> void
NOTIFY_INLINE auto has_draw_callback() -> bool
NOTIFY_INLINE auto set_draw_callback(const ImGuiToastDrawCallback &callback) -> void
ImGuiToast(ImGuiToastType type, const ImGuiToastDrawCallback &callback, int dismiss_time=NOTIFY_DEFAULT_DISMISS)
ImGuiToast(ImGuiToastType type, int dismiss_time=NOTIFY_DEFAULT_DISMISS)
NOTIFY_INLINE auto set_frame_padding(const ImVec2 &frame_padding) -> void
float y
float x
const char * icon
const char * title
uint16_t index
#define ICON_MDI_ALERT_BOX
#define ICON_MDI_ALERT_CIRCLE
#define ICON_MDI_CLOSE
#define ICON_MDI_CHECK_CIRCLE
#define ICON_MDI_INFORMATION
#define NOTIFY_MAX_MSG_LENGTH
ImGuiToastType_
@ ImGuiToastType_None
@ ImGuiToastType_Error
@ ImGuiToastType_Warning
@ ImGuiToastType_Success
@ ImGuiToastType_Info
@ ImGuiToastType_COUNT
#define NOTIFY_PADDING_X
ImGuiToastHorizontalPos_
@ ImGuiToastHorizontalPos_Left
@ ImGuiToastHorizontalPos_COUNT
@ ImGuiToastHorizontalPos_Right
ImGuiToastPhase_
@ ImGuiToastPhase_Wait
@ ImGuiToastPhase_COUNT
@ ImGuiToastPhase_Expired
@ ImGuiToastPhase_FadeIn
@ ImGuiToastPhase_FadeOut
int ImGuiToastType
#define NOTIFY_RENDER_LIMIT
#define NOTIFY_FADE_IN_OUT_TIME
#define NOTIFY_MAX_TOASTS
#define NOTIFY_PADDING_MESSAGE_Y
#define NOTIFY_OPACITY
#define NOTIFY_DEFAULT_DISMISS
int ImGuiToastPos
#define NOTIFY_INLINE
#define NOTIFY_PADDING_Y
int ImGuiToastHorizontalPos
#define NOTIFY_TOAST_FLAGS
#define NOTIFY_NULL_OR_EMPTY(str)
#define NOTIFY_FORMAT(fn, format,...)
ImGuiToastPos_
@ ImGuiToastPos_COUNT
@ ImGuiToastPos_BottomCenter
@ ImGuiToastPos_TopRight
@ ImGuiToastPos_TopLeft
@ ImGuiToastPos_Center
@ ImGuiToastPos_BottomLeft
@ ImGuiToastPos_BottomRight
@ ImGuiToastPos_TopCenter
int ImGuiToastPhase
#define NOTIFY_USE_DISMISS_BUTTON
std::function< void(const ImGuiToast &, float, const ImVec4 &)> ImGuiToastDrawCallback
texture_job_type type
phase_t phase
NOTIFY_INLINE void PushNotification(const ImGuiToast &toast)
Insert a new toast in the list.
NOTIFY_INLINE void RemoveNotification(int index)
Remove a toast from the list by its index.
NOTIFY_INLINE ImGuiToast * GetNotification(uint64_t unique_id)
NOTIFY_INLINE void RenderNotifications()
Render toasts, call at the end of your rendering!
NOTIFY_INLINE std::vector< ImGuiToast > notifications