| 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 | |