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
20NAMESPACE_BEGIN(nanogui)
21
22Widget::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
32Widget::~Widget() {
33 for (auto child : mChildren) {
34 if (child)
35 child->decRef();
36 }
37}
38
39void 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
47int Widget::fontSize() const {
48 return (mFontSize < 0 && mTheme) ? mTheme->mStandardFontSize : mFontSize;
49}
50
51Vector2i Widget::preferredSize(NVGcontext *ctx) const {
52 if (mLayout)
53 return mLayout->preferredSize(ctx, this);
54 else
55 return mSize;
56}
57
58void 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
73Widget *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
82bool 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
94bool 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
109bool 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
120bool Widget::mouseDragEvent(const Vector2i &, const Vector2i &, int, int) {
121 return false;
122}
123
124bool Widget::mouseEnterEvent(const Vector2i &, bool enter) {
125 mMouseFocus = enter;
126 return false;
127}
128
129bool Widget::focusEvent(bool focused) {
130 mFocused = focused;
131 return false;
132}
133
134bool Widget::keyboardEvent(int, int, int, int) {
135 return false;
136}
137
138bool Widget::keyboardCharacterEvent(unsigned int) {
139 return false;
140}
141
142void 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
150void Widget::addChild(Widget * widget) {
151 addChild(childCount(), widget);
152}
153
154void Widget::removeChild(const Widget *widget) {
155 mChildren.erase(std::remove(mChildren.begin(), mChildren.end(), widget), mChildren.end());
156 widget->decRef();
157}
158
159void Widget::removeChild(int index) {
160 Widget *widget = mChildren[index];
161 mChildren.erase(mChildren.begin() + index);
162 widget->decRef();
163}
164
165int 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
172Window *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
185Screen *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
198void Widget::requestFocus() {
199 Widget *widget = this;
200 while (widget->parent())
201 widget = widget->parent();
202 ((Screen *) widget)->updateFocus(this);
203}
204
205void 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
230void 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
242bool 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
255NAMESPACE_END(nanogui)
256