| 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 | |