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
17NAMESPACE_BEGIN(nanogui)
18
19Button::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
25Vector2i 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 *= icon_scale();
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
49bool 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
106void 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 *= icon_scale();
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
221void 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
232bool 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
244NAMESPACE_END(nanogui)
245