GCC Code Coverage Report


Directory: ./
File: lib/geogram_gfx/gui/application.cpp
Date: 2026-09-07 02:28:19
Exec Total Coverage
Lines: 0 639 0.0%
Functions: 0 57 0.0%
Branches: 0 474 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2023 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram_gfx/gui/application.h>
41 #include <geogram_gfx/gui/user_callback_android.h>
42
43 #include <geogram_gfx/imgui_ext/imgui_ext.h>
44 #include <geogram_gfx/third_party/imgui/backends/imgui_impl_opengl3.h>
45 #include <geogram_gfx/imgui_ext/icon_font.h>
46
47 #include <geogram_gfx/lua/lua_glup.h>
48 #include <geogram_gfx/lua/lua_imgui.h>
49
50 #include <geogram_gfx/third_party/imgui_fonts/cousine_regular.h>
51 #include <geogram_gfx/third_party/imgui_fonts/roboto_medium.h>
52 #include <geogram_gfx/third_party/imgui_fonts/fa_solid.h>
53
54 #include <geogram/image/image.h>
55 #include <geogram/basic/command_line.h>
56 #include <geogram/basic/command_line_args.h>
57 #include <geogram/basic/logger.h>
58 #include <geogram/basic/file_system.h>
59
60
61 #if defined(GEO_GLFW)
62 # include <geogram_gfx/third_party/imgui/backends/imgui_impl_glfw.h>
63 // Too many documentation warnings in glfw
64 // (glfw uses tags that clang does not understand).
65 # ifdef __clang__
66 # pragma GCC diagnostic ignored "-Wdocumentation"
67 # endif
68
69 # if defined(GEO_USE_SYSTEM_GLFW3) || defined(GEO_OS_EMSCRIPTEN)
70 # include <GLFW/glfw3.h>
71 # else
72 # include <third_party/glfw/include/GLFW/glfw3.h>
73 # endif
74
75 #ifdef GEO_OS_EMSCRIPTEN
76 # include <emscripten.h>
77 #endif
78
79 #elif defined(GEO_OS_ANDROID)
80
81 # include <geogram_gfx/imgui_ext/imgui_impl_android_ext.h>
82 # include <geogram/basic/android_utils.h>
83
84 # include <EGL/egl.h>
85 # include <EGL/eglext.h>
86 # include <GLES/gl.h>
87 # include <android_native_app_glue.h>
88
89 #endif
90
91 namespace ImGui {
92 void UpdateHoveredWindowAndCaptureFlags();
93 }
94
95 namespace GEO {
96
97 /**
98 * \brief Computes the pixel ratio for hidpi devices.
99 * \details Uses the current GLFW window.
100 */
101 double compute_pixel_ratio();
102
103 /**
104 * \brief Computes the scaling factor for hidpi devices.
105 * \details Uses the current GLFW window.
106 */
107 double compute_hidpi_scaling();
108
109
110 /**
111 * \brief If nothing happens during 100 frames, then
112 * we (micro)-sleep instead of redrawing the
113 * window.
114 */
115 const index_t NB_FRAMES_UPDATE_INIT = 100;
116
117 #if defined(GEO_GLFW)
118 class ApplicationData {
119 public:
120 ApplicationData() {
121 window_ = nullptr;
122 GLFW_callbacks_initialized_ = false;
123 }
124 GLFWwindow* window_;
125 bool GLFW_callbacks_initialized_;
126 };
127 #elif defined(GEO_OS_ANDROID)
128 class ApplicationData {
129 public:
130 ApplicationData() {
131 app = nullptr;
132 display = EGL_NO_DISPLAY;
133 surface = EGL_NO_SURFACE;
134 context = EGL_NO_CONTEXT;
135 GLES_version = 3;
136 has_focus = false;
137 has_window = false;
138 is_visible = false;
139 GL_initialized = false;
140 }
141 bool should_draw() const {
142 return has_focus && has_window && is_visible;
143 }
144 android_app* app;
145 EGLDisplay display;
146 EGLSurface surface;
147 EGLConfig config;
148 EGLContext context;
149 EGLint GLES_version;
150 bool has_focus;
151 bool has_window;
152 bool is_visible;
153 LoggerClient_var logger_client;
154 bool GL_initialized;
155 };
156 #else
157 # error "No windowing system"
158 #endif
159
160 }
161
162 namespace GEO {
163
164 Application* Application::instance_ = nullptr;
165
166 Application::Application(const std::string& name) {
167 geo_assert(instance_ == nullptr);
168 GEO::initialize(GEO::GEOGRAM_INSTALL_ALL);
169 instance_ = this;
170 name_ = name;
171 data_ = new ApplicationData();
172 ImGui_restart_ = false;
173 ImGui_reload_font_ = false;
174 ImGui_initialized_ = false;
175 ImGui_firsttime_init_ = false;
176 width_ = 800;
177 height_ = 800;
178 frame_buffer_width_ = 800;
179 frame_buffer_height_ = 800;
180 in_main_loop_ = false;
181 accept_drops_ = true;
182 scaling_ = 1.0;
183 font_size_ = 18;
184 nb_update_locks_ = 0;
185 nb_frames_update_ = NB_FRAMES_UPDATE_INIT;
186 hidpi_scaling_ = 1.0;
187 pixel_ratio_ = 1.0;
188 currently_drawing_gui_ = false;
189 animate_ = false;
190 menubar_visible_ = true;
191 phone_screen_ = false;
192 }
193
194 Application::~Application() {
195 delete_window();
196 geo_assert(instance_ == this);
197 delete data_;
198 data_ = nullptr;
199 instance_ = nullptr;
200 }
201
202 void Application::start(int argc, char** argv) {
203 if(argc != 0 && argv != nullptr) {
204 geogram_initialize(argc, argv);
205 }
206 if(CmdLine::arg_is_declared("gui:font_size")) {
207 set_font_size(CmdLine::get_arg_uint("gui:font_size"));
208 }
209 #if defined(GEO_OS_UNIX)
210 {
211 // NVidia/Optimus GPU selection under Linux:
212 // controlled by two environment variables.
213 std::string adapter = CmdLine::get_arg("gfx:adapter");
214 if(adapter != "default") {
215 ::setenv("__NV_PRIME_RENDER_OFFLOAD", "1", 1);
216 ::setenv("__GLX_VENDOR_LIBRARY_NAME", adapter.c_str(), 1);
217 }
218 }
219 #elif defined(GEO_OS_WINDOWS)
220 {
221 // NVidia/Optimus GPU selection under windows:
222 // GPU selection is enabled when this symbol is exported by
223 // the main program (or by a library *statically* linked to it).
224 // Then the NVidia GPU is selected if this value is non-zero when
225 // the OpenGL context is created.
226 std::string adapter = CmdLine::get_arg("gfx:adapter");
227 if(adapter != "default") {
228 HMODULE hModule = GetModuleHandle(nullptr);
229 DWORD* p_NvOptimusEnablement = reinterpret_cast<DWORD*>(
230 GetProcAddress(hModule, "NvOptimusEnablement")
231 );
232 if(p_NvOptimusEnablement == nullptr) {
233 Logger::warn("Application")
234 << "missing GEO_APPLICATION_GLOBALS declaration "
235 << "in applicatiion source"
236 << std::endl;
237 } else {
238 *p_NvOptimusEnablement = (adapter == "nvidia");
239 }
240 }
241 }
242 #endif
243 create_window();
244 main_loop();
245 }
246
247 void Application::stop() {
248 in_main_loop_ = false;
249 }
250
251 std::string Application::get_styles() {
252 return "Light;Dark;Polyscope";
253 }
254
255 void Application::set_style(const std::string& style_name) {
256 style_ = style_name;
257
258 if(style_name == "Light") {
259 ImGui::StyleColorsLight();
260 ImGuiStyle& style = ImGui::GetStyle();
261 style.WindowRounding = 10.0f;
262 style.FrameRounding = 5.0f;
263 style.GrabRounding = 10.0f;
264 style.WindowBorderSize = 1.5f;
265 style.FrameBorderSize = 1.0f;
266 style.PopupBorderSize = 1.0f;
267 ImVec4* colors = style.Colors;
268 colors[ImGuiCol_Text] = ImVec4(0.0f, 0.0f, 0.25f, 1.00f);
269 colors[ImGuiCol_TextDisabled] = ImVec4(0.25f, 0.25f, 0.75f, 1.00f);
270 colors[ImGuiCol_Separator] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f);
271 } else if(style_name == "Dark") {
272 ImGuiStyle& style = ImGui::GetStyle();
273 style.WindowRounding = 10.0f;
274 style.FrameRounding = 5.0f;
275 style.GrabRounding = 10.0f;
276 style.WindowBorderSize = 1.5f;
277 style.FrameBorderSize = 0.0f;
278 style.PopupBorderSize = 1.0f;
279 ImGui::StyleColorsDark();
280 // Tron vibes...
281 ImVec4* colors = style.Colors;
282 colors[ImGuiCol_Text] = ImVec4(0.50f, 1.00f, 1.00f, 1.00f);
283 colors[ImGuiCol_TextDisabled] = ImVec4(0.30f, 0.50f, 0.50f, 1.00f);
284 } else if(style_name == "Polyscope") {
285 ImGuiStyle& style = ImGui::GetStyle();
286
287 style.WindowRounding = 1;
288 style.FrameRounding = 1;
289 style.FramePadding.y = 4;
290 style.ScrollbarRounding = 1;
291 style.ScrollbarSize = 20;
292
293 style.FrameBorderSize = 0.0f;
294
295 // Colors
296 ImVec4* colors = style.Colors;
297 colors[ImGuiCol_Text] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
298 colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f);
299 colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.70f);
300 colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
301 colors[ImGuiCol_PopupBg] = ImVec4(0.11f, 0.11f, 0.14f, 0.92f);
302 colors[ImGuiCol_Border] = ImVec4(0.50f, 0.50f, 0.50f, 0.50f);
303 colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
304 colors[ImGuiCol_FrameBg] = ImVec4(0.63f, 0.63f, 0.63f, 0.39f);
305 colors[ImGuiCol_FrameBgHovered] = ImVec4(0.47f, 0.69f, 0.59f, 0.40f);
306 colors[ImGuiCol_FrameBgActive] = ImVec4(0.41f, 0.64f, 0.53f, 0.69f);
307 colors[ImGuiCol_TitleBg] = ImVec4(0.27f, 0.54f, 0.42f, 0.83f);
308 colors[ImGuiCol_TitleBgActive] = ImVec4(0.32f, 0.63f, 0.49f, 0.87f);
309 colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.27f, 0.54f, 0.42f, 0.83f);
310 colors[ImGuiCol_MenuBarBg] = ImVec4(0.40f, 0.55f, 0.48f, 0.80f);
311 colors[ImGuiCol_ScrollbarBg] = ImVec4(0.63f, 0.63f, 0.63f, 0.39f);
312 colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.00f, 0.00f, 0.00f, 0.30f);
313 colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.80f, 0.62f, 0.40f);
314 colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.39f, 0.80f, 0.61f, 0.60f);
315 colors[ImGuiCol_CheckMark] = ImVec4(0.90f, 0.90f, 0.90f, 0.50f);
316 colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f);
317 colors[ImGuiCol_SliderGrabActive] = ImVec4(0.39f, 0.80f, 0.61f, 0.60f);
318 colors[ImGuiCol_Button] = ImVec4(0.35f, 0.61f, 0.49f, 0.62f);
319 colors[ImGuiCol_ButtonHovered] = ImVec4(0.40f, 0.71f, 0.57f, 0.79f);
320 colors[ImGuiCol_ButtonActive] = ImVec4(0.46f, 0.80f, 0.64f, 1.00f);
321 colors[ImGuiCol_Header] = ImVec4(0.40f, 0.90f, 0.67f, 0.45f);
322 colors[ImGuiCol_HeaderHovered] = ImVec4(0.45f, 0.90f, 0.69f, 0.80f);
323 colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.87f, 0.71f, 0.80f);
324 colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
325 colors[ImGuiCol_SeparatorHovered] = ImVec4(0.60f, 0.70f, 0.66f, 1.00f);
326 colors[ImGuiCol_SeparatorActive] = ImVec4(0.70f, 0.90f, 0.81f, 1.00f);
327 colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.16f);
328 colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.78f, 1.00f, 0.90f, 0.60f);
329 colors[ImGuiCol_ResizeGripActive] = ImVec4(0.78f, 1.00f, 0.90f, 0.90f);
330 colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
331 colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
332 colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
333 colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
334 colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 0.00f, 1.00f, 0.35f);
335 colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
336 colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
337 colors[ImGuiCol_Tab] = ImVec4(0.27f, 0.54f, 0.42f, 0.83f);
338 colors[ImGuiCol_TabHovered] = ImVec4(0.34f, 0.68f, 0.53f, 0.83f);
339 colors[ImGuiCol_TabSelected] = ImVec4(0.38f, 0.76f, 0.58f, 0.83f);
340 } else {
341 set_style("Light");
342 Logger::err("Skin") << style_name << ": no such style"
343 << std::endl;
344 }
345
346 ImGuiStyle& style = ImGui::GetStyle();
347 if(CmdLine::get_arg_bool("gfx:transparent")) {
348 // Make ImGui windows opaque if background is
349 // transparent (else it becomes difficult to
350 // distinguish anything...)
351 style.Alpha = 1.0f;
352 } else {
353 style.Alpha = 0.90f;
354 }
355
356 if(phone_screen_) {
357 style.ScrollbarSize = 10.0f * float(scaling_);
358 style.GrabMinSize = 15.0f * float(scaling_);
359 }
360 }
361
362 void Application::set_font_size(index_t value) {
363 font_size_ = index_t(value);
364 scaling_ = double(font_size_)/16.0;
365 if(phone_screen_) {
366 scaling_ *= double(std::max(get_width(), get_height()))/600.0;
367 }
368 if(CmdLine::arg_is_declared("gui:font_size")) {
369 CmdLine::set_arg("gui:font_size", String::to_string(value));
370 }
371 if(ImGui_initialized_) {
372 ImGui_reload_font_ = true;
373 }
374 }
375
376 double Application::scaling() const {
377 return scaling_; // * hidpi_scaling_ / pixel_ratio_;
378 }
379
380 void Application::resize(
381 index_t w, index_t h, index_t fb_w, index_t fb_h
382 ) {
383 width_ = w;
384 height_ = h;
385 frame_buffer_width_ = fb_w;
386 frame_buffer_height_ = fb_h;
387 scaling_ = double(font_size_)/16.0;
388 if(phone_screen_) {
389 scaling_ *= double(std::max(get_width(), get_height()))/600.0;
390 }
391 update();
392 }
393
394 void Application::draw_gui() {
395 }
396
397 void Application::draw_graphics() {
398 }
399
400 void Application::mouse_button_callback(
401 int button, int action, int mods, int source
402 ) {
403 geo_argused(button);
404 geo_argused(action);
405 geo_argused(mods);
406 geo_argused(source);
407 }
408
409 void Application::scroll_callback(double xoffset, double yoffset) {
410 geo_argused(xoffset);
411 geo_argused(yoffset);
412 }
413
414 void Application::cursor_pos_callback(double x, double y, int source) {
415 geo_argused(x);
416 geo_argused(y);
417 geo_argused(source);
418 }
419
420 void Application::drop_callback(int nb, const char** f) {
421 geo_argused(nb);
422 geo_argused(f);
423 }
424
425 void Application::char_callback(unsigned int c) {
426 geo_argused(c);
427 }
428
429 void Application::key_callback(
430 int key, int scancode, int action, int mods
431 ) {
432 geo_argused(key);
433 geo_argused(scancode);
434 geo_argused(action);
435 geo_argused(mods);
436 }
437
438 void Application::draw_dock_space() {
439 ImGuiIO& io = ImGui::GetIO();
440 // Create window and dockspace for docking.
441 if((io.ConfigFlags & ImGuiConfigFlags_DockingEnable) != 0) {
442 ImGuiViewport* viewport = ImGui::GetMainViewport();
443 ImGui::SetNextWindowPos(viewport->Pos);
444 ImGui::SetNextWindowSize(viewport->Size);
445 ImGui::SetNextWindowViewport(viewport->ID);
446 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
447 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
448 ImGui::PushStyleVar(
449 ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)
450 );
451 static bool open = true;
452 ImGui::Begin(
453 "DockSpace", &open,
454 ImGuiWindowFlags_NoDocking |
455 ImGuiWindowFlags_NoTitleBar |
456 ImGuiWindowFlags_NoCollapse |
457 ImGuiWindowFlags_NoResize |
458 ImGuiWindowFlags_NoMove |
459 ImGuiWindowFlags_NoBringToFrontOnFocus |
460 ImGuiWindowFlags_NoNavFocus |
461 ImGuiWindowFlags_NoBackground |
462 (menubar_visible_ ? ImGuiWindowFlags_MenuBar : 0)
463 );
464 ImGui::PopStyleVar(3);
465 ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
466 ImGui::DockSpace(
467 dockspace_id, ImVec2(0.0f, 0.0f),
468 ImGuiDockNodeFlags_None |
469 ImGuiDockNodeFlags_PassthruCentralNode |
470 ImGuiDockNodeFlags_AutoHideTabBar
471 );
472 ImGui::End();
473 }
474 }
475
476 void Application::draw() {
477 if(!ImGui_initialized_) {
478 return;
479 }
480 update();
481 if(!updates_locked() && !Process::is_running_threads()) {
482 one_frame();
483 }
484 }
485
486 void Application::GL_initialize() {
487 GEO::Graphics::initialize();
488 geo_assert(glupCurrentContext() == nullptr);
489 glupMakeCurrent(glupCreateContext());
490 if(glupCurrentContext() == nullptr) {
491 Logger::err("Skin") << "Could not create GLUP context"
492 << std::endl;
493 exit(-1);
494 }
495 }
496
497 void Application::GL_terminate() {
498 glupDeleteContext(glupCurrentContext());
499 glupMakeCurrent(nullptr);
500 GEO::Graphics::terminate();
501 #ifdef GEO_OS_ANDROID
502 data_->GL_initialized = false;
503 #endif
504 }
505
506 void Application::ImGui_initialize() {
507 geo_assert(!ImGui_initialized_ );
508 ImGui::CreateContext();
509 {
510 std::string state = CmdLine::get_arg("gui:state");
511 for(size_t i=0; i<state.length(); ++i) {
512 if(state[i] == '\t') {
513 state[i] = '\n';
514 }
515 }
516 ImGui::LoadIniSettingsFromMemory(state.c_str());
517 }
518 ImGuiIO& io = ImGui::GetIO();
519 io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
520
521 // Viewports allow to drag ImGui windows outside the app's window,
522 // but it is still a bit unstable, so deactivated it for now.
523 if(
524 CmdLine::arg_is_declared("gui:viewports") &&
525 CmdLine::get_arg_bool("gui:viewports")
526 ) {
527 io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
528 }
529
530 if(
531 CmdLine::arg_is_declared("gui:keyboard_nav") &&
532 CmdLine::get_arg_bool("gui:keyboard_nav")
533 ) {
534 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
535 }
536
537 #if defined(GEO_GLFW)
538
539 // Second argument to true = install callbacks
540 // (note that this function can be called multiple times,
541 // e.g. when ImGui is restarted after re-loading a saved
542 // window state, this mechanism prevents the callbacks to
543 // be installed multiple times, which would be an error since
544 // they are chained).
545 ImGui_ImplGlfw_InitForOpenGL(
546 data_->window_, !data_->GLFW_callbacks_initialized_
547 );
548 #elif defined(GEO_OS_ANDROID)
549 ImGui_ImplAndroidExt_Init(data_->app);
550 #endif
551
552 #if defined(GEO_OS_APPLE)
553 ImGui_ImplOpenGL3_Init("#version 330");
554 #else
555 ImGui_ImplOpenGL3_Init("#version 100");
556 #endif
557 callbacks_initialize();
558
559 if(CmdLine::get_arg_bool("gui:phone_screen")) {
560 set_style("Dark");
561 } else if(style_ != "") {
562 set_style(style_);
563 } else if(Environment::instance()->has_value("gui:style")) {
564 std::string style = Environment::instance()->get_value("gui:style");
565 set_style(style);
566 } else {
567 set_style("Light");
568 }
569
570 ImGui_load_fonts();
571 ImGui_initialized_ = true;
572 ImGui_firsttime_init_ = true;
573 }
574
575 void Application::ImGui_load_fonts() {
576 font_sizes_.resize(0);
577
578 ImGuiIO& io = ImGui::GetIO();
579 io.IniFilename = nullptr;
580
581 float s = 1.0f;
582 if(phone_screen_) {
583 s = float(std::max(get_width(), get_height())) / 600.0f;
584 }
585
586 // Do not multiply by hidpiscaling_,seems that Dear ImGui does that
587 // on its own now.
588 float font_size = s * float(font_size_);
589
590 // Default font
591 io.FontDefault = io.Fonts->AddFontFromMemoryCompressedTTF(
592 roboto_medium_compressed_data,
593 roboto_medium_compressed_size, font_size
594 );
595 font_sizes_.push_back(font_size);
596
597 // Add icons to default font.
598 {
599 #define ICON_MIN_FA 0xf000
600 #define ICON_MAX_FA 0xf63c
601
602 ImFontConfig config;
603 config.MergeMode = true;
604
605 // Make the icon monospaced
606 config.GlyphMinAdvanceX = 1.5f*font_size;
607 config.GlyphOffset.y += 2.0f;
608
609 static const ImWchar icon_ranges[] = {
610 ICON_MIN_FA, ICON_MAX_FA, 0
611 };
612
613 io.Fonts->AddFontFromMemoryCompressedTTF(
614 fa_solid_compressed_data,
615 fa_solid_compressed_size, font_size,
616 &config, icon_ranges
617 );
618 font_sizes_.push_back(font_size);
619
620 init_icon_table();
621 }
622
623 // Fixed font for console and editor
624 io.Fonts->AddFontFromMemoryCompressedTTF(
625 cousine_regular_compressed_data,
626 cousine_regular_compressed_size, font_size
627 );
628 font_sizes_.push_back(font_size);
629
630 // Larger font
631 io.Fonts->AddFontFromMemoryCompressedTTF(
632 roboto_medium_compressed_data,
633 roboto_medium_compressed_size, font_size*1.5f
634 );
635 font_sizes_.push_back(font_size);
636
637 if(phone_screen_) {
638 // Smaller fixed font for console
639 io.Fonts->AddFontFromMemoryCompressedTTF(
640 cousine_regular_compressed_data,
641 cousine_regular_compressed_size, font_size*0.5f
642 );
643 font_sizes_.push_back(font_size);
644 }
645
646 font_global_scale_ = float(1.0 / pixel_ratio_);
647 }
648
649 void Application::ImGui_terminate() {
650 geo_assert(ImGui_initialized_);
651 ImGui_ImplOpenGL3_Shutdown();
652 #if defined(GEO_GLFW)
653 ImGui_ImplGlfw_Shutdown();
654 data_->GLFW_callbacks_initialized_ = false;
655 #elif defined(GEO_OS_ANDROID)
656 ImGui_ImplAndroidExt_Shutdown();
657 #endif
658 ImGui::DestroyContext();
659 ImGui_initialized_ = false;
660 }
661
662 void Application::ImGui_new_frame() {
663 ImGui_ImplOpenGL3_NewFrame();
664 #if defined(GEO_GLFW)
665 ImGui_ImplGlfw_NewFrame();
666 #elif defined(GEO_OS_ANDROID)
667 ImGui_ImplAndroidExt_NewFrame();
668 #endif
669 ImGui::NewFrame();
670 }
671
672 void Application::geogram_initialize(int argc, char** argv) {
673 GEO::initialize(GEO::GEOGRAM_INSTALL_ALL);
674 declare_args();
675 CmdLine::parse(argc, argv, filenames_);
676 phone_screen_ = CmdLine::get_arg_bool("gui:phone_screen");
677 #ifndef GEO_OS_ANDROID
678 if(phone_screen_ && CmdLine::get_arg("gfx:geometry") == "1024x1024") {
679 CmdLine::set_arg("gfx:geometry", "768x1024");
680 }
681 #endif
682 }
683
684 void Application::declare_args() {
685 CmdLine::import_arg_group("standard");
686 CmdLine::import_arg_group("algo");
687 CmdLine::import_arg_group("gfx");
688 CmdLine::import_arg_group("gui");
689 }
690
691 bool Application::needs_to_redraw() const {
692 return
693 animate_ ||
694 //ImGui::GetIO().WantCaptureMouse || // HERE: do not think
695 //ImGui::GetIO().WantCaptureKeyboard || // it is needed anymore !
696 (nb_frames_update_ > 0);
697 }
698
699 void Application::update() {
700 // We redraw several frames, in order to make
701 // sure all events are properly processed.
702 nb_frames_update_ = NB_FRAMES_UPDATE_INIT;
703 }
704
705 void Application::set_gui_state(std::string x) {
706 CmdLine::set_arg("gui:state", x);
707 if(!ImGui_initialized_) {
708 return;
709 }
710 ImGui_restart_ = true;
711 }
712
713 std::string Application::get_gui_state() const {
714 std::string state;
715 if(ImGui_initialized_) {
716 state = std::string(ImGui::SaveIniSettingsToMemory());
717 for(size_t i=0; i<state.length(); ++i) {
718 if(state[i] == '\n') {
719 state[i] = '\t';
720 }
721 }
722 }
723 return state;
724 }
725
726 /**************************** GLFW-specific code *********************/
727 #if defined(GEO_GLFW)
728
729
730 void Application::pre_draw() {
731 }
732
733 void Application::post_draw() {
734 }
735
736 void Application::create_window() {
737 if(!glfwInit()) {
738 Logger::err("Skin")
739 << "Could not initialize GLFW" << std::endl;
740 exit(-1);
741 }
742
743 if(CmdLine::get_arg("gfx:GL_profile") == "core") {
744 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
745 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
746 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
747 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
748 }
749
750 if(CmdLine::get_arg_bool("gfx:GL_debug")) {
751 glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
752 }
753
754 const char* title = name_.c_str();
755
756 {
757 std::string geometry = CmdLine::get_arg("gfx:geometry");
758 std::vector<std::string> words;
759 String::split_string(geometry, 'x', words);
760 if(words.size() != 2) {
761 Logger::err("Skin")
762 << "Invalid gfx:geometry:" << geometry << std::endl;
763 exit(-1);
764 }
765 if(
766 !String::string_to_unsigned_integer(words[0].c_str(), width_) ||
767 !String::string_to_unsigned_integer(words[1].c_str(), height_)
768 ) {
769 Logger::err("Skin")
770 << "Invalid gfx:geometry:" << geometry << std::endl;
771 exit(-1);
772 }
773 }
774
775 if(CmdLine::get_arg_bool("gfx:transparent")) {
776 #ifdef GLFW_TRANSPARENT_FRAMEBUFFER
777 glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
778 #else
779 Logger::warn("Skin")
780 << "Transparent not supported by this version of GLFW"
781 << std::endl;
782 #endif
783 }
784
785 bool full_screen = CmdLine::get_arg_bool("gfx:full_screen");
786 bool no_decoration = CmdLine::get_arg_bool("gfx:no_decoration");
787 bool hidden = CmdLine::get_arg_bool("gfx:hidden");
788 GLFWmonitor* monitor = glfwGetPrimaryMonitor();
789 {
790 int monitor_id = CmdLine::get_arg_int("gfx:monitor");
791 if(monitor_id >= 0) {
792 int nb_monitors;
793 GLFWmonitor** monitors = glfwGetMonitors(&nb_monitors);
794 if(monitor_id < nb_monitors) {
795 Logger::out("Skin") << "Using monitor " << monitor_id
796 << std::endl;
797 monitor = monitors[monitor_id];
798 } else {
799 Logger::warn("Skin") << "Invalid monitor " << monitor_id
800 << std::endl;
801 }
802 }
803 }
804
805 if(full_screen) {
806 const GLFWvidmode* vidmode = glfwGetVideoMode(monitor);
807 width_ = index_t(vidmode->width);
808 height_ = index_t(vidmode->height);
809 }
810
811 if(no_decoration && !hidden) {
812 glfwWindowHint(GLFW_FOCUSED,GL_TRUE);
813 glfwWindowHint(GLFW_DECORATED,GL_FALSE);
814 glfwWindowHint(GLFW_RESIZABLE,GL_FALSE);
815 glfwWindowHint(GLFW_AUTO_ICONIFY,GL_FALSE);
816 glfwWindowHint(GLFW_FLOATING,GL_FALSE);
817 glfwWindowHint(GLFW_MAXIMIZED,GL_TRUE);
818 } else {
819 glfwWindowHint(GLFW_VISIBLE,GL_FALSE);
820 }
821
822 data_->window_ = glfwCreateWindow(
823 int(width_), int(height_), title,
824 no_decoration ? monitor : nullptr,
825 nullptr
826 );
827
828 if(data_->window_ != nullptr) {
829 if(hidden) {
830 // seems that without the line below window cannot be made
831 // larger than default monitor (for offscreen windows)
832 glfwSetWindowSize(data_->window_, int(width_), int(height_));
833 } else {
834 // Move window to chosen monitor (this is done by
835 // querying monitor's offset in virtual desktop coordinate
836 // space).
837 int winposx = 0;
838 int winposy = 0;
839 glfwGetMonitorPos(monitor, &winposx, &winposy);
840 Logger::out("GLFW") << "Monitor "
841 << " pos="
842 << winposx << " " << winposy
843 << std::endl;
844 if(!full_screen) {
845 winposx += 100;
846 winposy += 100;
847 }
848 glfwSetWindowPos(data_->window_, winposx, winposy);
849 glfwShowWindow(data_->window_);
850 glfwSetWindowPos(data_->window_, winposx, winposy);
851 }
852 }
853
854 if(data_->window_ == nullptr) {
855 Logger::err("Skin")
856 << "Could not create GLFW window" << std::endl;
857 exit(-1);
858 }
859
860 glfwSetWindowUserPointer(data_->window_, this);
861
862 glfwMakeContextCurrent(data_->window_);
863 glfwSwapInterval(1);
864
865 hidpi_scaling_ = compute_hidpi_scaling();
866 pixel_ratio_ = compute_pixel_ratio();
867
868 Logger::out("Skin")
869 << "hidpi_scaling=" << hidpi_scaling_ << std::endl;
870 Logger::out("Skin")
871 << "pixel_ratio=" << pixel_ratio_ << std::endl;
872 }
873
874 void Application::delete_window() {
875 glfwDestroyWindow(data_->window_);
876 data_->window_ = nullptr;
877 glfwTerminate();
878 in_main_loop_ = false;
879 }
880
881
882 void Application::one_frame(bool draw_GUI) {
883 // Avoid nested ImGui calls
884 // (due to calling draw())
885 if(currently_drawing_gui_) {
886 return;
887 }
888
889 // Can happen when ImGui Graphite application
890 // triggers update too soon.
891 if(data_->window_ == nullptr) {
892 return;
893 }
894
895 if(glfwWindowShouldClose(data_->window_) || !in_main_loop_) {
896 return;
897 }
898
899
900 {
901 int cur_width, cur_height;
902 int cur_fb_width, cur_fb_height;
903
904 glfwGetWindowSize(data_->window_, &cur_width, &cur_height);
905 glfwGetFramebufferSize(
906 data_->window_, &cur_fb_width, &cur_fb_height
907 );
908
909 if(
910 int(width_) != cur_width ||
911 int(height_) != cur_height ||
912 int(frame_buffer_width_) != cur_fb_width ||
913 int(frame_buffer_height_) != cur_fb_height
914 ) {
915 resize(
916 index_t(cur_width), index_t(cur_height),
917 index_t(cur_fb_width), index_t(cur_fb_height)
918 );
919 }
920 }
921
922 {
923 // Detect if hidpi scaling changed. This can happen when
924 // dragging the window from the laptop screen to an external
925 // monitor.
926 if(
927 glfwGetCurrentContext() != nullptr &&
928 compute_hidpi_scaling() != hidpi_scaling_
929 ) {
930 hidpi_scaling_ = compute_hidpi_scaling();
931 pixel_ratio_ = compute_pixel_ratio();
932 set_font_size(font_size_); // This reloads the font.
933 }
934 }
935
936
937 glfwPollEvents();
938
939 if(needs_to_redraw()) {
940 pre_draw();
941
942 if(draw_GUI) {
943 currently_drawing_gui_ = true;
944 ImGui_new_frame();
945 }
946
947 draw_graphics();
948
949 if(draw_GUI) {
950 draw_gui();
951 ImGui::Render();
952 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
953 glUseProgram(0); // RenderDrawData() leaves a bound program
954 }
955
956 #ifndef GEO_OS_EMSCRIPTEN
957 // Update and Render additional Platform Windows
958 // (see ImGui demo app).
959 if(ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
960 GLFWwindow* backup_current_context = glfwGetCurrentContext();
961 ImGui::UpdatePlatformWindows();
962 ImGui::RenderPlatformWindowsDefault();
963 glfwMakeContextCurrent(backup_current_context);
964 }
965 #endif
966
967 if(draw_GUI) {
968 currently_drawing_gui_ = false;
969 }
970
971 #ifdef GEO_OS_EMSCRIPTEN
972 // Set alpha channel to 1 else the image is composited
973 // with the background
974 glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
975 glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
976 glClear(GL_COLOR_BUFFER_BIT);
977 glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
978 #endif
979
980 if(draw_GUI) {
981 glfwSwapBuffers(data_->window_);
982 }
983 post_draw();
984 if(
985 nb_frames_update_ > 0 && !animate_ &&
986 !(ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
987 ) {
988 --nb_frames_update_;
989 }
990 #ifndef GEO_OS_EMSCRIPTEN
991 // Be nice, sleep for 0.01 seconds, always
992 Process::sleep(10000);
993 #endif
994
995 } else {
996 // If there was no activity in the last few frames,
997 // sleep for 0.1 seconds, to let the processor cool-down
998 // instead of actively waiting (be a good citizen for the
999 // other processes.
1000 Process::sleep(100000);
1001 }
1002
1003 // ImGui needs to be restarted whenever docking state is reloaded.
1004 if(ImGui_restart_ || ImGui_reload_font_) {
1005 ImGui_restart_ = false;
1006 ImGui_terminate();
1007 if(CmdLine::arg_is_declared("gui:font_size")) {
1008 set_font_size(CmdLine::get_arg_uint("gui:font_size"));
1009 }
1010 ImGui_reload_font_ = false;
1011 ImGui_initialize();
1012 }
1013 }
1014
1015 #ifdef GEO_OS_EMSCRIPTEN
1016
1017 void emscripten_load_latest_file();
1018
1019 /**
1020 * \brief This function is called by the HTML shell each
1021 * time a file is loaded.
1022 */
1023 void emscripten_load_latest_file() {
1024 if(Application::instance() == nullptr) {
1025 return;
1026 }
1027 std::vector<std::string> all_files;
1028 GEO::FileSystem::get_directory_entries("/",all_files);
1029 std::string filename = "";
1030 Numeric::uint64 timestamp = 0;
1031 for(auto f: all_files) {
1032 if(GEO::FileSystem::is_file(f)) {
1033 Numeric::uint64 f_timestamp =
1034 GEO::FileSystem::get_time_stamp(f);
1035 if(f_timestamp > timestamp) {
1036 timestamp = f_timestamp;
1037 filename = f;
1038 }
1039 }
1040 }
1041 if(filename != "") {
1042 const char* filename_str = filename.c_str();
1043 Application::instance()->drop_callback(1, &filename_str);
1044 }
1045 }
1046
1047
1048 /**
1049 * \brief The job to be done for each frame when running
1050 * in Emscripten.
1051 * \details The browser keeps control of the main loop
1052 * in Emscripten. This function is a callback passed to
1053 * the Emscripten runtime.
1054 */
1055 void emscripten_one_frame() {
1056 if(Application::instance() == nullptr) {
1057 return;
1058 }
1059 Application::instance()->one_frame();
1060 }
1061 #endif
1062
1063 void Application::main_loop() {
1064 in_main_loop_ = true;
1065 #ifdef GEO_OS_EMSCRIPTEN
1066 FileSystem::set_file_system_changed_callback(
1067 emscripten_load_latest_file
1068 );
1069 GL_initialize();
1070 ImGui_initialize();
1071 emscripten_load_latest_file();
1072 emscripten_set_main_loop(emscripten_one_frame, 0, 1);
1073 #else
1074 bool initialized = false;
1075 while (!glfwWindowShouldClose(data_->window_) && in_main_loop_) {
1076 if(!initialized) {
1077 GL_initialize();
1078 ImGui_initialize();
1079 initialized = true;
1080 }
1081 one_frame();
1082 }
1083 if(initialized) {
1084 ImGui_terminate();
1085 GL_terminate();
1086 }
1087 #endif
1088 }
1089
1090 namespace {
1091 void GLFW_callback_mouse_button(
1092 GLFWwindow* w, int button, int action, int mods
1093 ) {
1094 Application* app = static_cast<Application*>(
1095 glfwGetWindowUserPointer(w)
1096 );
1097 app->lock_updates();
1098 app->update();
1099 ImGui_ImplGlfw_MouseButtonCallback(w, button, action, mods);
1100 if(!ImGui::GetIO().WantCaptureMouse) {
1101 app->mouse_button_callback(button,action,mods);
1102 }
1103 app->unlock_updates();
1104 }
1105
1106 void GLFW_callback_cursor_pos(
1107 GLFWwindow* w, double xf, double yf
1108 ) {
1109 Application* app = static_cast<Application*>(
1110 glfwGetWindowUserPointer(w)
1111 );
1112 app->lock_updates();
1113 app->update();
1114 ImGui_ImplGlfw_CursorPosCallback(w,xf,yf);
1115 if(!ImGui::GetIO().WantCaptureMouse) {
1116 app->cursor_pos_callback(xf, yf);
1117 }
1118 app->unlock_updates();
1119 }
1120
1121 void GLFW_callback_scroll(
1122 GLFWwindow* w, double xoffset, double yoffset
1123 ) {
1124 Application* app = static_cast<Application*>(
1125 glfwGetWindowUserPointer(w)
1126 );
1127 app->lock_updates();
1128 app->update();
1129 if(!ImGui::GetIO().WantCaptureMouse) {
1130 #ifdef GEO_OS_EMSCRIPTEN
1131 // Emscripten sometimes returns fantaisist
1132 // values for yoffset (100, -100).
1133 if(yoffset > 0) { yoffset = 1; }
1134 if(yoffset < 0) { yoffset = -1; }
1135 #endif
1136 #if defined(GEO_OS_EMSCRIPTEN) || defined(GEO_OS_APPLE)
1137 app->scroll_callback(xoffset,-yoffset);
1138 #else
1139 app->scroll_callback(xoffset, yoffset);
1140 #endif
1141 }
1142 ImGui_ImplGlfw_ScrollCallback(w,xoffset,yoffset);
1143 app->unlock_updates();
1144 }
1145
1146 void GLFW_callback_drop(
1147 GLFWwindow* w, int nb, const char** p
1148 ) {
1149 Application* app = static_cast<Application*>(
1150 glfwGetWindowUserPointer(w)
1151 );
1152 app->lock_updates();
1153 app->update();
1154 app->drop_callback(nb, p);
1155 app->unlock_updates();
1156 }
1157
1158 void GLFW_callback_char(GLFWwindow* w, unsigned int c) {
1159 Application* app = static_cast<Application*>(
1160 glfwGetWindowUserPointer(w)
1161 );
1162 app->lock_updates();
1163 app->update();
1164 ImGui_ImplGlfw_CharCallback(w, c);
1165 if(!ImGui::GetIO().WantCaptureKeyboard) {
1166 app->char_callback(c);
1167 }
1168 app->unlock_updates();
1169 }
1170
1171 void GLFW_callback_key(
1172 GLFWwindow* w, int key, int scancode, int action, int mods
1173 ) {
1174 Application* app = static_cast<Application*>(
1175 glfwGetWindowUserPointer(w)
1176 );
1177 app->lock_updates();
1178 app->update();
1179 ImGui_ImplGlfw_KeyCallback(w, key, scancode, action, mods);
1180 if(!ImGui::GetIO().WantCaptureKeyboard) {
1181 app->key_callback(key,scancode,action,mods);
1182 }
1183 app->unlock_updates();
1184 }
1185
1186 void GLFW_callback_refresh(GLFWwindow* w) {
1187 Application* app = static_cast<Application*>(
1188 glfwGetWindowUserPointer(w)
1189 );
1190 app->update();
1191 }
1192 }
1193
1194 void Application::callbacks_initialize() {
1195 if(!data_->GLFW_callbacks_initialized_) {
1196 GEO::Logger::out("ImGui") << "Viewer GUI init (GL3)"
1197 << std::endl;
1198 }
1199
1200 if (!data_->GLFW_callbacks_initialized_) {
1201 glfwSetMouseButtonCallback(
1202 data_->window_, GLFW_callback_mouse_button
1203 );
1204 glfwSetCursorPosCallback(
1205 data_->window_, GLFW_callback_cursor_pos
1206 );
1207 glfwSetScrollCallback(
1208 data_->window_, GLFW_callback_scroll
1209 );
1210 glfwSetCharCallback(
1211 data_->window_, GLFW_callback_char
1212 );
1213 glfwSetKeyCallback(
1214 data_->window_, GLFW_callback_key
1215 );
1216 glfwSetDropCallback(
1217 data_->window_, GLFW_callback_drop
1218 );
1219 glfwSetWindowRefreshCallback(
1220 data_->window_, GLFW_callback_refresh
1221 );
1222 data_->GLFW_callbacks_initialized_ = true;
1223 }
1224 }
1225
1226 void Application::set_window_icon(Image* icon_image) {
1227 GLFWimage glfw_image;
1228 glfw_image.width = int(icon_image->width());
1229 glfw_image.height = int(icon_image->height());
1230 glfw_image.pixels = icon_image->base_mem();
1231 glfwSetWindowIcon(data_->window_, 1, &glfw_image);
1232 }
1233
1234 void Application::set_full_screen_mode(
1235 index_t w, index_t h, index_t Hz, index_t monitor
1236 ) {
1237 if(data_->window_ == nullptr) {
1238 return;
1239 }
1240 int count;
1241 GLFWmonitor** monitors = glfwGetMonitors(&count);
1242 if(int(monitor) >= count) {
1243 Logger::err("Application") << monitor << ": no such monitor"
1244 << std::endl;
1245 }
1246 if((w == 0) || (h == 0) || (Hz == 0)) {
1247 Logger::out("Application")
1248 << "Using default video mode" << std::endl;
1249 const GLFWvidmode* mode = glfwGetVideoMode(monitors[monitor]);
1250 w = index_t(mode->width);
1251 h = index_t(mode->height);
1252 Hz = index_t(mode->refreshRate);
1253 }
1254 glfwSetWindowMonitor(
1255 data_->window_, monitors[monitor], 0, 0, int(w), int(h), int(Hz)
1256 );
1257 update();
1258 }
1259
1260 void Application::set_windowed_mode(index_t w, index_t h) {
1261 if(w != 0 && h != 0) {
1262 width_ = w;
1263 height_ = w;
1264 }
1265 // Seems that using 0,0,width,height as pos,dim does not escape from
1266 // fullscreen mode with new GLFw so I shrink 1 pixel.
1267 glfwSetWindowMonitor(
1268 data_->window_, nullptr, 1, 1, int(width_-1), int(height_-1),
1269 GLFW_DONT_CARE
1270 );
1271 update();
1272 }
1273
1274
1275 void Application::list_video_modes() {
1276 int nb_monitors = 0;
1277 GLFWmonitor** monitors = glfwGetMonitors(&nb_monitors);
1278 Logger::out("Application") << "Detected " << nb_monitors
1279 << " monitor(s)"
1280 << std::endl;
1281 for(int m=0; m<nb_monitors; ++m) {
1282 GLFWmonitor* monitor = monitors[m];
1283 Logger::out("Application") << "Monitor " << m << ":"
1284 << glfwGetMonitorName(monitor)
1285 << std::endl;
1286 int nb_modes = 0;
1287 const GLFWvidmode* modes = glfwGetVideoModes(monitor, &nb_modes);
1288 for(int mm=0; mm<nb_modes; ++mm) {
1289 const GLFWvidmode& mode = modes[mm];
1290 Logger::out("Application") << " mode " << mm << ":"
1291 << mode.width << "x" << mode.height
1292 << " " << mode.refreshRate << "Hz "
1293 << "R" << mode.redBits
1294 << "G" << mode.greenBits
1295 << "B" << mode.blueBits
1296 << std::endl;
1297 }
1298 }
1299 }
1300
1301
1302 void Application::iconify() {
1303 if(data_->window_ == nullptr ){
1304 return ;
1305 }
1306 glfwIconifyWindow(data_->window_);
1307 }
1308
1309 void Application::restore() {
1310 if(data_->window_ == nullptr ){
1311 return ;
1312 }
1313
1314 // In full screen mode, glfwRestoreWindow()
1315 // does not seem to work, so we switch to
1316 // windowed mode, deiconify, then switch back
1317 // to full screen mode.
1318 if(get_full_screen()) {
1319 set_full_screen(false);
1320 glfwRestoreWindow(data_->window_);
1321 set_full_screen(true);
1322 } else {
1323 glfwRestoreWindow(data_->window_);
1324 }
1325 }
1326
1327 bool Application::get_full_screen() const {
1328 return (data_->window_ != nullptr &&
1329 glfwGetWindowMonitor(data_->window_) != nullptr);
1330 }
1331
1332 void Application::set_full_screen(bool x) {
1333 if(x != get_full_screen()) {
1334 if(x) {
1335 set_full_screen_mode();
1336 } else {
1337 set_windowed_mode();
1338 }
1339 }
1340 }
1341
1342 void* Application::impl_window() {
1343 return data_->window_;
1344 }
1345
1346 /**************************** Android-specific code *********************/
1347 #elif defined(GEO_OS_ANDROID)
1348
1349 /**
1350 * \brief Redirects Geogram messages both to the console
1351 * and to Android log (adb logcat | grep GEOGRAM)
1352 */
1353 class AndroidLoggerClient : public LoggerClient {
1354 public:
1355 void div(const std::string& value) override {
1356 geo_argused(value);
1357 }
1358 void out(const std::string& value) override {
1359 __android_log_print(
1360 ANDROID_LOG_VERBOSE, "GEOGRAM", "%s", value.c_str()
1361 );
1362 }
1363 void warn(const std::string& value) override {
1364 __android_log_print(
1365 ANDROID_LOG_WARN, "GEOGRAM", "%s", value.c_str()
1366 );
1367 }
1368 void err(const std::string& value) override {
1369 __android_log_print(
1370 ANDROID_LOG_ERROR, "GEOGRAM", "%s", value.c_str()
1371 );
1372 }
1373 void status(const std::string& value) override {
1374 // Do not display error messages twice.
1375 if(GEO::String::string_starts_with(value, "Error:")) {
1376 return;
1377 }
1378 }
1379 };
1380
1381 void Application::pre_draw() {
1382 // We initialize graphic resources in pre_draw() rather
1383 // than create_window() because Android apps can be restarted
1384 // or loose graphic resources. This function, called at each frame,
1385 // re-creates everything that needs to be created.
1386
1387 // Get display and initialize GLES
1388 if(data_->display == EGL_NO_DISPLAY) {
1389 data_->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
1390 eglInitialize(data_->display, 0, 0);
1391 }
1392
1393 // Choose best matching config and create surface
1394 if(data_->surface == EGL_NO_SURFACE) {
1395 const EGLint attribs[] = {
1396 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR, // OpenGL ES 3.0
1397 EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
1398 EGL_BLUE_SIZE, 8,
1399 EGL_GREEN_SIZE, 8,
1400 EGL_RED_SIZE, 8,
1401 EGL_DEPTH_SIZE, 16,
1402 EGL_NONE
1403 };
1404
1405 EGLint numConfigs;
1406
1407 // Get the number of matching configs.
1408 eglChooseConfig(data_->display, attribs, nullptr, 0, &numConfigs);
1409 EGLConfig* supportedConfigs = new EGLConfig[numConfigs];
1410 assert(supportedConfigs != nullptr);
1411 // Do that again now that we know the number of configs.
1412 eglChooseConfig(
1413 data_->display, attribs, supportedConfigs,
1414 numConfigs, &numConfigs
1415 );
1416 assert(numConfigs != 0);
1417
1418 // Find the best matching config. among them.
1419 int i = 0;
1420 for (; i < numConfigs; i++) {
1421 auto& cfg = supportedConfigs[i];
1422 EGLint r, g, b, d;
1423 if (
1424 eglGetConfigAttrib(data_->display,cfg,EGL_RED_SIZE ,&r) &&
1425 eglGetConfigAttrib(data_->display,cfg,EGL_GREEN_SIZE,&g) &&
1426 eglGetConfigAttrib(data_->display,cfg,EGL_BLUE_SIZE, &b) &&
1427 eglGetConfigAttrib(data_->display,cfg,EGL_DEPTH_SIZE,&d) &&
1428 r == 8 && g == 8 && b == 8 && d == 16
1429 ) {
1430 data_->config = supportedConfigs[i];
1431 break;
1432 }
1433 }
1434 // In the worst case, use the first one.
1435 if (i == numConfigs) {
1436 data_->config = supportedConfigs[0];
1437 }
1438 delete[] supportedConfigs;
1439 data_->surface = eglCreateWindowSurface(
1440 data_->display, data_->config, data_->app->window, nullptr
1441 );
1442 }
1443
1444 // Create context. Try first OpenGL ES 3.0 then 2.0.
1445 if(data_->context == EGL_NO_CONTEXT) {
1446
1447 // Important: create an OpenGL es 3.0 context. Without this,
1448 // it defaults to an OpenGL es 1.0 context,
1449 // and glCreateProgram() / glCreateBuffer() silently
1450 // fail and return 0 (banged my head to the wall before
1451 // figuring out !!)
1452 data_->GLES_version = 3;
1453 const EGLint context_attribs[] = {
1454 EGL_CONTEXT_CLIENT_VERSION, 3,
1455 EGL_NONE
1456 };
1457
1458 data_->context = eglCreateContext(
1459 data_->display, data_->config, nullptr, context_attribs
1460 );
1461
1462 // If we do not succeed, we create an OpenGL es 2.0 context.
1463 if(data_->context == EGL_NO_CONTEXT) {
1464 const EGLint context_attribs_2[] = {
1465 EGL_CONTEXT_CLIENT_VERSION, 2,
1466 EGL_NONE
1467 };
1468 data_->GLES_version = 2;
1469 data_->context = eglCreateContext(
1470 data_->display, data_->config, nullptr, context_attribs_2
1471 );
1472 }
1473 }
1474
1475 eglMakeCurrent(
1476 data_->display, data_->surface, data_->surface, data_->context
1477 );
1478
1479 if(!data_->GL_initialized) {
1480 GL_initialize();
1481 data_->GL_initialized = true;
1482 }
1483 }
1484
1485 void Application::post_draw() {
1486 }
1487
1488 void Application::create_window() {
1489 data_->app = CmdLine::get_android_app();
1490 data_->app->userData = this;
1491
1492 // Create a logger client for debugging.
1493 // (use adb logcat | grep GEOGRAM to see the messages)
1494 data_->logger_client = new AndroidLoggerClient();
1495 GEO::Logger::instance()->register_client(data_->logger_client);
1496 // Note: the display/context/surface are created as needed in pre_draw()
1497 }
1498
1499 void Application::delete_window() {
1500 if (data_->display != EGL_NO_DISPLAY) {
1501 eglMakeCurrent(
1502 data_->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT
1503 );
1504 if (data_->context != EGL_NO_CONTEXT) {
1505 eglDestroyContext(data_->display, data_->context);
1506 }
1507 if (data_->surface != EGL_NO_SURFACE) {
1508 eglDestroySurface(data_->display, data_->surface);
1509 }
1510 eglTerminate(data_->display);
1511 }
1512 data_->display = EGL_NO_DISPLAY;
1513 data_->context = EGL_NO_CONTEXT;
1514 data_->surface = EGL_NO_SURFACE;
1515 }
1516
1517 void Application::one_frame() {
1518 // Avoid nested ImGui calls
1519 // (due to calling draw())
1520 if(currently_drawing_gui_) {
1521 return;
1522 }
1523 pre_draw();
1524 if(data_->display == EGL_NO_DISPLAY) {
1525 return;
1526 }
1527
1528 {
1529 EGLint new_width;
1530 EGLint new_height;
1531 eglQuerySurface(
1532 data_->display, data_->surface, EGL_WIDTH, &new_width
1533 );
1534 eglQuerySurface(
1535 data_->display, data_->surface, EGL_HEIGHT, &new_height
1536 );
1537 if(index_t(new_width) != width_ || index_t(new_height) != height_) {
1538 resize(
1539 index_t(new_width), index_t(new_height),
1540 index_t(new_width), index_t(new_height)
1541 );
1542 }
1543 }
1544
1545 // Initialize ImGui if not already initialized.
1546 if(ImGui::GetCurrentContext() == nullptr) {
1547 ImGui_initialize();
1548 }
1549
1550 if(needs_to_redraw()) {
1551 currently_drawing_gui_ = true;
1552 ImGui_new_frame();
1553 draw_graphics();
1554 draw_gui();
1555 ImGui::Render();
1556 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
1557 glUseProgram(0); // RenderDrawData() leaves a bound program
1558 currently_drawing_gui_ = false;
1559 eglSwapBuffers(data_->display, data_->surface);
1560 post_draw();
1561 currently_drawing_gui_ = false;
1562 if(nb_frames_update_ > 0 && !animate_) {
1563 --nb_frames_update_;
1564 }
1565 } else {
1566 // Sleep for 0.2 seconds, to let the processor cool-down
1567 // instead of actively waiting (be a good citizen for the
1568 // other processes.
1569 Process::sleep(20000);
1570 }
1571
1572 // ImGui needs to be restarted whenever docking state is reloaded.
1573 if(ImGui_restart_) {
1574 ImGui_restart_ = false;
1575 ImGui_terminate();
1576 if(CmdLine::arg_is_declared("gui:font_size")) {
1577 set_font_size(CmdLine::get_arg_uint("gui:font_size"));
1578 }
1579 ImGui_initialize();
1580 } else if(ImGui_reload_font_) {
1581 ImGuiIO& io = ImGui::GetIO();
1582 io.Fonts->Clear();
1583 ImGui_load_fonts();
1584 ImGui_ImplOpenGL3_DestroyDeviceObjects();
1585 ImGui_reload_font_ = false;
1586 }
1587 }
1588
1589 void Application::main_loop() {
1590 callbacks_initialize();
1591 in_main_loop_ = true;
1592 while(in_main_loop_) {
1593 int ident;
1594 int events;
1595 android_poll_source* source;
1596 while (
1597 (ident = ALooper_pollAll(
1598 // 0 = non-blocking, -1 = blocking
1599 data_->should_draw() ? 0 : -1,
1600 nullptr,
1601 &events,
1602 (void**)&source)
1603 ) >= 0
1604 ) {
1605 // process event
1606 if (source != nullptr) {
1607 source->process(data_->app, source);
1608 }
1609
1610 // Check if we are exiting.
1611 if (data_->app->destroyRequested != 0) {
1612 ::GEO::AndroidUtils::debug_log(
1613 "Destroy requested, freeing resources"
1614 );
1615 ImGui_terminate();
1616 GL_terminate();
1617 return;
1618 }
1619 }
1620
1621 if(data_->should_draw()) {
1622 one_frame();
1623 }
1624 }
1625 ::GEO::AndroidUtils::debug_log("End of main loop");
1626 ::GEO::AndroidUtils::debug_log("calling std::terminate()");
1627 // Not very clean, but for now I do not know another
1628 // solution to exit an Android app.
1629 std::terminate();
1630 }
1631
1632 namespace {
1633
1634 int32_t android_input_event_handler(
1635 struct android_app* app, AInputEvent* event
1636 ) {
1637 int32_t result = ImGui_ImplAndroidExt_HandleInputEvent(event);
1638 Application* geoapp = static_cast<Application*>(
1639 CmdLine::get_android_app()->userData
1640 );
1641 ImGui_ImplAndroidExt_HandleEventUserCallback(app, event);
1642 geoapp->update();
1643 return result;
1644 }
1645
1646 void android_command_handler(struct android_app* app, int32_t cmd) {
1647 Application* app_impl =
1648 static_cast<GEO::Application*>(app->userData);
1649 ApplicationData* data = app_impl->impl_data();
1650 app_impl->update();
1651
1652 switch (cmd) {
1653 case APP_CMD_INIT_WINDOW:
1654 ::GEO::AndroidUtils::debug_log("command: APP_CMD_INIT_WINDOW");
1655 data->has_window = (app->window != nullptr);
1656 break;
1657
1658 case APP_CMD_TERM_WINDOW:
1659 // This event may be triggered when the app is re-started.
1660 // Like in the endless-tunnel demo of the NDK,
1661 // we just destroy the surface. If the application is
1662 // restarted, the surface will be re-created by pre_draw()
1663 // that is called at the beginning of one_frame().
1664 // Note: AndroidManifest.xml needs to have:
1665 // android:configChanges=
1666 // "orientation|screenSize|keyboardHidden|keyboard|navigation"
1667 // else this event will be triggered when a physical
1668 // keyboard is connected/disconnected while the app is
1669 // running. Note "navigation"
1670 ::GEO::AndroidUtils::debug_log("command: APP_CMD_TERM_WINDOW");
1671 app_impl->ImGui_terminate();
1672 app_impl->GL_terminate();
1673 app_impl->delete_window();
1674 break;
1675
1676 case APP_CMD_GAINED_FOCUS:
1677 ::GEO::AndroidUtils::debug_log("command: APP_CMD_GAINED_FOCUS");
1678 data->has_focus = true;
1679 break;
1680
1681 case APP_CMD_LOST_FOCUS:
1682 ::GEO::AndroidUtils::debug_log("command: APP_CMD_LOST_FOCUS");
1683 data->has_focus = false;
1684 break;
1685
1686 case APP_CMD_START:
1687 ::GEO::AndroidUtils::debug_log("command: APP_CMD_START");
1688 data->is_visible = true;
1689 break;
1690
1691 case APP_CMD_STOP:
1692 ::GEO::AndroidUtils::debug_log("command: APP_CMD_STOP");
1693 data->is_visible = false;
1694 break;
1695
1696 case APP_CMD_SAVE_STATE:
1697 ::GEO::AndroidUtils::debug_log("command: APP_CMD_SAVE_STATE");
1698 break;
1699
1700 case APP_CMD_PAUSE:
1701 ::GEO::AndroidUtils::debug_log("command: APP_CMD_PAUSE");
1702 break;
1703
1704 case APP_CMD_RESUME:
1705 ::GEO::AndroidUtils::debug_log("command: APP_CMD_RESUME");
1706 break;
1707
1708 case APP_CMD_WINDOW_RESIZED:
1709 ::GEO::AndroidUtils::debug_log("command: APP_CMD_WINDOW_RESIZED");
1710 break;
1711
1712 case APP_CMD_CONFIG_CHANGED:
1713 ::GEO::AndroidUtils::debug_log("command: APP_CMD_CONFIG_CHANGED");
1714 break;
1715
1716 case APP_CMD_LOW_MEMORY:
1717 ::GEO::AndroidUtils::debug_log("command: APP_CMD_LOW_MEMORY");
1718 break;
1719 }
1720 }
1721
1722 /*
1723 * \brief The callback to handle Android mouse events in the rendering
1724 * area
1725 * \details See ImGui_ImplAndroidExt_SetMouseUserCallback()
1726 * \param[in] x , y window coordinates of the event
1727 * \param[in] button the button
1728 * \param[in] action the action (one of
1729 * EVENT_ACTION_UP, EVENT_ACTION_DOWN, EVENT_ACTION_DRAG)
1730 * \param[in] source the event source (one of EVENT_SOURCE_MOUSE,
1731 * EVENT_SOURCE_FINGER, EVENT_SOURCE_STYLUS)
1732 */
1733 void android_mouse_callback(
1734 float x, float y, int button, int action, int source
1735 ) {
1736 Application* app = static_cast<Application*>(
1737 CmdLine::get_android_app()->userData
1738 );
1739
1740 static const char* action_names[] = {
1741 "up", "down", "drag", "unknown"
1742 };
1743
1744 static const char* source_names[] = {
1745 "keyboard", "mouse", "finger", "stylus", "unknown"
1746 };
1747
1748 ::GEO::AndroidUtils::debug_log(
1749 std::string("mouse CB ") +
1750 " (" + String::to_string(x) + "," + String::to_string(y) + ")" +
1751 " btn=" + String::to_string(button) +
1752 " action=" + action_names[std::min(action,3)] +
1753 " source=" + source_names[std::min(source,4)]
1754 );
1755
1756 // For touch devices, hovering does not generate
1757 // events, and we need to update ImGui flags that
1758 // indicate whether we are hovering ImGui or another
1759 // zone of the window.
1760 if(
1761 action == EVENT_ACTION_DOWN &&
1762 (source == EVENT_SOURCE_FINGER || source == EVENT_SOURCE_MOUSE)
1763 ) {
1764 ImGui::GetIO().MousePos = ImVec2(x,y);
1765 ImGui::UpdateHoveredWindowAndCaptureFlags();
1766 }
1767
1768 if(action != EVENT_ACTION_UNKNOWN) {
1769 if(!ImGui::GetIO().WantCaptureMouse) {
1770 if(action != EVENT_ACTION_UP) {
1771 app->cursor_pos_callback(double(x), double(y), source);
1772 }
1773 app->mouse_button_callback(button, action, 0, source);
1774 }
1775 }
1776 app->update();
1777 }
1778
1779 }
1780
1781 void Application::callbacks_initialize() {
1782 data_->app->onAppCmd = android_command_handler;
1783 data_->app->onInputEvent = android_input_event_handler;
1784 ImGui_ImplAndroidExt_SetMouseUserCallback(android_mouse_callback);
1785 }
1786
1787 void Application::set_window_icon(Image* icon_image) {
1788 geo_argused(icon_image);
1789 }
1790
1791 void Application::set_full_screen_mode(
1792 index_t w, index_t h, index_t Hz, index_t monitor
1793 ) {
1794 geo_argused(w);
1795 geo_argused(h);
1796 geo_argused(Hz);
1797 geo_argused(monitor);
1798 }
1799
1800 void Application::set_windowed_mode(index_t w, index_t h) {
1801 geo_argused(w);
1802 geo_argused(h);
1803 }
1804
1805 void Application::list_video_modes() {
1806 }
1807
1808 void Application::iconify() {
1809 }
1810
1811 void Application::restore() {
1812 }
1813
1814 bool Application::get_full_screen() const {
1815 return true;
1816 }
1817
1818 void Application::set_full_screen(bool x) {
1819 geo_argused(x);
1820 }
1821
1822 void* Application::impl_window() {
1823 return nullptr;
1824 }
1825 #else
1826 # error "No windowing system"
1827 #endif
1828
1829
1830 #ifdef GEO_GLFW
1831 const char* Application::key_to_string(int key) {
1832 if(key == GLFW_KEY_LEFT) {
1833 return "left";
1834 }
1835 if(key == GLFW_KEY_RIGHT) {
1836 return "right";
1837 }
1838 if(key == GLFW_KEY_UP) {
1839 return "up";
1840 }
1841 if(key == GLFW_KEY_DOWN) {
1842 return "down";
1843 }
1844 if(key == GLFW_KEY_PAGE_UP) {
1845 return "page_up";
1846 }
1847 if(key == GLFW_KEY_PAGE_DOWN) {
1848 return "page_down";
1849 }
1850 if(key == GLFW_KEY_HOME) {
1851 return "home";
1852 }
1853 if(key == GLFW_KEY_END) {
1854 return "end";
1855 }
1856 if(key == GLFW_KEY_F1) {
1857 return "F1";
1858 }
1859 if(key == GLFW_KEY_F2) {
1860 return "F2";
1861 }
1862 if(key == GLFW_KEY_F3) {
1863 return "F3";
1864 }
1865 if(key == GLFW_KEY_F4) {
1866 return "F4";
1867 }
1868 if(key == GLFW_KEY_F5) {
1869 return "F5";
1870 }
1871 if(key == GLFW_KEY_F6) {
1872 return "F6";
1873 }
1874 if(key == GLFW_KEY_F7) {
1875 return "F7";
1876 }
1877 if(key == GLFW_KEY_F8) {
1878 return "F8";
1879 }
1880 if(key == GLFW_KEY_F9) {
1881 return "F9";
1882 }
1883 if(key == GLFW_KEY_F10) {
1884 return "F10";
1885 }
1886 if(key == GLFW_KEY_F11) {
1887 return "F11";
1888 }
1889 if(key == GLFW_KEY_F12) {
1890 return "F12";
1891 }
1892 if(key == GLFW_KEY_LEFT_CONTROL) {
1893 return "left_control";
1894 }
1895 if(key == GLFW_KEY_RIGHT_CONTROL) {
1896 return "right_control";
1897 }
1898 if(key == GLFW_KEY_LEFT_ALT) {
1899 return "left_alt";
1900 }
1901 if(key == GLFW_KEY_RIGHT_ALT) {
1902 return "right_alt";
1903 }
1904 if(key == GLFW_KEY_LEFT_SHIFT) {
1905 return "left_shift";
1906 }
1907 if(key == GLFW_KEY_RIGHT_SHIFT) {
1908 return "right_shift";
1909 }
1910 if(key == GLFW_KEY_ESCAPE) {
1911 return "escape";
1912 }
1913 if(key == GLFW_KEY_TAB) {
1914 return "tab";
1915 }
1916 if(key == GLFW_KEY_BACKSPACE) {
1917 return "backspace";
1918 }
1919 return "";
1920 }
1921 #else
1922 const char* Application::key_to_string(int key) {
1923 return "";
1924 }
1925 #endif
1926
1927 }
1928
1929 /************************ Utilities *************************************/
1930
1931 namespace GEO {
1932
1933 #if defined(GEO_GLFW) && !defined(GEO_OS_EMSCRIPTEN)
1934
1935 double compute_pixel_ratio() {
1936 int buf_size[2];
1937 int win_size[2];
1938 GLFWwindow* window = glfwGetCurrentContext();
1939 glfwGetFramebufferSize(window, &buf_size[0], &buf_size[1]);
1940 glfwGetWindowSize(window, &win_size[0], &win_size[1]);
1941 // The window may be iconified.
1942 if(win_size[0] == 0) {
1943 return 1.0;
1944 }
1945 return double(buf_size[0]) / double(win_size[0]);
1946 }
1947
1948 double compute_hidpi_scaling() {
1949 float xscale, yscale;
1950 GLFWwindow* window = glfwGetCurrentContext();
1951 glfwGetWindowContentScale(window, &xscale, &yscale);
1952 return 0.5 * double(xscale + yscale);
1953 }
1954
1955 #else
1956
1957 double compute_pixel_ratio() {
1958 return 1.0;
1959 }
1960
1961 double compute_hidpi_scaling() {
1962 return 1.0;
1963 }
1964
1965 #endif
1966
1967 }
1968