imgui_notify.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // imgui-notify by patrickcjk
  2. // https://github.com/patrickcjk/imgui-notify
  3. /*
  4. MIT License
  5. Copyright (c) 2021 Patrick
  6. Permission is hereby granted, free of charge, to any person obtaining a copy
  7. of this software and associated documentation files (the "Software"), to deal
  8. in the Software without restriction, including without limitation the rights
  9. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. copies of the Software, and to permit persons to whom the Software is
  11. furnished to do so, subject to the following conditions:
  12. The above copyright notice and this permission notice shall be included in all
  13. copies or substantial portions of the Software.
  14. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. SOFTWARE.
  21. */
  22. #ifndef IMGUI_NOTIFY
  23. #define IMGUI_NOTIFY
  24. //#pragma once
  25. #include <vector>
  26. #include <string>
  27. #include "font_awesome_5.h"
  28. #include "fa_solid_900.h"
  29. #define NOTIFY_MAX_MSG_LENGTH 4096 // Max message content length
  30. #define NOTIFY_PADDING_X 20.f // Bottom-left X padding
  31. #define NOTIFY_PADDING_Y 20.f // Bottom-left Y padding
  32. #define NOTIFY_PADDING_MESSAGE_Y 10.f // Padding Y between each message
  33. #define NOTIFY_FADE_IN_OUT_TIME 150 // Fade in and out duration
  34. #define NOTIFY_DEFAULT_DISMISS 3000 // Auto dismiss after X ms (default, applied only of no data provided in constructors)
  35. #define NOTIFY_OPACITY 1.0f // 0-1 Toast opacity
  36. #define NOTIFY_TOAST_FLAGS ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing
  37. // Comment out if you don't want any separator between title and content
  38. #define NOTIFY_USE_SEPARATOR
  39. #define NOTIFY_INLINE inline
  40. #define NOTIFY_NULL_OR_EMPTY(str) (!str ||! strlen(str))
  41. #define NOTIFY_FORMAT(fn, format, ...) if (format) { va_list args; va_start(args, format); fn(format, args, __VA_ARGS__); va_end(args); }
  42. typedef int ImGuiToastType;
  43. typedef int ImGuiToastPhase;
  44. typedef int ImGuiToastPos;
  45. enum ImGuiToastType_
  46. {
  47. ImGuiToastType_None,
  48. ImGuiToastType_Success,
  49. ImGuiToastType_Warning,
  50. ImGuiToastType_Error,
  51. ImGuiToastType_Info,
  52. ImGuiToastType_COUNT
  53. };
  54. enum ImGuiToastPhase_
  55. {
  56. ImGuiToastPhase_FadeIn,
  57. ImGuiToastPhase_Wait,
  58. ImGuiToastPhase_FadeOut,
  59. ImGuiToastPhase_Expired,
  60. ImGuiToastPhase_COUNT
  61. };
  62. enum ImGuiToastPos_
  63. {
  64. ImGuiToastPos_TopLeft,
  65. ImGuiToastPos_TopCenter,
  66. ImGuiToastPos_TopRight,
  67. ImGuiToastPos_BottomLeft,
  68. ImGuiToastPos_BottomCenter,
  69. ImGuiToastPos_BottomRight,
  70. ImGuiToastPos_Center,
  71. ImGuiToastPos_COUNT
  72. };
  73. class ImGuiToast
  74. {
  75. private:
  76. ImGuiToastType type = ImGuiToastType_None;
  77. char title[NOTIFY_MAX_MSG_LENGTH];
  78. char content[NOTIFY_MAX_MSG_LENGTH];
  79. int dismiss_time = NOTIFY_DEFAULT_DISMISS;
  80. uint64_t creation_time = 0;
  81. private:
  82. // Setters
  83. NOTIFY_INLINE auto set_title(const char* format, va_list args) { vsnprintf(this->title, sizeof(this->title), format, args); }
  84. NOTIFY_INLINE auto set_content(const char* format, va_list args) { vsnprintf(this->content, sizeof(this->content), format, args); }
  85. public:
  86. NOTIFY_INLINE auto set_title(const char* format, ...) -> void { NOTIFY_FORMAT(this->set_title, format); }
  87. NOTIFY_INLINE auto set_content(const char* format, ...) -> void { NOTIFY_FORMAT(this->set_content, format); }
  88. NOTIFY_INLINE auto set_type(const ImGuiToastType& type) -> void { IM_ASSERT(type < ImGuiToastType_COUNT); this->type = type; };
  89. public:
  90. // Getters
  91. NOTIFY_INLINE auto get_title() -> char* { return this->title; };
  92. NOTIFY_INLINE auto get_default_title() -> const char*
  93. {
  94. if (!strlen(this->title))
  95. {
  96. switch (this->type)
  97. {
  98. case ImGuiToastType_None:
  99. return NULL;
  100. case ImGuiToastType_Success:
  101. return "Success";
  102. case ImGuiToastType_Warning:
  103. return "Warning";
  104. case ImGuiToastType_Error:
  105. return "Error";
  106. case ImGuiToastType_Info:
  107. return "Info";
  108. }
  109. }
  110. return this->title;
  111. };
  112. NOTIFY_INLINE auto get_type() -> const ImGuiToastType& { return this->type; };
  113. NOTIFY_INLINE auto get_color() -> const ImVec4&
  114. {
  115. switch (this->type)
  116. {
  117. case ImGuiToastType_None:
  118. return { 255, 255, 255, 255 }; // White
  119. case ImGuiToastType_Success:
  120. return { 0, 255, 0, 255 }; // Green
  121. case ImGuiToastType_Warning:
  122. return { 255, 255, 0, 255 }; // Yellow
  123. case ImGuiToastType_Error:
  124. return { 255, 0, 0, 255 }; // Error
  125. case ImGuiToastType_Info:
  126. return { 0, 157, 255, 255 }; // Blue
  127. }
  128. }
  129. NOTIFY_INLINE auto get_icon() -> const char*
  130. {
  131. switch (this->type)
  132. {
  133. case ImGuiToastType_None:
  134. return NULL;
  135. case ImGuiToastType_Success:
  136. return ICON_FA_CHECK_CIRCLE;
  137. case ImGuiToastType_Warning:
  138. return ICON_FA_EXCLAMATION_TRIANGLE;
  139. case ImGuiToastType_Error:
  140. return ICON_FA_TIMES_CIRCLE;
  141. case ImGuiToastType_Info:
  142. return ICON_FA_INFO_CIRCLE;
  143. }
  144. }
  145. NOTIFY_INLINE auto get_content() -> char* { return this->content; };
  146. NOTIFY_INLINE auto get_elapsed_time() { return GetTickCount64() - this->creation_time; }
  147. NOTIFY_INLINE auto get_phase() -> const ImGuiToastPhase&
  148. {
  149. const auto elapsed = get_elapsed_time();
  150. if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismiss_time + NOTIFY_FADE_IN_OUT_TIME)
  151. {
  152. return ImGuiToastPhase_Expired;
  153. }
  154. else if (elapsed > NOTIFY_FADE_IN_OUT_TIME + this->dismiss_time)
  155. {
  156. return ImGuiToastPhase_FadeOut;
  157. }
  158. else if (elapsed > NOTIFY_FADE_IN_OUT_TIME)
  159. {
  160. return ImGuiToastPhase_Wait;
  161. }
  162. else
  163. {
  164. return ImGuiToastPhase_FadeIn;
  165. }
  166. }
  167. NOTIFY_INLINE auto get_fade_percent() -> const float
  168. {
  169. const auto phase = get_phase();
  170. const auto elapsed = get_elapsed_time();
  171. if (phase == ImGuiToastPhase_FadeIn)
  172. {
  173. return ((float)elapsed / (float)NOTIFY_FADE_IN_OUT_TIME) * NOTIFY_OPACITY;
  174. }
  175. else if (phase == ImGuiToastPhase_FadeOut)
  176. {
  177. return (1.f - (((float)elapsed - (float)NOTIFY_FADE_IN_OUT_TIME - (float)this->dismiss_time) / (float)NOTIFY_FADE_IN_OUT_TIME)) * NOTIFY_OPACITY;
  178. }
  179. return 1.f * NOTIFY_OPACITY;
  180. }
  181. public:
  182. // Constructors
  183. ImGuiToast(ImGuiToastType type, int dismiss_time = NOTIFY_DEFAULT_DISMISS)
  184. {
  185. IM_ASSERT(type < ImGuiToastType_COUNT);
  186. this->type = type;
  187. this->dismiss_time = dismiss_time;
  188. this->creation_time = GetTickCount64();
  189. memset(this->title, 0, sizeof(this->title));
  190. memset(this->content, 0, sizeof(this->content));
  191. }
  192. ImGuiToast(ImGuiToastType type, const char* format, ...) : ImGuiToast(type) { NOTIFY_FORMAT(this->set_content, format); }
  193. ImGuiToast(ImGuiToastType type, int dismiss_time, const char* format, ...) : ImGuiToast(type, dismiss_time) { NOTIFY_FORMAT(this->set_content, format); }
  194. };
  195. namespace {
  196. extern auto iconno = "null_icon";
  197. }
  198. namespace ImGui
  199. {
  200. //extern auto iconno = "null_icon";
  201. NOTIFY_INLINE std::vector<ImGuiToast> notifications;
  202. /// <summary>
  203. /// Insert a new toast in the list
  204. /// </summary>
  205. NOTIFY_INLINE VOID InsertNotification(const ImGuiToast& toast)
  206. {
  207. notifications.push_back(toast);
  208. }
  209. /// <summary>
  210. /// Remove a toast from the list by its index
  211. /// </summary>
  212. /// <param name="index">index of the toast to remove</param>
  213. NOTIFY_INLINE VOID RemoveNotification(int index)
  214. {
  215. notifications.erase(notifications.begin() + index);
  216. }
  217. /// <summary>
  218. /// Render toasts, call at the end of your rendering!
  219. /// </summary>
  220. NOTIFY_INLINE VOID RenderNotifications()
  221. {
  222. const auto vp_size = GetMainViewport()->Size;
  223. float height = 0.f;
  224. for (auto i = 0; i < notifications.size(); i++)
  225. {
  226. auto* current_toast = &notifications[i];
  227. // Remove toast if expired
  228. if (current_toast->get_phase() == ImGuiToastPhase_Expired)
  229. {
  230. RemoveNotification(i);
  231. continue;
  232. }
  233. // Get icon, title and other data
  234. iconno = current_toast->get_icon();
  235. const auto title = current_toast->get_title();
  236. const auto content = current_toast->get_content();
  237. const auto default_title = current_toast->get_default_title();
  238. const auto opacity = current_toast->get_fade_percent(); // Get opacity based of the current phase
  239. // Window rendering
  240. auto text_color = current_toast->get_color();
  241. text_color.w = opacity;
  242. // Generate new unique name for this toast
  243. char window_name[50];
  244. sprintf_s(window_name, "##TOAST%d", i);
  245. //PushStyleColor(ImGuiCol_Text, text_color);
  246. SetNextWindowBgAlpha(opacity);
  247. SetNextWindowPos(ImVec2(vp_size.x - NOTIFY_PADDING_X, vp_size.y - NOTIFY_PADDING_Y - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f));
  248. Begin(window_name, NULL, NOTIFY_TOAST_FLAGS);
  249. // Here we render the toast content
  250. {
  251. 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
  252. bool was_title_rendered = false;
  253. // If an icon is set
  254. if (!NOTIFY_NULL_OR_EMPTY(iconno))
  255. {
  256. //Text(icon); // Render icon text
  257. TextColored(text_color, iconno);
  258. was_title_rendered = true;
  259. }
  260. // If a title is set
  261. if (!NOTIFY_NULL_OR_EMPTY(title))
  262. {
  263. // If a title and an icon is set, we want to render on same line
  264. if (!NOTIFY_NULL_OR_EMPTY(iconno))
  265. SameLine();
  266. Text(title); // Render title text
  267. was_title_rendered = true;
  268. }
  269. else if (!NOTIFY_NULL_OR_EMPTY(default_title))
  270. {
  271. if (!NOTIFY_NULL_OR_EMPTY(iconno))
  272. SameLine();
  273. Text(default_title); // Render default title text (ImGuiToastType_Success -> "Success", etc...)
  274. was_title_rendered = true;
  275. }
  276. // In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically
  277. if (was_title_rendered && !NOTIFY_NULL_OR_EMPTY(content))
  278. {
  279. SetCursorPosY(GetCursorPosY() + 5.f); // Must be a better way to do this!!!!
  280. }
  281. // If a content is set
  282. if (!NOTIFY_NULL_OR_EMPTY(content))
  283. {
  284. if (was_title_rendered)
  285. {
  286. #ifdef NOTIFY_USE_SEPARATOR
  287. Separator();
  288. #endif
  289. }
  290. Text(content); // Render content text
  291. }
  292. PopTextWrapPos();
  293. }
  294. // Save height for next toasts
  295. height += GetWindowHeight() + NOTIFY_PADDING_MESSAGE_Y;
  296. // End
  297. End();
  298. }
  299. }
  300. /// <summary>
  301. /// Adds font-awesome font, must be called ONCE on initialization
  302. /// <param name="FontDataOwnedByAtlas">Fonts are loaded from read-only memory, should be set to false!</param>
  303. /// </summary>
  304. NOTIFY_INLINE VOID MergeIconsWithLatestFont(float font_size, bool FontDataOwnedByAtlas = false)
  305. {
  306. static const ImWchar icons_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
  307. ImFontConfig icons_config;
  308. icons_config.MergeMode = true;
  309. icons_config.PixelSnapH = true;
  310. icons_config.FontDataOwnedByAtlas = FontDataOwnedByAtlas;
  311. GetIO().Fonts->AddFontFromMemoryTTF((void*)fa_solid_900, sizeof(fa_solid_900), font_size, &icons_config, icons_ranges);
  312. }
  313. }
  314. #endif