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 | |
24 | NAMESPACE_BEGIN(nanogui) |
25 | |
26 | TabWidget::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 | |
43 | void 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 | |
52 | void TabWidget::setActiveTab(int tabIndex) { |
53 | mHeader->setActiveTab(tabIndex); |
54 | mContent->setSelectedIndex(tabIndex); |
55 | } |
56 | |
57 | int TabWidget::activeTab() const { |
58 | assert(mHeader->activeTab() == mContent->selectedIndex()); |
59 | return mContent->selectedIndex(); |
60 | } |
61 | |
62 | int TabWidget::tabCount() const { |
63 | assert(mContent->childCount() == mHeader->tabCount()); |
64 | return mHeader->tabCount(); |
65 | } |
66 | |
67 | Widget* TabWidget::createTab(int index, const std::string &label) { |
68 | Widget* tab = new Widget(nullptr); |
69 | addTab(index, label, tab); |
70 | return tab; |
71 | } |
72 | |
73 | Widget* TabWidget::createTab(const std::string &label) { |
74 | return createTab(tabCount(), label); |
75 | } |
76 | |
77 | void TabWidget::addTab(const std::string &name, Widget *tab) { |
78 | addTab(tabCount(), name, tab); |
79 | } |
80 | |
81 | void 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 | |
90 | int TabWidget::tabLabelIndex(const std::string &label) { |
91 | return mHeader->tabIndex(label); |
92 | } |
93 | |
94 | int TabWidget::tabIndex(Widget* tab) { |
95 | return mContent->childIndex(tab); |
96 | } |
97 | |
98 | void TabWidget::ensureTabVisible(int index) { |
99 | if (!mHeader->isTabVisible(index)) |
100 | mHeader->ensureTabVisible(index); |
101 | } |
102 | |
103 | const 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 | |
110 | Widget *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 | |
117 | const Widget *TabWidget::tab(int index) const { |
118 | if (index < 0 || index >= mContent->childCount()) |
119 | return nullptr; |
120 | return mContent->children()[index]; |
121 | } |
122 | |
123 | Widget *TabWidget::tab(int index) { |
124 | if (index < 0 || index >= mContent->childCount()) |
125 | return nullptr; |
126 | return mContent->children()[index]; |
127 | } |
128 | |
129 | bool 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 | |
137 | void 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 | |
145 | const std::string &TabWidget::tabLabelAt(int index) const { |
146 | return mHeader->tabLabelAt(index); |
147 | } |
148 | |
149 | void TabWidget::performLayout(NVGcontext* ctx) { |
150 | int = 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 | |
160 | Vector2i TabWidget::preferredSize(NVGcontext* ctx) const { |
161 | auto contentSize = mContent->preferredSize(ctx); |
162 | auto = 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 | |
169 | void 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 | |
201 | NAMESPACE_END(nanogui) |
202 | |