1/*
2 src/example1.cpp -- C++ version of an example application that shows
3 how to use the various widget classes. For a Python implementation, see
4 '../python/example1.py'.
5
6 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
7 The widget drawing code is based on the NanoVG demo application
8 by Mikko Mononen.
9
10 All rights reserved. Use of this source code is governed by a
11 BSD-style license that can be found in the LICENSE.txt file.
12*/
13
14#include <nanogui/opengl.h>
15#include <nanogui/glutil.h>
16#include <nanogui/screen.h>
17#include <nanogui/window.h>
18#include <nanogui/layout.h>
19#include <nanogui/label.h>
20#include <nanogui/checkbox.h>
21#include <nanogui/button.h>
22#include <nanogui/toolbutton.h>
23#include <nanogui/popupbutton.h>
24#include <nanogui/combobox.h>
25#include <nanogui/progressbar.h>
26#include <nanogui/entypo.h>
27#include <nanogui/messagedialog.h>
28#include <nanogui/textbox.h>
29#include <nanogui/slider.h>
30#include <nanogui/imagepanel.h>
31#include <nanogui/imageview.h>
32#include <nanogui/vscrollpanel.h>
33#include <nanogui/colorwheel.h>
34#include <nanogui/colorpicker.h>
35#include <nanogui/graph.h>
36#include <nanogui/tabwidget.h>
37#include <iostream>
38#include <string>
39
40// Includes for the GLTexture class.
41#include <cstdint>
42#include <memory>
43#include <utility>
44
45#if defined(__GNUC__)
46# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
47#endif
48#if defined(_WIN32)
49# pragma warning(push)
50# pragma warning(disable: 4457 4456 4005 4312)
51#endif
52
53#define STB_IMAGE_STATIC
54#define STB_IMAGE_IMPLEMENTATION
55#include <stb_image.h>
56
57#if defined(_WIN32)
58# pragma warning(pop)
59#endif
60#if defined(_WIN32)
61# if defined(APIENTRY)
62# undef APIENTRY
63# endif
64# include <windows.h>
65#endif
66
67using std::cout;
68using std::cerr;
69using std::endl;
70using std::string;
71using std::vector;
72using std::pair;
73using std::to_string;
74
75class GLTexture {
76public:
77 using handleType = std::unique_ptr<uint8_t[], void(*)(void*)>;
78 GLTexture() = default;
79 GLTexture(const std::string& textureName)
80 : mTextureName(textureName), mTextureId(0) {}
81
82 GLTexture(const std::string& textureName, GLint textureId)
83 : mTextureName(textureName), mTextureId(textureId) {}
84
85 GLTexture(const GLTexture& other) = delete;
86 GLTexture(GLTexture&& other) noexcept
87 : mTextureName(std::move(other.mTextureName)),
88 mTextureId(other.mTextureId) {
89 other.mTextureId = 0;
90 }
91 GLTexture& operator=(const GLTexture& other) = delete;
92 GLTexture& operator=(GLTexture&& other) noexcept {
93 mTextureName = std::move(other.mTextureName);
94 std::swap(mTextureId, other.mTextureId);
95 return *this;
96 }
97 ~GLTexture() noexcept {
98 if (mTextureId)
99 glDeleteTextures(1, &mTextureId);
100 }
101
102 GLuint texture() const { return mTextureId; }
103 const std::string& textureName() const { return mTextureName; }
104
105 /**
106 * Load a file in memory and create an OpenGL texture.
107 * Returns a handle type (an std::unique_ptr) to the loaded pixels.
108 */
109 handleType load(const std::string& fileName) {
110 if (mTextureId) {
111 glDeleteTextures(1, &mTextureId);
112 mTextureId = 0;
113 }
114 int force_channels = 0;
115 int w, h, n;
116 handleType textureData(stbi_load(fileName.c_str(), &w, &h, &n, force_channels), stbi_image_free);
117 if (!textureData)
118 throw std::invalid_argument("Could not load texture data from file " + fileName);
119 glGenTextures(1, &mTextureId);
120 glBindTexture(GL_TEXTURE_2D, mTextureId);
121 GLint internalFormat;
122 GLint format;
123 switch (n) {
124 case 1: internalFormat = GL_R8; format = GL_RED; break;
125 case 2: internalFormat = GL_RG8; format = GL_RG; break;
126 case 3: internalFormat = GL_RGB8; format = GL_RGB; break;
127 case 4: internalFormat = GL_RGBA8; format = GL_RGBA; break;
128 default: internalFormat = 0; format = 0; break;
129 }
130 glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, w, h, 0, format, GL_UNSIGNED_BYTE, textureData.get());
131 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
132 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
133 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
134 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
135 return textureData;
136 }
137
138private:
139 std::string mTextureName;
140 GLuint mTextureId;
141};
142
143class ExampleApplication : public nanogui::Screen {
144public:
145 ExampleApplication() : nanogui::Screen(Eigen::Vector2i(1024, 768), "NanoGUI Test") {
146 using namespace nanogui;
147
148 Window *window = new Window(this, "Button demo");
149 window->setPosition(Vector2i(15, 15));
150 window->setLayout(new GroupLayout());
151
152 /* No need to store a pointer, the data structure will be automatically
153 freed when the parent window is deleted */
154 new Label(window, "Push buttons", "sans-bold");
155
156 Button *b = new Button(window, "Plain button");
157 b->setCallback([] { cout << "pushed!" << endl; });
158 b->setTooltip("short tooltip");
159
160 /* Alternative construction notation using variadic template */
161 b = window->add<Button>("Styled", ENTYPO_ICON_ROCKET);
162 b->setBackgroundColor(Color(0, 0, 255, 25));
163 b->setCallback([] { cout << "pushed!" << endl; });
164 b->setTooltip("This button has a fairly long tooltip. It is so long, in "
165 "fact, that the shown text will span several lines.");
166
167 new Label(window, "Toggle buttons", "sans-bold");
168 b = new Button(window, "Toggle me");
169 b->setFlags(Button::ToggleButton);
170 b->setChangeCallback([](bool state) { cout << "Toggle button state: " << state << endl; });
171
172 new Label(window, "Radio buttons", "sans-bold");
173 b = new Button(window, "Radio button 1");
174 b->setFlags(Button::RadioButton);
175 b = new Button(window, "Radio button 2");
176 b->setFlags(Button::RadioButton);
177
178 new Label(window, "A tool palette", "sans-bold");
179 Widget *tools = new Widget(window);
180 tools->setLayout(new BoxLayout(Orientation::Horizontal,
181 Alignment::Middle, 0, 6));
182
183 b = new ToolButton(tools, ENTYPO_ICON_CLOUD);
184 b = new ToolButton(tools, ENTYPO_ICON_CONTROLLER_FAST_FORWARD);
185 b = new ToolButton(tools, ENTYPO_ICON_COMPASS);
186 b = new ToolButton(tools, ENTYPO_ICON_INSTALL);
187
188 new Label(window, "Popup buttons", "sans-bold");
189 PopupButton *popupBtn = new PopupButton(window, "Popup", ENTYPO_ICON_EXPORT);
190 Popup *popup = popupBtn->popup();
191 popup->setLayout(new GroupLayout());
192 new Label(popup, "Arbitrary widgets can be placed here");
193 new CheckBox(popup, "A check box");
194 // popup right
195 popupBtn = new PopupButton(popup, "Recursive popup", ENTYPO_ICON_FLASH);
196 Popup *popupRight = popupBtn->popup();
197 popupRight->setLayout(new GroupLayout());
198 new CheckBox(popupRight, "Another check box");
199 // popup left
200 popupBtn = new PopupButton(popup, "Recursive popup", ENTYPO_ICON_FLASH);
201 popupBtn->setSide(Popup::Side::Left);
202 Popup *popupLeft = popupBtn->popup();
203 popupLeft->setLayout(new GroupLayout());
204 new CheckBox(popupLeft, "Another check box");
205
206 window = new Window(this, "Basic widgets");
207 window->setPosition(Vector2i(200, 15));
208 window->setLayout(new GroupLayout());
209
210 new Label(window, "Message dialog", "sans-bold");
211 tools = new Widget(window);
212 tools->setLayout(new BoxLayout(Orientation::Horizontal,
213 Alignment::Middle, 0, 6));
214 b = new Button(tools, "Info");
215 b->setCallback([&] {
216 auto dlg = new MessageDialog(this, MessageDialog::Type::Information, "Title", "This is an information message");
217 dlg->setCallback([](int result) { cout << "Dialog result: " << result << endl; });
218 });
219 b = new Button(tools, "Warn");
220 b->setCallback([&] {
221 auto dlg = new MessageDialog(this, MessageDialog::Type::Warning, "Title", "This is a warning message");
222 dlg->setCallback([](int result) { cout << "Dialog result: " << result << endl; });
223 });
224 b = new Button(tools, "Ask");
225 b->setCallback([&] {
226 auto dlg = new MessageDialog(this, MessageDialog::Type::Warning, "Title", "This is a question message", "Yes", "No", true);
227 dlg->setCallback([](int result) { cout << "Dialog result: " << result << endl; });
228 });
229
230 vector<pair<int, string>>
231 icons = loadImageDirectory(mNVGContext, "icons");
232 #if defined(_WIN32)
233 string resourcesFolderPath("../resources/");
234 #else
235 string resourcesFolderPath("./");
236 #endif
237
238 new Label(window, "Image panel & scroll panel", "sans-bold");
239 PopupButton *imagePanelBtn = new PopupButton(window, "Image Panel");
240 imagePanelBtn->setIcon(ENTYPO_ICON_FOLDER);
241 popup = imagePanelBtn->popup();
242 VScrollPanel *vscroll = new VScrollPanel(popup);
243 ImagePanel *imgPanel = new ImagePanel(vscroll);
244 imgPanel->setImages(icons);
245 popup->setFixedSize(Vector2i(245, 150));
246
247 auto imageWindow = new Window(this, "Selected image");
248 imageWindow->setPosition(Vector2i(710, 15));
249 imageWindow->setLayout(new GroupLayout());
250
251 // Load all of the images by creating a GLTexture object and saving the pixel data.
252 for (auto& icon : icons) {
253 GLTexture texture(icon.second);
254 auto data = texture.load(resourcesFolderPath + icon.second + ".png");
255 mImagesData.emplace_back(std::move(texture), std::move(data));
256 }
257
258 // Set the first texture
259 auto imageView = new ImageView(imageWindow, mImagesData[0].first.texture());
260 mCurrentImage = 0;
261 // Change the active textures.
262 imgPanel->setCallback([this, imageView](int i) {
263 imageView->bindImage(mImagesData[i].first.texture());
264 mCurrentImage = i;
265 cout << "Selected item " << i << '\n';
266 });
267 imageView->setGridThreshold(20);
268 imageView->setPixelInfoThreshold(20);
269 imageView->setPixelInfoCallback(
270 [this, imageView](const Vector2i& index) -> pair<string, Color> {
271 auto& imageData = mImagesData[mCurrentImage].second;
272 auto& textureSize = imageView->imageSize();
273 string stringData;
274 uint16_t channelSum = 0;
275 for (int i = 0; i != 4; ++i) {
276 auto& channelData = imageData[4*index.y()*textureSize.x() + 4*index.x() + i];
277 channelSum += channelData;
278 stringData += (to_string(static_cast<int>(channelData)) + "\n");
279 }
280 float intensity = static_cast<float>(255 - (channelSum / 4)) / 255.0f;
281 float colorScale = intensity > 0.5f ? (intensity + 1) / 2 : intensity / 2;
282 Color textColor = Color(colorScale, 1.0f);
283 return { stringData, textColor };
284 });
285
286 new Label(window, "File dialog", "sans-bold");
287 tools = new Widget(window);
288 tools->setLayout(new BoxLayout(Orientation::Horizontal,
289 Alignment::Middle, 0, 6));
290 b = new Button(tools, "Open");
291 b->setCallback([&] {
292 cout << "File dialog result: " << file_dialog(
293 { {"png", "Portable Network Graphics"}, {"txt", "Text file"} }, false) << endl;
294 });
295 b = new Button(tools, "Save");
296 b->setCallback([&] {
297 cout << "File dialog result: " << file_dialog(
298 { {"png", "Portable Network Graphics"}, {"txt", "Text file"} }, true) << endl;
299 });
300
301 new Label(window, "Combo box", "sans-bold");
302 new ComboBox(window, { "Combo box item 1", "Combo box item 2", "Combo box item 3"});
303 new Label(window, "Check box", "sans-bold");
304 CheckBox *cb = new CheckBox(window, "Flag 1",
305 [](bool state) { cout << "Check box 1 state: " << state << endl; }
306 );
307 cb->setChecked(true);
308 cb = new CheckBox(window, "Flag 2",
309 [](bool state) { cout << "Check box 2 state: " << state << endl; }
310 );
311 new Label(window, "Progress bar", "sans-bold");
312 mProgress = new ProgressBar(window);
313
314 new Label(window, "Slider and text box", "sans-bold");
315
316 Widget *panel = new Widget(window);
317 panel->setLayout(new BoxLayout(Orientation::Horizontal,
318 Alignment::Middle, 0, 20));
319
320 Slider *slider = new Slider(panel);
321 slider->setValue(0.5f);
322 slider->setFixedWidth(80);
323
324 TextBox *textBox = new TextBox(panel);
325 textBox->setFixedSize(Vector2i(60, 25));
326 textBox->setValue("50");
327 textBox->setUnits("%");
328 slider->setCallback([textBox](float value) {
329 textBox->setValue(std::to_string((int) (value * 100)));
330 });
331 slider->setFinalCallback([&](float value) {
332 cout << "Final slider value: " << (int) (value * 100) << endl;
333 });
334 textBox->setFixedSize(Vector2i(60,25));
335 textBox->setFontSize(20);
336 textBox->setAlignment(TextBox::Alignment::Right);
337
338 window = new Window(this, "Misc. widgets");
339 window->setPosition(Vector2i(425,15));
340 window->setLayout(new GroupLayout());
341
342 TabWidget* tabWidget = window->add<TabWidget>();
343
344 Widget* layer = tabWidget->createTab("Color Wheel");
345 layer->setLayout(new GroupLayout());
346
347 // Use overloaded variadic add to fill the tab widget with Different tabs.
348 layer->add<Label>("Color wheel widget", "sans-bold");
349 layer->add<ColorWheel>();
350
351 layer = tabWidget->createTab("Function Graph");
352 layer->setLayout(new GroupLayout());
353
354 layer->add<Label>("Function graph widget", "sans-bold");
355
356 Graph *graph = layer->add<Graph>("Some Function");
357
358 graph->setHeader("E = 2.35e-3");
359 graph->setFooter("Iteration 89");
360 VectorXf &func = graph->values();
361 func.resize(100);
362 for (int i = 0; i < 100; ++i)
363 func[i] = 0.5f * (0.5f * std::sin(i / 10.f) +
364 0.5f * std::cos(i / 23.f) + 1);
365
366 // Dummy tab used to represent the last tab button.
367 tabWidget->createTab("+");
368
369 // A simple counter.
370 int counter = 1;
371 tabWidget->setCallback([tabWidget, this, counter] (int index) mutable {
372 if (index == (tabWidget->tabCount()-1)) {
373 // When the "+" tab has been clicked, simply add a new tab.
374 string tabName = "Dynamic " + to_string(counter);
375 Widget* layerDyn = tabWidget->createTab(index, tabName);
376 layerDyn->setLayout(new GroupLayout());
377 layerDyn->add<Label>("Function graph widget", "sans-bold");
378 Graph *graphDyn = layerDyn->add<Graph>("Dynamic function");
379
380 graphDyn->setHeader("E = 2.35e-3");
381 graphDyn->setFooter("Iteration " + to_string(index*counter));
382 VectorXf &funcDyn = graphDyn->values();
383 funcDyn.resize(100);
384 for (int i = 0; i < 100; ++i)
385 funcDyn[i] = 0.5f *
386 std::abs((0.5f * std::sin(i / 10.f + counter) +
387 0.5f * std::cos(i / 23.f + 1 + counter)));
388 ++counter;
389 // We must invoke perform layout from the screen instance to keep everything in order.
390 // This is essential when creating tabs dynamically.
391 performLayout();
392 // Ensure that the newly added header is visible on screen
393 tabWidget->ensureTabVisible(index);
394
395 }
396 });
397 tabWidget->setActiveTab(0);
398
399 // A button to go back to the first tab and scroll the window.
400 panel = window->add<Widget>();
401 panel->add<Label>("Jump to tab: ");
402 panel->setLayout(new BoxLayout(Orientation::Horizontal,
403 Alignment::Middle, 0, 6));
404
405 auto ib = panel->add<IntBox<int>>();
406 ib->setEditable(true);
407
408 b = panel->add<Button>("", ENTYPO_ICON_FORWARD);
409 b->setFixedSize(Vector2i(22, 22));
410 ib->setFixedHeight(22);
411 b->setCallback([tabWidget, ib] {
412 int value = ib->value();
413 if (value >= 0 && value < tabWidget->tabCount()) {
414 tabWidget->setActiveTab(value);
415 tabWidget->ensureTabVisible(value);
416 }
417 });
418
419 window = new Window(this, "Grid of small widgets");
420 window->setPosition(Vector2i(425, 300));
421 GridLayout *layout =
422 new GridLayout(Orientation::Horizontal, 2,
423 Alignment::Middle, 15, 5);
424 layout->setColAlignment(
425 { Alignment::Maximum, Alignment::Fill });
426 layout->setSpacing(0, 10);
427 window->setLayout(layout);
428
429 /* FP widget */ {
430 new Label(window, "Floating point :", "sans-bold");
431 textBox = new TextBox(window);
432 textBox->setEditable(true);
433 textBox->setFixedSize(Vector2i(100, 20));
434 textBox->setValue("50");
435 textBox->setUnits("GiB");
436 textBox->setDefaultValue("0.0");
437 textBox->setFontSize(16);
438 textBox->setFormat("[-]?[0-9]*\\.?[0-9]+");
439 }
440
441 /* Positive integer widget */ {
442 new Label(window, "Positive integer :", "sans-bold");
443 auto intBox = new IntBox<int>(window);
444 intBox->setEditable(true);
445 intBox->setFixedSize(Vector2i(100, 20));
446 intBox->setValue(50);
447 intBox->setUnits("Mhz");
448 intBox->setDefaultValue("0");
449 intBox->setFontSize(16);
450 intBox->setFormat("[1-9][0-9]*");
451 intBox->setSpinnable(true);
452 intBox->setMinValue(1);
453 intBox->setValueIncrement(2);
454 }
455
456 /* Checkbox widget */ {
457 new Label(window, "Checkbox :", "sans-bold");
458
459 cb = new CheckBox(window, "Check me");
460 cb->setFontSize(16);
461 cb->setChecked(true);
462 }
463
464 new Label(window, "Combo box :", "sans-bold");
465 ComboBox *cobo =
466 new ComboBox(window, { "Item 1", "Item 2", "Item 3" });
467 cobo->setFontSize(16);
468 cobo->setFixedSize(Vector2i(100,20));
469
470 new Label(window, "Color picker :", "sans-bold");
471 auto cp = new ColorPicker(window, {255, 120, 0, 255});
472 cp->setFixedSize({100, 20});
473 cp->setFinalCallback([](const Color &c) {
474 std::cout << "ColorPicker Final Callback: ["
475 << c.r() << ", "
476 << c.g() << ", "
477 << c.b() << ", "
478 << c.w() << "]" << std::endl;
479 });
480 // setup a fast callback for the color picker widget on a new window
481 // for demonstrative purposes
482 window = new Window(this, "Color Picker Fast Callback");
483 layout =
484 new GridLayout(Orientation::Horizontal, 2,
485 Alignment::Middle, 15, 5);
486 layout->setColAlignment(
487 { Alignment::Maximum, Alignment::Fill });
488 layout->setSpacing(0, 10);
489 window->setLayout(layout);
490 window->setPosition(Vector2i(425, 500));
491 new Label(window, "Combined: ");
492 b = new Button(window, "ColorWheel", ENTYPO_ICON_500PX);
493 new Label(window, "Red: ");
494 auto redIntBox = new IntBox<int>(window);
495 redIntBox->setEditable(false);
496 new Label(window, "Green: ");
497 auto greenIntBox = new IntBox<int>(window);
498 greenIntBox->setEditable(false);
499 new Label(window, "Blue: ");
500 auto blueIntBox = new IntBox<int>(window);
501 blueIntBox->setEditable(false);
502 new Label(window, "Alpha: ");
503 auto alphaIntBox = new IntBox<int>(window);
504 cp->setCallback([b,redIntBox,blueIntBox,greenIntBox,alphaIntBox](const Color &c) {
505 b->setBackgroundColor(c);
506 b->setTextColor(c.contrastingColor());
507 int red = (int) (c.r() * 255.0f);
508 redIntBox->setValue(red);
509 int green = (int) (c.g() * 255.0f);
510 greenIntBox->setValue(green);
511 int blue = (int) (c.b() * 255.0f);
512 blueIntBox->setValue(blue);
513 int alpha = (int) (c.w() * 255.0f);
514 alphaIntBox->setValue(alpha);
515
516 });
517
518 performLayout();
519
520 /* All NanoGUI widgets are initialized at this point. Now
521 create an OpenGL shader to draw the main window contents.
522
523 NanoGUI comes with a simple Eigen-based wrapper around OpenGL 3,
524 which eliminates most of the tedious and error-prone shader and
525 buffer object management.
526 */
527
528 mShader.init(
529 /* An identifying name */
530 "a_simple_shader",
531
532 /* Vertex shader */
533 "#version 330\n"
534 "uniform mat4 modelViewProj;\n"
535 "in vec3 position;\n"
536 "void main() {\n"
537 " gl_Position = modelViewProj * vec4(position, 1.0);\n"
538 "}",
539
540 /* Fragment shader */
541 "#version 330\n"
542 "out vec4 color;\n"
543 "uniform float intensity;\n"
544 "void main() {\n"
545 " color = vec4(vec3(intensity), 1.0);\n"
546 "}"
547 );
548
549 MatrixXu indices(3, 2); /* Draw 2 triangles */
550 indices.col(0) << 0, 1, 2;
551 indices.col(1) << 2, 3, 0;
552
553 MatrixXf positions(3, 4);
554 positions.col(0) << -1, -1, 0;
555 positions.col(1) << 1, -1, 0;
556 positions.col(2) << 1, 1, 0;
557 positions.col(3) << -1, 1, 0;
558
559 mShader.bind();
560 mShader.uploadIndices(indices);
561 mShader.uploadAttrib("position", positions);
562 mShader.setUniform("intensity", 0.5f);
563 }
564
565 ~ExampleApplication() {
566 mShader.free();
567 }
568
569 virtual bool keyboardEvent(int key, int scancode, int action, int modifiers) {
570 if (Screen::keyboardEvent(key, scancode, action, modifiers))
571 return true;
572 if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
573 setVisible(false);
574 return true;
575 }
576 return false;
577 }
578
579 virtual void draw(NVGcontext *ctx) {
580 /* Animate the scrollbar */
581 mProgress->setValue(std::fmod((float) glfwGetTime() / 10, 1.0f));
582
583 /* Draw the user interface */
584 Screen::draw(ctx);
585 }
586
587 virtual void drawContents() {
588 using namespace nanogui;
589
590 /* Draw the window contents using OpenGL */
591 mShader.bind();
592
593 Matrix4f mvp;
594 mvp.setIdentity();
595 mvp.topLeftCorner<3,3>() = Matrix3f(Eigen::AngleAxisf((float) glfwGetTime(), Vector3f::UnitZ())) * 0.25f;
596
597 mvp.row(0) *= (float) mSize.y() / (float) mSize.x();
598
599 mShader.setUniform("modelViewProj", mvp);
600
601 /* Draw 2 triangles starting at index 0 */
602 mShader.drawIndexed(GL_TRIANGLES, 0, 2);
603 }
604private:
605 nanogui::ProgressBar *mProgress;
606 nanogui::GLShader mShader;
607
608 using imagesDataType = vector<pair<GLTexture, GLTexture::handleType>>;
609 imagesDataType mImagesData;
610 int mCurrentImage;
611};
612
613int main(int /* argc */, char ** /* argv */) {
614 try {
615 nanogui::init();
616
617 /* scoped variables */ {
618 nanogui::ref<ExampleApplication> app = new ExampleApplication();
619 app->drawAll();
620 app->setVisible(true);
621 nanogui::mainloop();
622 }
623
624 nanogui::shutdown();
625 } catch (const std::runtime_error &e) {
626 std::string error_msg = std::string("Caught a fatal error: ") + std::string(e.what());
627 #if defined(_WIN32)
628 MessageBoxA(nullptr, error_msg.c_str(), NULL, MB_ICONERROR | MB_OK);
629 #else
630 std::cerr << error_msg << endl;
631 #endif
632 return -1;
633 }
634
635 return 0;
636}
637