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#pragma once
33#include <vector>
34#include <chrono>
35#include <functional>
37#include <imgui_includes.h>
38
39#define NOTIFY_MAX_TOASTS 10
40#define NOTIFY_MAX_MSG_LENGTH 4096 // Max message content length
41#define NOTIFY_PADDING_X 20.f // Bottom-left X padding
42#define NOTIFY_PADDING_Y 20.f // Bottom-left Y padding
43#define NOTIFY_PADDING_MESSAGE_Y 10.f // Padding Y between each message
44#define NOTIFY_FADE_IN_OUT_TIME 150 // Fade in and out duration
45#define NOTIFY_DEFAULT_DISMISS 3000 // Auto dismiss after X ms (default, applied only of no data provided in constructors)
46#define NOTIFY_OPACITY 1.0f // 0-1 Toast opacity
47#define NOTIFY_TOAST_FLAGS ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing
48// Comment out if you don't want any separator between title and content
49#define NOTIFY_USE_SEPARATOR
50
51#define NOTIFY_INLINE inline
52#define NOTIFY_NULL_OR_EMPTY(str) (!str ||! strlen(str))
53#define NOTIFY_FORMAT(fn, format, ...) if (format) { va_list args; va_start(args, format); fn(format, args, ##__VA_ARGS__); va_end(args); }
54
55typedef int ImGuiToastType;
56typedef int ImGuiToastPhase;
57typedef int ImGuiToastPos;
58
59// Forward declaration
60class ImGuiToast;
61
62// Draw callback function signature
63// Parameters: toast reference, opacity for fading, text_color for theming
64typedef std::function<void(const ImGuiToast&, float, const ImVec4&)> ImGuiToastDrawCallback;
65
75
84
96
98{
99private:
101 char title[NOTIFY_MAX_MSG_LENGTH];
102 char content[NOTIFY_MAX_MSG_LENGTH];
103 int dismiss_time = NOTIFY_DEFAULT_DISMISS;
104 uint64_t creation_time = 0;
105 ImGuiToastDrawCallback draw_callback = nullptr;
106private:
107 // Setters
108
109 NOTIFY_INLINE auto set_title(const char* format, va_list args) { vsnprintf(this->title, sizeof(this->title), format, args); }
110
111 NOTIFY_INLINE auto set_content(const char* format, va_list args) { vsnprintf(this->content, sizeof(this->content), format, args); }
112
113public:
114 uint64_t unique_id = 0;
115
116 NOTIFY_INLINE auto set_title(const char* format, ...) -> void { NOTIFY_FORMAT(this->set_title, format); }
117
118 NOTIFY_INLINE auto set_content(const char* format, ...) -> void { NOTIFY_FORMAT(this->set_content, format); }
119
120 NOTIFY_INLINE auto set_type(const ImGuiToastType& type) -> void { IM_ASSERT(type < ImGuiToastType_COUNT); this->type = type; };
121
122 NOTIFY_INLINE auto set_draw_callback(const ImGuiToastDrawCallback& callback) -> void { this->draw_callback = callback; };
123
124public:
125 // Getters
126
127 NOTIFY_INLINE auto get_title() -> char* { return this->title; };
128
129 NOTIFY_INLINE auto get_default_title() -> const char*
130 {
131 if (!strlen(this->title))
132 {
133 switch (this->type)
134 {
136 return NULL;
138 return "Success";
140 return "Warning";
142 return "Error";
144 return "Info";
145 default:
146 return NULL;
147 }
148 }
149
150 return this->title;
151 };
152
153 NOTIFY_INLINE auto get_type() -> const ImGuiToastType& { return this->type; };
154
155
156 static NOTIFY_INLINE auto get_color(ImGuiToastType type) -> const ImVec4
157 {
158 switch (type)
159 {
161 return { 255, 255, 255, 255 }; // White
163 return { 0, 255, 0, 255 }; // Green
165 return { 255, 255, 0, 255 }; // Yellow
167 return { 255, 0, 0, 255 }; // Error
169 return { 255, 255, 255, 255 }; // Blue
170 default:
171 return { 255, 255, 255, 255 }; // White
172 }
173 }
174
175 NOTIFY_INLINE auto get_color() -> const ImVec4
176 {
177 return get_color(this->type);
178 }
179
180 static NOTIFY_INLINE auto get_icon(ImGuiToastType type) -> const char*
181 {
182 switch (type)
183 {
185 return NULL;
189 return ICON_MDI_ALERT_BOX;
194 default:
195 return NULL;
196 }
197 }
198
199 NOTIFY_INLINE auto get_icon() -> const char*
200 {
201 return get_icon(this->type);
202 }
203
204 NOTIFY_INLINE auto get_content() -> char* { return this->content; };
205
206 NOTIFY_INLINE auto get_draw_callback() -> const ImGuiToastDrawCallback& { return this->draw_callback; };
207
208 NOTIFY_INLINE auto has_draw_callback() -> bool { return this->draw_callback != nullptr; };
209
210 NOTIFY_INLINE auto get_elapsed_time() { return get_tick_count() - this->creation_time; }
211
213 {
214 const auto elapsed = get_elapsed_time();
215
216 if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismiss_time + NOTIFY_FADE_IN_OUT_TIME)
217 {
219 }
220 else if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismiss_time)
221 {
223 }
225 {
227 }
228 else
229 {
231 }
232 }
233
234 NOTIFY_INLINE auto get_fade_percent() -> const float
235 {
236 const auto phase = get_phase();
237 const auto elapsed = get_elapsed_time();
238
239 if (phase == ImGuiToastPhase_FadeIn)
240 {
241 return ((float)elapsed / (float)NOTIFY_FADE_IN_OUT_TIME) * NOTIFY_OPACITY;
242 }
243 else if (phase == ImGuiToastPhase_FadeOut)
244 {
245 return (1.f - (((float)elapsed - (float)NOTIFY_FADE_IN_OUT_TIME - (float)this->dismiss_time) / (float)NOTIFY_FADE_IN_OUT_TIME)) * NOTIFY_OPACITY;
246 }
247
248 return 1.f * NOTIFY_OPACITY;
249 }
250
251 NOTIFY_INLINE static auto get_tick_count() -> const unsigned long long
252 {
253 using namespace std::chrono;
254 return duration_cast<milliseconds>(steady_clock::now().time_since_epoch()).count();
255 }
256
257 NOTIFY_INLINE auto set_creation_time(uint64_t offset) -> const uint64_t { return this->creation_time = get_tick_count() + offset; }
258
259public:
260 // Constructors
261
263 {
264 IM_ASSERT(type < ImGuiToastType_COUNT);
265
266 this->type = type;
267 this->dismiss_time = dismiss_time;
268 this->set_creation_time(0);
269
270 memset(this->title, 0, sizeof(this->title));
271 memset(this->content, 0, sizeof(this->content));
272 }
273
274 ImGuiToast(ImGuiToastType type, const char* format, ...) : ImGuiToast(type) { NOTIFY_FORMAT(this->set_content, format); }
275
276 ImGuiToast(ImGuiToastType type, int dismiss_time, const char* format, ...) : ImGuiToast(type, dismiss_time) { NOTIFY_FORMAT(this->set_content, format); }
277
278 // Constructor with draw callback
280 : ImGuiToast(type, dismiss_time)
281 {
282 this->draw_callback = callback;
283 }
284};
285
286namespace ImGui
287{
288 NOTIFY_INLINE std::vector<ImGuiToast> notifications;
289
290
292 {
293 for(auto& toast : notifications)
294 {
295 if(toast.unique_id == unique_id)
296 {
297 return &toast;
298 }
299 }
300 return nullptr;
301 }
302
307 {
308 notifications.push_back(toast);
309 }
310
318 NOTIFY_INLINE void PushNotification(uint64_t unique_id, const ImGuiToast& toast)
319 {
320 auto notification = GetNotification(unique_id);
321 if(notification)
322 {
323 // Check current phase before updating
324 auto current_phase = notification->get_phase();
325 auto current_elapsed = notification->get_elapsed_time();
326
327 // Update the notification contents
328 *notification = toast;
329 notification->unique_id = unique_id;
330
331 // Refresh timing based on current phase
332 if(current_phase == ImGuiToastPhase_FadeIn)
333 {
334 // Still fading in - preserve current fade-in progress
335 notification->set_creation_time(static_cast<uint64_t>(-static_cast<int64_t>(current_elapsed)));
336 }
337 else if(current_phase == ImGuiToastPhase_Wait || current_phase == ImGuiToastPhase_FadeOut)
338 {
339 // Already fully visible or fading out - reset to start of wait phase
340 notification->set_creation_time(static_cast<uint64_t>(-static_cast<int64_t>(NOTIFY_FADE_IN_OUT_TIME)));
341 }
342 else
343 {
344 // Expired - start fresh
345 notification->set_creation_time(0);
346 }
347 }
348 else
349 {
350 notifications.push_back(toast);
351 notifications.back().unique_id = unique_id;
352 }
353 }
354
360 {
361 notifications.erase(notifications.begin() + index);
362 }
363
368 {
369 const auto vp_pos = GetMainViewport()->Pos;
370 const auto vp_size = GetMainViewport()->Size;
371
372 float height = 0.f;
373
374 for (auto i = 0; i < std::min<size_t>(notifications.size(), NOTIFY_MAX_TOASTS); i++)
375 {
376 auto* current_toast = &notifications[i];
377
378 // Remove toast if expired
379 if (current_toast->get_phase() == ImGuiToastPhase_Expired)
380 {
382 continue;
383 }
384
385 // Get icon, title and other data
386 const auto icon = current_toast->get_icon();
387 const auto title = current_toast->get_title();
388 const auto content = current_toast->get_content();
389 const auto default_title = current_toast->get_default_title();
390 const auto opacity = current_toast->get_fade_percent(); // Get opacity based of the current phase
391
392 // Window rendering
393 auto text_color = current_toast->get_color();
394 text_color.w = opacity;
395
396 // Generate new unique name for this toast
397 char window_name[50]{};
398 snprintf(window_name, sizeof(window_name), "##TOAST%d", i);
399
400 //PushStyleColor(ImGuiCol_Text, text_color);
401 SetNextWindowBgAlpha(opacity);
402 //SetNextWindowSizeConstraints(ImVec2(180, 0), ImVec2(FLT_MAX, FLT_MAX ));
403 ImVec2 window_pos = vp_pos + ImVec2(vp_size.x - NOTIFY_PADDING_X, vp_size.y - NOTIFY_PADDING_Y - height);
404 SetNextWindowPos(window_pos, ImGuiCond_Always, ImVec2(1.0f, 1.0f));
405 Begin(window_name, NULL, NOTIFY_TOAST_FLAGS);
406
407 // Here we render the toast content
408 {
409 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
410
411 bool was_title_rendered = false;
412
413 // If an icon is set
415 {
416 //Text(icon); // Render icon text
417 TextColored(text_color, "%s", icon);
418 was_title_rendered = true;
419 }
420
421 // If a title is set
422 if (!NOTIFY_NULL_OR_EMPTY(title))
423 {
424 // If a title and an icon is set, we want to render on same line
426 SameLine();
427
428 Text("%s",title); // Render title text
429 was_title_rendered = true;
430 }
431 else if (!NOTIFY_NULL_OR_EMPTY(default_title))
432 {
434 SameLine();
435
436 Text("%s", default_title); // Render default title text (ImGuiToastType_Success -> "Success", etc...)
437 was_title_rendered = true;
438 }
439
440 // In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically
441 if (was_title_rendered && !NOTIFY_NULL_OR_EMPTY(content))
442 {
443 SetCursorPosY(GetCursorPosY() + 5.f); // Must be a better way to do this!!!!
444 }
445
446 // If a content is set, render default text
447 if (!NOTIFY_NULL_OR_EMPTY(content))
448 {
449 if (was_title_rendered)
450 {
451#ifdef NOTIFY_USE_SEPARATOR
452 Separator();
453#endif
454 }
455
456 Text("%s", content); // Render content text
457 }
458
459 // If a draw callback is set, call it after the text content
460 if (current_toast->has_draw_callback())
461 {
462 // Call the custom draw callback for additional rendering
463 current_toast->get_draw_callback()(*current_toast, opacity, text_color);
464 }
465
466 PopTextWrapPos();
467 }
468
469 // Save height for next toasts
470 height += GetWindowHeight() + NOTIFY_PADDING_MESSAGE_Y;
471
472 // End
473 End();
474 }
475 }
476
477}
478
479#endif
manifold_type type
float elapsed
NOTIFY_INLINE auto get_phase() -> const ImGuiToastPhase
NOTIFY_INLINE auto get_color() -> const ImVec4
NOTIFY_INLINE auto get_elapsed_time()
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_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 get_fade_percent() -> const float
uint64_t unique_id
NOTIFY_INLINE auto set_content(const char *format,...) -> void
NOTIFY_INLINE auto get_icon() -> const char *
NOTIFY_INLINE auto get_default_title() -> const char *
static NOTIFY_INLINE auto get_color(ImGuiToastType type) -> const ImVec4
NOTIFY_INLINE auto set_title(const char *format,...) -> void
ImGuiToast(ImGuiToastType type, int dismiss_time, const char *format,...)
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)
NOTIFY_INLINE auto get_title() -> char *
ImGuiToast(ImGuiToastType type, int dismiss_time=NOTIFY_DEFAULT_DISMISS)
#define ICON_MDI_ALERT_BOX
#define ICON_MDI_ALERT_CIRCLE
#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
ImGuiToastPhase_
@ ImGuiToastPhase_Wait
@ ImGuiToastPhase_COUNT
@ ImGuiToastPhase_Expired
@ ImGuiToastPhase_FadeIn
@ ImGuiToastPhase_FadeOut
int ImGuiToastType
#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
#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
std::function< void(const ImGuiToast &, float, const ImVec4 &)> ImGuiToastDrawCallback
const char * icon
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