Visual Computing Library
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() = default;
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 show()
175 {
176 DerivedRenderApp::WM::init(derived());
177 while (!glfwWindowShouldClose(mWindow)) {
178 glfwPollEvents();
179 DerivedRenderApp::WM::paint(derived());
180#ifdef VCLIB_RENDER_BACKEND_OPENGL2
181 glfwSwapBuffers(mWindow);
182#endif
183 }
184 }
185
191 bool isMinimized() const
192 {
193 return glfwGetWindowAttrib(mWindow, GLFW_ICONIFIED);
194 }
195
196 // required by the WindowManagerConcept
197 // because when the Canvas draws, then it requires the WindowManger to
198 // update. Here is empty because the show() method does and manages
199 // internally the loop.
200 void update() {}
201
202 Point2f dpiScale() const { return Point2f(mScaleX, mScaleY); }
203
204 void* winId() const
205 {
206 void* nwh = nullptr;
207
208#if defined(__linux__)
209#ifdef VCLIB_RENDER_WITH_WAYLAND
210 nwh = (void*) (uintptr_t) glfwGetWaylandWindow(mWindow);
211#else
212 nwh = (void*) (uintptr_t) glfwGetX11Window(mWindow);
213#endif
214#elif defined(_WIN32)
215 nwh = glfwGetWin32Window(mWindow);
216#elif defined(__APPLE__)
217 nwh = glfwGetCocoaWindow(mWindow);
218#endif
219
220 return nwh;
221 }
222
223 void* displayId() const
224 {
225 void* ndt = nullptr;
226#ifdef __linux__
227#ifdef VCLIB_RENDER_WITH_WAYLAND
228 ndt = (void*) (uintptr_t) glfwGetWaylandDisplay();
229#else
230 ndt = (void*) (uintptr_t) glfwGetX11Display();
231#endif
232#endif
233 return ndt;
234 }
235
236protected:
237 void* windowPtr() { return reinterpret_cast<void*>(mWindow); }
238
239 // callbacks
240 virtual void glfwFramebufferSizeCallback(GLFWwindow*, int width, int height)
241 {
242 DerivedRenderApp::WM::resize(derived(), width, height);
243 }
244
245 virtual void glfwContentScaleCallback(
246 GLFWwindow*,
247 float xscale,
248 float yscale)
249 {
250 mScaleX = xscale;
251 mScaleY = yscale;
252
253 int width, height;
254 glfwGetFramebufferSize(mWindow, &width, &height);
255 DerivedRenderApp::WM::resize(derived(), width, height);
256 }
257
258 virtual void glfwKeyCallback(
259 GLFWwindow*,
260 int key,
261 int,
262 int action,
263 int mods)
264 {
265#if defined GLFW_EXPOSE_NATIVE_X11
266 // Fix modifiers on X11
267 // maybe it will be fixed https://github.com/glfw/glfw/issues/1630
268 mods = fixKeyboardMods(key, action, mods);
269#endif
270
271 // GLFW modifiers are always set
272 DerivedRenderApp::WM::setModifiers(
273 derived(), glfw::fromGLFW((glfw::KeyboardModifiers) mods));
274
275 vcl::Key::Enum k = glfw::fromGLFW((glfw::Key) key);
276
277 if (action == GLFW_PRESS || action == GLFW_REPEAT) {
278 DerivedRenderApp::WM::keyPress(derived(), k);
279 }
280 else if (action == GLFW_RELEASE) {
281 DerivedRenderApp::WM::keyRelease(derived(), k);
282 }
283 }
284
285 virtual void glfwMouseButtonCallback(
286 GLFWwindow* win,
287 int button,
288 int action,
289 int mods)
290 {
291 vcl::MouseButton::Enum btn = glfw::fromGLFW((glfw::MouseButton) button);
292
293 DerivedRenderApp::WM::setModifiers(
294 derived(), glfw::fromGLFW((glfw::KeyboardModifiers) mods));
295
296 Point2d pos;
297 Point2f scale;
298 glfwGetCursorPos(win, &pos.x(), &pos.y());
299#ifdef __APPLE__
300 // only macOS has coherent coordinates with content scale
301 pos.x() *= dpiScale().x();
302 pos.y() *= dpiScale().y();
303#endif
304
305 if (action == GLFW_PRESS) {
306 // handle double click
307 const double timeSeconds = glfwGetTime();
308
309 if (timeSeconds - mLastPressedTime < DOUBLE_CLICK_TIME_SECS &&
310 button == mLastPressedButton &&
311 (mLastPressedPos - pos).norm() < DOUBLE_CLICK_DIST_PIXELS) {
312 mLastPressedTime = 0.0;
313 mLastPressedButton = NO_BUTTON;
314 DerivedRenderApp::WM::mouseDoubleClick(
315 derived(), btn, pos.x(), pos.y());
316 }
317 else {
318 mLastPressedTime = timeSeconds;
319 mLastPressedButton = button;
320 mLastPressedPos = pos;
321 DerivedRenderApp::WM::mousePress(
322 derived(), btn, pos.x(), pos.y());
323 }
324 }
325 else if (action == GLFW_RELEASE) {
326 DerivedRenderApp::WM::mouseRelease(
327 derived(), btn, pos.x(), pos.y());
328 }
329 }
330
331 virtual void glfwCursorPosCallback(GLFWwindow*, double xpos, double ypos)
332 {
333#ifdef __APPLE__
334 // only macOS has coherent coordinates with content scale
335 xpos *= dpiScale().x();
336 ypos *= dpiScale().y();
337#endif
338 DerivedRenderApp::WM::mouseMove(derived(), xpos, ypos);
339 }
340
341 virtual void glfwScrollCallback(GLFWwindow*, double xoffset, double yoffset)
342 {
343 {
344 // This is ok for macOS
345 // TODO: check other platforms
346 // TODO: check if content scale must be used
347 const double TO_PIXEL_FACTOR = 10;
348 DerivedRenderApp::WM::mouseScroll(
349 derived(),
350 xoffset * TO_PIXEL_FACTOR,
351 yoffset * TO_PIXEL_FACTOR);
352 }
353 }
354
355private:
356 void setCallbacks()
357 {
358 // framebuffer size callback
359 glfwSetFramebufferSizeCallback(
360 mWindow, [](GLFWwindow* window, int width, int height) {
361 auto* self = static_cast<WindowManager*>(
362 glfwGetWindowUserPointer(window));
363 self->glfwFramebufferSizeCallback(window, width, height);
364 });
365
366 // content scale callback
367 glfwSetWindowContentScaleCallback(
368 mWindow, [](GLFWwindow* window, float xscale, float yscale) {
369 auto* self = static_cast<WindowManager*>(
370 glfwGetWindowUserPointer(window));
371 self->glfwContentScaleCallback(window, xscale, yscale);
372 });
373
374 // key callback
375 glfwSetKeyCallback(
376 mWindow,
377 [](GLFWwindow* window,
378 int key,
379 int scancode,
380 int action,
381 int mods) {
382 auto* self = static_cast<WindowManager*>(
383 glfwGetWindowUserPointer(window));
384 self->glfwKeyCallback(window, key, scancode, action, mods);
385 });
386
387 // mouse position callback
388 glfwSetCursorPosCallback(
389 mWindow, [](GLFWwindow* window, double xpos, double ypos) {
390 auto* self = static_cast<WindowManager*>(
391 glfwGetWindowUserPointer(window));
392 self->glfwCursorPosCallback(window, xpos, ypos);
393 });
394
395 // mouse button callback
396 glfwSetMouseButtonCallback(
397 mWindow, [](GLFWwindow* window, int button, int action, int mods) {
398 auto* self = static_cast<WindowManager*>(
399 glfwGetWindowUserPointer(window));
400 self->glfwMouseButtonCallback(window, button, action, mods);
401 });
402
403 // scroll callback
404 glfwSetScrollCallback(
405 mWindow, [](GLFWwindow* window, double xoffset, double yoffset) {
406 auto* self = static_cast<WindowManager*>(
407 glfwGetWindowUserPointer(window));
408 self->glfwScrollCallback(window, xoffset, yoffset);
409 });
410 }
411
412 auto* derived() { return static_cast<DerivedRenderApp*>(this); }
413
414 const auto* derived() const
415 {
416 return static_cast<const DerivedRenderApp*>(this);
417 }
418
419 static int fixKeyboardMods(int key, int action, int mods)
420 {
421 switch (key) {
422 case GLFW_KEY_LEFT_SHIFT:
423 case GLFW_KEY_RIGHT_SHIFT:
424 return (action == GLFW_PRESS) ? mods | GLFW_MOD_SHIFT :
425 mods & (~GLFW_MOD_SHIFT);
426 case GLFW_KEY_LEFT_CONTROL:
427 case GLFW_KEY_RIGHT_CONTROL:
428 return (action == GLFW_PRESS) ? mods | GLFW_MOD_CONTROL :
429 mods & (~GLFW_MOD_CONTROL);
430 case GLFW_KEY_LEFT_ALT:
431 case GLFW_KEY_RIGHT_ALT:
432 return (action == GLFW_PRESS) ? mods | GLFW_MOD_ALT :
433 mods & (~GLFW_MOD_ALT);
434 case GLFW_KEY_LEFT_SUPER:
435 case GLFW_KEY_RIGHT_SUPER:
436 return (action == GLFW_PRESS) ? mods | GLFW_MOD_SUPER :
437 mods & (~GLFW_MOD_SUPER);
438 default: break;
439 }
440
441 return mods;
442 }
443};
444
445} // namespace vcl::glfw
446
447#endif // VCL_GLFW_WINDOW_MANAGER_H
The Point class represents an N-dimensional point containing N scalar values.
Definition point.h:58
ScalarType & x()
Returns a reference to the x-component of the Point object.
Definition point.h:131
ScalarType & y()
Returns a reference to the y-component of the Point object.
Definition point.h:153
A class representing a line segment in n-dimensional space. The class is parameterized by a PointConc...
Definition segment.h:43
Definition window_manager.h:62
bool isMinimized() const
Returns true if the window is minimized (i.e. iconified), false otherwise.
Definition window_manager.h:191
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:755
Point2< float > Point2f
A convenience alias for a 2-dimensional Point with floating-point components.
Definition point.h:743