1 | /* |
2 | src/widget.cpp -- Base class of all widgets |
3 | |
4 | NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>. |
5 | The widget drawing code is based on the NanoVG demo application |
6 | by Mikko Mononen. |
7 | |
8 | All rights reserved. Use of this source code is governed by a |
9 | BSD-style license that can be found in the LICENSE.txt file. |
10 | */ |
11 | |
12 | #include <nanogui/widget.h> |
13 | #include <nanogui/layout.h> |
14 | #include <nanogui/theme.h> |
15 | #include <nanogui/window.h> |
16 | #include <nanogui/opengl.h> |
17 | #include <nanogui/screen.h> |
18 | #include <nanogui/serializer/core.h> |
19 | |
20 | NAMESPACE_BEGIN(nanogui) |
21 | |
22 | Widget::Widget(Widget *parent) |
23 | : mParent(nullptr), mTheme(nullptr), mLayout(nullptr), |
24 | mPos(Vector2i::Zero()), mSize(Vector2i::Zero()), |
25 | mFixedSize(Vector2i::Zero()), mVisible(true), mEnabled(true), |
26 | mFocused(false), mMouseFocus(false), mTooltip("" ), mFontSize(-1.0f), |
27 | mIconExtraScale(1.0f), mCursor(Cursor::Arrow) { |
28 | if (parent) |
29 | parent->addChild(this); |
30 | } |
31 | |
32 | Widget::~Widget() { |
33 | for (auto child : mChildren) { |
34 | if (child) |
35 | child->decRef(); |
36 | } |
37 | } |
38 | |
39 | void Widget::setTheme(Theme *theme) { |
40 | if (mTheme.get() == theme) |
41 | return; |
42 | mTheme = theme; |
43 | for (auto child : mChildren) |
44 | child->setTheme(theme); |
45 | } |
46 | |
47 | int Widget::fontSize() const { |
48 | return (mFontSize < 0 && mTheme) ? mTheme->mStandardFontSize : mFontSize; |
49 | } |
50 | |
51 | Vector2i Widget::preferredSize(NVGcontext *ctx) const { |
52 | if (mLayout) |
53 | return mLayout->preferredSize(ctx, this); |
54 | else |
55 | return mSize; |
56 | } |
57 | |
58 | void Widget::performLayout(NVGcontext *ctx) { |
59 | if (mLayout) { |
60 | mLayout->performLayout(ctx, this); |
61 | } else { |
62 | for (auto c : mChildren) { |
63 | Vector2i pref = c->preferredSize(ctx), fix = c->fixedSize(); |
64 | c->setSize(Vector2i( |
65 | fix[0] ? fix[0] : pref[0], |
66 | fix[1] ? fix[1] : pref[1] |
67 | )); |
68 | c->performLayout(ctx); |
69 | } |
70 | } |
71 | } |
72 | |
73 | Widget *Widget::findWidget(const Vector2i &p) { |
74 | for (auto it = mChildren.rbegin(); it != mChildren.rend(); ++it) { |
75 | Widget *child = *it; |
76 | if (child->visible() && child->contains(p - mPos)) |
77 | return child->findWidget(p - mPos); |
78 | } |
79 | return contains(p) ? this : nullptr; |
80 | } |
81 | |
82 | bool Widget::mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) { |
83 | for (auto it = mChildren.rbegin(); it != mChildren.rend(); ++it) { |
84 | Widget *child = *it; |
85 | if (child->visible() && child->contains(p - mPos) && |
86 | child->mouseButtonEvent(p - mPos, button, down, modifiers)) |
87 | return true; |
88 | } |
89 | if (button == GLFW_MOUSE_BUTTON_1 && down && !mFocused) |
90 | requestFocus(); |
91 | return false; |
92 | } |
93 | |
94 | bool Widget::mouseMotionEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) { |
95 | for (auto it = mChildren.rbegin(); it != mChildren.rend(); ++it) { |
96 | Widget *child = *it; |
97 | if (!child->visible()) |
98 | continue; |
99 | bool contained = child->contains(p - mPos), prevContained = child->contains(p - mPos - rel); |
100 | if (contained != prevContained) |
101 | child->mouseEnterEvent(p, contained); |
102 | if ((contained || prevContained) && |
103 | child->mouseMotionEvent(p - mPos, rel, button, modifiers)) |
104 | return true; |
105 | } |
106 | return false; |
107 | } |
108 | |
109 | bool Widget::scrollEvent(const Vector2i &p, const Vector2f &rel) { |
110 | for (auto it = mChildren.rbegin(); it != mChildren.rend(); ++it) { |
111 | Widget *child = *it; |
112 | if (!child->visible()) |
113 | continue; |
114 | if (child->contains(p - mPos) && child->scrollEvent(p - mPos, rel)) |
115 | return true; |
116 | } |
117 | return false; |
118 | } |
119 | |
120 | bool Widget::mouseDragEvent(const Vector2i &, const Vector2i &, int, int) { |
121 | return false; |
122 | } |
123 | |
124 | bool Widget::mouseEnterEvent(const Vector2i &, bool enter) { |
125 | mMouseFocus = enter; |
126 | return false; |
127 | } |
128 | |
129 | bool Widget::focusEvent(bool focused) { |
130 | mFocused = focused; |
131 | return false; |
132 | } |
133 | |
134 | bool Widget::keyboardEvent(int, int, int, int) { |
135 | return false; |
136 | } |
137 | |
138 | bool Widget::keyboardCharacterEvent(unsigned int) { |
139 | return false; |
140 | } |
141 | |
142 | void Widget::addChild(int index, Widget * widget) { |
143 | assert(index <= childCount()); |
144 | mChildren.insert(mChildren.begin() + index, widget); |
145 | widget->incRef(); |
146 | widget->setParent(this); |
147 | widget->setTheme(mTheme); |
148 | } |
149 | |
150 | void Widget::addChild(Widget * widget) { |
151 | addChild(childCount(), widget); |
152 | } |
153 | |
154 | void Widget::removeChild(const Widget *widget) { |
155 | mChildren.erase(std::remove(mChildren.begin(), mChildren.end(), widget), mChildren.end()); |
156 | widget->decRef(); |
157 | } |
158 | |
159 | void Widget::removeChild(int index) { |
160 | Widget *widget = mChildren[index]; |
161 | mChildren.erase(mChildren.begin() + index); |
162 | widget->decRef(); |
163 | } |
164 | |
165 | int Widget::childIndex(Widget *widget) const { |
166 | auto it = std::find(mChildren.begin(), mChildren.end(), widget); |
167 | if (it == mChildren.end()) |
168 | return -1; |
169 | return (int) (it - mChildren.begin()); |
170 | } |
171 | |
172 | Window *Widget::window() { |
173 | Widget *widget = this; |
174 | while (true) { |
175 | if (!widget) |
176 | throw std::runtime_error( |
177 | "Widget:internal error (could not find parent window)" ); |
178 | Window *window = dynamic_cast<Window *>(widget); |
179 | if (window) |
180 | return window; |
181 | widget = widget->parent(); |
182 | } |
183 | } |
184 | |
185 | Screen *Widget::screen() { |
186 | Widget *widget = this; |
187 | while (true) { |
188 | if (!widget) |
189 | throw std::runtime_error( |
190 | "Widget:internal error (could not find parent screen)" ); |
191 | Screen *screen = dynamic_cast<Screen *>(widget); |
192 | if (screen) |
193 | return screen; |
194 | widget = widget->parent(); |
195 | } |
196 | } |
197 | |
198 | void Widget::requestFocus() { |
199 | Widget *widget = this; |
200 | while (widget->parent()) |
201 | widget = widget->parent(); |
202 | ((Screen *) widget)->updateFocus(this); |
203 | } |
204 | |
205 | void Widget::draw(NVGcontext *ctx) { |
206 | #if NANOGUI_SHOW_WIDGET_BOUNDS |
207 | nvgStrokeWidth(ctx, 1.0f); |
208 | nvgBeginPath(ctx); |
209 | nvgRect(ctx, mPos.x() - 0.5f, mPos.y() - 0.5f, mSize.x() + 1, mSize.y() + 1); |
210 | nvgStrokeColor(ctx, nvgRGBA(255, 0, 0, 255)); |
211 | nvgStroke(ctx); |
212 | #endif |
213 | |
214 | if (mChildren.empty()) |
215 | return; |
216 | |
217 | nvgSave(ctx); |
218 | nvgTranslate(ctx, mPos.x(), mPos.y()); |
219 | for (auto child : mChildren) { |
220 | if (child->visible()) { |
221 | nvgSave(ctx); |
222 | nvgIntersectScissor(ctx, child->mPos.x(), child->mPos.y(), child->mSize.x(), child->mSize.y()); |
223 | child->draw(ctx); |
224 | nvgRestore(ctx); |
225 | } |
226 | } |
227 | nvgRestore(ctx); |
228 | } |
229 | |
230 | void Widget::save(Serializer &s) const { |
231 | s.set("position" , mPos); |
232 | s.set("size" , mSize); |
233 | s.set("fixedSize" , mFixedSize); |
234 | s.set("visible" , mVisible); |
235 | s.set("enabled" , mEnabled); |
236 | s.set("focused" , mFocused); |
237 | s.set("tooltip" , mTooltip); |
238 | s.set("fontSize" , mFontSize); |
239 | s.set("cursor" , (int) mCursor); |
240 | } |
241 | |
242 | bool Widget::load(Serializer &s) { |
243 | if (!s.get("position" , mPos)) return false; |
244 | if (!s.get("size" , mSize)) return false; |
245 | if (!s.get("fixedSize" , mFixedSize)) return false; |
246 | if (!s.get("visible" , mVisible)) return false; |
247 | if (!s.get("enabled" , mEnabled)) return false; |
248 | if (!s.get("focused" , mFocused)) return false; |
249 | if (!s.get("tooltip" , mTooltip)) return false; |
250 | if (!s.get("fontSize" , mFontSize)) return false; |
251 | if (!s.get("cursor" , mCursor)) return false; |
252 | return true; |
253 | } |
254 | |
255 | NAMESPACE_END(nanogui) |
256 | |