1 /*
2  *  Copyright (c) 2012-2016, Bruno Levy All rights reserved.
3  *
4  *  Redistribution and use in source and binary forms, with or without
5  *  modification, are permitted provided that the following conditions are met:
6  *
7  *  * Redistributions of source code must retain the above copyright notice,
8  *  this list of conditions and the following disclaimer.
9  *  * Redistributions in binary form must reproduce the above copyright notice,
10  *  this list of conditions and the following disclaimer in the documentation
11  *  and/or other materials provided with the distribution.
12  *  * Neither the name of the ALICE Project-Team nor the names of its
13  *  contributors may be used to endorse or promote products derived from this
14  *  software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  *
28  *  If you modify this software, you should include a notice giving the
29  *  name of the person performing the modification, the date of modification,
30  *  and the reason for such modification.
31  *
32  *  Contact: Bruno Levy
33  *
34  *     Bruno.Levy@inria.fr
35  *     http://www.loria.fr/~levy
36  *
37  *     ALICE Project
38  *     LORIA, INRIA Lorraine,
39  *     Campus Scientifique, BP 239
40  *     54506 VANDOEUVRE LES NANCY CEDEX
41  *     FRANCE
42  *
43  */
44 
45 #include <geogram_gfx/gui/application.h>
46 
47 #include <geogram_gfx/third_party/ImGui/imgui.h>
48 
49 #include <geogram_gfx/third_party/ImGui/imgui_impl_opengl3.h>
50 #include <geogram_gfx/ImGui_ext/icon_font.h>
51 
52 #include <geogram_gfx/lua/lua_glup.h>
53 #include <geogram_gfx/lua/lua_imgui.h>
54 
55 #include <geogram_gfx/third_party/imgui_fonts/cousine_regular.h>
56 #include <geogram_gfx/third_party/imgui_fonts/roboto_medium.h>
57 #include <geogram_gfx/third_party/imgui_fonts/fa_solid.h>
58 
59 #include <geogram/image/image.h>
60 #include <geogram/basic/command_line.h>
61 #include <geogram/basic/command_line_args.h>
62 #include <geogram/basic/logger.h>
63 #include <geogram/basic/file_system.h>
64 
65 
66 #if defined(GEO_GLFW)
67 #  include <geogram_gfx/third_party/ImGui/imgui_impl_glfw.h>
68 // Too many documentation warnings in glfw
69 // (glfw uses tags that clang does not understand).
70 #  ifdef __clang__
71 #    pragma GCC diagnostic ignored "-Wdocumentation"
72 #  endif
73 
74 #  if defined(GEO_USE_SYSTEM_GLFW3) || defined(GEO_OS_EMSCRIPTEN)
75 #    include <GLFW/glfw3.h>
76 #  else
77 #    include <third_party/glfw/include/GLFW/glfw3.h>
78 #  endif
79 
80 #ifdef GEO_OS_EMSCRIPTEN
81 #  include <emscripten.h>
82 #endif
83 
84 
85 #endif
86 
87 
88 namespace GEO {
89 
90     void StyleColorsCorporateGrey(bool threeD);
91     /**
92      * \brief Computes the pixel ratio for hidpi devices.
93      * \details Uses the current GLFW window.
94      */
95     double compute_pixel_ratio();
96 
97     /**
98      * \brief Computes the scaling factor for hidpi devices.
99      * \details Uses the current GLFW window.
100      */
101     double compute_hidpi_scaling();
102 
103 
104     /**
105      * \brief If nothing happens during 100 frames, then
106      * we (micro)-sleep instead of redrawing the
107      * window.
108      */
109     const index_t NB_FRAMES_UPDATE_INIT = 100;
110 
111 #if defined(GEO_GLFW)
112     class ApplicationData {
113     public:
ApplicationData()114 	ApplicationData() {
115 	    window_ = nullptr;
116 	    GLFW_callbacks_initialized_ = false;
117 	    ImGui_callback_mouse_button = nullptr;
118 	    ImGui_callback_cursor_pos = nullptr;
119 	    ImGui_callback_scroll = nullptr;
120 	    ImGui_callback_key = nullptr;
121 	    ImGui_callback_char = nullptr;
122 	    ImGui_callback_drop = nullptr;
123 	    ImGui_callback_refresh = nullptr;
124 	}
125 	GLFWwindow* window_;
126 	bool GLFW_callbacks_initialized_;
127 	GLFWmousebuttonfun ImGui_callback_mouse_button;
128 	GLFWcursorposfun ImGui_callback_cursor_pos;
129 	GLFWscrollfun ImGui_callback_scroll;
130 	GLFWkeyfun ImGui_callback_key;
131 	GLFWcharfun ImGui_callback_char;
132 	GLFWdropfun ImGui_callback_drop;
133 	GLFWwindowrefreshfun ImGui_callback_refresh;
134     };
135 
136 #else
137 #  error "No windowing system"
138 #endif
139 
140 }
141 
142 namespace GEO {
143 
144     Application* Application::instance_ = nullptr;
145 
Application(const std::string & name)146     Application::Application(const std::string& name) {
147 	geo_assert(instance_ == nullptr);
148 	GEO::initialize();
149 	instance_ = this;
150 	name_ = name;
151 	data_ = new ApplicationData();
152 	ImGui_restart_ = false;
153 	ImGui_reload_font_ = false;
154 	ImGui_initialized_ = false;
155 	ImGui_firsttime_init_ = false;
156 	width_ = 800;
157 	height_ = 800;
158 	frame_buffer_width_ = 800;
159 	frame_buffer_height_ = 800;
160 	in_main_loop_ = false;
161 	accept_drops_ = true;
162 	scaling_ = 1.0;
163 	font_size_ = 18;
164 	nb_update_locks_ = 0;
165 	nb_frames_update_ = NB_FRAMES_UPDATE_INIT;
166 	hidpi_scaling_ = 1.0;
167 	pixel_ratio_ = 1.0;
168 	currently_drawing_gui_ = false;
169 	animate_ = false;
170 	menubar_visible_ = true;
171 	phone_screen_ = false;
172 	soft_keyboard_visible_ = false;
173     }
174 
~Application()175     Application::~Application() {
176 	delete_window();
177 	geo_assert(instance_ == this);
178 	delete data_;
179 	data_ = nullptr;
180 	instance_ = nullptr;
181     }
182 
start(int argc,char ** argv)183     void Application::start(int argc, char** argv) {
184 	if(argc != 0 && argv != nullptr) {
185 	    geogram_initialize(argc, argv);
186 	}
187 	if(CmdLine::arg_is_declared("gui:font_size")) {
188 	    set_font_size(CmdLine::get_arg_uint("gui:font_size"));
189 	}
190 	create_window();
191 	main_loop();
192     }
193 
stop()194     void Application::stop() {
195 	in_main_loop_ = false;
196     }
197 
get_styles()198     std::string Application::get_styles() {
199 #ifdef GEO_OS_ANDROID
200 	return "Light;Dark";
201 #else
202 	return "Light;Dark;CorporateGrey";
203 #endif
204     }
205 
set_style(const std::string & style_name)206     void Application::set_style(const std::string& style_name) {
207 	style_ = style_name;
208 
209 	if(style_name == "Light") {
210 	    ImGui::StyleColorsLight();
211 	    ImGuiStyle& style = ImGui::GetStyle();
212 	    style.WindowRounding = 10.0f;
213 	    style.FrameRounding = 5.0f;
214 	    style.GrabRounding = 10.0f;
215 	    style.WindowBorderSize = 1.5f;
216 	    style.FrameBorderSize = 1.0f;
217 	    style.PopupBorderSize = 1.0f;
218 	    ImVec4* colors = style.Colors;
219 	    colors[ImGuiCol_Text]         = ImVec4(0.0f,  0.0f,  0.25f, 1.00f);
220 	    colors[ImGuiCol_TextDisabled] = ImVec4(0.25f, 0.25f, 0.75f, 1.00f);
221 	    colors[ImGuiCol_Separator]    = ImVec4(0.75f, 0.75f, 0.75f, 1.00f);
222 	} else if(style_name == "Dark") {
223 	    ImGuiStyle& style = ImGui::GetStyle();
224 	    style.WindowRounding = 10.0f;
225 	    style.FrameRounding = 5.0f;
226 	    style.GrabRounding = 10.0f;
227 	    style.WindowBorderSize = 1.5f;
228 	    style.FrameBorderSize = 0.0f;
229 	    style.PopupBorderSize = 1.0f;
230 	    ImGui::StyleColorsDark();
231 	} else if(style_name == "CorporateGrey") {
232 	    StyleColorsCorporateGrey(true);
233 	} else {
234 	    set_style("Light");
235 	    Logger::err("Skin") << style_name << ": no such style"
236 				<< std::endl;
237 	}
238 
239 	ImGuiStyle& style = ImGui::GetStyle();
240 	if(CmdLine::get_arg_bool("gfx:transparent")) {
241 	    // Make ImGui windows opaque if background is
242 	    // transparent (else it becomes difficult to
243 	    // distinguish anything...)
244 	    style.Alpha = 1.0f;
245 	} else {
246 	    style.Alpha = 0.90f;
247 	}
248 
249 	if(phone_screen_) {
250 	    style.ScrollbarSize = 10.0f * float(scaling_);
251 	    style.GrabMinSize   = 15.0f * float(scaling_);
252 	}
253     }
254 
set_font_size(index_t value)255     void Application::set_font_size(index_t value) {
256 	font_size_ = index_t(value);
257 	scaling_ = double(font_size_)/16.0;
258 	if(phone_screen_) {
259 	    scaling_ *= double(std::max(get_width(), get_height()))/600.0;
260 	}
261 	if(CmdLine::arg_is_declared("gui:font_size")) {
262 	    CmdLine::set_arg("gui:font_size", String::to_string(value));
263 	}
264 	if(ImGui_initialized_) {
265 	    ImGui_reload_font_ = true;
266 	}
267     }
268 
scaling() const269     double Application::scaling() const {
270         return scaling_ * hidpi_scaling_ / pixel_ratio_;
271     }
272 
resize(index_t w,index_t h,index_t fb_w,index_t fb_h)273     void Application::resize(
274 	index_t w, index_t h, index_t fb_w, index_t fb_h
275     ) {
276 	width_ = w;
277 	height_ = h;
278 	frame_buffer_width_ = fb_w;
279 	frame_buffer_height_ = fb_h;
280 	scaling_ = double(font_size_)/16.0;
281 	if(phone_screen_) {
282 	    scaling_ *= double(std::max(get_width(), get_height()))/600.0;
283 	}
284 	update();
285     }
286 
draw_gui()287     void Application::draw_gui() {
288     }
289 
draw_graphics()290     void Application::draw_graphics() {
291     }
292 
mouse_button_callback(int button,int action,int mods,int source)293     void Application::mouse_button_callback(
294 	int button, int action, int mods, int source
295     ) {
296 	geo_argused(button);
297 	geo_argused(action);
298 	geo_argused(mods);
299 	geo_argused(source);
300     }
301 
scroll_callback(double xoffset,double yoffset)302     void Application::scroll_callback(double xoffset, double yoffset) {
303 	geo_argused(xoffset);
304 	geo_argused(yoffset);
305     }
306 
cursor_pos_callback(double x,double y,int source)307     void Application::cursor_pos_callback(double x, double y, int source) {
308 	geo_argused(x);
309 	geo_argused(y);
310 	geo_argused(source);
311     }
312 
drop_callback(int nb,const char ** f)313     void Application::drop_callback(int nb, const char** f) {
314 	geo_argused(nb);
315 	geo_argused(f);
316     }
317 
char_callback(unsigned int c)318     void Application::char_callback(unsigned int c) {
319 	geo_argused(c);
320     }
321 
key_callback(int key,int scancode,int action,int mods)322     void Application::key_callback(
323 	int key, int scancode, int action, int mods
324     ) {
325 	geo_argused(key);
326 	geo_argused(scancode);
327 	geo_argused(action);
328 	geo_argused(mods);
329     }
330 
draw_dock_space()331     void Application::draw_dock_space() {
332 	ImGuiIO& io = ImGui::GetIO();
333 	// Create window and dockspace for docking.
334 	if((io.ConfigFlags & ImGuiConfigFlags_DockingEnable) != 0) {
335 	    ImGuiViewport* viewport = ImGui::GetMainViewport();
336 	    ImGui::SetNextWindowPos(viewport->Pos);
337 	    ImGui::SetNextWindowSize(viewport->Size);
338 	    ImGui::SetNextWindowViewport(viewport->ID);
339 	    ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
340 	    ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
341 	    ImGui::PushStyleVar(
342 		ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)
343 	    );
344 	    static bool open = true;
345 	    ImGui::Begin(
346 		"DockSpace", &open,
347 		ImGuiWindowFlags_NoDocking |
348 		ImGuiWindowFlags_NoTitleBar |
349 		ImGuiWindowFlags_NoCollapse |
350 		ImGuiWindowFlags_NoResize |
351 		ImGuiWindowFlags_NoMove |
352 		ImGuiWindowFlags_NoBringToFrontOnFocus |
353 		ImGuiWindowFlags_NoNavFocus |
354 		ImGuiWindowFlags_NoBackground |
355 		(menubar_visible_ ? ImGuiWindowFlags_MenuBar : 0)
356 	    );
357 	    ImGui::PopStyleVar(3);
358 	    ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
359 	    ImGui::DockSpace(
360 		dockspace_id, ImVec2(0.0f, 0.0f),
361 		ImGuiDockNodeFlags_None |
362 		ImGuiDockNodeFlags_PassthruCentralNode |
363 		ImGuiDockNodeFlags_AutoHideTabBar
364 	    );
365 	    ImGui::End();
366 	}
367     }
368 
draw()369     void Application::draw() {
370 	if(!ImGui_initialized_) {
371 	    return;
372 	}
373 	update();
374 	if(nb_update_locks_ == 0 && !Process::is_running_threads()) {
375 	    one_frame();
376 	}
377     }
378 
GL_initialize()379     void Application::GL_initialize() {
380 	GEO::Graphics::initialize();
381 	geo_assert(glupCurrentContext() == nullptr);
382 	glupMakeCurrent(glupCreateContext());
383 	if(glupCurrentContext() == nullptr) {
384 	    Logger::err("Skin") << "Could not create GLUP context"
385 				<< std::endl;
386 	    exit(-1);
387 	}
388     }
389 
GL_terminate()390     void Application::GL_terminate() {
391 	glupDeleteContext(glupCurrentContext());
392 	glupMakeCurrent(nullptr);
393 	GEO::Graphics::terminate();
394     }
395 
ImGui_initialize()396     void Application::ImGui_initialize() {
397 	geo_assert(!ImGui_initialized_ );
398 	ImGui::CreateContext();
399 	{
400 	    std::string state = CmdLine::get_arg("gui:state");
401 	    for(size_t i=0; i<state.length(); ++i) {
402 		if(state[i] == '\t') {
403 		    state[i] = '\n';
404 		}
405 	    }
406 	    ImGui::LoadIniSettingsFromMemory(state.c_str());
407 	}
408 	ImGuiIO& io = ImGui::GetIO();
409 	io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
410 
411 	// Viewports allow to drag ImGui windows outside the app's window,
412 	// but it is still a bit unstable, so deactivated it for now.
413 	if(
414 	   CmdLine::arg_is_declared("gui:viewports") &&
415 	   CmdLine::get_arg_bool("gui:viewports")
416 	) {
417 	    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
418 	}
419 
420 	// Note: NavKeyboard sets WantsCaptureKeyboard all the time and
421 	// thus prevents from nanosleeping !
422 	if(
423 	   CmdLine::arg_is_declared("gui:keyboard_nav") &&
424 	   CmdLine::get_arg_bool("gui:keyboard_nav")
425 	) {
426 	    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
427 	}
428 
429 #if defined(GEO_GLFW)
430 
431 	// Second argument to true = install callbacks
432 	// (note that this function can be called multiple times,
433 	//  e.g. when ImGui is restarted after re-loading a saved
434 	//  window state, this mechanism prevents the callbacks to
435 	//  be installed multiple times, which would be an error since
436 	//  they are chained).
437 	ImGui_ImplGlfw_InitForOpenGL(
438 	    data_->window_, !data_->GLFW_callbacks_initialized_
439 	);
440 #endif
441 
442 #if defined(GEO_OS_APPLE)
443 	ImGui_ImplOpenGL3_Init("#version 330");
444 #else
445 	ImGui_ImplOpenGL3_Init("#version 100");
446 #endif
447 	callbacks_initialize();
448 
449 	if(style_ != "") {
450 	    set_style(style_);
451 	} else if(Environment::instance()->has_value("gui:style")) {
452             std::string style = Environment::instance()->get_value("gui:style");
453             set_style(style);
454         } else {
455 	    set_style("Light");
456 	}
457 
458 	ImGui_load_fonts();
459 	ImGui_initialized_ = true;
460 	ImGui_firsttime_init_ = true;
461     }
462 
ImGui_load_fonts()463     void Application::ImGui_load_fonts() {
464 	ImGuiIO& io = ImGui::GetIO();
465 	io.IniFilename = nullptr;
466 
467 	float s = 1.0f;
468 	if(phone_screen_) {
469 	    s = float(std::max(get_width(), get_height())) / 600.0f;
470 	}
471 
472 	float font_size = s * float(double(font_size_) * hidpi_scaling_);
473 
474 	// Default font
475 	io.FontDefault = io.Fonts->AddFontFromMemoryCompressedTTF(
476 	    roboto_medium_compressed_data,
477 	    roboto_medium_compressed_size, font_size
478 	);
479 
480 	// Add icons to default font.
481 	{
482 #define ICON_MIN_FA 0xf000
483 #define ICON_MAX_FA 0xf63c
484 
485 	    ImFontConfig config;
486 	    config.MergeMode = true;
487 
488             // Make the icon monospaced
489 	    config.GlyphMinAdvanceX = 1.5f*font_size;
490 	    config.GlyphOffset.y += 2.0f;
491 
492 	    static const ImWchar icon_ranges[] = {
493 		ICON_MIN_FA, ICON_MAX_FA, 0
494 	    };
495 
496 	    io.Fonts->AddFontFromMemoryCompressedTTF(
497 		fa_solid_compressed_data,
498 		fa_solid_compressed_size, font_size,
499 		&config, icon_ranges
500 	    );
501 
502 	    init_icon_table();
503 	}
504 
505 	// Fixed font for console and editor
506 	io.Fonts->AddFontFromMemoryCompressedTTF(
507 	    cousine_regular_compressed_data,
508 	    cousine_regular_compressed_size, font_size
509 	);
510 
511 	// Larger font
512 	io.Fonts->AddFontFromMemoryCompressedTTF(
513 	    roboto_medium_compressed_data,
514 	    roboto_medium_compressed_size, font_size*1.5f
515 	);
516 
517 	if(phone_screen_) {
518 	    // Smaller fixed font for console
519 	    io.Fonts->AddFontFromMemoryCompressedTTF(
520 		cousine_regular_compressed_data,
521 		cousine_regular_compressed_size, font_size*0.5f
522 	    );
523 	}
524 
525 	io.FontGlobalScale = float(1.0 / pixel_ratio_);
526     }
527 
ImGui_terminate()528     void Application::ImGui_terminate() {
529 	geo_assert(ImGui_initialized_);
530 	ImGui_ImplOpenGL3_Shutdown();
531 #if defined(GEO_GLFW)
532 	// Note: normally, with the new ImGui (1.74), this
533 	// desinstalls user callbacks and reinstalls the previous
534 	// callbacks. I deactivated this mechanism to have the same
535 	// behavior as in ImGui 1.72. TODO: do that properly !
536 	ImGui_ImplGlfw_Shutdown();
537 #endif
538 	ImGui::DestroyContext();
539 	ImGui_initialized_ = false;
540     }
541 
ImGui_new_frame()542     void Application::ImGui_new_frame() {
543 	ImGui_ImplOpenGL3_NewFrame();
544 #if defined(GEO_GLFW)
545 	ImGui_ImplGlfw_NewFrame();
546 #endif
547 	ImGui::NewFrame();
548 
549     }
550 
geogram_initialize(int argc,char ** argv)551     void Application::geogram_initialize(int argc, char** argv) {
552 	GEO::initialize();
553 	CmdLine::import_arg_group("standard");
554 	CmdLine::import_arg_group("algo");
555 	CmdLine::import_arg_group("gfx");
556 	CmdLine::import_arg_group("gui");
557 	CmdLine::parse(argc, argv, filenames_);
558 	phone_screen_ = CmdLine::get_arg_bool("gui:phone_screen");
559 #ifndef GEO_OS_ANDROID
560 	if(phone_screen_ && CmdLine::get_arg("gfx:geometry") == "1024x1024") {
561 	    CmdLine::set_arg("gfx:geometry", "768x1024");
562 	}
563 #endif
564     }
565 
needs_to_redraw() const566     bool Application::needs_to_redraw() const {
567 	return
568 	    animate_ ||
569 	    ImGui::GetIO().WantCaptureMouse ||
570 	    ImGui::GetIO().WantCaptureKeyboard ||
571 	    (nb_frames_update_ > 0);
572     }
573 
update()574     void Application::update() {
575 	// We redraw several frames, in order to make
576 	// sure all events are properly processed.
577 	nb_frames_update_ = NB_FRAMES_UPDATE_INIT;
578     }
579 
set_gui_state(std::string x)580     void Application::set_gui_state(std::string x) {
581 	CmdLine::set_arg("gui:state", x);
582 	if(!ImGui_initialized_) {
583 	    return;
584 	}
585 	ImGui_restart_ = true;
586     }
587 
get_gui_state() const588     std::string Application::get_gui_state() const {
589 	std::string state;
590 	if(ImGui_initialized_) {
591 	    state = std::string(ImGui::SaveIniSettingsToMemory());
592 	    for(size_t i=0; i<state.length(); ++i) {
593 		if(state[i] == '\n') {
594 		    state[i] = '\t';
595 		}
596 	    }
597 	}
598 	return state;
599     }
600 
601     /**************************** GLFW-specific code *********************/
602 #if defined(GEO_GLFW)
603 
604 
pre_draw()605     void Application::pre_draw() {
606     }
607 
post_draw()608     void Application::post_draw() {
609     }
610 
create_window()611     void Application::create_window() {
612 	if(!glfwInit()) {
613 	    Logger::err("Skin")
614 		<<  "Could not initialize GLFW" << std::endl;
615 	    exit(-1);
616 	}
617 
618 	if(CmdLine::get_arg("gfx:GL_profile") == "core") {
619 	    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
620 	    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
621 	    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
622 	    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
623 	}
624 
625     	if(CmdLine::get_arg_bool("gfx:GL_debug")) {
626 	    glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
627 	}
628 
629 	const char* title = name_.c_str();
630 
631 	{
632 	    std::string geometry = CmdLine::get_arg("gfx:geometry");
633 	    std::vector<std::string> words;
634 	    String::split_string(geometry, 'x', words);
635 	    if(words.size() != 2) {
636 		Logger::err("Skin")
637 		    << "Invalid gfx:geometry:" << geometry << std::endl;
638 		exit(-1);
639 	    }
640 	    if(
641 		!String::string_to_unsigned_integer(words[0].c_str(), width_) ||
642 		!String::string_to_unsigned_integer(words[1].c_str(), height_)
643 	    ) {
644 		Logger::err("Skin")
645 		    << "Invalid gfx:geometry:" << geometry << std::endl;
646 		exit(-1);
647 	    }
648 	}
649 
650 	if(CmdLine::get_arg_bool("gfx:transparent")) {
651 #ifdef GLFW_TRANSPARENT_FRAMEBUFFER
652 	    glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
653 #else
654 	    Logger::warn("Skin")
655 		<< "Transparent not supported by this version of GLFW"
656 		<< std::endl;
657 #endif
658 	}
659 
660 	if(CmdLine::get_arg_bool("gfx:full_screen")) {
661 	    GLFWmonitor* monitor = glfwGetPrimaryMonitor();
662 	    const GLFWvidmode* vidmode = glfwGetVideoMode(monitor);
663 	    width_ = index_t(vidmode->width);
664 	    height_ = index_t(vidmode->height);
665 
666 	    bool no_decoration = CmdLine::get_arg_bool("gfx:no_decoration");
667 
668 	    if(no_decoration) {
669 	       glfwWindowHint(GLFW_FOCUSED,GL_TRUE);
670 	       glfwWindowHint(GLFW_DECORATED,GL_FALSE);
671 	       glfwWindowHint(GLFW_RESIZABLE,GL_FALSE);
672 	       glfwWindowHint(GLFW_AUTO_ICONIFY,GL_FALSE);
673 	       glfwWindowHint(GLFW_FLOATING,GL_FALSE);
674 	       glfwWindowHint(GLFW_MAXIMIZED,GL_TRUE);
675 	    }
676 
677 	    data_->window_ = glfwCreateWindow(
678 		int(width_), int(height_), title,
679 		no_decoration ? glfwGetPrimaryMonitor() : nullptr,
680 		nullptr
681 	    );
682 
683 	} else {
684 	    data_->window_ = glfwCreateWindow(
685 		int(width_), int(height_), title, nullptr, nullptr
686 	    );
687 	}
688 
689 	if(data_->window_ == nullptr) {
690 	    Logger::err("Skin")
691 		<< "Could not create GLFW window" << std::endl;
692 	    exit(-1);
693 	}
694 
695 	glfwSetWindowUserPointer(data_->window_, this);
696 
697 	glfwMakeContextCurrent(data_->window_);
698 	glfwSwapInterval(1);
699 
700 	hidpi_scaling_ = compute_hidpi_scaling();
701 	pixel_ratio_ = compute_pixel_ratio();
702 
703 	Logger::out("Skin")
704 	    << "hidpi_scaling=" << hidpi_scaling_ << std::endl;
705 	Logger::out("Skin")
706 	    << "pixel_ratio=" << pixel_ratio_ << std::endl;
707     }
708 
delete_window()709     void Application::delete_window() {
710 	glfwDestroyWindow(data_->window_);
711 	data_->window_ = nullptr;
712 	glfwTerminate();
713 	in_main_loop_ = false;
714     }
715 
716 
one_frame()717     void Application::one_frame() {
718 	// Avoid nested ImGui calls
719 	// (due to calling draw())
720 	if(currently_drawing_gui_) {
721 	    return;
722 	}
723 
724 	// Can happen when ImGui Graphite application
725 	// triggers update too soon.
726 	if(data_->window_ == nullptr) {
727 	    return;
728 	}
729 
730 	if(glfwWindowShouldClose(data_->window_) || !in_main_loop_) {
731 	    return;
732 	}
733 
734 
735 	{
736 	    int cur_width, cur_height;
737 	    int cur_fb_width,  cur_fb_height;
738 
739 	    glfwGetWindowSize(data_->window_, &cur_width, &cur_height);
740 	    glfwGetFramebufferSize(
741 		data_->window_, &cur_fb_width, &cur_fb_height
742 	    );
743 
744 	    if(
745 		int(width_) != cur_width ||
746 		int(height_) != cur_height ||
747 		int(frame_buffer_width_) != cur_fb_width ||
748 		int(frame_buffer_height_) != cur_fb_height
749 	    ) {
750 		resize(
751 		    index_t(cur_width), index_t(cur_height),
752 		    index_t(cur_fb_width), index_t(cur_fb_height)
753 		);
754 	    }
755 	}
756 
757 	{
758 	    // Detect if hidpi scaling changed. This can happen when
759 	    // dragging the window from the laptop screen to an external
760 	    // monitor.
761 	    if(
762 		glfwGetCurrentContext() != nullptr &&
763 		compute_hidpi_scaling() != hidpi_scaling_
764 	    ) {
765 		hidpi_scaling_ = compute_hidpi_scaling();
766 		pixel_ratio_ = compute_pixel_ratio();
767 		set_font_size(font_size_); // This reloads the font.
768 	    }
769 	}
770 
771 
772 	glfwPollEvents();
773 
774 	if(needs_to_redraw()) {
775 	    pre_draw();
776 	    currently_drawing_gui_ = true;
777 	    ImGui_new_frame();
778 	    draw_graphics();
779 	    draw_gui();
780 	    ImGui::Render();
781 	    ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
782 
783 #ifndef GEO_OS_EMSCRIPTEN
784 	    // Update and Render additional Platform Windows
785 	    // (see ImGui demo app).
786 	    if(ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
787 		GLFWwindow* backup_current_context = glfwGetCurrentContext();
788 		ImGui::UpdatePlatformWindows();
789 		ImGui::RenderPlatformWindowsDefault();
790 		glfwMakeContextCurrent(backup_current_context);
791 	    }
792 #endif
793 
794 	    currently_drawing_gui_ = false;
795 
796 #ifdef GEO_OS_EMSCRIPTEN
797 	    // Set alpha channel to 1 else the image is composited
798 	    // with the background
799 	    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
800 	    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
801 	    glClear(GL_COLOR_BUFFER_BIT);
802 	    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
803 #endif
804 
805 	    glfwSwapBuffers(data_->window_);
806 	    post_draw();
807 	    if(
808 		nb_frames_update_ > 0 && !animate_ &&
809 	        !(ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
810 	    ) {
811 		--nb_frames_update_;
812 	    }
813 	} else {
814 	    // Sleep for 0.2 seconds, to let the processor cold-down
815 	    // instead of actively waiting (be a good citizen for the
816 	    // other processes.
817 	    Process::sleep(20000);
818 	}
819 
820 	// ImGui needs to be restarted whenever docking state is reloaded.
821 	if(ImGui_restart_) {
822 	    ImGui_restart_ = false;
823 	    ImGui_terminate();
824 	    if(CmdLine::arg_is_declared("gui:font_size")) {
825 		set_font_size(CmdLine::get_arg_uint("gui:font_size"));
826 		ImGui_reload_font_ = false;
827 	    }
828 	    ImGui_initialize();
829 	} else if(ImGui_reload_font_) {
830 	    ImGuiIO& io = ImGui::GetIO();
831 	    io.Fonts->Clear();
832 	    ImGui_load_fonts();
833 	    ImGui_ImplOpenGL3_DestroyDeviceObjects();
834 	    ImGui_reload_font_ = false;
835 	}
836     }
837 
838 #ifdef GEO_OS_EMSCRIPTEN
839 
840     void emscripten_load_latest_file();
841 
842     /**
843      * \brief This function is called by the HTML shell each
844      *  time a file is loaded.
845      */
emscripten_load_latest_file()846     void emscripten_load_latest_file() {
847 	if(Application::instance() == nullptr) {
848 	    return;
849 	}
850 	std::vector<std::string> all_files;
851 	GEO::FileSystem::get_directory_entries("/",all_files);
852 	std::string filename = "";
853 	Numeric::uint64 timestamp = 0;
854 	for(auto f: all_files) {
855 	    if(GEO::FileSystem::is_file(f)) {
856 		Numeric::uint64 f_timestamp =
857 		    GEO::FileSystem::get_time_stamp(f);
858 		if(f_timestamp > timestamp) {
859 		    timestamp = f_timestamp;
860 		    filename = f;
861 		}
862 	    }
863 	}
864 	if(filename != "") {
865 	    const char* filename_str = filename.c_str();
866 	    Application::instance()->drop_callback(1, &filename_str);
867 	}
868     }
869 
870 
871     /**
872      * \brief The job to be done for each frame when running
873      *  in Emscripten.
874      * \details The browser keeps control of the main loop
875      *  in Emscripten. This function is a callback passed to
876      *  the Emscripten runtime.
877      */
emscripten_one_frame()878     void emscripten_one_frame() {
879 	if(Application::instance() == nullptr) {
880 	    return;
881 	}
882 	Application::instance()->one_frame();
883     }
884 #endif
885 
main_loop()886     void Application::main_loop() {
887 	in_main_loop_ = true;
888 #ifdef GEO_OS_EMSCRIPTEN
889 	FileSystem::set_file_system_changed_callback(
890 	    emscripten_load_latest_file
891 	);
892 	GL_initialize();
893 	ImGui_initialize();
894 	emscripten_load_latest_file();
895 	emscripten_set_main_loop(emscripten_one_frame, 0, 1);
896 #else
897 	bool initialized = false;
898 	while (!glfwWindowShouldClose(data_->window_) && in_main_loop_) {
899 	    if(!initialized) {
900 		GL_initialize();
901 		ImGui_initialize();
902 		initialized = true;
903 	    }
904 	    one_frame();
905 	}
906 	if(initialized) {
907 	    ImGui_terminate();
908 	    GL_terminate();
909 	}
910 #endif
911     }
912 
913     namespace {
GLFW_callback_mouse_button(GLFWwindow * w,int button,int action,int mods)914 	void GLFW_callback_mouse_button(
915 	    GLFWwindow* w, int button, int action, int mods
916 	) {
917 	    Application* app = static_cast<Application*>(
918 		glfwGetWindowUserPointer(w)
919 	    );
920 	    app->update();
921 	    if(!ImGui::GetIO().WantCaptureMouse) {
922 		app->mouse_button_callback(button,action,mods);
923 	    }
924 	    // Note: when a menu is open and you click elsewhere, the
925 	    // WantCaptureMouse flag is still set, and the framework
926 	    // misses the "mouse button up" event. If a translation is
927 	    // active, it remains active later ("sticky translation" bug).
928 	    // The following code always generates a "mouse button up" event
929 	    // to solve this problem.
930 	    if(ImGui::GetIO().WantCaptureMouse && action==EVENT_ACTION_UP) {
931 		ImVec2 mouse_pos = ImGui::GetIO().MousePos;
932 		app->cursor_pos_callback(
933 		    double(mouse_pos.x), double(mouse_pos.y)
934 		);
935 		app->mouse_button_callback(button,action,mods);
936 	    }
937 	    if(app->impl_data()->ImGui_callback_mouse_button != nullptr) {
938 		app->impl_data()->ImGui_callback_mouse_button(
939 		    w, button, action, mods
940 		);
941 	    }
942 	}
943 
GLFW_callback_cursor_pos(GLFWwindow * w,double xf,double yf)944 	void GLFW_callback_cursor_pos(
945 	    GLFWwindow* w, double xf, double yf
946 	) {
947 	    Application* app = static_cast<Application*>(
948 		glfwGetWindowUserPointer(w)
949 	    );
950 	    app->update();
951 	    if(app->impl_data()->ImGui_callback_cursor_pos != nullptr) {
952 		app->impl_data()->ImGui_callback_cursor_pos(w, xf, yf);
953 	    }
954 	    if(!ImGui::GetIO().WantCaptureMouse) {
955 		app->cursor_pos_callback(xf, yf);
956 	    }
957 	}
958 
GLFW_callback_scroll(GLFWwindow * w,double xoffset,double yoffset)959 	void GLFW_callback_scroll(
960 	    GLFWwindow* w, double xoffset, double yoffset
961 	) {
962 	    Application* app = static_cast<Application*>(
963 		glfwGetWindowUserPointer(w)
964 	    );
965 	    app->update();
966 	    if(!ImGui::GetIO().WantCaptureMouse) {
967 #if defined(GEO_OS_EMSCRIPTEN) || defined(GEO_OS_APPLE)
968 		app->scroll_callback(xoffset,-yoffset);
969 #else
970 		app->scroll_callback(xoffset, yoffset);
971 #endif
972 	    }
973 	    if(app->impl_data()->ImGui_callback_scroll != nullptr) {
974 		app->impl_data()->ImGui_callback_scroll(w, xoffset, yoffset);
975 	    }
976 	}
977 
GLFW_callback_drop(GLFWwindow * w,int nb,const char ** p)978 	void GLFW_callback_drop(
979 	    GLFWwindow* w, int nb, const char** p
980 	) {
981 	    Application* app = static_cast<Application*>(
982 		glfwGetWindowUserPointer(w)
983 	    );
984 	    app->update();
985 	    if(app->impl_data()->ImGui_callback_drop != nullptr) {
986 		app->impl_data()->ImGui_callback_drop(w, nb, p);
987 	    }
988 	    app->drop_callback(nb, p);
989 	}
990 
GLFW_callback_char(GLFWwindow * w,unsigned int c)991 	void GLFW_callback_char(GLFWwindow* w, unsigned int c) {
992 	    Application* app = static_cast<Application*>(
993 		glfwGetWindowUserPointer(w)
994 	    );
995 	    app->update();
996 	    if(app->impl_data()->ImGui_callback_char != nullptr) {
997 		app->impl_data()->ImGui_callback_char(w, c);
998 	    }
999 	    if(!ImGui::GetIO().WantCaptureKeyboard) {
1000 		app->char_callback(c);
1001 	    }
1002 	}
1003 
GLFW_callback_key(GLFWwindow * w,int key,int scancode,int action,int mods)1004 	void GLFW_callback_key(
1005 	    GLFWwindow* w, int key, int scancode, int action, int mods
1006 	) {
1007 	    Application* app = static_cast<Application*>(
1008 		glfwGetWindowUserPointer(w)
1009 	    );
1010 	    app->update();
1011 	    if(app->impl_data()->ImGui_callback_key != nullptr) {
1012 		app->impl_data()->ImGui_callback_key(
1013 		    w, key, scancode, action, mods
1014 		);
1015 	    }
1016 	    if(!ImGui::GetIO().WantCaptureKeyboard) {
1017 		app->key_callback(key,scancode,action,mods);
1018 	    }
1019 	}
1020 
GLFW_callback_refresh(GLFWwindow * w)1021 	void GLFW_callback_refresh(GLFWwindow* w) {
1022 	    Application* app = static_cast<Application*>(
1023 		glfwGetWindowUserPointer(w)
1024 	    );
1025 	    app->update();
1026 	    if(app->impl_data()->ImGui_callback_refresh != nullptr) {
1027 		app->impl_data()->ImGui_callback_refresh(w);
1028 	    }
1029 	}
1030     }
1031 
callbacks_initialize()1032     void Application::callbacks_initialize() {
1033 	if(!data_->GLFW_callbacks_initialized_) {
1034 	    GEO::Logger::out("ImGui") << "Viewer GUI init (GL3)"
1035 				      << std::endl;
1036 	}
1037 
1038 	if(!data_->GLFW_callbacks_initialized_) {
1039 	    // Get previous callbacks so that we can call them if ImGui
1040 	    // wants to handle user input.
1041 	    data_->ImGui_callback_mouse_button = glfwSetMouseButtonCallback(
1042 		data_->window_,GLFW_callback_mouse_button
1043 	    );
1044 	    data_->ImGui_callback_cursor_pos = glfwSetCursorPosCallback(
1045 		data_->window_,GLFW_callback_cursor_pos
1046 	    );
1047 	    data_->ImGui_callback_scroll = glfwSetScrollCallback(
1048 		data_->window_,GLFW_callback_scroll
1049 	    );
1050 	    data_->ImGui_callback_char = glfwSetCharCallback(
1051 		data_->window_,GLFW_callback_char
1052 	    );
1053 	    data_->ImGui_callback_key = glfwSetKeyCallback(
1054 		data_->window_,GLFW_callback_key
1055 	    );
1056 	    data_->ImGui_callback_drop = glfwSetDropCallback(
1057 		data_->window_,GLFW_callback_drop
1058 	    );
1059 	    data_->ImGui_callback_refresh = glfwSetWindowRefreshCallback(
1060 		data_->window_,GLFW_callback_refresh
1061 	    );
1062 
1063 #ifdef GEO_OS_EMSCRIPTEN
1064 	    // It seems that Emscripten's implementation of
1065 	    // glfwSetXXXCallback() does not always return previous
1066 	    // callback bindings.
1067 	    data_->ImGui_callback_mouse_button =
1068 		ImGui_ImplGlfw_MouseButtonCallback;
1069 	    data_->ImGui_callback_char = ImGui_ImplGlfw_CharCallback;
1070 	    data_->ImGui_callback_key  = ImGui_ImplGlfw_KeyCallback;
1071 	    data_->ImGui_callback_scroll  = ImGui_ImplGlfw_ScrollCallback;
1072 #endif
1073 	    data_->GLFW_callbacks_initialized_ = true;
1074 	}
1075     }
1076 
set_window_icon(Image * icon_image)1077     void Application::set_window_icon(Image* icon_image) {
1078 	GLFWimage glfw_image;
1079 	glfw_image.width = int(icon_image->width());
1080 	glfw_image.height = int(icon_image->height());
1081 	glfw_image.pixels = icon_image->base_mem();
1082 	glfwSetWindowIcon(data_->window_, 1, &glfw_image);
1083     }
1084 
set_full_screen_mode(index_t w,index_t h,index_t Hz,index_t monitor)1085     void Application::set_full_screen_mode(
1086 	index_t w, index_t h, index_t Hz, index_t monitor
1087     ) {
1088 	if(data_->window_ == nullptr) {
1089 	    return;
1090 	}
1091 	int count;
1092 	GLFWmonitor** monitors = glfwGetMonitors(&count);
1093 	if(int(monitor) >= count) {
1094 	    Logger::err("Application") << monitor << ": no such monitor"
1095 	                               << std::endl;
1096 	}
1097 	if((w == 0) || (h == 0) || (Hz == 0)) {
1098 	    Logger::out("Application")
1099 		<< "Using default video mode" << std::endl;
1100 	    const GLFWvidmode* mode = glfwGetVideoMode(monitors[monitor]);
1101 	    w = index_t(mode->width);
1102 	    h = index_t(mode->height);
1103 	    Hz = index_t(mode->refreshRate);
1104 	}
1105 	glfwSetWindowMonitor(
1106 	    data_->window_, monitors[monitor], 0, 0, int(w), int(h), int(Hz)
1107 	);
1108 	update();
1109     }
1110 
set_windowed_mode(index_t w,index_t h)1111     void Application::set_windowed_mode(index_t w, index_t h) {
1112 	if(w != 0 && h != 0) {
1113 	    width_ = w;
1114 	    height_ = w;
1115 	}
1116 	glfwSetWindowMonitor(
1117 	    data_->window_, nullptr, 0, 0, int(width_), int(height_), 50
1118 	);
1119 	update();
1120     }
1121 
1122 
list_video_modes()1123     void Application::list_video_modes() {
1124 	int nb_monitors = 0;
1125 	GLFWmonitor** monitors = glfwGetMonitors(&nb_monitors);
1126 	Logger::out("Application") << "Detected " << nb_monitors
1127 				   << " monitor(s)"
1128 				   << std::endl;
1129 	for(int m=0; m<nb_monitors; ++m) {
1130 	    GLFWmonitor* monitor = monitors[m];
1131 	    Logger::out("Application") << "Monitor " << m << ":"
1132 			       << glfwGetMonitorName(monitor)
1133 			       << std::endl;
1134 	    int nb_modes = 0;
1135 	    const GLFWvidmode* modes = glfwGetVideoModes(monitor, &nb_modes);
1136 	    for(int mm=0; mm<nb_modes; ++mm) {
1137 		const GLFWvidmode& mode = modes[mm];
1138 		Logger::out("Application") << "   mode " << mm << ":"
1139 					   << mode.width << "x" << mode.height
1140 					   << " " << mode.refreshRate << "Hz "
1141 					   << "R" << mode.redBits
1142 					   << "G" << mode.greenBits
1143 					   << "B" << mode.blueBits
1144 					   << std::endl;
1145 	    }
1146 	}
1147     }
1148 
1149 
iconify()1150     void Application::iconify() {
1151 	if(data_->window_ == nullptr ){
1152 	    return ;
1153 	}
1154 	glfwIconifyWindow(data_->window_);
1155     }
1156 
restore()1157     void Application::restore() {
1158 	if(data_->window_ == nullptr ){
1159 	    return ;
1160 	}
1161 
1162 	// In full screen mode, glfwRestoreWindow()
1163 	// does not seem to work, so we switch to
1164 	// windowed mode, deiconify, then switch back
1165 	// to full screen mode.
1166 	if(get_full_screen()) {
1167 	    set_full_screen(false);
1168 	    glfwRestoreWindow(data_->window_);
1169 	    set_full_screen(true);
1170 	} else {
1171 	    glfwRestoreWindow(data_->window_);
1172 	}
1173     }
1174 
get_full_screen() const1175     bool Application::get_full_screen() const {
1176 	return (data_->window_ != nullptr &&
1177 		glfwGetWindowMonitor(data_->window_) != nullptr);
1178     }
1179 
set_full_screen(bool x)1180     void Application::set_full_screen(bool x) {
1181 	if(x != get_full_screen()) {
1182 	    if(x) {
1183 		set_full_screen_mode();
1184 	    } else {
1185 		set_windowed_mode();
1186 	    }
1187 	}
1188     }
1189 
impl_window()1190     void* Application::impl_window() {
1191 	return data_->window_;
1192     }
1193 
1194 #else
1195 # error "No windowing system"
1196 #endif
1197 
1198 
1199 #ifdef GEO_GLFW
key_to_string(int key)1200     const char* Application::key_to_string(int key) {
1201 	if(key == GLFW_KEY_LEFT) {
1202 	    return "left";
1203 	}
1204 	if(key == GLFW_KEY_RIGHT) {
1205 	    return "right";
1206 	}
1207 	if(key == GLFW_KEY_UP) {
1208 	    return "up";
1209 	}
1210 	if(key == GLFW_KEY_DOWN) {
1211 	    return "down";
1212 	}
1213 	if(key == GLFW_KEY_F1) {
1214 	    return "F1";
1215 	}
1216 	if(key == GLFW_KEY_F2) {
1217 	    return "F2";
1218 	}
1219 	if(key == GLFW_KEY_F3) {
1220 	    return "F3";
1221 	}
1222 	if(key == GLFW_KEY_F4) {
1223 	    return "F4";
1224 	}
1225 	if(key == GLFW_KEY_F5) {
1226 	    return "F5";
1227 	}
1228 	if(key == GLFW_KEY_F6) {
1229 	    return "F6";
1230 	}
1231 	if(key == GLFW_KEY_F7) {
1232 	    return "F7";
1233 	}
1234 	if(key == GLFW_KEY_F8) {
1235 	    return "F8";
1236 	}
1237 	if(key == GLFW_KEY_F9) {
1238 	    return "F9";
1239 	}
1240 	if(key == GLFW_KEY_F10) {
1241 	    return "F10";
1242 	}
1243 	if(key == GLFW_KEY_F11) {
1244 	    return "F11";
1245 	}
1246 	if(key == GLFW_KEY_F12) {
1247 	    return "F12";
1248 	}
1249 	if(key == GLFW_KEY_LEFT_CONTROL) {
1250 	    return "left_control";
1251 	}
1252 	if(key == GLFW_KEY_RIGHT_CONTROL) {
1253 	    return "right_control";
1254 	}
1255 	if(key == GLFW_KEY_LEFT_ALT) {
1256 	    return "left_alt";
1257 	}
1258 	if(key == GLFW_KEY_RIGHT_ALT) {
1259 	    return "right_alt";
1260 	}
1261 	if(key == GLFW_KEY_LEFT_SHIFT) {
1262 	    return "left_shift";
1263 	}
1264 	if(key == GLFW_KEY_RIGHT_SHIFT) {
1265 	    return "right_shift";
1266 	}
1267 	if(key == GLFW_KEY_ESCAPE) {
1268 	    return "escape";
1269 	}
1270 	if(key == GLFW_KEY_TAB) {
1271 	    return "tab";
1272 	}
1273 	if(key == GLFW_KEY_BACKSPACE) {
1274 	    return "backspace";
1275 	}
1276 	return "";
1277     }
1278 #else
key_to_string(int key)1279     const char* Application::key_to_string(int key) {
1280 	return "";
1281     }
1282 #endif
1283 
1284 }
1285 
1286 /************************ Utilities *************************************/
1287 
1288 namespace GEO {
1289 
StyleColorsCorporateGrey(bool threeD)1290     void StyleColorsCorporateGrey(bool threeD) {
1291 	ImGuiStyle & style = ImGui::GetStyle();
1292 	ImVec4 * colors = style.Colors;
1293 
1294 	/// 0 = FLAT APPEARENCE
1295 	/// 1 = MORE "3D" LOOK
1296 	float is3D = threeD ? 1.0f : 0.0f;
1297 
1298 	colors[ImGuiCol_Text]                   = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
1299 	colors[ImGuiCol_TextDisabled]           = ImVec4(0.40f, 0.40f, 0.40f, 1.00f);
1300 	colors[ImGuiCol_ChildBg]                = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); //BL orig=0.25
1301 	colors[ImGuiCol_WindowBg]               = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); //BL orig=0.25
1302 	colors[ImGuiCol_PopupBg]                = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
1303 	colors[ImGuiCol_Border]                 = ImVec4(0.12f, 0.12f, 0.12f, 0.71f);
1304 	colors[ImGuiCol_BorderShadow]           = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
1305 	colors[ImGuiCol_FrameBg]                = ImVec4(0.42f, 0.42f, 0.42f, 0.54f);
1306 	colors[ImGuiCol_FrameBgHovered]         = ImVec4(0.42f, 0.42f, 0.42f, 0.40f);
1307 	colors[ImGuiCol_FrameBgActive]          = ImVec4(0.56f, 0.56f, 0.56f, 0.67f);
1308 	colors[ImGuiCol_TitleBg]                = ImVec4(0.19f, 0.19f, 0.19f, 1.00f);
1309 	colors[ImGuiCol_TitleBgActive]          = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
1310 	colors[ImGuiCol_TitleBgCollapsed]       = ImVec4(0.17f, 0.17f, 0.17f, 0.90f);
1311 	colors[ImGuiCol_MenuBarBg]              = ImVec4(0.335f, 0.335f, 0.335f, 1.000f);
1312 	colors[ImGuiCol_ScrollbarBg]            = ImVec4(0.24f, 0.24f, 0.24f, 0.53f);
1313 	colors[ImGuiCol_ScrollbarGrab]          = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
1314 	colors[ImGuiCol_ScrollbarGrabHovered]   = ImVec4(0.52f, 0.52f, 0.52f, 1.00f);
1315 	colors[ImGuiCol_ScrollbarGrabActive]    = ImVec4(0.76f, 0.76f, 0.76f, 1.00f);
1316 	colors[ImGuiCol_CheckMark]              = ImVec4(0.65f, 0.65f, 0.65f, 1.00f);
1317 	colors[ImGuiCol_SliderGrab]             = ImVec4(0.52f, 0.52f, 0.52f, 1.00f);
1318 	colors[ImGuiCol_SliderGrabActive]       = ImVec4(0.64f, 0.64f, 0.64f, 1.00f);
1319 	colors[ImGuiCol_Button]                 = ImVec4(0.54f, 0.54f, 0.54f, 0.35f);
1320 	colors[ImGuiCol_ButtonHovered]          = ImVec4(0.52f, 0.52f, 0.52f, 0.59f);
1321 	colors[ImGuiCol_ButtonActive]           = ImVec4(0.76f, 0.76f, 0.76f, 1.00f);
1322 	colors[ImGuiCol_Header]                 = ImVec4(0.38f, 0.38f, 0.38f, 1.00f);
1323 	colors[ImGuiCol_HeaderHovered]          = ImVec4(0.47f, 0.47f, 0.47f, 1.00f);
1324 	colors[ImGuiCol_HeaderActive]           = ImVec4(0.76f, 0.76f, 0.76f, 0.77f);
1325 	colors[ImGuiCol_Separator]              = ImVec4(0.000f, 0.000f, 0.000f, 0.137f);
1326 	colors[ImGuiCol_SeparatorHovered]       = ImVec4(0.700f, 0.671f, 0.600f, 0.290f);
1327 	colors[ImGuiCol_SeparatorActive]        = ImVec4(0.702f, 0.671f, 0.600f, 0.674f);
1328 	colors[ImGuiCol_ResizeGrip]             = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
1329 	colors[ImGuiCol_ResizeGripHovered]      = ImVec4(0.26f, 0.59f, 0.98f, 0.67f);
1330 	colors[ImGuiCol_ResizeGripActive]       = ImVec4(0.26f, 0.59f, 0.98f, 0.95f);
1331 	colors[ImGuiCol_PlotLines]              = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
1332 	colors[ImGuiCol_PlotLinesHovered]       = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
1333 	colors[ImGuiCol_PlotHistogram]          = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
1334 	colors[ImGuiCol_PlotHistogramHovered]   = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
1335 	colors[ImGuiCol_TextSelectedBg]         = ImVec4(0.73f, 0.73f, 0.73f, 0.35f);
1336 	colors[ImGuiCol_ModalWindowDimBg]       = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
1337 	colors[ImGuiCol_DragDropTarget]         = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
1338 	colors[ImGuiCol_NavHighlight]           = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
1339 	colors[ImGuiCol_NavWindowingHighlight]  = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
1340 	colors[ImGuiCol_NavWindowingDimBg]      = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
1341 
1342 	style.PopupRounding = 3;
1343 
1344 	style.WindowPadding = ImVec2(4, 4);
1345 	style.FramePadding  = ImVec2(6, 4);
1346 	style.ItemSpacing   = ImVec2(6, 2);
1347 
1348 	style.ScrollbarSize = 18;
1349 
1350 	style.WindowBorderSize = 1;
1351 	style.ChildBorderSize  = 1;
1352 	style.PopupBorderSize  = 1;
1353 	style.FrameBorderSize  = is3D;
1354 
1355 	style.WindowRounding    = 3;
1356 	style.ChildRounding     = 3;
1357 	style.FrameRounding     = 3;
1358 	style.ScrollbarRounding = 2;
1359 	style.GrabRounding      = 3;
1360 
1361 #ifdef IMGUI_HAS_DOCK
1362 	style.TabBorderSize = is3D;
1363 	style.TabRounding   = 3;
1364 
1365 	colors[ImGuiCol_DockingEmptyBg]     = ImVec4(0.38f, 0.38f, 0.38f, 1.00f);
1366 	colors[ImGuiCol_Tab]                = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
1367 	colors[ImGuiCol_TabHovered]         = ImVec4(0.40f, 0.40f, 0.40f, 1.00f);
1368 	colors[ImGuiCol_TabActive]          = ImVec4(0.33f, 0.33f, 0.33f, 1.00f);
1369 	colors[ImGuiCol_TabUnfocused]       = ImVec4(0.25f, 0.25f, 0.25f, 1.00f);
1370 	colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f);
1371 	colors[ImGuiCol_DockingPreview]     = ImVec4(0.85f, 0.85f, 0.85f, 0.28f);
1372 
1373 	if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
1374 	{
1375 	    style.WindowRounding = 0.0f;
1376 	    style.Colors[ImGuiCol_WindowBg].w = 1.0f;
1377 	}
1378 #endif
1379     }
1380 
1381 
1382 #if defined(GEO_GLFW) && !defined(GEO_OS_EMSCRIPTEN)
1383 
compute_pixel_ratio()1384     double compute_pixel_ratio() {
1385 	int buf_size[2];
1386 	int win_size[2];
1387 	GLFWwindow* window = glfwGetCurrentContext();
1388 	glfwGetFramebufferSize(window, &buf_size[0], &buf_size[1]);
1389 	glfwGetWindowSize(window, &win_size[0], &win_size[1]);
1390 	// The window may be iconified.
1391 	if(win_size[0] == 0) {
1392 	    return 1.0;
1393 	}
1394 	return double(buf_size[0]) / double(win_size[0]);
1395     }
1396 
compute_hidpi_scaling()1397     double compute_hidpi_scaling() {
1398 	float xscale, yscale;
1399 	GLFWwindow* window = glfwGetCurrentContext();
1400 	glfwGetWindowContentScale(window, &xscale, &yscale);
1401 	return 0.5 * double(xscale + yscale);
1402     }
1403 
1404 #else
1405 
compute_pixel_ratio()1406     double compute_pixel_ratio() {
1407 	return 1.0;
1408     }
1409 
compute_hidpi_scaling()1410     double compute_hidpi_scaling() {
1411 	return 1.0;
1412     }
1413 
1414 #endif
1415 
1416 }
1417 
1418