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 | |
42 | NAMESPACE_BEGIN(nanogui) |
43 | |
44 | std::map<GLFWwindow *, Screen *> __nanogui_screens; |
45 | |
46 | #if defined(NANOGUI_GLAD) |
47 | static bool gladInitialized = false; |
48 | #endif |
49 | |
50 | /* Calculate pixel ratio for hi-dpi devices. */ |
51 | static 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 | |
123 | Screen::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 | |
130 | Screen::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 | |
305 | void 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 | |
363 | Screen::~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 | |
375 | void 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 | |
386 | void Screen::setCaption(const std::string &caption) { |
387 | if (caption != mCaption) { |
388 | glfwSetWindowTitle(mGLFWWindow, caption.c_str()); |
389 | mCaption = caption; |
390 | } |
391 | } |
392 | |
393 | void 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 | |
403 | void 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 | |
413 | void 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 | |
488 | bool 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 | |
498 | bool 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 | |
507 | bool Screen::resizeEvent(const Vector2i& size) { |
508 | if (mResizeCallback) { |
509 | mResizeCallback(size); |
510 | return true; |
511 | } |
512 | return false; |
513 | } |
514 | |
515 | bool 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 | |
551 | bool 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 | |
601 | bool 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 | |
611 | bool 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 | |
622 | bool 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 | |
629 | bool 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 | |
648 | bool 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 | |
672 | void 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 | |
693 | void 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 | |
701 | void 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 | |
709 | void 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 | |
731 | NAMESPACE_END(nanogui) |
732 | |