1/*
2 src/screen.cpp -- Top-level widget and interface between NanoGUI and GLFW
3
4 A significant redesign of this code was contributed by Christian Schueller.
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/screen.h>
15#include <nanogui/theme.h>
16#include <nanogui/opengl.h>
17#include <nanogui/window.h>
18#include <nanogui/popup.h>
19#include <map>
20#include <iostream>
21
22#if defined(_WIN32)
23# ifndef NOMINMAX
24# define NOMINMAX
25# endif
26# undef APIENTRY
27
28# ifndef WIN32_LEAN_AND_MEAN
29# define WIN32_LEAN_AND_MEAN
30# endif
31# include <windows.h>
32
33# define GLFW_EXPOSE_NATIVE_WGL
34# define GLFW_EXPOSE_NATIVE_WIN32
35# include <GLFW/glfw3native.h>
36#endif
37
38/* Allow enforcing the GL2 implementation of NanoVG */
39#define NANOVG_GL3_IMPLEMENTATION
40#include <nanovg_gl.h>
41
42NAMESPACE_BEGIN(nanogui)
43
44std::map<GLFWwindow *, Screen *> __nanogui_screens;
45
46#if defined(NANOGUI_GLAD)
47static bool gladInitialized = false;
48#endif
49
50/* Calculate pixel ratio for hi-dpi devices. */
51static float get_pixel_ratio(GLFWwindow *window) {
52#if defined(_WIN32)
53 HWND hWnd = glfwGetWin32Window(window);
54 HMONITOR monitor = nullptr;
55 #if defined(MONITOR_DEFAULTTONEAREST)
56 monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
57 #else
58 static HMONITOR (WINAPI *MonitorFromWindow_)(HWND, DWORD) = nullptr;
59 static bool MonitorFromWindow_tried = false;
60 if (!MonitorFromWindow_tried) {
61 auto user32 = LoadLibrary(TEXT("user32"));
62 if (user32)
63 MonitorFromWindow_ = (decltype(MonitorFromWindow_)) GetProcAddress(user32, "MonitorFromWindow");
64 MonitorFromWindow_tried = true;
65 }
66 if (MonitorFromWindow_)
67 monitor = MonitorFromWindow_(hWnd, 2);
68 #endif // defined(MONITOR_DEFAULTTONEAREST)
69 /* The following function only exists on Windows 8.1+, but we don't want to make that a dependency */
70 static HRESULT (WINAPI *GetDpiForMonitor_)(HMONITOR, UINT, UINT*, UINT*) = nullptr;
71 static bool GetDpiForMonitor_tried = false;
72
73 if (!GetDpiForMonitor_tried) {
74 auto shcore = LoadLibrary(TEXT("shcore"));
75 if (shcore)
76 GetDpiForMonitor_ = (decltype(GetDpiForMonitor_)) GetProcAddress(shcore, "GetDpiForMonitor");
77 GetDpiForMonitor_tried = true;
78 }
79
80 if (GetDpiForMonitor_ && monitor) {
81 uint32_t dpiX, dpiY;
82 if (GetDpiForMonitor_(monitor, 0 /* effective DPI */, &dpiX, &dpiY) == S_OK)
83 return dpiX / 96.0;
84 }
85 return 1.f;
86#elif defined(__linux__)
87 (void) window;
88
89 float ratio = 1.0f;
90 FILE *fp;
91 /* Try to read the pixel ratio from KDEs config */
92 auto currentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
93 if (currentDesktop && currentDesktop == std::string("KDE")) {
94 fp = popen("kreadconfig5 --group KScreen --key ScaleFactor", "r");
95 if (!fp)
96 return 1;
97
98 if (fscanf(fp, "%f", &ratio) != 1)
99 return 1;
100 } else {
101 /* Try to read the pixel ratio from GTK */
102 fp = popen("gsettings get org.gnome.desktop.interface scaling-factor", "r");
103 if (!fp)
104 return 1;
105
106 int ratioInt = 1;
107 if (fscanf(fp, "uint32 %i", &ratioInt) != 1)
108 return 1;
109 ratio = ratioInt;
110 }
111 if (pclose(fp) != 0)
112 return 1;
113 return ratio >= 1 ? ratio : 1;
114
115#else
116 Vector2i fbSize, size;
117 glfwGetFramebufferSize(window, &fbSize[0], &fbSize[1]);
118 glfwGetWindowSize(window, &size[0], &size[1]);
119 return (float)fbSize[0] / (float)size[0];
120#endif
121}
122
123Screen::Screen()
124 : Widget(nullptr), mGLFWWindow(nullptr), mNVGContext(nullptr),
125 mCursor(Cursor::Arrow), mBackground(0.3f, 0.3f, 0.32f, 1.f),
126 mShutdownGLFWOnDestruct(false), mFullscreen(false) {
127 memset(mCursors, 0, sizeof(GLFWcursor *) * (int) Cursor::CursorCount);
128}
129
130Screen::Screen(const Vector2i &size, const std::string &caption, bool resizable,
131 bool fullscreen, int colorBits, int alphaBits, int depthBits,
132 int stencilBits, int nSamples,
133 unsigned int glMajor, unsigned int glMinor)
134 : Widget(nullptr), mGLFWWindow(nullptr), mNVGContext(nullptr),
135 mCursor(Cursor::Arrow), mBackground(0.3f, 0.3f, 0.32f, 1.f), mCaption(caption),
136 mShutdownGLFWOnDestruct(false), mFullscreen(fullscreen) {
137 memset(mCursors, 0, sizeof(GLFWcursor *) * (int) Cursor::CursorCount);
138
139 /* Request a forward compatible OpenGL glMajor.glMinor core profile context.
140 Default value is an OpenGL 3.3 core profile context. */
141 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, glMajor);
142 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, glMinor);
143 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
144 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
145
146 glfwWindowHint(GLFW_SAMPLES, nSamples);
147 glfwWindowHint(GLFW_RED_BITS, colorBits);
148 glfwWindowHint(GLFW_GREEN_BITS, colorBits);
149 glfwWindowHint(GLFW_BLUE_BITS, colorBits);
150 glfwWindowHint(GLFW_ALPHA_BITS, alphaBits);
151 glfwWindowHint(GLFW_STENCIL_BITS, stencilBits);
152 glfwWindowHint(GLFW_DEPTH_BITS, depthBits);
153 glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
154 glfwWindowHint(GLFW_RESIZABLE, resizable ? GL_TRUE : GL_FALSE);
155
156 if (fullscreen) {
157 GLFWmonitor *monitor = glfwGetPrimaryMonitor();
158 const GLFWvidmode *mode = glfwGetVideoMode(monitor);
159 mGLFWWindow = glfwCreateWindow(mode->width, mode->height,
160 caption.c_str(), monitor, nullptr);
161 } else {
162 mGLFWWindow = glfwCreateWindow(size.x(), size.y(),
163 caption.c_str(), nullptr, nullptr);
164 }
165
166 if (!mGLFWWindow)
167 throw std::runtime_error("Could not create an OpenGL " +
168 std::to_string(glMajor) + "." +
169 std::to_string(glMinor) + " context!");
170
171 glfwMakeContextCurrent(mGLFWWindow);
172
173#if defined(NANOGUI_GLAD)
174 if (!gladInitialized) {
175 gladInitialized = true;
176 if (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress))
177 throw std::runtime_error("Could not initialize GLAD!");
178 glGetError(); // pull and ignore unhandled errors like GL_INVALID_ENUM
179 }
180#endif
181
182 glfwGetFramebufferSize(mGLFWWindow, &mFBSize[0], &mFBSize[1]);
183 glViewport(0, 0, mFBSize[0], mFBSize[1]);
184 glClearColor(mBackground[0], mBackground[1], mBackground[2], mBackground[3]);
185 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
186 glfwSwapInterval(0);
187 glfwSwapBuffers(mGLFWWindow);
188
189#if defined(__APPLE__)
190 /* Poll for events once before starting a potentially
191 lengthy loading process. This is needed to be
192 classified as "interactive" by other software such
193 as iTerm2 */
194
195 glfwPollEvents();
196#endif
197
198 /* Propagate GLFW events to the appropriate Screen instance */
199 glfwSetCursorPosCallback(mGLFWWindow,
200 [](GLFWwindow *w, double x, double y) {
201 auto it = __nanogui_screens.find(w);
202 if (it == __nanogui_screens.end())
203 return;
204 Screen *s = it->second;
205 if (!s->mProcessEvents)
206 return;
207 s->cursorPosCallbackEvent(x, y);
208 }
209 );
210
211 glfwSetMouseButtonCallback(mGLFWWindow,
212 [](GLFWwindow *w, int button, int action, int modifiers) {
213 auto it = __nanogui_screens.find(w);
214 if (it == __nanogui_screens.end())
215 return;
216 Screen *s = it->second;
217 if (!s->mProcessEvents)
218 return;
219 s->mouseButtonCallbackEvent(button, action, modifiers);
220 }
221 );
222
223 glfwSetKeyCallback(mGLFWWindow,
224 [](GLFWwindow *w, int key, int scancode, int action, int mods) {
225 auto it = __nanogui_screens.find(w);
226 if (it == __nanogui_screens.end())
227 return;
228 Screen *s = it->second;
229 if (!s->mProcessEvents)
230 return;
231 s->keyCallbackEvent(key, scancode, action, mods);
232 }
233 );
234
235 glfwSetCharCallback(mGLFWWindow,
236 [](GLFWwindow *w, unsigned int codepoint) {
237 auto it = __nanogui_screens.find(w);
238 if (it == __nanogui_screens.end())
239 return;
240 Screen *s = it->second;
241 if (!s->mProcessEvents)
242 return;
243 s->charCallbackEvent(codepoint);
244 }
245 );
246
247 glfwSetDropCallback(mGLFWWindow,
248 [](GLFWwindow *w, int count, const char **filenames) {
249 auto it = __nanogui_screens.find(w);
250 if (it == __nanogui_screens.end())
251 return;
252 Screen *s = it->second;
253 if (!s->mProcessEvents)
254 return;
255 s->dropCallbackEvent(count, filenames);
256 }
257 );
258
259 glfwSetScrollCallback(mGLFWWindow,
260 [](GLFWwindow *w, double x, double y) {
261 auto it = __nanogui_screens.find(w);
262 if (it == __nanogui_screens.end())
263 return;
264 Screen *s = it->second;
265 if (!s->mProcessEvents)
266 return;
267 s->scrollCallbackEvent(x, y);
268 }
269 );
270
271 /* React to framebuffer size events -- includes window
272 size events and also catches things like dragging
273 a window from a Retina-capable screen to a normal
274 screen on Mac OS X */
275 glfwSetFramebufferSizeCallback(mGLFWWindow,
276 [](GLFWwindow *w, int width, int height) {
277 auto it = __nanogui_screens.find(w);
278 if (it == __nanogui_screens.end())
279 return;
280 Screen *s = it->second;
281
282 if (!s->mProcessEvents)
283 return;
284
285 s->resizeCallbackEvent(width, height);
286 }
287 );
288
289 // notify when the screen has lost focus (e.g. application switch)
290 glfwSetWindowFocusCallback(mGLFWWindow,
291 [](GLFWwindow *w, int focused) {
292 auto it = __nanogui_screens.find(w);
293 if (it == __nanogui_screens.end())
294 return;
295
296 Screen *s = it->second;
297 // focused: 0 when false, 1 when true
298 s->focusEvent(focused != 0);
299 }
300 );
301
302 initialize(mGLFWWindow, true);
303}
304
305void Screen::initialize(GLFWwindow *window, bool shutdownGLFWOnDestruct) {
306 mGLFWWindow = window;
307 mShutdownGLFWOnDestruct = shutdownGLFWOnDestruct;
308 glfwGetWindowSize(mGLFWWindow, &mSize[0], &mSize[1]);
309 glfwGetFramebufferSize(mGLFWWindow, &mFBSize[0], &mFBSize[1]);
310
311 mPixelRatio = get_pixel_ratio(window);
312
313#if defined(_WIN32) || defined(__linux__)
314 if (mPixelRatio != 1 && !mFullscreen)
315 glfwSetWindowSize(window, mSize.x() * mPixelRatio, mSize.y() * mPixelRatio);
316#endif
317
318#if defined(NANOGUI_GLAD)
319 if (!gladInitialized) {
320 gladInitialized = true;
321 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
322 throw std::runtime_error("Could not initialize GLAD!");
323 glGetError(); // pull and ignore unhandled errors like GL_INVALID_ENUM
324 }
325#endif
326
327 /* Detect framebuffer properties and set up compatible NanoVG context */
328 GLint nStencilBits = 0, nSamples = 0;
329 glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER,
330 GL_STENCIL, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &nStencilBits);
331 glGetIntegerv(GL_SAMPLES, &nSamples);
332
333 int flags = 0;
334 if (nStencilBits >= 8)
335 flags |= NVG_STENCIL_STROKES;
336 if (nSamples <= 1)
337 flags |= NVG_ANTIALIAS;
338#if !defined(NDEBUG)
339 flags |= NVG_DEBUG;
340#endif
341
342 mNVGContext = nvgCreateGL3(flags);
343 if (mNVGContext == nullptr)
344 throw std::runtime_error("Could not initialize NanoVG!");
345
346 mVisible = glfwGetWindowAttrib(window, GLFW_VISIBLE) != 0;
347 setTheme(new Theme(mNVGContext));
348 mMousePos = Vector2i::Zero();
349 mMouseState = mModifiers = 0;
350 mDragActive = false;
351 mLastInteraction = glfwGetTime();
352 mProcessEvents = true;
353 __nanogui_screens[mGLFWWindow] = this;
354
355 for (int i=0; i < (int) Cursor::CursorCount; ++i)
356 mCursors[i] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR + i);
357
358 /// Fixes retina display-related font rendering issue (#185)
359 nvgBeginFrame(mNVGContext, mSize[0], mSize[1], mPixelRatio);
360 nvgEndFrame(mNVGContext);
361}
362
363Screen::~Screen() {
364 __nanogui_screens.erase(mGLFWWindow);
365 for (int i=0; i < (int) Cursor::CursorCount; ++i) {
366 if (mCursors[i])
367 glfwDestroyCursor(mCursors[i]);
368 }
369 if (mNVGContext)
370 nvgDeleteGL3(mNVGContext);
371 if (mGLFWWindow && mShutdownGLFWOnDestruct)
372 glfwDestroyWindow(mGLFWWindow);
373}
374
375void Screen::setVisible(bool visible) {
376 if (mVisible != visible) {
377 mVisible = visible;
378
379 if (visible)
380 glfwShowWindow(mGLFWWindow);
381 else
382 glfwHideWindow(mGLFWWindow);
383 }
384}
385
386void Screen::setCaption(const std::string &caption) {
387 if (caption != mCaption) {
388 glfwSetWindowTitle(mGLFWWindow, caption.c_str());
389 mCaption = caption;
390 }
391}
392
393void Screen::setSize(const Vector2i &size) {
394 Widget::setSize(size);
395
396#if defined(_WIN32) || defined(__linux__)
397 glfwSetWindowSize(mGLFWWindow, size.x() * mPixelRatio, size.y() * mPixelRatio);
398#else
399 glfwSetWindowSize(mGLFWWindow, size.x(), size.y());
400#endif
401}
402
403void Screen::drawAll() {
404 glClearColor(mBackground[0], mBackground[1], mBackground[2], mBackground[3]);
405 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
406
407 drawContents();
408 drawWidgets();
409
410 glfwSwapBuffers(mGLFWWindow);
411}
412
413void Screen::drawWidgets() {
414 if (!mVisible)
415 return;
416
417 glfwMakeContextCurrent(mGLFWWindow);
418
419 glfwGetFramebufferSize(mGLFWWindow, &mFBSize[0], &mFBSize[1]);
420 glfwGetWindowSize(mGLFWWindow, &mSize[0], &mSize[1]);
421
422#if defined(_WIN32) || defined(__linux__)
423 mSize = (mSize.cast<float>() / mPixelRatio).cast<int>();
424 mFBSize = (mSize.cast<float>() * mPixelRatio).cast<int>();
425#else
426 /* Recompute pixel ratio on OSX */
427 if (mSize[0])
428 mPixelRatio = (float) mFBSize[0] / (float) mSize[0];
429#endif
430
431 glViewport(0, 0, mFBSize[0], mFBSize[1]);
432 glBindSampler(0, 0);
433 nvgBeginFrame(mNVGContext, mSize[0], mSize[1], mPixelRatio);
434
435 draw(mNVGContext);
436
437 double elapsed = glfwGetTime() - mLastInteraction;
438
439 if (elapsed > 0.5f) {
440 /* Draw tooltips */
441 const Widget *widget = findWidget(mMousePos);
442 if (widget && !widget->tooltip().empty()) {
443 int tooltipWidth = 150;
444
445 float bounds[4];
446 nvgFontFace(mNVGContext, "sans");
447 nvgFontSize(mNVGContext, 15.0f);
448 nvgTextAlign(mNVGContext, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
449 nvgTextLineHeight(mNVGContext, 1.1f);
450 Vector2i pos = widget->absolutePosition() +
451 Vector2i(widget->width() / 2, widget->height() + 10);
452
453 nvgTextBounds(mNVGContext, pos.x(), pos.y(),
454 widget->tooltip().c_str(), nullptr, bounds);
455 int h = (bounds[2] - bounds[0]) / 2;
456 if (h > tooltipWidth / 2) {
457 nvgTextAlign(mNVGContext, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
458 nvgTextBoxBounds(mNVGContext, pos.x(), pos.y(), tooltipWidth,
459 widget->tooltip().c_str(), nullptr, bounds);
460
461 h = (bounds[2] - bounds[0]) / 2;
462 }
463 nvgGlobalAlpha(mNVGContext,
464 std::min(1.0, 2 * (elapsed - 0.5f)) * 0.8);
465
466 nvgBeginPath(mNVGContext);
467 nvgFillColor(mNVGContext, Color(0, 255));
468 nvgRoundedRect(mNVGContext, bounds[0] - 4 - h, bounds[1] - 4,
469 (int) (bounds[2] - bounds[0]) + 8,
470 (int) (bounds[3] - bounds[1]) + 8, 3);
471
472 int px = (int) ((bounds[2] + bounds[0]) / 2) - h;
473 nvgMoveTo(mNVGContext, px, bounds[1] - 10);
474 nvgLineTo(mNVGContext, px + 7, bounds[1] + 1);
475 nvgLineTo(mNVGContext, px - 7, bounds[1] + 1);
476 nvgFill(mNVGContext);
477
478 nvgFillColor(mNVGContext, Color(255, 255));
479 nvgFontBlur(mNVGContext, 0.0f);
480 nvgTextBox(mNVGContext, pos.x() - h, pos.y(), tooltipWidth,
481 widget->tooltip().c_str(), nullptr);
482 }
483 }
484
485 nvgEndFrame(mNVGContext);
486}
487
488bool Screen::keyboardEvent(int key, int scancode, int action, int modifiers) {
489 if (mFocusPath.size() > 0) {
490 for (auto it = mFocusPath.rbegin() + 1; it != mFocusPath.rend(); ++it)
491 if ((*it)->focused() && (*it)->keyboardEvent(key, scancode, action, modifiers))
492 return true;
493 }
494
495 return false;
496}
497
498bool Screen::keyboardCharacterEvent(unsigned int codepoint) {
499 if (mFocusPath.size() > 0) {
500 for (auto it = mFocusPath.rbegin() + 1; it != mFocusPath.rend(); ++it)
501 if ((*it)->focused() && (*it)->keyboardCharacterEvent(codepoint))
502 return true;
503 }
504 return false;
505}
506
507bool Screen::resizeEvent(const Vector2i& size) {
508 if (mResizeCallback) {
509 mResizeCallback(size);
510 return true;
511 }
512 return false;
513}
514
515bool Screen::cursorPosCallbackEvent(double x, double y) {
516 Vector2i p((int) x, (int) y);
517
518#if defined(_WIN32) || defined(__linux__)
519 p = (p.cast<float>() / mPixelRatio).cast<int>();
520#endif
521
522 bool ret = false;
523 mLastInteraction = glfwGetTime();
524 try {
525 p -= Vector2i(1, 2);
526
527 if (!mDragActive) {
528 Widget *widget = findWidget(p);
529 if (widget != nullptr && widget->cursor() != mCursor) {
530 mCursor = widget->cursor();
531 glfwSetCursor(mGLFWWindow, mCursors[(int) mCursor]);
532 }
533 } else {
534 ret = mDragWidget->mouseDragEvent(
535 p - mDragWidget->parent()->absolutePosition(), p - mMousePos,
536 mMouseState, mModifiers);
537 }
538
539 if (!ret)
540 ret = mouseMotionEvent(p, p - mMousePos, mMouseState, mModifiers);
541
542 mMousePos = p;
543
544 return ret;
545 } catch (const std::exception &e) {
546 std::cerr << "Caught exception in event handler: " << e.what() << std::endl;
547 return false;
548 }
549}
550
551bool Screen::mouseButtonCallbackEvent(int button, int action, int modifiers) {
552 mModifiers = modifiers;
553 mLastInteraction = glfwGetTime();
554 try {
555 if (mFocusPath.size() > 1) {
556 const Window *window =
557 dynamic_cast<Window *>(mFocusPath[mFocusPath.size() - 2]);
558 if (window && window->modal()) {
559 if (!window->contains(mMousePos))
560 return false;
561 }
562 }
563
564 if (action == GLFW_PRESS)
565 mMouseState |= 1 << button;
566 else
567 mMouseState &= ~(1 << button);
568
569 auto dropWidget = findWidget(mMousePos);
570 if (mDragActive && action == GLFW_RELEASE &&
571 dropWidget != mDragWidget)
572 mDragWidget->mouseButtonEvent(
573 mMousePos - mDragWidget->parent()->absolutePosition(), button,
574 false, mModifiers);
575
576 if (dropWidget != nullptr && dropWidget->cursor() != mCursor) {
577 mCursor = dropWidget->cursor();
578 glfwSetCursor(mGLFWWindow, mCursors[(int) mCursor]);
579 }
580
581 if (action == GLFW_PRESS && (button == GLFW_MOUSE_BUTTON_1 || button == GLFW_MOUSE_BUTTON_2)) {
582 mDragWidget = findWidget(mMousePos);
583 if (mDragWidget == this)
584 mDragWidget = nullptr;
585 mDragActive = mDragWidget != nullptr;
586 if (!mDragActive)
587 updateFocus(nullptr);
588 } else {
589 mDragActive = false;
590 mDragWidget = nullptr;
591 }
592
593 return mouseButtonEvent(mMousePos, button, action == GLFW_PRESS,
594 mModifiers);
595 } catch (const std::exception &e) {
596 std::cerr << "Caught exception in event handler: " << e.what() << std::endl;
597 return false;
598 }
599}
600
601bool Screen::keyCallbackEvent(int key, int scancode, int action, int mods) {
602 mLastInteraction = glfwGetTime();
603 try {
604 return keyboardEvent(key, scancode, action, mods);
605 } catch (const std::exception &e) {
606 std::cerr << "Caught exception in event handler: " << e.what() << std::endl;
607 return false;
608 }
609}
610
611bool Screen::charCallbackEvent(unsigned int codepoint) {
612 mLastInteraction = glfwGetTime();
613 try {
614 return keyboardCharacterEvent(codepoint);
615 } catch (const std::exception &e) {
616 std::cerr << "Caught exception in event handler: " << e.what()
617 << std::endl;
618 return false;
619 }
620}
621
622bool Screen::dropCallbackEvent(int count, const char **filenames) {
623 std::vector<std::string> arg(count);
624 for (int i = 0; i < count; ++i)
625 arg[i] = filenames[i];
626 return dropEvent(arg);
627}
628
629bool Screen::scrollCallbackEvent(double x, double y) {
630 mLastInteraction = glfwGetTime();
631 try {
632 if (mFocusPath.size() > 1) {
633 const Window *window =
634 dynamic_cast<Window *>(mFocusPath[mFocusPath.size() - 2]);
635 if (window && window->modal()) {
636 if (!window->contains(mMousePos))
637 return false;
638 }
639 }
640 return scrollEvent(mMousePos, Vector2f(x, y));
641 } catch (const std::exception &e) {
642 std::cerr << "Caught exception in event handler: " << e.what()
643 << std::endl;
644 return false;
645 }
646}
647
648bool Screen::resizeCallbackEvent(int, int) {
649 Vector2i fbSize, size;
650 glfwGetFramebufferSize(mGLFWWindow, &fbSize[0], &fbSize[1]);
651 glfwGetWindowSize(mGLFWWindow, &size[0], &size[1]);
652
653#if defined(_WIN32) || defined(__linux__)
654 size = (size.cast<float>() / mPixelRatio).cast<int>();
655#endif
656
657 if (fbSize == Vector2i(0, 0) || size == Vector2i(0, 0))
658 return false;
659
660 mFBSize = fbSize; mSize = size;
661 mLastInteraction = glfwGetTime();
662
663 try {
664 return resizeEvent(mSize);
665 } catch (const std::exception &e) {
666 std::cerr << "Caught exception in event handler: " << e.what()
667 << std::endl;
668 return false;
669 }
670}
671
672void Screen::updateFocus(Widget *widget) {
673 for (auto w: mFocusPath) {
674 if (!w->focused())
675 continue;
676 w->focusEvent(false);
677 }
678 mFocusPath.clear();
679 Widget *window = nullptr;
680 while (widget) {
681 mFocusPath.push_back(widget);
682 if (dynamic_cast<Window *>(widget))
683 window = widget;
684 widget = widget->parent();
685 }
686 for (auto it = mFocusPath.rbegin(); it != mFocusPath.rend(); ++it)
687 (*it)->focusEvent(true);
688
689 if (window)
690 moveWindowToFront((Window *) window);
691}
692
693void Screen::disposeWindow(Window *window) {
694 if (std::find(mFocusPath.begin(), mFocusPath.end(), window) != mFocusPath.end())
695 mFocusPath.clear();
696 if (mDragWidget == window)
697 mDragWidget = nullptr;
698 removeChild(window);
699}
700
701void Screen::centerWindow(Window *window) {
702 if (window->size() == Vector2i::Zero()) {
703 window->setSize(window->preferredSize(mNVGContext));
704 window->performLayout(mNVGContext);
705 }
706 window->setPosition((mSize - window->size()) / 2);
707}
708
709void Screen::moveWindowToFront(Window *window) {
710 mChildren.erase(std::remove(mChildren.begin(), mChildren.end(), window), mChildren.end());
711 mChildren.push_back(window);
712 /* Brute force topological sort (no problem for a few windows..) */
713 bool changed = false;
714 do {
715 size_t baseIndex = 0;
716 for (size_t index = 0; index < mChildren.size(); ++index)
717 if (mChildren[index] == window)
718 baseIndex = index;
719 changed = false;
720 for (size_t index = 0; index < mChildren.size(); ++index) {
721 Popup *pw = dynamic_cast<Popup *>(mChildren[index]);
722 if (pw && pw->parentWindow() == window && index < baseIndex) {
723 moveWindowToFront(pw);
724 changed = true;
725 break;
726 }
727 }
728 } while (changed);
729}
730
731NAMESPACE_END(nanogui)
732