Visual Computing Library  devel
Loading...
Searching...
No Matches
window_manager.h
1/*****************************************************************************
2 * VCLib *
3 * Visual Computing Library *
4 * *
5 * Copyright(C) 2021-2025 *
6 * Visual Computing Lab *
7 * ISTI - Italian National Research Council *
8 * *
9 * All rights reserved. *
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the Mozilla Public License Version 2.0 as published *
13 * by the Mozilla Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 * This program is distributed in the hope that it will be useful, *
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
19 * Mozilla Public License Version 2.0 *
20 * (https://www.mozilla.org/en-US/MPL/2.0/) for more details. *
21 ****************************************************************************/
22
23#ifndef VCL_GLFW_WINDOW_MANAGER_H
24#define VCL_GLFW_WINDOW_MANAGER_H
25
26#include "input.h"
27
28#include <vclib/render/concepts/render_app.h>
29#include <vclib/render/window_managers.h>
30#include <vclib/space/core/point.h>
31
32#if defined(__linux__)
33#ifdef VCLIB_RENDER_WITH_WAYLAND
34#define GLFW_EXPOSE_NATIVE_WAYLAND
35#else
36#define GLFW_EXPOSE_NATIVE_X11
37#endif
38#elif defined(_WIN32)
39#define GLFW_EXPOSE_NATIVE_WIN32
40#elif defined(__APPLE__)
41#define GLFW_EXPOSE_NATIVE_COCOA
42#endif
43
44#include <GLFW/glfw3.h>
45#include <GLFW/glfw3native.h>
46
47#include <iostream>
48
49namespace vcl::glfw {
50
51namespace detail {
52
53inline void glfwErrorCallback(int error, const char* description)
54{
55 std::cerr << "GLFW error: " << error << ": " << description << std::endl;
56}
57
58} // namespace detail
59
60template<typename DerivedRenderApp>
62{
63 std::string mTitle;
64
65 // double click management
66 static constexpr int NO_BUTTON = GLFW_MOUSE_BUTTON_LAST + 1;
67
68 // delta time between two clicks
69 static constexpr double DOUBLE_CLICK_TIME_SECS = 0.25;
70 static constexpr double DOUBLE_CLICK_DIST_PIXELS = 4.0; // pixels?
71
72 double mLastPressedTime = 0.0;
73 int mLastPressedButton = NO_BUTTON;
74 Point2d mLastPressedPos = {0, 0};
75
76protected:
77 GLFWwindow* mWindow = nullptr;
78 float mScaleX = 1.0f; // content scaling
79 float mScaleY = 1.0f;
80
81public:
88
94 static const uint WINDOW_MANAGER_ID = WindowManagerId::GLFW_WINDOW;
95
96 WindowManager(ParentType* parent = nullptr) :
97 WindowManager("", 1024, 768, parent)
98 {
99 }
100
102 const std::string& windowTitle,
103 uint width = 1024,
104 uint height = 768,
105 ParentType* = nullptr) : mTitle(windowTitle)
106 {
107 static_assert(
109 "The DerivedRenderApp must satisfy the RenderAppConcept.");
110
111 glfwSetErrorCallback(detail::glfwErrorCallback);
112 if (!glfwInit()) {
113 std::cerr << "Failed to initialize GLFW" << std::endl;
115 }
116
117#if defined(VCLIB_RENDER_BACKEND_BGFX)
120#if defined(__APPLE__)
122#endif
123#elif defined(VCLIB_RENDER_BACKEND_OPENGL2)
127#endif
128
130
131 mWindow = glfwCreateWindow(
132 width, height, windowTitle.c_str(), nullptr, nullptr);
133 if (!mWindow) {
134 std::cerr << "Failed to create GLFW window" << std::endl;
137 }
138
139#ifdef VCLIB_RENDER_BACKEND_OPENGL2
140 glfwMakeContextCurrent(mWindow);
141#endif
142
143 // get content scale (e.g. for macOS retina displays)
144 glfwGetWindowContentScale(mWindow, &mScaleX, &mScaleY);
145
146 glfwSetWindowUserPointer(mWindow, this);
147 setCallbacks();
148 }
149
150 virtual ~WindowManager() { cleanup(); }
151
152 const std::string& windowTitle() const { return mTitle; }
153
154 void setWindowTitle(const std::string& title)
155 {
156 mTitle = title;
157 glfwSetWindowTitle(mWindow, mTitle.c_str());
158 }
159
160 uint width() const
161 {
162 int width, height;
163 glfwGetWindowSize(mWindow, &width, &height);
164 return width;
165 }
166
167 uint height() const
168 {
169 int width, height;
170 glfwGetWindowSize(mWindow, &width, &height);
171 return height;
172 }
173
174 void resize(uint width, uint height)
175 {
176 glfwSetWindowSize(mWindow, width, height);
177 }
178
179 void show()
180 {
181 DerivedRenderApp::WM::init(derived());
182 while (!glfwWindowShouldClose(mWindow)) {
183 glfwPollEvents();
184 DerivedRenderApp::WM::paint(derived());
185#ifdef VCLIB_RENDER_BACKEND_OPENGL2
186 glfwSwapBuffers(mWindow);
187#endif
188 }
189 // Window was closed by user, clean up
190 cleanup();
191 }
192
198 bool isMinimized() const
199 {
200 return glfwGetWindowAttrib(mWindow, GLFW_ICONIFIED);
201 }
202
203 // required by the WindowManagerConcept
204 // because when the Canvas draws, then it requires the WindowManger to
205 // update. Here is empty because the show() method does and manages
206 // internally the loop.
207 void update() {}
208
209 Point2f dpiScale() const { return Point2f(mScaleX, mScaleY); }
210
211 void* winId() const
212 {
213 void* nwh = nullptr;
214
215#if defined(__linux__)
216#ifdef VCLIB_RENDER_WITH_WAYLAND
217 nwh = (void*) (uintptr_t) glfwGetWaylandWindow(mWindow);
218#else
219 nwh = (void*) (uintptr_t) glfwGetX11Window(mWindow);
220#endif
221#elif defined(_WIN32)
222 nwh = glfwGetWin32Window(mWindow);
223#elif defined(__APPLE__)
224 nwh = glfwGetCocoaWindow(mWindow);
225#endif
226
227 return nwh;
228 }
229
230 void* displayId() const
231 {
232 void* ndt = nullptr;
233#ifdef __linux__
234#ifdef VCLIB_RENDER_WITH_WAYLAND
235 ndt = (void*) (uintptr_t) glfwGetWaylandDisplay();
236#else
237 ndt = (void*) (uintptr_t) glfwGetX11Display();
238#endif
239#endif
240 return ndt;
241 }
242
243protected:
244 void* windowPtr() { return reinterpret_cast<void*>(mWindow); }
245
246 // callbacks
247 virtual void glfwFramebufferSizeCallback(GLFWwindow*, int width, int height)
248 {
249 DerivedRenderApp::WM::resize(derived(), width, height);
250 }
251
252 virtual void glfwContentScaleCallback(
253 GLFWwindow*,
254 float xscale,
255 float yscale)
256 {
257 mScaleX = xscale;
258 mScaleY = yscale;
259
260 int width, height;
261 glfwGetFramebufferSize(mWindow, &width, &height);
262 DerivedRenderApp::WM::resize(derived(), width, height);
263 }
264
265 virtual void glfwKeyCallback(
266 GLFWwindow*,
267 int key,
268 int,
269 int action,
270 int mods)
271 {
272#if defined GLFW_EXPOSE_NATIVE_X11
273 // Fix modifiers on X11
274 // maybe it will be fixed https://github.com/glfw/glfw/issues/1630
275 mods = fixKeyboardMods(key, action, mods);
276#endif
277
278 // GLFW modifiers are always set
279 DerivedRenderApp::WM::setModifiers(
280 derived(), glfw::fromGLFW((glfw::KeyboardModifiers) mods));
281
282 vcl::Key::Enum k = glfw::fromGLFW((glfw::Key) key);
283
284 if (action == GLFW_PRESS || action == GLFW_REPEAT) {
285 DerivedRenderApp::WM::keyPress(derived(), k);
286 }
287 else if (action == GLFW_RELEASE) {
288 DerivedRenderApp::WM::keyRelease(derived(), k);
289 }
290 }
291
292 virtual void glfwMouseButtonCallback(
293 GLFWwindow* win,
294 int button,
295 int action,
296 int mods)
297 {
298 vcl::MouseButton::Enum btn = glfw::fromGLFW((glfw::MouseButton) button);
299
300 DerivedRenderApp::WM::setModifiers(
301 derived(), glfw::fromGLFW((glfw::KeyboardModifiers) mods));
302
303 Point2d pos;
304 Point2f scale;
305 glfwGetCursorPos(win, &pos.x(), &pos.y());
306#ifdef __APPLE__
307 // only macOS has coherent coordinates with content scale
308 pos.x() *= dpiScale().x();
309 pos.y() *= dpiScale().y();
310#endif
311
312 if (action == GLFW_PRESS) {
313 // handle double click
314 const double timeSeconds = glfwGetTime();
315
316 if (timeSeconds - mLastPressedTime < DOUBLE_CLICK_TIME_SECS &&
317 button == mLastPressedButton &&
318 (mLastPressedPos - pos).norm() < DOUBLE_CLICK_DIST_PIXELS) {
319 mLastPressedTime = 0.0;
320 mLastPressedButton = NO_BUTTON;
321 DerivedRenderApp::WM::mouseDoubleClick(
322 derived(), btn, pos.x(), pos.y());
323 }
324 else {
325 mLastPressedTime = timeSeconds;
326 mLastPressedButton = button;
327 mLastPressedPos = pos;
328 DerivedRenderApp::WM::mousePress(
329 derived(), btn, pos.x(), pos.y());
330 }
331 }
332 else if (action == GLFW_RELEASE) {
333 DerivedRenderApp::WM::mouseRelease(
334 derived(), btn, pos.x(), pos.y());
335 }
336 }
337
338 virtual void glfwCursorPosCallback(GLFWwindow*, double xpos, double ypos)
339 {
340#ifdef __APPLE__
341 // only macOS has coherent coordinates with content scale
342 xpos *= dpiScale().x();
343 ypos *= dpiScale().y();
344#endif
345 DerivedRenderApp::WM::mouseMove(derived(), xpos, ypos);
346 }
347
348 virtual void glfwScrollCallback(GLFWwindow*, double xoffset, double yoffset)
349 {
350 {
351 // This is ok for macOS
352 // TODO: check other platforms
353 // TODO: check if content scale must be used
354 const double TO_PIXEL_FACTOR = 10;
355 DerivedRenderApp::WM::mouseScroll(
356 derived(),
357 xoffset * TO_PIXEL_FACTOR,
358 yoffset * TO_PIXEL_FACTOR);
359 }
360 }
361
362private:
363 void cleanup()
364 {
365 if (mWindow) {
366 glfwDestroyWindow(mWindow);
367 mWindow = nullptr;
368 }
369 }
370
371 void setCallbacks()
372 {
373 // framebuffer size callback
374 glfwSetFramebufferSizeCallback(
375 mWindow, [](GLFWwindow* window, int width, int height) {
376 auto* self = static_cast<WindowManager*>(
377 glfwGetWindowUserPointer(window));
378 self->glfwFramebufferSizeCallback(window, width, height);
379 });
380
381 // content scale callback
382 glfwSetWindowContentScaleCallback(
383 mWindow, [](GLFWwindow* window, float xscale, float yscale) {
384 auto* self = static_cast<WindowManager*>(
385 glfwGetWindowUserPointer(window));
386 self->glfwContentScaleCallback(window, xscale, yscale);
387 });
388
389 // key callback
390 glfwSetKeyCallback(
391 mWindow,
392 [](GLFWwindow* window,
393 int key,
394 int scancode,
395 int action,
396 int mods) {
397 auto* self = static_cast<WindowManager*>(
398 glfwGetWindowUserPointer(window));
399 self->glfwKeyCallback(window, key, scancode, action, mods);
400 });
401
402 // mouse position callback
403 glfwSetCursorPosCallback(
404 mWindow, [](GLFWwindow* window, double xpos, double ypos) {
405 auto* self = static_cast<WindowManager*>(
406 glfwGetWindowUserPointer(window));
407 self->glfwCursorPosCallback(window, xpos, ypos);
408 });
409
410 // mouse button callback
411 glfwSetMouseButtonCallback(
412 mWindow, [](GLFWwindow* window, int button, int action, int mods) {
413 auto* self = static_cast<WindowManager*>(
414 glfwGetWindowUserPointer(window));
415 self->glfwMouseButtonCallback(window, button, action, mods);
416 });
417
418 // scroll callback
419 glfwSetScrollCallback(
420 mWindow, [](GLFWwindow* window, double xoffset, double yoffset) {
421 auto* self = static_cast<WindowManager*>(
422 glfwGetWindowUserPointer(window));
423 self->glfwScrollCallback(window, xoffset, yoffset);
424 });
425 }
426
427 auto* derived() { return static_cast<DerivedRenderApp*>(this); }
428
429 const auto* derived() const
430 {
431 return static_cast<const DerivedRenderApp*>(this);
432 }
433
434 static int fixKeyboardMods(int key, int action, int mods)
435 {
436 switch (key) {
437 case GLFW_KEY_LEFT_SHIFT:
438 case GLFW_KEY_RIGHT_SHIFT:
439 return (action == GLFW_PRESS) ? mods | GLFW_MOD_SHIFT :
440 mods & (~GLFW_MOD_SHIFT);
441 case GLFW_KEY_LEFT_CONTROL:
442 case GLFW_KEY_RIGHT_CONTROL:
443 return (action == GLFW_PRESS) ? mods | GLFW_MOD_CONTROL :
444 mods & (~GLFW_MOD_CONTROL);
445 case GLFW_KEY_LEFT_ALT:
446 case GLFW_KEY_RIGHT_ALT:
447 return (action == GLFW_PRESS) ? mods | GLFW_MOD_ALT :
448 mods & (~GLFW_MOD_ALT);
449 case GLFW_KEY_LEFT_SUPER:
450 case GLFW_KEY_RIGHT_SUPER:
451 return (action == GLFW_PRESS) ? mods | GLFW_MOD_SUPER :
452 mods & (~GLFW_MOD_SUPER);
453 default: break;
454 }
455
456 return mods;
457 }
458};
459
460} // namespace vcl::glfw
461
462#endif // VCL_GLFW_WINDOW_MANAGER_H
A class representing a box in N-dimensional space.
Definition box.h:46
The Point class represents an N-dimensional point containing N scalar values.
Definition point.h:55
ScalarType & x()
Returns a reference to the x-component of the Point object.
Definition point.h:128
ScalarType & y()
Returns a reference to the y-component of the Point object.
Definition point.h:150
Definition window_manager.h:62
bool isMinimized() const
Returns true if the window is minimized (i.e. iconified), false otherwise.
Definition window_manager.h:198
void ParentType
The ParentType is the type of the parent class. It is used to initialize the base class (if any)....
Definition window_manager.h:87
static const uint WINDOW_MANAGER_ID
The WINDOW_MANAGER_ID is the ID of the window manager. It is used to identify the window manager impl...
Definition window_manager.h:94
Definition render_app.h:31
Point2< double > Point2d
A convenience alias for a 2-dimensional Point with double-precision floating-point components.
Definition point.h:730
Point2< float > Point2f
A convenience alias for a 2-dimensional Point with floating-point components.
Definition point.h:718