/* ************************************************************************************** * Author: Scott Mudge * MAIL@SCOTTMUDGE.COM * * MIT License * ----------- * MAIL@SCOTTMUDGE.COM * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * **************************************************************************************/ // File Name: imgui_tabs.cpp // File Description: Custom ImGui tab system module #include "imgui_tabs.h" ImGui::ImGuiUserStyle::ImGuiUserStyle() { Colors[ImGuiUserCol_TabNormal] = ImVec4(0.65f, 0.65f, 0.68f, 1.00f); Colors[ImGuiUserCol_TabTitleTextNormal] = ImVec4(0.0f, 0.0f, 0.0f, 0.5f); Colors[ImGuiUserCol_TabTitleTextSelected] = ImVec4(1.0f, 1.0f, 1.0f, 1.00f); Colors[ImGuiUserCol_TabBorder] = Colors[ImGuiUserCol_TabNormal] * ImVec4(1.15f, 1.15f, 1.15f, 1.0f); Colors[ImGuiUserCol_TabBorderShadow] = Colors[ImGuiUserCol_TabNormal] * ImVec4(0.65f, 0.65f, 0.65f, 1.0f); Colors[ImGuiUserCol_TabHover] = Colors[ImGuiUserCol_TabNormal] * ImVec4(1.15f, 1.15f, 1.15f, 1.0f); } const ImU32 ImGui::GetColorU32(ImGuiUserCol_ idx, float alpha_mul) { ImVec4 c = UserStyle.Colors[idx]; if (alpha_mul > 0.0f) c.w *= GImGui->Style.Alpha * alpha_mul; return ColorConvertFloat4ToU32(c); } void ImGui::_drawPartialRect(const ImVec2 a, const ImVec2 b, const float rounding, const int rounding_corners, ImDrawList* dl, const _EdgeType edges, const ImU32 color, const bool shadow, const _EdgeType shadow_edges, const float shadow_offset, const float shadow_alpha) { float r = rounding; r = ImMin(r, fabsf(b.x - a.x) * (((rounding_corners & (1 | 2)) == (1 | 2)) || ((rounding_corners & (4 | 8)) == (4 | 8)) ? 0.5f : 1.0f) - 1.0f); r = ImMin(r, fabsf(b.y - a.y) * (((rounding_corners & (1 | 8)) == (1 | 8)) || ((rounding_corners & (2 | 4)) == (2 | 4)) ? 0.5f : 1.0f) - 1.0f); if (r <= 0.0f || rounding_corners == 0) { if (edges & EDGE_TOP) { dl->PathLineTo(a); dl->PathLineTo(ImVec2(b.x, a.y)); dl->PathStroke(color, false, 1.0f); } if (edges & EDGE_RIGHT) { dl->PathLineTo(ImVec2(b.x, a.y)); dl->PathStroke(color, false, 1.0f); dl->PathLineTo(b); } if (edges & EDGE_BOTTOM) { dl->PathLineTo(b); dl->PathLineTo(ImVec2(a.x, b.y)); dl->PathStroke(color, false, 1.0f); } if (edges & EDGE_LEFT) { dl->PathLineTo(ImVec2(a.x, b.y)); dl->PathLineTo(a); dl->PathStroke(color, false, 1.0f); } } else { const float r0 = (rounding_corners & 1) ? r : 0.0f; const float r1 = (rounding_corners & 2) ? r : 0.0f; const float r2 = (rounding_corners & 4) ? r : 0.0f; const float r3 = (rounding_corners & 8) ? r : 0.0f; if (edges & EDGE_TOP) { dl->PathArcToFast(ImVec2(a.x + r0, a.y + r0), r0, 6, 9); dl->PathArcToFast(ImVec2(b.x - r1, a.y + r1), r1, 9, 12); dl->PathStroke(color, false, 1.0f); } if (edges & EDGE_RIGHT) { dl->PathArcToFast(ImVec2(b.x - r1, a.y + r1), r1, 9, 12); dl->PathArcToFast(ImVec2(b.x - r2, b.y - r2), r2, 0, 3); dl->PathStroke(color, false, 1.0f); } if (edges & EDGE_BOTTOM) { dl->PathArcToFast(ImVec2(b.x - r2, b.y - r2), r2, 0, 3); dl->PathArcToFast(ImVec2(a.x + r3, b.y - r3), r3, 3, 6); dl->PathStroke(color, false, 1.0f); } if (edges & EDGE_LEFT) { dl->PathArcToFast(ImVec2(a.x + r3, b.y - r3), r3, 3, 6); dl->PathArcToFast(ImVec2(a.x + r0, a.y + r0), r0, 6, 9); dl->PathStroke(color, false, 1.0f); } if (shadow) { const ImColor col = ImColor(0.0f, 0.0f, 0.0f, shadow_alpha); if (shadow_edges & EDGE_LEFT) { dl->PathArcToFast(ImVec2(a.x + r3 - shadow_offset, b.y - r3), r3, 3, 6); dl->PathArcToFast(ImVec2(a.x + r0 - shadow_offset, a.y + r0), r0, 6, 9); dl->PathLineTo(ImVec2(a.x, b.y)); dl->PathFill(col); } if (shadow_edges & EDGE_RIGHT) { dl->PathArcToFast(ImVec2(b.x - r1 + shadow_offset, a.y + r1), r1, 9, 12); dl->PathArcToFast(ImVec2(b.x - r2 + shadow_offset, b.y - r2), r2, 0, 3); dl->PathLineTo(ImVec2(b.x, b.y)); dl->PathFill(col); } } } } ImGui::TabBar::TabBar(const char* label, const ImVec2 tab_bar_size) { hash = ImHash(label, 0); size = tab_bar_size; } void ImGui::TabBar::setActiveTab(const unsigned idx) { activeTab = (int)idx; // This get's checked at the beginning of } const int ImGui::TabBar::getActiveTab() { return activeTab; } void ImGui::TabBar::_drawTabBarTop(const char* label) { if (!hasBeenInitialized) return; // Gather pointers and references ImGuiWindow* wind = ImGui::GetCurrentWindow(); ImDrawList* dl = wind->DrawList; ImGuiDrawContext& dc = wind->DC; ImGuiStyle& style = ImGui::GetStyle(); std::string str = label; barTitle = str.substr(0, str.find_first_of("#")); if (barTitle.length() > 0) ImGui::Text(std::string("\t" + barTitle).c_str(), "%s"); // Get some geometric data const ImVec2 padding = style.WindowPadding; const ImVec2 frame_padding = style.FramePadding; ImVec2 pos = dc.CursorPos; const float division = ((wind->Size.x - 2 * padding.x) / (float)tabCount); // Compile-time constants static constexpr const float rounding = 6.0f; // This is the border shrink in px. Not sure why it needs to be 1, but it works. Adjust to your border size static constexpr const float shrink = 1.0f; const float tab_height = CalcTextSize(tabTitles[0]).y + (frame_padding.y * 2); float selected_offset = 0; _TabType selected_tab_type; ImVec2 selected_expands; _EdgeType selected_shadow_edges = EDGE_NONE; int selected_idx = 0; // Store maximum/minimum x value for clipping rect. const float max_x = wind->Pos.x + wind->Size.x - padding.x; // Used for clipping rect adjustment. const float min_x = wind->Pos.x + padding.x; if (activeTab > tabCount - 1) activeTab = tabCount - 1; // Idx of the selected tab newSelected = -1; // Draw the tabs for (int i = 0; i < tabCount; i++) { // Calculate offset const float offs = i * division; const bool selected = (activeTab == i); ImVec2 expands; if (!selected) { if (i == 0) { expands = ImVec2(0, 2); } else if (i == tabCount - 1) { expands = ImVec2(1, 0); } else { expands = ImVec2(1, 2); } const float xl = offs - expands.x + shrink; const float xr = offs + division - shrink + expands.y; const ImRect bb = ImRect(ImVec2(pos + ImVec2(xl, 0)), ImVec2(pos + ImVec2(xr, tab_height))); bool hovered, held; bool pressed = ButtonBehavior(bb, tabHashes[i], &hovered, &held); if (held) newSelected = i; ImU32 col; hovered ? col = GetColorU32(ImGuiUserCol_TabHover) : col = GetColorU32(ImGuiUserCol_TabNormal); // Draw background rect dl->AddRectFilled(bb.Min, bb.Max, col, rounding, ImGuiCorner_TopLeft | ImGuiCorner_TopRight); // Draw the border _drawPartialRect(bb.Min, bb.Max, rounding, ImGuiCorner_TopLeft | ImGuiCorner_TopRight, dl, (_EdgeType)(EDGE_LEFT | EDGE_RIGHT | EDGE_TOP), GetColorU32(ImGuiUserCol_TabBorderShadow)); // Draw the text const ImVec2 text_size = CalcTextSize(tabTitles[i]); const ImVec2 text_pos = pos + ImVec2(offs + ((xr - xl) - text_size.x) / 2.0f, ((text_size.y - frame_padding.y * 2.0f) / 2.0f)); dl->AddText(text_pos, GetColorU32(ImGuiUserCol_TabTitleTextNormal), tabTitles[i]); } else { selected_offset = offs; selected_idx = i; if (i == 0) { selected_expands = ImVec2(0, rounding); selected_shadow_edges = EDGE_RIGHT; } else if (i == tabCount - 1) { selected_expands = ImVec2(rounding, 0); selected_shadow_edges = EDGE_LEFT; } else { selected_expands = ImVec2(rounding, rounding); selected_shadow_edges = (_EdgeType)(EDGE_RIGHT | EDGE_LEFT); } } } const float xl = selected_offset - selected_expands.x + shrink; const float xr = selected_offset + division - shrink + selected_expands.y; const ImRect bb = ImRect(ImVec2(pos + ImVec2(xl, 0)), ImVec2(pos + ImVec2(xr, tab_height))); // Draw the selected tab on top of everything else dl->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiUserCol_TabNormal), rounding, ImGuiCorner_TopLeft | ImGuiCorner_TopRight); dl->AddRectFilled(bb.Min, bb.Max, ImColor(1.0f, 1.0f, 1.0f, 0.35f), rounding, ImGuiCorner_TopLeft | ImGuiCorner_TopRight); // Draw the border _drawPartialRect(bb.Min, bb.Max, rounding, ImGuiCorner_TopLeft | ImGuiCorner_TopRight, dl, (_EdgeType)(EDGE_LEFT | EDGE_RIGHT | EDGE_TOP), GetColorU32(ImGuiUserCol_TabBorderShadow), true, selected_shadow_edges); // Draw the text const ImVec2 text_size = CalcTextSize(tabTitles[selected_idx]); const ImVec2 text_pos = pos + ImVec2(selected_offset + ((xr - xl) - text_size.x) / 2.0f - selected_expands.x, ((text_size.y - frame_padding.y * 2.0f) / 2.0f)); dl->AddText(text_pos + ImVec2(1, 1), GetColorU32(ImGuiUserCol_TabTitleTextNormal), tabTitles[selected_idx]); // Shadow dl->AddText(text_pos, GetColorU32(ImGuiUserCol_TabTitleTextSelected), tabTitles[selected_idx]); dc.CursorPos += ImVec2(0, tab_height + padding.y); // Add all the extra height used above. upperLeft = dc.CursorPos - ImVec2(0, padding.y); Indent(padding.x); corner_rounding = rounding; ImGui::PushClipRect(ImVec2(min_x, bb.Max.y), ImVec2(max_x, wind->ClipRect.Max.y), false); } void ImGui::TabBar::_drawTabBarBottom() { ImGuiWindow* wind = ImGui::GetCurrentWindow(); ImDrawList* dl = wind->DrawList; ImGuiStyle& style = ImGui::GetStyle(); ImGuiDrawContext& dc = wind->DC; const ImVec2 padding = style.WindowPadding; // dc.CursorPos.y = bottom of tabs + padding.y // wind->Size.y gives the full window height // the zero below the tabs is dc.CursorPos.y - padding.y // and if we wnt to add the tab height, we'll have to remove a constant // (= all the additional offsets used for the layout). FIXME : use the real name(s) instead const float height = dc.CursorPos.y - padding.y + wind->Size.y - 92.0f; const ImVec2 pos = ImVec2(wind->Pos.x + wind->Size.x - padding.x, height); // Draw the background in a given color + alpha dl->AddRectFilled(upperLeft, pos, ImColor(1.0f, 1.0f, 1.0f, 0.15f), corner_rounding, ImGuiCorner_BottomLeft | ImGuiCorner_BottomRight); // Draw the border in a given color + alpha dl->AddRect(upperLeft, pos, ImColor(1.0f, 1.0f, 1.0f, 0.35f), corner_rounding, ImGuiCorner_BottomLeft | ImGuiCorner_TopRight); dc.CursorPos += ImVec2(0, corner_rounding + 2.0f); // Add all the extra height used above. ImGui::PopClipRect(); } void ImGui::TabBar::_setTabCount() { if (newSelected >= 0) { activeTab = newSelected; } // If the tab count changes, reinitialize the list. if (hasBeenInitialized && tabCount != idxCounter) { hasBeenInitialized = false; tabTitles.clear(); tabHashes.clear(); return; } tabCount = idxCounter; hasBeenInitialized = true; } ImGui::TabBar* ImGui::TabBarStack::getCurrentTabBar() { if (CurrentTabBar > TabBarCount) { std::cerr << "TabBar (Error @getCurrentTabBar()) -- CurrentTabBar > TabBarCount" << std::endl; return NULL; } return &TabBars[CurrentTabBar]; } void ImGui::TabBarStack::clearIdxCountCurrentTabBar() { TabBars[CurrentTabBar].idxCounter = 0; } const bool ImGui::TabBarStack::doesTabBarExist(const ImU32 hash, unsigned int* const idx) { for (unsigned i = 0; i < TabBarCount; i++) if (hash == TabBars[i].hash) { if (idx) *idx = i; return true; } return false; } const bool ImGui::TabBarStack::doesTabBarExist(const char* id, unsigned int* const idx) { const ImU32 hash = ImHash(id, 0); for (unsigned i = 0; i < TabBarCount; i++) if (hash == TabBars[i].hash) { if (idx) *idx = i; return true; } *idx = 0; return false; } void ImGui::BeginTabBar(const char* label, const ImVec2 size) { // Pass this to doesTabBarExist() to get the index of the started TabBar unsigned idx; // If a tab bar doesn't exist, let's create one. if (!TabStack.doesTabBarExist(label, &idx)) { TabBar bar = TabBar(label, size); TabStack.TabBars.push_back(bar); TabStack.CurrentTabBar = TabStack.TabBarCount; TabStack.TabBarCount++; } else { // Set the current active tab bar TabStack.CurrentTabBar = idx; TabStack.clearIdxCountCurrentTabBar(); } TabStack.getCurrentTabBar()->_drawTabBarTop(label); } const bool ImGui::AddTab(const char* title) { // Get current TabBar; TabBar* bar = TabStack.getCurrentTabBar(); // Check to make sure there aren't any null pointers assert(bar != NULL); bar->idxCounter++; // Always increase the counter; if (bar->idxCounter > bar->tabCount) bar->hasBeenInitialized = false; if (!bar->hasBeenInitialized) { bar->tabTitles.push_back(title); bar->tabHashes.push_back(ImHash(std::string(title + std::to_string(bar->idxCounter)).c_str(), 0)); } if (bar->activeTab != (bar->idxCounter - 1)) return false; return bar->hasBeenInitialized; } void ImGui::DrawTabsBackground() { TabBar* bar = TabStack.getCurrentTabBar(); if (bar->hasBeenInitialized) bar->_drawTabBarBottom(); } void ImGui::EndTabBar() { TabBar* bar = TabStack.getCurrentTabBar(); bar->_setTabCount(); } /* FIXME : unused ? void ImGui::SetActiveTabOfCurrentTabBar(const unsigned idx) { TabStack.getCurrentTabBar()->setActiveTab(idx); } */