1 module creator.widgets.category; 2 import creator.core; 3 import creator.widgets; 4 import bindbc.imgui; 5 6 private { 7 struct CategoryData { 8 bool open; 9 bool badColor; 10 bool isDark; 11 ImVec4 contentBounds; 12 IncCategoryFlags flags; 13 } 14 15 void incGetCategoryColors(ImVec4 color, out ImVec4 hoverColor, out ImVec4 activeColor, out ImVec4 bgColor, out ImVec4 shadowColor, ref ImVec4 textColor, ref bool isDark) { 16 17 // First get HSV version of color 18 float h, s, v; 19 hoverColor = color; 20 activeColor = color; 21 shadowColor = color; 22 bgColor = color; 23 24 // Assume all colors are dark and invert them if we're in light mode 25 igColorConvertRGBtoHSV(color.x, color.y, color.z, &h, &s, &v); 26 isDark = v <= 0.5; 27 if (!isDark) textColor = ImVec4(0, 0, 0, 1); 28 else textColor = ImVec4(1, 1, 1, 1); 29 30 // Then darken/lighten it the first time for active color 31 s -= 0.075*s; 32 if (v > 0.5) v -= 0.05; 33 else v += 0.15; 34 igColorConvertHSVtoRGB(h, s, v, &activeColor.x, &activeColor.y, &activeColor.z); 35 36 // Then darken/lighten it the second time for hover color 37 s -= 0.075*s; 38 if (v > 0.5) v -= 0.05; 39 else v += 0.15; 40 igColorConvertHSVtoRGB(h, s, v, &hoverColor.x, &hoverColor.y, &hoverColor.z); 41 42 // Then darken it for bg color 43 igColorConvertRGBtoHSV(color.x, color.y, color.z, &h, &s, &v); 44 s *= 0.90; 45 if (v <= 0.15) v = 0.15; 46 else v *= 0.80; 47 igColorConvertHSVtoRGB(h, s, v, &bgColor.x, &bgColor.y, &bgColor.z); 48 49 // Finally the shadow color 50 igColorConvertRGBtoHSV(color.x, color.y, color.z, &h, &s, &v); 51 s *= 0.85; 52 v *= 0.75; 53 igColorConvertHSVtoRGB(h, s, v, &shadowColor.x, &shadowColor.y, &shadowColor.z); 54 shadowColor.w *= 2; 55 } 56 } 57 58 enum IncCategoryFlags { 59 None = 0, 60 NoCollapse = 1 61 } 62 63 /** 64 Begins a category using slightly darkened versions of the main UI colors 65 66 Remember to call incEndCategory after! 67 */ 68 bool incBeginCategory(const(char)* title, IncCategoryFlags flags = IncCategoryFlags.None) { 69 ImVec4 col = igGetStyle().Colors[ImGuiCol.WindowBg]; 70 col = ImVec4(col.x-0.025, col.y-0.025, col.z-0.025, col.w); 71 return incBeginCategory(title, col, flags); 72 } 73 74 /** 75 Begins a category using the defined color 76 77 Remember to call incEndCategory after! 78 */ 79 bool incBeginCategory(const(char)* title, ImVec4 color, IncCategoryFlags flags = IncCategoryFlags.None) { 80 import inmath : clamp; 81 82 // We do not support transparency 83 color.w = 1; 84 color.x = clamp(color.x, 0.15, 1); 85 color.y = clamp(color.y, 0.15, 1); 86 color.z = clamp(color.z, 0.15, 1); 87 88 // Calculate colors for category 89 ImVec4 hoverColor; 90 ImVec4 activeColor; 91 ImVec4 shadowColor; 92 ImVec4 bgColor; 93 ImVec4 textColor; 94 bool isDark; 95 incGetCategoryColors(color, hoverColor, activeColor, bgColor, shadowColor, textColor, isDark); 96 97 // Push ID of our category 98 igPushID(title); 99 auto storage = igGetStateStorage(); 100 auto id = igGetID("CATEGORYDATA"); 101 auto window = igGetCurrentWindow(); 102 103 // The fun stuff starts 104 igPushStyleColor(ImGuiCol.HeaderHovered, hoverColor); 105 igPushStyleColor(ImGuiCol.HeaderActive, activeColor); 106 igPushStyleColor(ImGuiCol.Text, textColor); 107 CategoryData* data = cast(CategoryData*)ImGuiStorage_GetVoidPtr(storage, id); 108 if (!data) { 109 data = cast(CategoryData*)igMemAlloc(CategoryData.sizeof); 110 data.open = false; 111 data.contentBounds = ImVec4( 112 0, 0, 0, 0 113 ); 114 ImGuiStorage_SetVoidPtr(storage, id, data); 115 } 116 117 // Calculate some values for drawing our background color. 118 data.flags = flags; 119 data.badColor = isDark != incGetDarkMode(); 120 data.isDark = isDark; 121 data.contentBounds.x = igGetCursorPosX(); 122 data.contentBounds.y = igGetCursorPosY(); 123 data.contentBounds.z = incAvailableSpace().x; 124 125 ImVec2 cursor; 126 igGetCursorScreenPos(&cursor); 127 128 // Draw background color 129 ImDrawList_AddRectFilled( 130 igGetWindowDrawList(), 131 ImVec2(cursor.x, cursor.y), 132 ImVec2(cursor.x+data.contentBounds.z, cursor.y+data.contentBounds.w+1), 133 igGetColorU32(bgColor) 134 ); 135 136 // Draw "shadow" underneath 137 ImDrawList_AddRectFilled( 138 igGetWindowDrawList(), 139 ImVec2(cursor.x, cursor.y+data.contentBounds.w-1), 140 ImVec2(cursor.x+data.contentBounds.z, cursor.y+data.contentBounds.w+1), 141 igGetColorU32(shadowColor) 142 ); 143 144 // Our fancy tree node which will be used to open/close the category. 145 incDummy(ImVec2(0, 2)); 146 147 float paddingX = igGetStyle().WindowPadding.x/2; 148 window.ContentRegionRect.Min.x -= paddingX; 149 window.WorkRect.Min.x -= paddingX; 150 igSetCursorPosX(igGetCursorPosX()+paddingX); 151 152 if ((data.flags & IncCategoryFlags.NoCollapse) == IncCategoryFlags.NoCollapse) { 153 data.open = true; 154 igIndent(); 155 igText(title); 156 igUnindent(); 157 } else data.open = igTreeNodeEx(title, ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.NoTreePushOnOpen | ImGuiTreeNodeFlags.SpanAvailWidth); 158 159 window.ContentRegionRect.Min.x += paddingX; 160 window.WorkRect.Min.x += paddingX; 161 162 if (data.open) { 163 ImVec2 newCursor; 164 igGetCursorScreenPos(&newCursor); 165 166 float diffY = newCursor.y-cursor.y; 167 168 // Draw lighter fill color for contents when open 169 ImDrawList_AddRectFilled( 170 igGetWindowDrawList(), 171 ImVec2(newCursor.x+2, newCursor.y), 172 ImVec2(newCursor.x+data.contentBounds.z-2, newCursor.y+data.contentBounds.w-(diffY+1)), 173 igGetColorU32(color) 174 ); 175 } 176 177 // NOTE: We use these instead of incDummy since otherwise you can't drag 178 // the tree node via drag/drop. 179 igSetCursorPosY(igGetCursorPosY()+2); 180 181 if (data.open) { 182 igSetCursorPosY(igGetCursorPosY()+2); 183 igIndent(); 184 } 185 186 187 igPopStyleColor(3); 188 189 // We force our childrens' content to fit better within ourselves 190 // This gets undone after incEndCategory is called. 191 float indentSpacing = igGetStyle().IndentSpacing; 192 window.ContentRegionRect.Min.x -= indentSpacing; 193 window.WorkRect.Min.x -= indentSpacing; 194 195 if (data.badColor && data.isDark) incPushDarkColorScheme(); 196 else if (data.badColor && !data.isDark) incPushLightColorScheme(); 197 198 199 return data.open; 200 } 201 202 /** 203 Ends a category 204 */ 205 void incEndCategory() { 206 auto window = igGetCurrentWindow(); 207 208 // Undo our fancy content fit math. 209 // This needs to be here otherwise the usable size of the child 210 // window will continue to shrink 211 window.ContentRegionRect.Min.x += igGetStyle().IndentSpacing; 212 window.WorkRect.Min.x += igGetStyle().IndentSpacing; 213 214 auto storage = igGetStateStorage(); 215 auto id = igGetID("CATEGORYDATA"); 216 if (CategoryData* data = cast(CategoryData*)ImGuiStorage_GetVoidPtr(storage, id)) { 217 if (data.badColor) incPopColorScheme(); 218 if (data.open) { 219 igUnindent(); 220 incDummy(ImVec2(0, 2)); 221 } 222 223 data.contentBounds.w = igGetCursorPosY()-data.contentBounds.y; 224 225 igSpacing(); 226 igSpacing(); 227 } 228 229 igPopID(); 230 }