391 lines
15 KiB
C++
391 lines
15 KiB
C++
/* **************************************************************************************
|
|
* 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);
|
|
}
|
|
*/
|