├── include ├── impop_clock.h ├── impop_ostream.h ├── impop_ease.h ├── impop_text.h ├── impop_datepicker.h ├── impop_canvas.h ├── impop_footer.h ├── impop_transport.h ├── impop_config.h ├── impop_osd.h └── impop_color.h ├── LICENSE.txt ├── demo └── rectangle_demo.h └── README.md /include/impop_clock.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "imgui.h" 7 | 8 | namespace ImPop { 9 | 10 | static inline void TextClock() 11 | { 12 | std::time_t t = std::time(nullptr); 13 | std::tm* now = std::localtime(&t); 14 | ImGui::Text("%02d:%02d:%02d", now->tm_hour, now->tm_min, now->tm_sec); 15 | } 16 | 17 | } // namespace ImPop 18 | -------------------------------------------------------------------------------- /include/impop_ostream.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "imgui.h" 6 | 7 | static inline std::ostream& operator<<(std::ostream& os, const ImVec2& v) { 8 | return os << "(" << v.x << "," << v.y << ")"; 9 | } 10 | 11 | static inline std::ostream& operator<<(std::ostream& os, const ImVec4& v) { 12 | return os << "(" << v.x << "," << v.y << "," << v.z << "," << v.w << ")"; 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Conrad Parker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /include/impop_ease.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace ImPop { 4 | 5 | float Linear(float t) { 6 | return t; 7 | } 8 | 9 | float QuadEaseIn(float t) { 10 | return t * t; 11 | } 12 | 13 | float QuadEaseOut(float t) { 14 | return t * (2 - t); 15 | } 16 | 17 | float QuadEaseInOut(float t) { 18 | return t < 0.5f ? 2 * t * t : -1 + (4 - 2 * t) * t; 19 | } 20 | 21 | float ExpoEaseIn(float t) { 22 | return t == 0 ? 0 : pow(2, 10 * (t - 1)); 23 | } 24 | 25 | float ExpoEaseOut(float t) { 26 | return t == 1 ? 1 : 1 - pow(2, -10 * t); 27 | } 28 | 29 | float ExpoEaseInOut(float t) { 30 | if (t == 0) return 0; 31 | if (t == 1) return 1; 32 | return t < 0.5f ? 0.5f * pow(2, 20 * t - 10) : 1 - 0.5f * pow(2, -20 * t + 10); 33 | } 34 | 35 | float LogEaseIn(float t) { 36 | return log(1 + 9 * t) / log(10); 37 | } 38 | 39 | float LogEaseOut(float t) { 40 | return 1 - log(1 + 9 * (1 - t)) / log(10); 41 | } 42 | 43 | float BounceEaseOut(float t) { 44 | if (t < 1 / 2.75f) { 45 | return 7.5625f * t * t; 46 | } else if (t < 2 / 2.75f) { 47 | t -= 1.5f / 2.75f; 48 | return 7.5625f * t * t + 0.75f; 49 | } else if (t < 2.5 / 2.75) { 50 | t -= 2.25f / 2.75f; 51 | return 7.5625f * t * t + 0.9375f; 52 | } else { 53 | t -= 2.625f / 2.75f; 54 | return 7.5625f * t * t + 0.984375f; 55 | } 56 | } 57 | 58 | float BounceEaseIn(float t) { 59 | return 1 - BounceEaseOut(1 - t); 60 | } 61 | 62 | float BounceEaseInOut(float t) { 63 | return t < 0.5f ? BounceEaseIn(t * 2) * 0.5f : BounceEaseOut(t * 2 - 1) * 0.5f + 0.5f; 64 | } 65 | 66 | } // namespace ImPop 67 | -------------------------------------------------------------------------------- /include/impop_text.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | namespace ImPop { 6 | 7 | static inline void OutlineTextColoredV(ImDrawList* drawList, const ImVec2& pos, 8 | ImU32 text_color, ImU32 outline_color, 9 | const char* fmt, va_list args) 10 | { 11 | static char buffer[1024]; 12 | vsnprintf(buffer, sizeof(buffer), fmt, args); 13 | 14 | // Draw the outline (four directions around the original position) 15 | drawList->AddText(ImVec2(pos.x + 1, pos.y + 1), outline_color, buffer); 16 | drawList->AddText(ImVec2(pos.x - 1, pos.y + 1), outline_color, buffer); 17 | drawList->AddText(ImVec2(pos.x + 1, pos.y - 1), outline_color, buffer); 18 | drawList->AddText(ImVec2(pos.x - 1, pos.y - 1), outline_color, buffer); 19 | 20 | // Draw the main text 21 | drawList->AddText(pos, text_color, buffer); 22 | } 23 | 24 | static inline void OutlineTextV(ImDrawList* drawList, const ImVec2& pos, 25 | const char* fmt, va_list args) 26 | { 27 | OutlineTextColoredV(drawList, pos, IM_COL32_WHITE, IM_COL32_BLACK, fmt, args); 28 | } 29 | 30 | static inline void OutlineTextColored(ImDrawList* drawList, const ImVec2& pos, 31 | ImU32 text_color, ImU32 outline_color, 32 | const char* fmt, ...) 33 | { 34 | va_list args; 35 | va_start(args, fmt); 36 | OutlineTextColoredV(drawList, pos, text_color, outline_color, fmt, args); 37 | va_end(args); 38 | } 39 | 40 | static inline void OutlineText(ImDrawList* drawList, const ImVec2& pos, const char* fmt, ...) 41 | { 42 | va_list args; 43 | va_start(args, fmt); 44 | OutlineTextColoredV(drawList, pos, IM_COL32_WHITE, IM_COL32_BLACK, fmt, args); 45 | va_end(args); 46 | } 47 | 48 | 49 | } // namespace ImPop 50 | -------------------------------------------------------------------------------- /include/impop_datepicker.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | #include "implot.h" 5 | #include "implot_internal.h" 6 | 7 | namespace ImPop { 8 | 9 | constexpr int SECONDS_IN_A_DAY = 86400; 10 | 11 | static constexpr int time_t_to_days(time_t t) { 12 | return t / SECONDS_IN_A_DAY; 13 | } 14 | 15 | static constexpr time_t days_to_time_t(int days) { 16 | return days * SECONDS_IN_A_DAY; 17 | } 18 | 19 | static constexpr int implot_time_to_days(const ImPlotTime& t) { 20 | return time_t_to_days(t.S); 21 | } 22 | 23 | static inline ImPlotTime days_to_implot_time(int days) { 24 | return ImPlotTime(days_to_time_t(days), 0); 25 | } 26 | 27 | static inline ImPlotTime LocTimeNow() { 28 | time_t now = time(NULL); 29 | struct tm now_tm; 30 | localtime_r(&now, &now_tm); 31 | return ImPlot::MkLocTime(&now_tm); 32 | } 33 | 34 | static inline ImPlotTime GmtTimeNow() { 35 | time_t now = time(NULL); 36 | struct tm now_tm; 37 | gmtime_r(&now, &now_tm); 38 | return ImPlot::MkGmtTime(&now_tm); 39 | } 40 | 41 | static inline bool DatePicker(const char* id, ImPlotTime* t, const ImPlotTime* default_time = nullptr, const ImPlotTime* min_time = nullptr, const ImPlotTime* max_time = nullptr) 42 | { 43 | ImGuiContext& g = *ImGui::GetCurrentContext(); 44 | ImGuiStorage* storage = ImGui::GetStateStorage(); 45 | 46 | ImGuiID key = ImGui::GetID(id); 47 | int currentDays = storage->GetInt(key, implot_time_to_days(t->S ? *t : *default_time)); 48 | ImPlotTime currentT = days_to_implot_time(currentDays); 49 | 50 | int level = 0; 51 | bool pressed = ImPlot::ShowDatePicker("##date", &level, ¤tT, min_time, max_time); 52 | if (pressed) { 53 | *t = currentT; 54 | } else { 55 | currentDays = implot_time_to_days(currentT); 56 | storage->SetInt(key, currentDays); 57 | } 58 | 59 | return pressed; 60 | } 61 | 62 | } // namespace ImPop 63 | -------------------------------------------------------------------------------- /include/impop_canvas.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | #include "impop_text.h" 6 | 7 | namespace ImPop { 8 | 9 | struct Canvas { 10 | ImVec2 top_left; 11 | ImDrawList* draw_list; 12 | 13 | Canvas() 14 | : top_left(ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin()), 15 | draw_list(ImGui::GetWindowDrawList()) 16 | {} 17 | 18 | void Line(const ImVec2& start, const ImVec2& end, const ImU32 color) { 19 | ImVec2 lmin = top_left + start; 20 | ImVec2 lmax = top_left + end; 21 | draw_list->AddLine(lmin, lmax, color); 22 | } 23 | 24 | void Rect(const ImVec2& rect_min, const ImVec2& rect_dim, const ImU32 color) { 25 | ImVec2 rmin = top_left + rect_min; 26 | ImVec2 rmax = rmin + rect_dim; 27 | draw_list->AddRect(rmin, rmax, color); 28 | } 29 | 30 | void RectFilled(const ImVec2& rect_min, const ImVec2& rect_dim, const ImU32 color) { 31 | ImVec2 rmin = top_left + rect_min; 32 | ImVec2 rmax = rmin + rect_dim; 33 | draw_list->AddRect(rmin, rmax, color); 34 | } 35 | 36 | void RectFilledBorder(const ImVec2& rect_min, const ImVec2& rect_dim, 37 | const ImU32 border_color, const ImU32 fill_color) { 38 | ImVec2 rmin = top_left + rect_min; 39 | ImVec2 rmax = rmin + rect_dim; 40 | draw_list->AddRectFilled(rmin, rmax, fill_color); 41 | draw_list->AddRect(rmin, rmax, border_color); 42 | } 43 | 44 | void TextClipRect(const ImVec2& clip_min, const ImVec2& clip_dim, const ImVec2& text_offset, 45 | const char * text_str, const ImU32 text_color) { 46 | ImVec2 cmin = top_left + clip_min; 47 | ImVec2 cmax = cmin + clip_dim; 48 | draw_list->PushClipRect(cmin, cmax, true); 49 | draw_list->AddText(cmin+text_offset, text_color, text_str); 50 | draw_list->PopClipRect(); 51 | } 52 | 53 | void OutlineText(const ImVec2& pos, const char* fmt, ...) { 54 | va_list args; 55 | va_start(args, fmt); 56 | ImPop::OutlineTextV(draw_list, top_left + pos, fmt, args); 57 | va_end(args); 58 | } 59 | }; 60 | 61 | } // namespace ImPop 62 | -------------------------------------------------------------------------------- /include/impop_footer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "imgui.h" 6 | #include "impop_text.h" 7 | 8 | namespace ImPop { 9 | 10 | template 11 | static inline std::string readable_bytes(T num) { 12 | char buffer[64]; 13 | 14 | if (num >= 1e15) { 15 | snprintf(buffer, sizeof(buffer), "%.2f PB", (float)num / 1e15); 16 | } else if (num >= 1e12) { 17 | snprintf(buffer, sizeof(buffer), "%.2f TB", (float)num / 1e12); 18 | } else if (num >= 1e9) { 19 | snprintf(buffer, sizeof(buffer), "%.2f GB", (float)num / 1e9); 20 | } else if (num >= 1e6) { 21 | snprintf(buffer, sizeof(buffer), "%.2f MB", (float)num / 1e6); 22 | } else if (num >= 1e3) { 23 | snprintf(buffer, sizeof(buffer), "%.2f kB", (float)num / 1e3); 24 | } else { 25 | snprintf(buffer, sizeof(buffer), "%.2f B", (float)num); 26 | } 27 | 28 | return std::string(buffer); 29 | } 30 | 31 | static inline void PerfFooter() { 32 | ImGuiIO& io = ImGui::GetIO(); 33 | const ImVec2 screenSize = io.DisplaySize; 34 | 35 | // Access the foreground draw list (drawn after all windows) 36 | ImDrawList* draw_list = ImGui::GetForegroundDrawList(); 37 | 38 | // Set the rectangle and text parameters 39 | const ImVec2 footerSize(screenSize.x, 20); // Adjust the height as needed 40 | const ImVec2 footerPos(0, screenSize.y - footerSize.y); 41 | const ImU32 backgroundColor = IM_COL32(0, 0, 0, 102); // Semi-transparent black 42 | 43 | // Draw the rectangle with the specified color 44 | draw_list->AddRectFilled(footerPos, footerPos + footerSize, backgroundColor); 45 | 46 | struct mallinfo2 mi = mallinfo2(); 47 | size_t totalFreeSpace = mi.fordblks; 48 | size_t totalAllocatedSpace = mi.uordblks; 49 | double fragmentationRatio = static_cast(totalFreeSpace) / (totalAllocatedSpace + totalFreeSpace); 50 | 51 | OutlineText(draw_list, ImVec2(screenSize.x-700, footerPos.y + 3), 52 | "%9s arena | %9s free | %9s alloc | %4.1f%% frag", 53 | readable_bytes(mi.arena).c_str(), 54 | readable_bytes(totalFreeSpace).c_str(), 55 | readable_bytes(totalAllocatedSpace).c_str(), 56 | fragmentationRatio*100.0); 57 | 58 | OutlineText(draw_list, ImVec2(screenSize.x-60, footerPos.y + 3), 59 | "%.1f FPS", io.Framerate); 60 | } 61 | 62 | } // namespace ImPop 63 | -------------------------------------------------------------------------------- /include/impop_transport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | namespace ImPop { 6 | 7 | static inline void PlayIcon(ImDrawList* drawList, const ImVec2& rect_min, const ImVec2& rect_dim, ImU32 color) 8 | { 9 | ImVec2 p2 = ImVec2(rect_min.x + rect_dim.x, rect_min.y + rect_dim.y/2.0); 10 | ImVec2 p3 = ImVec2(rect_min.x, rect_min.y + rect_dim.y); 11 | drawList->AddTriangleFilled(rect_min, p2, p3, color); 12 | } 13 | 14 | static inline void PauseIcon(ImDrawList* drawList, const ImVec2& rect_min, const ImVec2& rect_dim, ImU32 color) 15 | { 16 | ImVec2 rect_max = ImVec2(rect_min.x + rect_dim.x, rect_min.y + rect_dim.y); 17 | { 18 | ImVec2 p = ImVec2(rect_min.x + rect_dim.x/3.0, rect_max.y); 19 | drawList->AddRectFilled(rect_min, p, color); 20 | } 21 | { 22 | ImVec2 p = ImVec2(rect_max.x - rect_dim.x/3.0, rect_min.y); 23 | drawList->AddRectFilled(p, rect_max, color); 24 | } 25 | } 26 | 27 | static inline void StopIcon(ImDrawList* drawList, const ImVec2& rect_min, const ImVec2& rect_dim, ImU32 color) 28 | { 29 | ImVec2 rect_max = ImVec2(rect_min.x + rect_dim.x, rect_min.y + rect_dim.y); 30 | drawList->AddRectFilled(rect_min, rect_max, color); 31 | } 32 | 33 | static inline void ForwardIcon(ImDrawList* drawList, const ImVec2& rect_min, const ImVec2& rect_dim, ImU32 color) 34 | { 35 | ImVec2 center = ImVec2(rect_min.x + rect_dim.x/2.0, rect_min.y + rect_dim.y/2.0); 36 | ImVec2 rect_max = ImVec2(rect_min.x + rect_dim.x, rect_min.y + rect_dim.y); 37 | { 38 | ImVec2 p3 = ImVec2(rect_min.x, rect_max.y); 39 | drawList->AddTriangleFilled(rect_min, center, p3, color); 40 | } 41 | { 42 | ImVec2 p1 = ImVec2(center.x, rect_min.y); 43 | ImVec2 p2 = ImVec2(rect_max.x, center.y); 44 | ImVec2 p3 = ImVec2(center.x, rect_max.y); 45 | drawList->AddTriangleFilled(p1, p2, p3, color); 46 | } 47 | } 48 | 49 | static inline void ReverseIcon(ImDrawList* drawList, const ImVec2& rect_min, const ImVec2& rect_dim, ImU32 color) 50 | { 51 | ImVec2 center = ImVec2(rect_min.x + rect_dim.x/2.0, rect_min.y + rect_dim.y/2.0); 52 | ImVec2 rect_max = ImVec2(rect_min.x + rect_dim.x, rect_min.y + rect_dim.y); 53 | { 54 | ImVec2 p1 = ImVec2(center.x, rect_min.y); 55 | ImVec2 p2 = ImVec2(rect_min.x, center.y); 56 | ImVec2 p3 = ImVec2(center.x, rect_max.y); 57 | drawList->AddTriangleFilled(p1, p2, p3, color); 58 | } 59 | { 60 | ImVec2 p1 = ImVec2(rect_max.x, rect_min.y); 61 | drawList->AddTriangleFilled(p1, center, rect_max, color); 62 | } 63 | } 64 | 65 | } // namespace ImPop 66 | -------------------------------------------------------------------------------- /demo/rectangle_demo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | static inline void RenderScrollableRectangles() 6 | { 7 | // Number of rectangles 8 | int num_rects = 10000; 9 | // Height of each rectangle 10 | int rect_height = 20; 11 | // Total height of all rectangles 12 | int total_height = num_rects * rect_height; 13 | 14 | // Get the initial cursor position before creating the child window 15 | ImVec2 initial_cursor_pos = ImGui::GetCursorScreenPos(); 16 | 17 | // Begin the child window with vertical scrollbar 18 | ImGui::BeginChild("ScrollableRegion", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar); 19 | 20 | // Get the draw list 21 | ImDrawList* draw_list = ImGui::GetWindowDrawList(); 22 | 23 | // Get the position and size of the child window 24 | ImVec2 child_pos = ImGui::GetCursorScreenPos(); 25 | ImVec2 child_size = ImGui::GetContentRegionAvail(); 26 | 27 | // Calculate the nearest scroll position constrained to the box height 28 | float scroll_y = ImGui::GetScrollY(); 29 | float constrained_scroll_y = rect_height * round(scroll_y / rect_height); 30 | ImGui::SetScrollY(constrained_scroll_y); 31 | 32 | // Calculate the adjusted child position 33 | child_pos.y = initial_cursor_pos.y - constrained_scroll_y; 34 | 35 | // Calculate the number of visible rectangles 36 | int first_visible_rect = static_cast(constrained_scroll_y / rect_height); 37 | int num_visible_rects = static_cast(child_size.y / rect_height) + 1; // +1 for partial visibility 38 | 39 | // Clamp the range of visible rectangles 40 | first_visible_rect = std::max(0, first_visible_rect); 41 | num_visible_rects = std::min(num_visible_rects, num_rects - first_visible_rect); 42 | 43 | // Set the child window size to the total height 44 | ImGui::Dummy(ImVec2(0.0f, total_height)); 45 | 46 | // Ensure consistent starting position 47 | ImGui::SetCursorScreenPos(child_pos); 48 | 49 | // Render the visible rectangles 50 | for (int i = 0; i < num_visible_rects; i++) 51 | { 52 | int rect_index = first_visible_rect + i; 53 | ImVec2 rect_min = ImVec2(child_pos.x, child_pos.y + 1 + rect_index * rect_height); 54 | ImVec2 rect_max = ImVec2(child_pos.x + child_size.x, rect_min.y + rect_height - 2); 55 | draw_list->AddRect(rect_min, rect_max, IM_COL32(255, 255, 255, 255)); 56 | 57 | // Render the text label 58 | char label[16]; 59 | snprintf(label, sizeof(label), "%d", rect_index + 1); 60 | draw_list->AddText(ImVec2(rect_min.x + 5, rect_min.y + 2), IM_COL32(255, 255, 0, 255), label); 61 | } 62 | 63 | ImGui::EndChild(); 64 | } 65 | 66 | // In your main render loop 67 | static inline void RenderRectangles() 68 | { 69 | ImGui::Begin("Rectangles"); 70 | RenderScrollableRectangles(); 71 | ImGui::End(); 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImPop 2 | 3 | This repository contains useful utilities for 4 | [Dear ImGui](https://github.com/ocornut/imgui) and 5 | [ImPlot](https://github.com/epezent/implot). 6 | 7 | `ImPop` should work in the same environments as `ImGui`. Care has been taken to use only 8 | basic C++11 facilities like `constexpr`. It avoids the use of the C++ standard library, 9 | except for the standalone header `impop_ostream.h`. 10 | Please report any build issues or incompatibilities. 11 | 12 | ## Usage 13 | 14 | `ImPop` consists of various (mostly-)independent header files. You can use just the 15 | files you need. Use it as a git submodule or copy individual files into your project. 16 | 17 | - `ImPop::DatePicker` 18 | - Lightweight config management 19 | - Color conversion and palette generation 20 | - `ImPop::OutlineText` 21 | - `ImPop::PerfFooter`: displays performance metrics 22 | 23 |
24 | 25 | ### ImPop::DatePicker 26 | 27 | The DatePicker from ImPlot, with a simplified interface that maintains the currently 28 | browsed (not yet selected) date within `ImGui::Storage`. 29 | 30 | ```cpp 31 | #include "impop_datepicker.h" 32 | 33 | // ... 34 | 35 | ImPlotTime t; // current value 36 | ImPlotTime default_time = ImPlot::MakeTime(2024, 1, 1); 37 | ImPlotTime min_time = ImPlot::MakeTime(2023, 1, 1); 38 | ImPlotTime max_time = ImPop::LocTimeNow(); 39 | 40 | if (ImPop::DatePicker("date", &t, &default_time, &min_time, &max_time)) { 41 | // `t` has been updated 42 | } 43 | ``` 44 | 45 |
46 | 47 | ### Lightweight config management 48 | 49 | Uses ImGui's ini file for application state. Provides a dropdown menu 50 | to edit config variables. 51 | 52 | #### `app_config.h` 53 | 54 | Your custom config must be a POD struct containing only a sequence of 55 | `ImPop::ConfigItem`. 56 | 57 | The given string will be used as a config key in the ini file, and also as the 58 | text for the menu. 59 | 60 | ```cpp 61 | #include "impop_config.h" 62 | 63 | struct Config { 64 | ImPop::ConfigItem use_foo_bar{"Use Foo and Bar", true}; 65 | ImPop::ConfigItem baz_coeff{"BazCoefficient (tm)", 3.14f}; 66 | ImPop::ConfigItem count_quux{"Number of Quux", 7}; 67 | } config; 68 | 69 | ``` 70 | 71 | #### `main.cpp` 72 | 73 | Initialize the config manager, and read any stored config values. Note that you 74 | must call `ImPop::InitializeConfig(config)` during a Frame, as it needs 75 | to access storage via the current window in the ImGui::Context. As you usually 76 | only want to load the ini file once on application startup, setup a frame before 77 | entering your main application loop. 78 | 79 | ```cpp 80 | #include "impop_config.h" 81 | #include "myconfig.h" 82 | 83 | // ... 84 | 85 | //ImGui_Impl*_NewFrame(); // Set the frame dimensions 86 | ImGui::NewFrame(); // Create a window for GetID() 87 | ImPop::InitializeConfig(config); 88 | ImGui::Render(); 89 | 90 | ``` 91 | 92 | #### `app.cpp` 93 | 94 | In your application code, read and write your config values directly from your `struct Config`: 95 | 96 | ```cpp 97 | #include "app_config.h" 98 | 99 | if (config.use_foo_bar.bool_value) { 100 | // Do stuff with foo and bar 101 | } 102 | 103 | int cakes = config.count_quux.int_value * 3; // 3 cakes each 104 | 105 | // Update the current estimate 106 | config.baz_coeff.float_value = 3.1417; 107 | 108 | ``` 109 | 110 | #### `app_gui.cpp` 111 | 112 | Display a menu for setting the config values: 113 | 114 | ```cpp 115 | #include "impop_config.h" 116 | 117 | if (ImGui::BeginMenu("My Application")) { 118 | ImPop::ConfigMenu(); 119 | ImGui::EndMenu(); 120 | } 121 | 122 | ``` 123 | 124 |
125 | 126 | ### Color conversion and palette generation 127 | 128 | These are declared constexpr, so they can be used in constructor initializers, or 129 | defined statically and evaluated at compile-time. 130 | 131 | 132 | ```cpp 133 | #include "impop_color.h" 134 | 135 | // ... 136 | 137 | 138 | // Color 139 | static constexpr ImU32 red = IM_COL32(246, 70, 93, 255); 140 | static constexpr ImU32 orange = IM_COL32(163, 129, 17, 255); 141 | static constexpr ImU32 green = IM_COL32(46, 189, 133, 255); 142 | 143 | // Compile-time color conversions 144 | static constexpr ImVec4 red_vec4 = ImPop::ColorConvertU32ToFloat4(red); 145 | static constexpr ImVec4 orange_vec4 = ImPop::ColorConvertU32ToFloat4(orange); 146 | static constexpr ImVec4 green_vec4 = ImPop::ColorConvertU32ToFloat4(green); 147 | 148 | // Compile-time palette generation 149 | static constexpr size_t palette_size = 16; 150 | static constexpr ImVec4 color_0 = red_vec4; 151 | static constexpr ImVec4 color_1 = ImPop::AdjustBrightness(orange_vec4, 0.6); 152 | static constexpr ImVec4 color_2 = ImPop::AdjustBrightness(ImPop::AdjustSaturation(green_vec4, 0.8), 0.8); 153 | static constexpr std::array traffic_lights = 154 | ImPop::GeneratePalette(color_0, color_1, color_2); 155 | ``` 156 | 157 |
158 | 159 | ### ImPop::OutlineText 160 | 161 | White text with a black outline. 162 | 163 | 164 | ```cpp 165 | #include "impop_text.h" 166 | 167 | //... 168 | 169 | ImPop::OutlineText(draw_list, ImVec2(pos.x, pos.y), "Welcome %s", name); 170 | 171 | ``` 172 | 173 |
174 | 175 | ### ImPop::PerfFooter 176 | 177 | This provides a handy footer displaying performance metrics: 178 | 179 | * Memory Usage: Arena, Free Space, Allocated Space 180 | * Memory Fragmentation 181 | * Current Framerate (FPS) 182 | 183 | ```cpp 184 | #include "impop_footer.h" 185 | 186 | //... 187 | 188 | ImPop::PerfFooter(); 189 | ``` 190 | 191 |
192 | 193 | License 194 | ------- 195 | 196 | ImPop is licensed under the MIT License, see [LICENSE.txt](https://github.com/kfish/impop/blob/master/LICENSE.txt) for more information. 197 | -------------------------------------------------------------------------------- /include/impop_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "imgui.h" 6 | #include "imgui_internal.h" 7 | 8 | namespace ImPop { 9 | 10 | // Enum for config item types 11 | enum class ConfigType { Bool, Int, Float, String }; 12 | 13 | // Structure to hold configuration items 14 | struct ConfigItem { 15 | const char* name; 16 | ConfigType type; 17 | union { 18 | bool bool_value; 19 | int int_value; 20 | float float_value; 21 | }; 22 | const char* string_value; 23 | 24 | ConfigItem(const char* n, bool value) : name(n), type(ConfigType::Bool), bool_value(value) {} 25 | ConfigItem(const char* n, int value) : name(n), type(ConfigType::Int), int_value(value) {} 26 | ConfigItem(const char* n, float value) : name(n), type(ConfigType::Float), float_value(value) {} 27 | ConfigItem(const char* n, const char* value) : name(n), type(ConfigType::String), string_value(value) {} 28 | }; 29 | 30 | static ConfigItem* config_items = nullptr; 31 | static int config_items_count = 0; 32 | static ImGuiStorage config_storage; 33 | 34 | template 35 | inline void InitializeConfig(T& items) { 36 | config_items = (ConfigItem*)(&items); 37 | config_items_count = sizeof(T) / sizeof(ConfigItem); 38 | 39 | ImGuiSettingsHandler ini_handler; 40 | ini_handler.TypeName = "CustomSettings"; 41 | ini_handler.TypeHash = ImHashStr("CustomSettings"); 42 | ini_handler.ReadOpenFn = [](ImGuiContext*, ImGuiSettingsHandler*, const char* name) -> void* { 43 | if (strcmp(name, "Settings") == 0) return (void*)1; 44 | return nullptr; 45 | }; 46 | ini_handler.ReadLineFn = [](ImGuiContext*, ImGuiSettingsHandler*, void*, const char* line) { 47 | char key_name[128]; 48 | char value[128]; 49 | if (sscanf(line, "%127[^=]=%127[^\n]", key_name, value) == 2) { 50 | for (int i = 0; i < config_items_count; ++i) { 51 | ConfigItem& item = config_items[i]; 52 | if (strcmp(item.name, key_name) == 0) { 53 | ImGuiID key = ImGui::GetID(item.name); 54 | switch (item.type) { 55 | case ConfigType::Bool: 56 | item.bool_value = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0); 57 | config_storage.SetBool(key, item.bool_value); 58 | break; 59 | case ConfigType::Int: 60 | item.int_value = atoi(value); 61 | config_storage.SetInt(key, item.int_value); 62 | break; 63 | case ConfigType::Float: 64 | item.float_value = static_cast(atof(value)); 65 | config_storage.SetFloat(key, item.float_value); 66 | break; 67 | case ConfigType::String: 68 | item.string_value = strdup(value); 69 | config_storage.SetVoidPtr(key, (void*)item.string_value); 70 | break; 71 | } 72 | break; 73 | } 74 | } 75 | } 76 | }; 77 | ini_handler.WriteAllFn = [](ImGuiContext*, ImGuiSettingsHandler* handler, ImGuiTextBuffer* out_buf) { 78 | out_buf->appendf("[%s][Settings]\n", handler->TypeName); 79 | for (int i = 0; i < config_items_count; ++i) { 80 | ConfigItem& item = config_items[i]; 81 | switch (item.type) { 82 | case ConfigType::Bool: 83 | out_buf->appendf("%s=%s\n", item.name, item.bool_value ? "true" : "false"); 84 | break; 85 | case ConfigType::Int: 86 | out_buf->appendf("%s=%d\n", item.name, item.int_value); 87 | break; 88 | case ConfigType::Float: 89 | out_buf->appendf("%s=%.3f\n", item.name, item.float_value); 90 | break; 91 | case ConfigType::String: 92 | out_buf->appendf("%s=%s\n", item.name, item.string_value); 93 | break; 94 | } 95 | } 96 | }; 97 | ImGui::GetCurrentContext()->SettingsHandlers.push_back(ini_handler); 98 | 99 | ImGui::LoadIniSettingsFromDisk(ImGui::GetIO().IniFilename); 100 | } 101 | 102 | inline void ConfigMenu(const char* name = "Config") { 103 | if (ImGui::BeginMenu(name)) { 104 | for (int i = 0; i < config_items_count; ++i) { 105 | ConfigItem& item = config_items[i]; 106 | ImGuiID key = ImGui::GetID(item.name); 107 | switch (item.type) { 108 | case ConfigType::Bool: 109 | if (ImGui::MenuItem(item.name, nullptr, &item.bool_value)) { 110 | config_storage.SetBool(key, item.bool_value); 111 | } 112 | break; 113 | case ConfigType::Int: 114 | if (ImGui::InputInt(item.name, &item.int_value)) { 115 | config_storage.SetInt(key, item.int_value); 116 | } 117 | break; 118 | case ConfigType::Float: 119 | if (ImGui::InputFloat(item.name, &item.float_value)) { 120 | config_storage.SetFloat(key, item.float_value); 121 | } 122 | break; 123 | case ConfigType::String: 124 | char buffer[256]; 125 | strncpy(buffer, item.string_value, sizeof(buffer)); 126 | if (ImGui::InputText(item.name, buffer, sizeof(buffer))) { 127 | item.string_value = strdup(buffer); 128 | config_storage.SetVoidPtr(key, (void*)item.string_value); 129 | } 130 | break; 131 | } 132 | } 133 | ImGui::EndMenu(); 134 | } 135 | } 136 | 137 | } // namespace ImPop 138 | -------------------------------------------------------------------------------- /include/impop_osd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | #include "impop_ease.h" 6 | #include "impop_transport.h" 7 | 8 | namespace ImPop { 9 | 10 | enum class OSDType { 11 | None=0, 12 | Text, 13 | Play, 14 | Pause, 15 | Stop, 16 | Forward, 17 | Reverse 18 | }; 19 | 20 | inline OSDType osd_type = OSDType::None; 21 | inline char osd_text_buffer[1024]; 22 | inline ImVec2 osd_text_pos; 23 | inline float osd_font_size; 24 | inline int osd_forward_reverse_speed = 1; 25 | 26 | inline float osd_start = 0; 27 | inline float osd_timeout = 0.5; 28 | inline float (*osd_ease_func)(float) = QuadEaseInOut; 29 | 30 | static inline void OSDSetTimeout(float timeout) { 31 | osd_timeout = timeout; 32 | } 33 | 34 | // Set to a different function from impop_ease.h 35 | static inline void OSDSetEaseFunc(float (*ease_func)(float)) { 36 | osd_ease_func = ease_func; 37 | } 38 | 39 | static inline void OSDClear() { 40 | osd_start = 0; 41 | osd_type = OSDType::None; 42 | } 43 | 44 | static inline ImVec2 OSDCalcTextSize(float max_text_width) { 45 | ImFont* font = ImGui::GetFont(); // Get the default font 46 | 47 | // Start with a large font size 48 | osd_font_size = 800.0f; 49 | ImVec2 text_size = font->CalcTextSizeA(osd_font_size, FLT_MAX, 0.0f, osd_text_buffer); 50 | 51 | // Adjust font size to fit the text within the screen width 52 | while (text_size.x > max_text_width && osd_font_size > 1.0f) { 53 | osd_font_size -= 1.0f; 54 | text_size = font->CalcTextSizeA(osd_font_size, FLT_MAX, 0.0f, osd_text_buffer); 55 | } 56 | 57 | return text_size; 58 | } 59 | 60 | static inline void OSDTextV(const char* fmt, va_list args) { 61 | osd_type = OSDType::Text; 62 | osd_start = ImGui::GetTime(); 63 | 64 | vsnprintf(osd_text_buffer, sizeof(osd_text_buffer), fmt, args); 65 | 66 | ImGuiIO& io = ImGui::GetIO(); 67 | float max_text_width = io.DisplaySize.x * 0.9f; // Let's use 90% of the screen width for text 68 | 69 | ImVec2 text_size = OSDCalcTextSize(max_text_width); 70 | 71 | // Calculate the position to center the text 72 | osd_text_pos = ImVec2((io.DisplaySize.x - text_size.x) * 0.5f, (io.DisplaySize.y - text_size.y) * 0.5f); 73 | } 74 | 75 | static inline void OSDText(const char* fmt, ...) { 76 | va_list args; 77 | va_start(args, fmt); 78 | OSDTextV(fmt, args); 79 | va_end(args); 80 | } 81 | 82 | static inline void OSDPlay() { 83 | osd_type = OSDType::Play; 84 | osd_start = ImGui::GetTime(); 85 | } 86 | 87 | static inline void OSDPause() { 88 | osd_type = OSDType::Pause; 89 | osd_start = ImGui::GetTime(); 90 | } 91 | 92 | static inline void OSDStop() { 93 | osd_type = OSDType::Stop; 94 | osd_start = ImGui::GetTime(); 95 | } 96 | 97 | static inline void OSDSetSpeed(int speed) { 98 | osd_forward_reverse_speed = speed; 99 | 100 | if (speed > 1) { 101 | snprintf(osd_text_buffer, sizeof(osd_text_buffer), "x%d", speed); 102 | ImGuiIO& io = ImGui::GetIO(); 103 | const ImVec2 sz = io.DisplaySize; 104 | float max_text_width = sz.x * 0.125f; 105 | 106 | ImVec2 text_size = OSDCalcTextSize(max_text_width); 107 | 108 | ImFont* font = ImGui::GetFont(); // Get the default font 109 | float ascent = font->Ascent * osd_font_size / font->FontSize; 110 | 111 | float offset = std::min(sz.x, sz.y)*0.25f; 112 | osd_text_pos = ImVec2(sz.x*0.5f + offset, sz.y*0.5f + offset - ascent); 113 | } else { 114 | osd_text_buffer[0] = '\0'; 115 | } 116 | } 117 | 118 | static inline void OSDForward(int speed=1) { 119 | osd_type = OSDType::Forward; 120 | osd_start = ImGui::GetTime(); 121 | 122 | OSDSetSpeed(speed); 123 | } 124 | 125 | static inline void OSDReverse(int speed=1) { 126 | osd_type = OSDType::Reverse; 127 | osd_start = ImGui::GetTime(); 128 | 129 | OSDSetSpeed(speed); 130 | } 131 | 132 | static inline void OSDShow() { 133 | if (osd_type == OSDType::None) return; 134 | 135 | // Update timeout 136 | float timer = ImGui::GetTime() - osd_start; 137 | if (timer > osd_timeout) { 138 | OSDClear(); 139 | return; 140 | } 141 | 142 | float t = 1.0 - (timer / osd_timeout); 143 | 144 | ImU32 alpha = 128 * osd_ease_func(t); 145 | ImU32 color = IM_COL32(255, 255, 255, alpha); 146 | ImU32 outline_color = IM_COL32(0, 0, 0, alpha); 147 | 148 | ImGuiIO& io = ImGui::GetIO(); 149 | const ImVec2 sz = io.DisplaySize; 150 | 151 | const ImVec2 center = ImVec2(sz.x*0.5f, sz.y*0.5f); 152 | const float side = std::min(center.x, center.y); 153 | const ImVec2 square = ImVec2(side, side); 154 | const ImVec2 square_min = ImVec2(center.x - side*0.5f, center.y - side*0.5f); 155 | 156 | // Access the foreground draw list (drawn after all windows) 157 | ImDrawList* draw_list = ImGui::GetForegroundDrawList(); 158 | 159 | switch (osd_type) { 160 | case OSDType::None: 161 | break; 162 | case OSDType::Text: 163 | draw_list->AddText(nullptr, osd_font_size, osd_text_pos, color, osd_text_buffer); 164 | break; 165 | case OSDType::Play: 166 | PlayIcon(draw_list, square_min, square, color); 167 | break; 168 | case OSDType::Pause: 169 | PauseIcon(draw_list, square_min, square, color); 170 | break; 171 | case OSDType::Stop: 172 | StopIcon(draw_list, square_min, square, color); 173 | break; 174 | case OSDType::Forward: 175 | ForwardIcon(draw_list, square_min, square, color); 176 | if (osd_forward_reverse_speed > 1) { 177 | draw_list->AddText(nullptr, osd_font_size, osd_text_pos, color, osd_text_buffer); 178 | } 179 | break; 180 | case OSDType::Reverse: 181 | ReverseIcon(draw_list, square_min, square, color); 182 | if (osd_forward_reverse_speed > 1) { 183 | draw_list->AddText(nullptr, osd_font_size, osd_text_pos, color, osd_text_buffer); 184 | } 185 | break; 186 | default: 187 | break; 188 | } 189 | } 190 | 191 | } // namespace ImPop 192 | -------------------------------------------------------------------------------- /include/impop_color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "imgui.h" 4 | 5 | namespace ImPop { 6 | 7 | // constexpr re-implementation of ImClamp. 8 | // See https://github.com/ocornut/imgui/pull/7662 9 | constexpr float Clamp(float v, float lo, float hi) { 10 | return (v < lo) ? lo : (v > hi) ? hi : v; 11 | } 12 | 13 | // constexpr re-implementation of ImGui::ColorConvertU32ToFloat4 14 | constexpr ImVec4 ColorConvertU32ToFloat4(ImU32 in) { 15 | float s = 1.0f / 255.0f; 16 | return ImVec4( 17 | ((in >> IM_COL32_R_SHIFT) & 0xFF) * s, 18 | ((in >> IM_COL32_G_SHIFT) & 0xFF) * s, 19 | ((in >> IM_COL32_B_SHIFT) & 0xFF) * s, 20 | ((in >> IM_COL32_A_SHIFT) & 0xFF) * s 21 | ); 22 | } 23 | 24 | // constexpr re-implementation of ImGui::ColorConvertFloat4ToU32 25 | constexpr ImU32 ColorConvertFloat4ToU32(const ImVec4& in) { 26 | ImU32 out; 27 | out = ((ImU32)(in.x * 255.0f + 0.5f) & 0xFF) << IM_COL32_R_SHIFT; 28 | out |= ((ImU32)(in.y * 255.0f + 0.5f) & 0xFF) << IM_COL32_G_SHIFT; 29 | out |= ((ImU32)(in.z * 255.0f + 0.5f) & 0xFF) << IM_COL32_B_SHIFT; 30 | out |= ((ImU32)(in.w * 255.0f + 0.5f) & 0xFF) << IM_COL32_A_SHIFT; 31 | return out; 32 | } 33 | 34 | // constexpr re-implementation of ImGui::ColorConvertRBGtoHSV 35 | constexpr void ColorConvertRGBtoHSV(float r, float g, float b, float& out_h, float& out_s, float& out_v) { 36 | float K = 0.0f; 37 | if (g < b) { 38 | std::swap(g, b); 39 | K = -1.0f; 40 | } 41 | if (r < g) { 42 | std::swap(r, g); 43 | K = -2.0f / 6.0f - K; 44 | } 45 | 46 | float chroma = r - (g < b ? g : b); 47 | out_h = fabsf(K + (g - b) / (6.0f * chroma + 1e-20f)); 48 | out_s = chroma / (r + 1e-20f); 49 | out_v = r; 50 | } 51 | 52 | // constexpr re-implementation of ImGui::ColorConvertHSVtoRGB 53 | constexpr void ColorConvertHSVtoRGB(float h, float s, float v, float& out_r, float& out_g, float& out_b) { 54 | if (s == 0.0f) { 55 | // gray 56 | out_r = out_g = out_b = v; 57 | return; 58 | } 59 | 60 | h = (h - static_cast(h)); // Equivalent to fmodf(h, 1.0f) 61 | h *= 6.0f; 62 | int i = static_cast(h); 63 | float f = h - (float)i; 64 | float p = v * (1.0f - s); 65 | float q = v * (1.0f - s * f); 66 | float t = v * (1.0f - s * (1.0f - f)); 67 | 68 | switch (i) { 69 | case 0: out_r = v; out_g = t; out_b = p; break; 70 | case 1: out_r = q; out_g = v; out_b = p; break; 71 | case 2: out_r = p; out_g = v; out_b = t; break; 72 | case 3: out_r = p; out_g = q; out_b = v; break; 73 | case 4: out_r = t; out_g = p; out_b = v; break; 74 | default: out_r = v; out_g = p; out_b = q; break; 75 | } 76 | } 77 | 78 | constexpr ImVec4 AdjustBrightness(ImVec4 color, float brightness) { 79 | // Ensure brightness is within the valid range [0, 1] 80 | brightness = (brightness < 0.0f) ? 0.0f : (brightness > 1.0f) ? 1.0f : brightness; 81 | 82 | // Adjust the color's RGB values by the brightness factor 83 | color.x *= brightness; 84 | color.y *= brightness; 85 | color.z *= brightness; 86 | 87 | // Ensure the RGB values remain within the valid range [0, 1] 88 | color.x = (color.x > 1.0f) ? 1.0f : color.x; 89 | color.y = (color.y > 1.0f) ? 1.0f : color.y; 90 | color.z = (color.z > 1.0f) ? 1.0f : color.z; 91 | 92 | return color; 93 | } 94 | 95 | // Helper function to convert RGB to HSL 96 | constexpr void RGBToHSL(const ImVec4& color, float& outH, float& outS, float& outL) { 97 | float r = color.x; 98 | float g = color.y; 99 | float b = color.z; 100 | 101 | float max = fmaxf(fmaxf(r, g), b); 102 | float min = fminf(fminf(r, g), b); 103 | outL = (max + min) / 2.0f; 104 | 105 | if (max == min) { 106 | outH = outS = 0.0f; // achromatic 107 | } else { 108 | float d = max - min; 109 | outS = (outL > 0.5f) ? d / (2.0f - max - min) : d / (max + min); 110 | 111 | if (max == r) { 112 | outH = (g - b) / d + (g < b ? 6.0f : 0.0f); 113 | } else if (max == g) { 114 | outH = (b - r) / d + 2.0f; 115 | } else { 116 | outH = (r - g) / d + 4.0f; 117 | } 118 | outH /= 6.0f; 119 | } 120 | } 121 | 122 | // Helper function to convert HSL to RGB 123 | constexpr ImVec4 HSLToRGB(float h, float s, float l, float a) { 124 | float r, g, b; 125 | 126 | auto hue2rgb = [](float p, float q, float t) { 127 | if (t < 0.0f) t += 1.0f; 128 | if (t > 1.0f) t -= 1.0f; 129 | if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t; 130 | if (t < 1.0f / 2.0f) return q; 131 | if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f; 132 | return p; 133 | }; 134 | 135 | if (s == 0.0f) { 136 | r = g = b = l; // achromatic 137 | } else { 138 | float q = (l < 0.5f) ? l * (1.0f + s) : l + s - l * s; 139 | float p = 2.0f * l - q; 140 | r = hue2rgb(p, q, h + 1.0f / 3.0f); 141 | g = hue2rgb(p, q, h); 142 | b = hue2rgb(p, q, h - 1.0f / 3.0f); 143 | } 144 | 145 | return ImVec4(r, g, b, a); 146 | } 147 | 148 | // Constexpr function to adjust saturation 149 | constexpr ImVec4 AdjustSaturation(const ImVec4& color, float saturation) { 150 | float h, s, l; 151 | RGBToHSL(color, h, s, l); 152 | 153 | // Clamp the saturation value 154 | saturation = (saturation < 0.0f) ? 0.0f : (saturation > 1.0f) ? 1.0f : saturation; 155 | 156 | // Set new saturation 157 | s = saturation; 158 | 159 | return HSLToRGB(h, s, l, color.w); 160 | } 161 | 162 | // Interpolate between two colors in HSV space and return RGB 163 | constexpr ImVec4 InterpolateColorsHSV(const ImVec4& color1, const ImVec4& color2, float t) { 164 | // Clamp 't' to the range [0, 1] to avoid extrapolation 165 | t = Clamp(t, 0.0f, 1.0f); 166 | 167 | // Convert RGB to HSV 168 | float h1, s1, v1; 169 | float h2, s2, v2; 170 | ColorConvertRGBtoHSV(color1.x, color1.y, color1.z, h1, s1, v1); 171 | ColorConvertRGBtoHSV(color2.x, color2.y, color2.z, h2, s2, v2); 172 | 173 | // Handle hue wrap-around 174 | if (fabsf(h2 - h1) > 0.5f) { 175 | if (h1 < h2) { 176 | h1 += 1.0f; 177 | } else { 178 | h2 += 1.0f; 179 | } 180 | } 181 | 182 | // Interpolate H, S, and V components separately 183 | float h = h1 + t * (h2 - h1); 184 | float s = s1 + t * (s2 - s1); 185 | float v = v1 + t * (v2 - v1); 186 | 187 | // Wrap hue back to [0, 1] 188 | if (h >= 1.0f) h -= 1.0f; 189 | if (h < 0.0f) h += 1.0f; 190 | 191 | // Convert HSV back to RGB 192 | float r, g, b; 193 | ColorConvertHSVtoRGB(h, s, v, r, g, b); 194 | 195 | // Interpolate alpha 196 | float a = color1.w + t * (color2.w - color1.w); 197 | 198 | return ImVec4(r, g, b, a); 199 | } 200 | 201 | // Generic function to interpolate using a quadratic Bézier curve 202 | constexpr float BezierInterpolate(float p0, float p1, float p2, float t) { 203 | float oneMinusT = 1.0f - t; 204 | return (oneMinusT * oneMinusT * p0) + (2.0f * oneMinusT * t * p1) + (t * t * p2); 205 | } 206 | 207 | // Interpolate between three colors using a quadratic Bézier curve in HSV space 208 | constexpr ImVec4 BezierInterpolateColorsHSV(const ImVec4& color1, const ImVec4& color2, const ImVec4& color3, float t) { 209 | // Clamp 't' to the range [0, 1] 210 | t = (t < 0.0f) ? 0.0f : (t > 1.0f) ? 1.0f : t; 211 | 212 | // Convert RGB to HSV 213 | float h1, s1, v1; 214 | float h2, s2, v2; 215 | float h3, s3, v3; 216 | ColorConvertRGBtoHSV(color1.x, color1.y, color1.z, h1, s1, v1); 217 | ColorConvertRGBtoHSV(color2.x, color2.y, color2.z, h2, s2, v2); 218 | ColorConvertRGBtoHSV(color3.x, color3.y, color3.z, h3, s3, v3); 219 | 220 | // Interpolate HSV components using the generic Bézier interpolation function 221 | float h = BezierInterpolate(h1, h2, h3, t); 222 | float s = BezierInterpolate(s1, s2, s3, t); 223 | float v = BezierInterpolate(v1, v2, v3, t); 224 | 225 | // Convert interpolated HSV back to RGB 226 | float r, g, b; 227 | ColorConvertHSVtoRGB(h, s, v, r, g, b); 228 | 229 | // Interpolate alpha using the Bézier formula 230 | float a = BezierInterpolate(color1.w, color2.w, color3.w, t); 231 | 232 | return ImVec4(r, g, b, a); 233 | } 234 | 235 | // Constexpr function to generate a palette of ImU32 colors 236 | template 237 | constexpr std::array GeneratePalette(const ImVec4& color1, const ImVec4& color2, const ImVec4& color3) { 238 | std::array palette = {}; 239 | for (size_t i = 0; i < 16; ++i) { 240 | float t = static_cast(i) / static_cast(N-1); 241 | ImVec4 interpolatedColor = BezierInterpolateColorsHSV(color1, color2, color3, t); 242 | palette[i] = ColorConvertFloat4ToU32(interpolatedColor); 243 | } 244 | return palette; 245 | } 246 | 247 | } // namespace ImPop 248 | --------------------------------------------------------------------------------