1/*
2 nanogui/tabwidget.cpp -- A wrapper around the widgets TabHeader and StackedWidget
3 which hooks the two classes together.
4
5 The tab widget was contributed by Stefan Ivanov.
6
7 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
8 The widget drawing code is based on the NanoVG demo application
9 by Mikko Mononen.
10
11 All rights reserved. Use of this source code is governed by a
12 BSD-style license that can be found in the LICENSE.txt file.
13*/
14
15#include <nanogui/tabwidget.h>
16#include <nanogui/tabheader.h>
17#include <nanogui/stackedwidget.h>
18#include <nanogui/theme.h>
19#include <nanogui/opengl.h>
20#include <nanogui/window.h>
21#include <nanogui/screen.h>
22#include <algorithm>
23
24NAMESPACE_BEGIN(nanogui)
25
26TabWidget::TabWidget(Widget* parent)
27 : Widget(parent)
28 , mHeader(new TabHeader(nullptr)) // create using nullptr, add children below
29 , mContent(new StackedWidget(nullptr)) {
30
31 // since TabWidget::addChild is going to throw an exception to prevent
32 // mis-use of this class, add the child directly
33 Widget::addChild(childCount(), mHeader);
34 Widget::addChild(childCount(), mContent);
35
36 mHeader->setCallback([this](int i) {
37 mContent->setSelectedIndex(i);
38 if (mCallback)
39 mCallback(i);
40 });
41}
42
43void TabWidget::addChild(int /*index*/, Widget * /*widget*/) {
44 // there may only be two children: mHeader and mContent, created in the constructor
45 throw std::runtime_error(
46 "TabWidget: do not add children directly to the TabWidget, create tabs "
47 "and add children to the tabs. See TabWidget class documentation for "
48 "example usage."
49 );
50}
51
52void TabWidget::setActiveTab(int tabIndex) {
53 mHeader->setActiveTab(tabIndex);
54 mContent->setSelectedIndex(tabIndex);
55}
56
57int TabWidget::activeTab() const {
58 assert(mHeader->activeTab() == mContent->selectedIndex());
59 return mContent->selectedIndex();
60}
61
62int TabWidget::tabCount() const {
63 assert(mContent->childCount() == mHeader->tabCount());
64 return mHeader->tabCount();
65}
66
67Widget* TabWidget::createTab(int index, const std::string &label) {
68 Widget* tab = new Widget(nullptr);
69 addTab(index, label, tab);
70 return tab;
71}
72
73Widget* TabWidget::createTab(const std::string &label) {
74 return createTab(tabCount(), label);
75}
76
77void TabWidget::addTab(const std::string &name, Widget *tab) {
78 addTab(tabCount(), name, tab);
79}
80
81void TabWidget::addTab(int index, const std::string &label, Widget *tab) {
82 assert(index <= tabCount());
83 // It is important to add the content first since the callback
84 // of the header will automatically fire when a new tab is added.
85 mContent->addChild(index, tab);
86 mHeader->addTab(index, label);
87 assert(mHeader->tabCount() == mContent->childCount());
88}
89
90int TabWidget::tabLabelIndex(const std::string &label) {
91 return mHeader->tabIndex(label);
92}
93
94int TabWidget::tabIndex(Widget* tab) {
95 return mContent->childIndex(tab);
96}
97
98void TabWidget::ensureTabVisible(int index) {
99 if (!mHeader->isTabVisible(index))
100 mHeader->ensureTabVisible(index);
101}
102
103const Widget *TabWidget::tab(const std::string &tabName) const {
104 int index = mHeader->tabIndex(tabName);
105 if (index == -1 || index == mContent->childCount())
106 return nullptr;
107 return mContent->children()[index];
108}
109
110Widget *TabWidget::tab(const std::string &tabName) {
111 int index = mHeader->tabIndex(tabName);
112 if (index == -1 || index == mContent->childCount())
113 return nullptr;
114 return mContent->children()[index];
115}
116
117const Widget *TabWidget::tab(int index) const {
118 if (index < 0 || index >= mContent->childCount())
119 return nullptr;
120 return mContent->children()[index];
121}
122
123Widget *TabWidget::tab(int index) {
124 if (index < 0 || index >= mContent->childCount())
125 return nullptr;
126 return mContent->children()[index];
127}
128
129bool TabWidget::removeTab(const std::string &tabName) {
130 int index = mHeader->removeTab(tabName);
131 if (index == -1)
132 return false;
133 mContent->removeChild(index);
134 return true;
135}
136
137void TabWidget::removeTab(int index) {
138 assert(mContent->childCount() < index);
139 mHeader->removeTab(index);
140 mContent->removeChild(index);
141 if (activeTab() == index)
142 setActiveTab(index == (index - 1) ? index - 1 : 0);
143}
144
145const std::string &TabWidget::tabLabelAt(int index) const {
146 return mHeader->tabLabelAt(index);
147}
148
149void TabWidget::performLayout(NVGcontext* ctx) {
150 int headerHeight = mHeader->preferredSize(ctx).y();
151 int margin = mTheme->mTabInnerMargin;
152 mHeader->setPosition({ 0, 0 });
153 mHeader->setSize({ mSize.x(), headerHeight });
154 mHeader->performLayout(ctx);
155 mContent->setPosition({ margin, headerHeight + margin });
156 mContent->setSize({ mSize.x() - 2 * margin, mSize.y() - 2*margin - headerHeight });
157 mContent->performLayout(ctx);
158}
159
160Vector2i TabWidget::preferredSize(NVGcontext* ctx) const {
161 auto contentSize = mContent->preferredSize(ctx);
162 auto headerSize = mHeader->preferredSize(ctx);
163 int margin = mTheme->mTabInnerMargin;
164 auto borderSize = Vector2i(2 * margin, 2 * margin);
165 Vector2i tabPreferredSize = contentSize + borderSize + Vector2i(0, headerSize.y());
166 return tabPreferredSize;
167}
168
169void TabWidget::draw(NVGcontext* ctx) {
170 int tabHeight = mHeader->preferredSize(ctx).y();
171 auto activeArea = mHeader->activeButtonArea();
172
173
174 for (int i = 0; i < 3; ++i) {
175 nvgSave(ctx);
176 if (i == 0)
177 nvgIntersectScissor(ctx, mPos.x(), mPos.y(), activeArea.first.x() + 1, mSize.y());
178 else if (i == 1)
179 nvgIntersectScissor(ctx, mPos.x() + activeArea.second.x(), mPos.y(), mSize.x() - activeArea.second.x(), mSize.y());
180 else
181 nvgIntersectScissor(ctx, mPos.x(), mPos.y() + tabHeight + 2, mSize.x(), mSize.y());
182
183 nvgBeginPath(ctx);
184 nvgStrokeWidth(ctx, 1.0f);
185 nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + tabHeight + 1.5f, mSize.x() - 1,
186 mSize.y() - tabHeight - 2, mTheme->mButtonCornerRadius);
187 nvgStrokeColor(ctx, mTheme->mBorderLight);
188 nvgStroke(ctx);
189
190 nvgBeginPath(ctx);
191 nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + tabHeight + 0.5f, mSize.x() - 1,
192 mSize.y() - tabHeight - 2, mTheme->mButtonCornerRadius);
193 nvgStrokeColor(ctx, mTheme->mBorderDark);
194 nvgStroke(ctx);
195 nvgRestore(ctx);
196 }
197
198 Widget::draw(ctx);
199}
200
201NAMESPACE_END(nanogui)
202