ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
GLFWWindowSystem.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
10 #include <GLFW/glfw3.h>
11 
12 #include <iostream>
13 #include <unordered_map>
14 
18 #ifdef __APPLE__
20 #endif
25 
26 namespace cloudViewer {
27 namespace visualization {
28 namespace gui {
29 
30 namespace {
31 static constexpr int FALLBACK_MONITOR_WIDTH = 1024;
32 static constexpr int FALLBACK_MONITOR_HEIGHT = 768;
33 
34 // GLFW doesn't provide double-click messages, nor will it read the default
35 // values from the OS, so we need to do it ourselves.
36 static constexpr double DOUBLE_CLICK_TIME = 0.300; // 300 ms is a typical value
37 
38 // These are used in the GLFW callbacks, which are global functions, and it's
39 // not worth creating a wrapper around Window just for this.
40 double g_last_button_down_time = 0.0;
41 MouseButton g_last_button_down = MouseButton::NONE;
42 
43 int MouseButtonFromGLFW(int button) {
44  switch (button) {
45  case GLFW_MOUSE_BUTTON_LEFT:
46  return int(MouseButton::LEFT);
47  case GLFW_MOUSE_BUTTON_RIGHT:
48  return int(MouseButton::RIGHT);
49  case GLFW_MOUSE_BUTTON_MIDDLE:
50  return int(MouseButton::MIDDLE);
51  case GLFW_MOUSE_BUTTON_4:
52  return int(MouseButton::BUTTON4);
53  case GLFW_MOUSE_BUTTON_5:
54  return int(MouseButton::BUTTON5);
55  default:
56  return int(MouseButton::NONE);
57  }
58 }
59 
60 int KeymodsFromGLFW(int glfw_mods) {
61  int keymods = 0;
62  if (glfw_mods & GLFW_MOD_SHIFT) {
63  keymods |= int(KeyModifier::SHIFT);
64  }
65  if (glfw_mods & GLFW_MOD_CONTROL) {
66 #if __APPLE__
67  keymods |= int(KeyModifier::ALT);
68 #else
69  keymods |= int(KeyModifier::CTRL);
70 #endif // __APPLE__
71  }
72  if (glfw_mods & GLFW_MOD_ALT) {
73 #if __APPLE__
74  keymods |= int(KeyModifier::META);
75 #else
76  keymods |= int(KeyModifier::ALT);
77 #endif // __APPLE__
78  }
79  if (glfw_mods & GLFW_MOD_SUPER) {
80 #if __APPLE__
81  keymods |= int(KeyModifier::CTRL);
82 #else
83  keymods |= int(KeyModifier::META);
84 #endif // __APPLE__
85  }
86  return keymods;
87 }
88 
89 float CallGLFWGetWindowContentScale(GLFWwindow* w) {
90  float xscale, yscale;
91  glfwGetWindowContentScale(w, &xscale, &yscale);
92  return std::min(xscale, yscale);
93 }
94 
95 } // namespace
96 
98 
100 
102 #if __APPLE__
103  // If we are running from Python we might not be running from a bundle
104  // and would therefore not be a Proper app yet.
105  MacTransformIntoApp();
106 
107  glfwInitHint(GLFW_COCOA_MENUBAR, GLFW_FALSE); // no auto-create menubar
108  // Don't change directory to resource directory in bundle (which is awkward
109  // if using a framework version of Python).
110  glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_FALSE);
111 #endif
112  glfwInit();
113 }
114 
115 void GLFWWindowSystem::Uninitialize() { glfwTerminate(); }
116 
117 void GLFWWindowSystem::WaitEventsTimeout(double timeout_secs) {
118  glfwWaitEventsTimeout(timeout_secs);
119  const char* err;
120  if (glfwGetError(&err) != GLFW_NO_ERROR) {
121  std::cerr << "[error] GLFW error: " << err << std::endl;
122  }
123 }
124 
126  int screen_width = FALLBACK_MONITOR_WIDTH;
127  int screen_height = FALLBACK_MONITOR_HEIGHT;
128  auto* monitor = glfwGetWindowMonitor((GLFWwindow*)w);
129  if (!monitor) {
130  monitor = glfwGetPrimaryMonitor();
131  }
132  if (monitor) {
133  const GLFWvidmode* mode = glfwGetVideoMode(monitor);
134  if (mode) {
135  screen_width = mode->width;
136  screen_height = mode->height;
137  }
138  // TODO: if we can update GLFW we can replace the above with this
139  // Also, see below.
140  // int xpos, ypos;
141  // glfwGetMonitorWorkarea(monitor, &xpos, &ypos,
142  // &screen_width, &screen_height);
143  }
144 
145  return Size(screen_width, screen_height);
146 }
147 
149  int width,
150  int height,
151  const char* title,
152  int flags) {
153  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
154  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
155  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
156  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
157  // NOTE: Setting alpha and stencil bits to match GLX standard default
158  // values. GLFW sets these internally to 8 and 8 respectively if not
159  // specified which causes problems with Filament on Linux with Nvidia binary
160  // driver
161  glfwWindowHint(GLFW_ALPHA_BITS, 0);
162  glfwWindowHint(GLFW_STENCIL_BITS, 0);
163 
164 #if __APPLE__
165  glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
166 #endif
167  bool visible = !(flags & FLAG_HIDDEN);
168  glfwWindowHint(GLFW_VISIBLE, visible ? GLFW_TRUE : GLFW_FALSE);
169  glfwWindowHint(GLFW_FLOATING,
170  ((flags & FLAG_TOPMOST) != 0 ? GLFW_TRUE : GLFW_FALSE));
171 
172  auto* glfw_window = glfwCreateWindow(width, height, title, NULL, NULL);
173 
174  glfwSetWindowUserPointer(glfw_window, o3d_window);
175  glfwSetWindowSizeCallback(glfw_window, ResizeCallback);
176  glfwSetWindowPosCallback(glfw_window, WindowMovedCallback);
177  glfwSetWindowRefreshCallback(glfw_window, DrawCallback);
178  glfwSetCursorPosCallback(glfw_window, MouseMoveCallback);
179  glfwSetMouseButtonCallback(glfw_window, MouseButtonCallback);
180  glfwSetScrollCallback(glfw_window, MouseScrollCallback);
181  glfwSetKeyCallback(glfw_window, KeyCallback);
182  glfwSetCharCallback(glfw_window, CharCallback);
183  glfwSetDropCallback(glfw_window, DragDropCallback);
184  glfwSetWindowCloseCallback(glfw_window, CloseCallback);
185 
186  return glfw_window;
187 }
188 
190  glfwDestroyWindow((GLFWwindow*)w);
191 }
192 
194  PostNativeExposeEvent((GLFWwindow*)w);
195 }
196 
198  return glfwGetWindowAttrib((GLFWwindow*)w, GLFW_VISIBLE);
199 }
200 
202  if (show) {
203  glfwShowWindow((GLFWwindow*)w);
204  } else {
205  glfwHideWindow((GLFWwindow*)w);
206  }
207 }
208 
210  glfwFocusWindow((GLFWwindow*)w);
211 }
212 
214  return glfwGetWindowAttrib((GLFWwindow*)w, GLFW_FOCUSED);
215 }
216 
218  int x, y;
219  glfwGetWindowPos((GLFWwindow*)w, &x, &y);
220  return Point(x, y);
221 }
222 
224  glfwSetWindowPos((GLFWwindow*)w, x, y);
225 }
226 
228  int width, height;
229  glfwGetWindowSize((GLFWwindow*)w, &width, &height);
230  return Size(width, height);
231 }
232 
234  glfwSetWindowSize((GLFWwindow*)w, width, height);
235 }
236 
238  uint32_t width, height;
239  glfwGetFramebufferSize((GLFWwindow*)w, (int*)&width, (int*)&height);
240  return Size(width, height);
241 }
242 
244  std::cout << "[o3d] TODO: implement GLFWWindowSystem::SetWindowSizePixels()"
245  << std::endl;
246 }
247 
249  // This function returns the number of device pixels per OS distance-unit.
250  // Windows and Linux keep one pixel equal to one real pixel, whereas
251  // macOS keeps the unit of measurement the same (1 pt = 1/72 inch) and
252  // changes the number pixels in one "virtual pixel". This function returns
253  // the scale factor as macOS thinks of it. This function should be used
254  // in converting to/from OS coordinates (e.g. mouse events), but not for
255  // sizing user interface elements like fonts.
256 #if __APPLE__
257  return CallGLFWGetWindowContentScale((GLFWwindow*)w);
258 #else
259  return 1.0f;
260 #endif // __APPLE__
261 }
262 
264  // This function returns the scale factor needed to have appropriately
265  // sized user interface elements.
266  return CallGLFWGetWindowContentScale((GLFWwindow*)w);
267 }
268 
269 void GLFWWindowSystem::SetWindowTitle(OSWindow w, const char* title) {
270  glfwSetWindowTitle((GLFWwindow*)w, title);
271 }
272 
274  double mx, my;
275  glfwGetCursorPos((GLFWwindow*)w, &mx, &my);
276  auto scaling = GetWindowScaleFactor((GLFWwindow*)w);
277  return Point(int(float(mx) * scaling), int(float(my) * scaling));
278 }
279 
281  GLFWwindow* gw = (GLFWwindow*)w;
282  int buttons = 0;
283  if (glfwGetMouseButton(gw, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) {
284  buttons |= int(MouseButton::LEFT);
285  }
286  if (glfwGetMouseButton(gw, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) {
287  buttons |= int(MouseButton::RIGHT);
288  }
289  if (glfwGetMouseButton(gw, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) {
290  buttons |= int(MouseButton::MIDDLE);
291  }
292  return buttons;
293 }
294 
296  glfwSetWindowShouldClose((GLFWwindow*)w, 0);
297 }
298 
299 // ----------------------------------------------------------------------------
300 void GLFWWindowSystem::DrawCallback(GLFWwindow* window) {
301  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
302  w->OnDraw();
303 }
304 
305 void GLFWWindowSystem::ResizeCallback(GLFWwindow* window,
306  int os_width,
307  int os_height) {
308  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
309  w->OnResize();
310 }
311 
312 void GLFWWindowSystem::WindowMovedCallback(GLFWwindow* window,
313  int os_x,
314  int os_y) {
315 #ifdef __APPLE__
316  // On macOS we need to recreate the swap chain if the window changes
317  // size OR MOVES!
318  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
319  w->OnResize();
320 #endif
321 }
322 
323 void GLFWWindowSystem::RescaleCallback(GLFWwindow* window,
324  float xscale,
325  float yscale) {
326  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
327  w->OnResize();
328 }
329 
330 void GLFWWindowSystem::MouseMoveCallback(GLFWwindow* window,
331  double x,
332  double y) {
333  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
334  int buttons = 0;
335  for (int b = GLFW_MOUSE_BUTTON_1; b < GLFW_MOUSE_BUTTON_5; ++b) {
336  if (glfwGetMouseButton(window, b) == GLFW_PRESS) {
337  buttons |= MouseButtonFromGLFW(b);
338  }
339  }
340  float scaling =
342  window);
343  int ix = int(std::ceil(x * scaling));
344  int iy = int(std::ceil(y * scaling));
345 
346  auto type = (buttons == 0 ? MouseEvent::MOVE : MouseEvent::DRAG);
347  MouseEvent me = MouseEvent::MakeButtonEvent(type, ix, iy, w->GetMouseMods(),
348  MouseButton(buttons), 1);
349 
350  w->OnMouseEvent(me);
351 }
352 
353 void GLFWWindowSystem::MouseButtonCallback(GLFWwindow* window,
354  int button,
355  int action,
356  int mods) {
357  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
358 
359  auto type = (action == GLFW_PRESS ? MouseEvent::BUTTON_DOWN
361  double mx, my;
362  glfwGetCursorPos(window, &mx, &my);
363  float scaling =
365  window);
366  int ix = int(std::ceil(mx * scaling));
367  int iy = int(std::ceil(my * scaling));
368 
369  MouseEvent me = MouseEvent::MakeButtonEvent(
370  type, ix, iy, KeymodsFromGLFW(mods),
371  MouseButton(MouseButtonFromGLFW(button)), 1);
372 
373  double now = Application::GetInstance().Now();
374  if (g_last_button_down == me.button.button) {
375  double dt = now - g_last_button_down_time;
376  if (dt > 0.0 && dt < DOUBLE_CLICK_TIME) {
377  me.button.count += 1;
378  }
379  }
380  if (type == MouseEvent::BUTTON_DOWN) {
381  g_last_button_down = me.button.button;
382  g_last_button_down_time = now;
383  }
384  w->OnMouseEvent(me);
385 }
386 
387 void GLFWWindowSystem::MouseScrollCallback(GLFWwindow* window,
388  double dx,
389  double dy) {
390  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
391 
392  double mx, my;
393  glfwGetCursorPos(window, &mx, &my);
394  float scaling =
396  window);
397  int ix = int(std::ceil(mx * scaling));
398  int iy = int(std::ceil(my * scaling));
399 
400  // GLFW doesn't give us any information about whether this scroll event
401  // came from a mousewheel or a trackpad two-finger scroll.
402 #if __APPLE__
403  bool isTrackpad = true;
404 #else
405  bool isTrackpad = false;
406 #endif // __APPLE__
407 
408  // Note that although pixels are integers, the trackpad value needs to
409  // be a float, since macOS trackpads produce fractional values when
410  // scrolling slowly. These fractional values need to be passed all the way
411  // down to the MatrixInteractorLogic::Dolly() in order for dollying to
412  // feel buttery smooth with the trackpad.
413  MouseEvent me = MouseEvent::MakeWheelEvent(
414  MouseEvent::WHEEL, ix, iy, w->GetMouseMods(), dx, dy, isTrackpad);
415 
416  w->OnMouseEvent(me);
417 }
418 
419 void GLFWWindowSystem::KeyCallback(
420  GLFWwindow* window, int key, int scancode, int action, int mods) {
421  static std::unordered_map<int, uint32_t> g_GLFW2Key = {
422  {GLFW_KEY_BACKSPACE, KEY_BACKSPACE},
423  {GLFW_KEY_TAB, KEY_TAB},
424  {GLFW_KEY_ENTER, KEY_ENTER},
425  {GLFW_KEY_ESCAPE, KEY_ESCAPE},
426  {GLFW_KEY_DELETE, KEY_DELETE},
427  {GLFW_KEY_LEFT_SHIFT, KEY_LSHIFT},
428  {GLFW_KEY_RIGHT_SHIFT, KEY_RSHIFT},
429  {GLFW_KEY_LEFT_CONTROL, KEY_LCTRL},
430  {GLFW_KEY_RIGHT_CONTROL, KEY_RCTRL},
431  {GLFW_KEY_LEFT_ALT, KEY_ALT},
432  {GLFW_KEY_RIGHT_ALT, KEY_ALT},
433  {GLFW_KEY_LEFT_SUPER, KEY_META},
434  {GLFW_KEY_RIGHT_SUPER, KEY_META},
435  {GLFW_KEY_CAPS_LOCK, KEY_CAPSLOCK},
436  {GLFW_KEY_LEFT, KEY_LEFT},
437  {GLFW_KEY_RIGHT, KEY_RIGHT},
438  {GLFW_KEY_UP, KEY_UP},
439  {GLFW_KEY_DOWN, KEY_DOWN},
440  {GLFW_KEY_INSERT, KEY_INSERT},
441  {GLFW_KEY_HOME, KEY_HOME},
442  {GLFW_KEY_END, KEY_END},
443  {GLFW_KEY_PAGE_UP, KEY_PAGEUP},
444  {GLFW_KEY_PAGE_DOWN, KEY_PAGEDOWN},
445  };
446  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
447 
448  auto type = (action == GLFW_RELEASE ? KeyEvent::Type::UP
449  : KeyEvent::Type::DOWN);
450 
451  uint32_t k = key;
452  if (key >= 'A' && key <= 'Z') {
453  k += 32; // GLFW gives uppercase for letters, convert to lowercase
454  } else {
455  auto it = g_GLFW2Key.find(key);
456  if (it != g_GLFW2Key.end()) {
457  k = it->second;
458  }
459  }
460  KeyEvent e = {type, k, (action == GLFW_REPEAT)};
461 
462  w->OnKeyEvent(e);
463 }
464 
465 void GLFWWindowSystem::CharCallback(GLFWwindow* window,
466  unsigned int utf32char) {
467  // Convert utf-32 to utf8
468  // From https://stackoverflow.com/a/42013433/218226
469  // Note: This code handles all characters, but non-European characters
470  // won't draw unless we will include them in the ImGUI font (which
471  // is prohibitively large for hanzi/kanji)
472  char utf8[5];
473  if (utf32char <= 0x7f) {
474  utf8[0] = utf32char;
475  utf8[1] = '\0';
476  } else if (utf32char <= 0x7ff) {
477  utf8[0] = 0xc0 | (utf32char >> 6);
478  utf8[1] = 0x80 | (utf32char & 0x3f);
479  utf8[2] = '\0';
480  } else if (utf32char <= 0xffff) {
481  utf8[0] = 0xe0 | (utf32char >> 12);
482  utf8[1] = 0x80 | ((utf32char >> 6) & 0x3f);
483  utf8[2] = 0x80 | (utf32char & 0x3f);
484  utf8[3] = '\0';
485  } else if (utf32char <= 0x10ffff) {
486  utf8[0] = 0xf0 | (utf32char >> 18);
487  utf8[1] = 0x80 | ((utf32char >> 12) & 0x3f);
488  utf8[2] = 0x80 | ((utf32char >> 6) & 0x3f);
489  utf8[3] = 0x80 | (utf32char & 0x3f);
490  utf8[4] = '\0';
491  } else {
492  // These characters are supposed to be forbidden, but just in case
493  utf8[0] = '?';
494  utf8[1] = '\0';
495  }
496 
497  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
498  w->OnTextInput(TextInputEvent{utf8});
499 }
500 
501 void GLFWWindowSystem::DragDropCallback(GLFWwindow* window,
502  int count,
503  const char* paths[]) {
504  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
505  for (int i = 0; i < count; ++i) {
506  w->OnDragDropped(paths[i]);
507  }
508 }
509 
510 void GLFWWindowSystem::CloseCallback(GLFWwindow* window) {
511  Window* w = static_cast<Window*>(glfwGetWindowUserPointer(window));
512  w->Close();
513 }
514 
516  return gui::GetNativeDrawable((GLFWwindow*)w);
517 }
518 
520  return new rendering::FilamentRenderer(
523 }
524 
526  rendering::FilamentRenderer* renderer) {
527 #if __APPLE__
528  // We need to recreate the swap chain after resizing a window on macOS
529  // otherwise things look very wrong. SwapChain does not need to be resized
530  // on other platforms.
531  renderer->UpdateSwapChain();
532 #endif // __APPLE__
533 }
534 
536 #ifdef __APPLE__
537  return new MenuMacOS();
538 #else
539  return new MenuImgui();
540 #endif
541 }
542 
543 } // namespace gui
544 } // namespace visualization
545 } // namespace cloudViewer
Window * o3d_window
int width
int size
int height
int count
char type
#define NULL
float GetWindowScaleFactor(OSWindow w) const override
float GetUIScaleFactor(OSWindow w) const override
OSWindow CreateOSWindow(Window *o3d_window, int width, int height, const char *title, int flags) override
rendering::FilamentRenderer * CreateRenderer(OSWindow w) override
Point GetMousePosInWindow(OSWindow w) const override
void SetWindowPos(OSWindow w, int x, int y) override
Size GetWindowSizePixels(OSWindow w) const override
void WaitEventsTimeout(double timeout_secs) override
void ResizeRenderer(OSWindow w, rendering::FilamentRenderer *renderer) override
void SetWindowSizePixels(OSWindow w, const Size &size) override
void SetWindowSize(OSWindow w, int width, int height) override
bool GetWindowIsVisible(OSWindow w) const override
void ShowWindow(OSWindow w, bool show) override
void SetWindowTitle(OSWindow w, const char *title) override
virtual float GetWindowScaleFactor(OSWindow w) const =0
static FilamentResourceManager & GetResourceManager()
int min(int a, int b)
Definition: cutil_math.h:53
float scaling
Definition: Window.cpp:78
QTextStream & endl(QTextStream &stream)
Definition: QtCompat.h:718
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
void PostNativeExposeEvent(GLFWwindow *glfw_window)
Definition: NativeLinux.cpp:38
void * GetNativeDrawable(GLFWwindow *glfw_window)
Definition: NativeLinux.cpp:23
Generic file read and write utility for python interface.
static MouseEvent MakeWheelEvent(const Type type, const int x, const int y, const int modifiers, const float dx, const float dy, const bool isTrackpad)
Definition: Events.cpp:169
static MouseEvent MakeButtonEvent(const Type type, const int x, const int y, const int modifiers, const MouseButton button, const int count)
Definition: Events.cpp:153