1 /*
2     src/button.cpp -- [Normal/Toggle/Radio/Popup] Button widget
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/button.h>
13 #include <nanogui/theme.h>
14 #include <nanogui/opengl.h>
15 #include <nanogui/serializer/core.h>
16 
NAMESPACE_BEGIN(nanogui)17 NAMESPACE_BEGIN(nanogui)
18 
19 Button::Button(Widget *parent, const std::string &caption, int icon)
20     : Widget(parent), mCaption(caption), mIcon(icon),
21       mIconPosition(IconPosition::LeftCentered), mPushed(false),
22       mFlags(NormalButton), mBackgroundColor(Color(0, 0)),
23       mTextColor(Color(0, 0)) { }
24 
preferredSize(NVGcontext * ctx) const25 Vector2i Button::preferredSize(NVGcontext *ctx) const {
26     int fontSize = mFontSize == -1 ? mTheme->mButtonFontSize : mFontSize;
27     nvgFontSize(ctx, fontSize);
28     nvgFontFace(ctx, "sans-bold");
29     float tw = nvgTextBounds(ctx, 0,0, mCaption.c_str(), nullptr, nullptr);
30     float iw = 0.0f, ih = fontSize;
31 
32     if (mIcon) {
33         if (nvgIsFontIcon(mIcon)) {
34             ih *= 1.5f;
35             nvgFontFace(ctx, "icons");
36             nvgFontSize(ctx, ih);
37             iw = nvgTextBounds(ctx, 0, 0, utf8(mIcon).data(), nullptr, nullptr)
38                 + mSize.y() * 0.15f;
39         } else {
40             int w, h;
41             ih *= 0.9f;
42             nvgImageSize(ctx, mIcon, &w, &h);
43             iw = w * ih / h;
44         }
45     }
46     return Vector2i((int)(tw + iw) + 20, fontSize + 10);
47 }
48 
mouseButtonEvent(const Vector2i & p,int button,bool down,int modifiers)49 bool Button::mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) {
50     Widget::mouseButtonEvent(p, button, down, modifiers);
51     /* Temporarily increase the reference count of the button in case the
52        button causes the parent window to be destructed */
53     ref<Button> self = this;
54 
55     if (button == GLFW_MOUSE_BUTTON_1 && mEnabled) {
56         bool pushedBackup = mPushed;
57         if (down) {
58             if (mFlags & RadioButton) {
59                 if (mButtonGroup.empty()) {
60                     for (auto widget : parent()->children()) {
61                         Button *b = dynamic_cast<Button *>(widget);
62                         if (b != this && b && (b->flags() & RadioButton) && b->mPushed) {
63                             b->mPushed = false;
64                             if (b->mChangeCallback)
65                                 b->mChangeCallback(false);
66                         }
67                     }
68                 } else {
69                     for (auto b : mButtonGroup) {
70                         if (b != this && (b->flags() & RadioButton) && b->mPushed) {
71                             b->mPushed = false;
72                             if (b->mChangeCallback)
73                                 b->mChangeCallback(false);
74                         }
75                     }
76                 }
77             }
78             if (mFlags & PopupButton) {
79                 for (auto widget : parent()->children()) {
80                     Button *b = dynamic_cast<Button *>(widget);
81                     if (b != this && b && (b->flags() & PopupButton) && b->mPushed) {
82                         b->mPushed = false;
83                         if (b->mChangeCallback)
84                             b->mChangeCallback(false);
85                     }
86                 }
87             }
88             if (mFlags & ToggleButton)
89                 mPushed = !mPushed;
90             else
91                 mPushed = true;
92         } else if (mPushed) {
93             if (contains(p) && mCallback)
94                 mCallback();
95             if (mFlags & NormalButton)
96                 mPushed = false;
97         }
98         if (pushedBackup != mPushed && mChangeCallback)
99             mChangeCallback(mPushed);
100 
101         return true;
102     }
103     return false;
104 }
105 
draw(NVGcontext * ctx)106 void Button::draw(NVGcontext *ctx) {
107     Widget::draw(ctx);
108 
109     NVGcolor gradTop = mTheme->mButtonGradientTopUnfocused;
110     NVGcolor gradBot = mTheme->mButtonGradientBotUnfocused;
111 
112     if (mPushed) {
113         gradTop = mTheme->mButtonGradientTopPushed;
114         gradBot = mTheme->mButtonGradientBotPushed;
115     } else if (mMouseFocus && mEnabled) {
116         gradTop = mTheme->mButtonGradientTopFocused;
117         gradBot = mTheme->mButtonGradientBotFocused;
118     }
119 
120     nvgBeginPath(ctx);
121 
122     nvgRoundedRect(ctx, mPos.x() + 1, mPos.y() + 1.0f, mSize.x() - 2,
123                    mSize.y() - 2, mTheme->mButtonCornerRadius - 1);
124 
125     if (mBackgroundColor.w() != 0) {
126         nvgFillColor(ctx, Color(mBackgroundColor.head<3>(), 1.f));
127         nvgFill(ctx);
128         if (mPushed) {
129             gradTop.a = gradBot.a = 0.8f;
130         } else {
131             double v = 1 - mBackgroundColor.w();
132             gradTop.a = gradBot.a = mEnabled ? v : v * .5f + .5f;
133         }
134     }
135 
136     NVGpaint bg = nvgLinearGradient(ctx, mPos.x(), mPos.y(), mPos.x(),
137                                     mPos.y() + mSize.y(), gradTop, gradBot);
138 
139     nvgFillPaint(ctx, bg);
140     nvgFill(ctx);
141 
142     nvgBeginPath(ctx);
143     nvgStrokeWidth(ctx, 1.0f);
144     nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + (mPushed ? 0.5f : 1.5f), mSize.x() - 1,
145                    mSize.y() - 1 - (mPushed ? 0.0f : 1.0f), mTheme->mButtonCornerRadius);
146     nvgStrokeColor(ctx, mTheme->mBorderLight);
147     nvgStroke(ctx);
148 
149     nvgBeginPath(ctx);
150     nvgRoundedRect(ctx, mPos.x() + 0.5f, mPos.y() + 0.5f, mSize.x() - 1,
151                    mSize.y() - 2, mTheme->mButtonCornerRadius);
152     nvgStrokeColor(ctx, mTheme->mBorderDark);
153     nvgStroke(ctx);
154 
155     int fontSize = mFontSize == -1 ? mTheme->mButtonFontSize : mFontSize;
156     nvgFontSize(ctx, fontSize);
157     nvgFontFace(ctx, "sans-bold");
158     float tw = nvgTextBounds(ctx, 0,0, mCaption.c_str(), nullptr, nullptr);
159 
160     Vector2f center = mPos.cast<float>() + mSize.cast<float>() * 0.5f;
161     Vector2f textPos(center.x() - tw * 0.5f, center.y() - 1);
162     NVGcolor textColor =
163         mTextColor.w() == 0 ? mTheme->mTextColor : mTextColor;
164     if (!mEnabled)
165         textColor = mTheme->mDisabledTextColor;
166 
167     if (mIcon) {
168         auto icon = utf8(mIcon);
169 
170         float iw, ih = fontSize;
171         if (nvgIsFontIcon(mIcon)) {
172             ih *= 1.5f;
173             nvgFontSize(ctx, ih);
174             nvgFontFace(ctx, "icons");
175             iw = nvgTextBounds(ctx, 0, 0, icon.data(), nullptr, nullptr);
176         } else {
177             int w, h;
178             ih *= 0.9f;
179             nvgImageSize(ctx, mIcon, &w, &h);
180             iw = w * ih / h;
181         }
182         if (mCaption != "")
183             iw += mSize.y() * 0.15f;
184         nvgFillColor(ctx, textColor);
185         nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
186         Vector2f iconPos = center;
187         iconPos.y() -= 1;
188 
189         if (mIconPosition == IconPosition::LeftCentered) {
190             iconPos.x() -= (tw + iw) * 0.5f;
191             textPos.x() += iw * 0.5f;
192         } else if (mIconPosition == IconPosition::RightCentered) {
193             textPos.x() -= iw * 0.5f;
194             iconPos.x() += tw * 0.5f;
195         } else if (mIconPosition == IconPosition::Left) {
196             iconPos.x() = mPos.x() + 8;
197         } else if (mIconPosition == IconPosition::Right) {
198             iconPos.x() = mPos.x() + mSize.x() - iw - 8;
199         }
200 
201         if (nvgIsFontIcon(mIcon)) {
202             nvgText(ctx, iconPos.x(), iconPos.y()+1, icon.data(), nullptr);
203         } else {
204             NVGpaint imgPaint = nvgImagePattern(ctx,
205                     iconPos.x(), iconPos.y() - ih/2, iw, ih, 0, mIcon, mEnabled ? 0.5f : 0.25f);
206 
207             nvgFillPaint(ctx, imgPaint);
208             nvgFill(ctx);
209         }
210     }
211 
212     nvgFontSize(ctx, fontSize);
213     nvgFontFace(ctx, "sans-bold");
214     nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE);
215     nvgFillColor(ctx, mTheme->mTextColorShadow);
216     nvgText(ctx, textPos.x(), textPos.y(), mCaption.c_str(), nullptr);
217     nvgFillColor(ctx, textColor);
218     nvgText(ctx, textPos.x(), textPos.y() + 1, mCaption.c_str(), nullptr);
219 }
220 
save(Serializer & s) const221 void Button::save(Serializer &s) const {
222     Widget::save(s);
223     s.set("caption", mCaption);
224     s.set("icon", mIcon);
225     s.set("iconPosition", (int) mIconPosition);
226     s.set("pushed", mPushed);
227     s.set("flags", mFlags);
228     s.set("backgroundColor", mBackgroundColor);
229     s.set("textColor", mTextColor);
230 }
231 
load(Serializer & s)232 bool Button::load(Serializer &s) {
233     if (!Widget::load(s)) return false;
234     if (!s.get("caption", mCaption)) return false;
235     if (!s.get("icon", mIcon)) return false;
236     if (!s.get("iconPosition", mIconPosition)) return false;
237     if (!s.get("pushed", mPushed)) return false;
238     if (!s.get("flags", mFlags)) return false;
239     if (!s.get("backgroundColor", mBackgroundColor)) return false;
240     if (!s.get("textColor", mTextColor)) return false;
241     return true;
242 }
243 
244 NAMESPACE_END(nanogui)
245