GCC Code Coverage Report


Directory: ./
File: lib/geogram_gfx/gui/simple_application.cpp
Date: 2026-09-07 02:37:58
Exec Total Coverage
Lines: 0 1020 0.0%
Functions: 0 69 0.0%
Branches: 0 1507 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 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/simple_application.h>
41 #include <geogram_gfx/gui/geogram_logo_256.xpm>
42
43 #include <geogram/basic/file_system.h>
44 #include <geogram/basic/string.h>
45 #include <geogram/basic/logger.h>
46 #include <geogram/basic/command_line.h>
47 #include <geogram/basic/stopwatch.h>
48 #include <geogram/basic/command_line.h>
49 #include <geogram/basic/command_line_args.h>
50 #include <geogram/image/image.h>
51 #include <geogram/image/image_library.h>
52
53 #include <geogram/bibliography/bibliography.h>
54
55 #include <geogram_gfx/full_screen_effects/ambient_occlusion.h>
56 #include <geogram_gfx/full_screen_effects/unsharp_masking.h>
57
58
59 #include <geogram_gfx/gui/colormaps/french.xpm>
60 #include <geogram_gfx/gui/colormaps/black_white.xpm>
61 #include <geogram_gfx/gui/colormaps/viridis.xpm>
62 #include <geogram_gfx/gui/colormaps/rainbow.xpm>
63 #include <geogram_gfx/gui/colormaps/cei_60757.xpm>
64 #include <geogram_gfx/gui/colormaps/inferno.xpm>
65 #include <geogram_gfx/gui/colormaps/magma.xpm>
66 #include <geogram_gfx/gui/colormaps/parula.xpm>
67 #include <geogram_gfx/gui/colormaps/plasma.xpm>
68 #include <geogram_gfx/gui/colormaps/blue_red.xpm>
69
70 #ifdef GEOGRAM_WITH_LUA
71
72 # include <geogram_gfx/lua/lua_glup.h>
73 # include <geogram_gfx/lua/lua_simple_application.h>
74 # include <geogram_gfx/lua/lua_imgui.h>
75 # include <geogram/lua/lua_io.h>
76
77 #endif
78
79 #ifdef GEO_OS_EMSCRIPTEN
80 # include <emscripten.h>
81 #endif
82
83 #ifdef GEO_OS_ANDROID
84 # include <geogram/basic/android_utils.h>
85 #endif
86
87 namespace {
88 # include <geogram_gfx/gui/gui_state_v.h>
89 # include <geogram_gfx/gui/gui_state_h.h>
90 # include <geogram_gfx/gui/gui_state.h>
91 }
92
93 /******************************************************************************/
94
95 namespace {
96
97 /**
98 * \brief Converts a complete path to a file to a label
99 * displayed in the file browser.
100 * \details Strips viewer_path from the input path.
101 * \param[in] path the complete path, can be either a directory or
102 * a file
103 * \return the label to be displayed in the menu
104 */
105 std::string path_to_label(
106 const std::string& viewer_path, const std::string& path
107 ) {
108 std::string result = path;
109 if(GEO::String::string_starts_with(result, viewer_path)) {
110 result = result.substr(
111 viewer_path.length(), result.length()-viewer_path.length()
112 );
113 }
114 return result;
115 }
116
117
118 }
119
120 /******************************************************************************/
121
122 namespace GEO {
123
124 SimpleApplication::SimpleApplication(
125 const std::string& name
126 ) :
127 Application(name),
128 text_editor_(&text_editor_visible_)
129 {
130 lighting_ = true;
131 edit_light_ = false;
132 clipping_ = false;
133 clip_mode_ = GLUP_CLIP_STRADDLING_CELLS;
134 edit_clip_ = false;
135 fixed_clip_ = false;
136 background_color_ = vec4f(0.0f, 0.0f, 0.0f, 1.0f);
137 effect_ = GLenum(0);
138
139 filename_[0] = '\0';
140 geogram_logo_texture_ = 0;
141 viewer_properties_visible_ = true;
142 object_properties_visible_ = true;
143 console_visible_ = true;
144 use_text_editor_ = false;
145 text_editor_visible_ = false;
146 menubar_visible_ = true;
147 command_line_editor_visible_ = false;
148
149 console_ = new Console(&console_visible_);
150 console_->hide_command_prompt();
151 text_editor_.set_fixed_layout(false);
152 status_bar_ = new StatusBar;
153
154 add_key_func("q", [this]() { stop(); }, "quit");
155 add_key_func("z", [this]() { zoom_in(); }, "zoom in");
156 add_key_func("Z", [this]() { zoom_out(); }, "zoom out");
157 add_key_func("H", [this]() { home(); }, "home");
158
159 add_key_toggle("L", &lighting_, "light");
160 add_key_toggle("l", &edit_light_, "light edit");
161 add_key_toggle("F1", &clipping_, "clipping");
162 add_key_toggle("F2", &edit_clip_, "clip plane edit");
163 add_key_toggle("F3", &fixed_clip_, "fixed clip plane");
164 add_key_toggle("a", animate_ptr(), "animate");
165 add_key_toggle("F6", &text_editor_visible_, "text editor");
166 add_key_toggle("F7", &viewer_properties_visible_, "viewer properties");
167 add_key_toggle("F8", &object_properties_visible_, "object properties");
168 add_key_toggle("F9", &console_visible_, "console");
169 add_key_toggle("F12", &menubar_visible_, "menubar");
170
171 add_key_func("F5", replay_latest_command, "replay latest command");
172 add_key_func("F11", [this]() { snapshot("",true); },"snapshot");
173
174 set_region_of_interest(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
175
176 object_translation_ = vec3(0.0, 0.0, 0.0);
177
178 mouse_op_ = MOUSE_NOOP;
179 mouse_target_ = MOUSE_NOTARGET;
180
181 three_D_ = true;
182 zoom_ = 1.0;
183 zoom_down_ = 1.0;
184
185 #ifdef GEOGRAM_WITH_LUA
186 lua_error_occured_ = false;
187 lua_state_ = luaL_newstate();
188 luaL_openlibs(lua_state_);
189 init_lua_io(lua_state_);
190 init_lua_glup(lua_state_);
191 init_lua_simple_application(lua_state_);
192 init_lua_imgui(lua_state_);
193 #else
194 lua_error_occured_ = false;
195 lua_state_ = nullptr;
196 #endif
197 geo_cite_with_info(
198 "WEB:ImGUI",
199 "Used to create the GUI of GEOGRAM utilities "
200 "(vorpaview, geobox, geocod)."
201 );
202
203 // TODO: flip Y only in mouse callback
204 object_rotation_.set_y_inverted(true);
205 light_rotation_.set_y_inverted(true);
206 clip_rotation_.set_y_inverted(true);
207
208 props_pinned_ = false;
209 locked_ = false;
210 }
211
212 SimpleApplication::~SimpleApplication() {
213 #ifdef GEOGRAM_WITH_LUA
214 if(lua_state_ != nullptr) {
215 lua_close(lua_state_);
216 lua_state_ = nullptr;
217 }
218 #endif
219 }
220
221 void SimpleApplication::home() {
222 zoom_ = 1.0;
223 object_translation_ = vec3(0.0, 0.0, 0.0);
224 object_rotation_.reset();
225 light_rotation_.reset();
226 clip_rotation_.reset();
227 clip_translation_ = vec3(0.0, 0.0, 0.0);
228 clipping_ = false;
229 edit_clip_ = false;
230 fixed_clip_ = false;
231 edit_light_ = false;
232 }
233
234 void SimpleApplication::add_key_func(
235 const std::string& key, std::function<void()> cb,
236 const char* help
237 ) {
238 key_funcs_[key] = cb;
239 if(help != nullptr) {
240 key_funcs_help_[key] = help;
241 }
242 }
243
244 void SimpleApplication::add_key_toggle(
245 const std::string& key, bool* p_val,
246 const char* help
247 ) {
248 add_key_func(
249 key, [p_val]() { *p_val = !*p_val; },
250 (help != nullptr) ?
251 (std::string("toggle ") + help).c_str() : nullptr
252 );
253 }
254
255 void SimpleApplication::char_callback(unsigned int c){
256 Application::char_callback(c);
257 if(text_editor_visible_) {
258 return;
259 }
260 std::string k=" ";
261 k[0] = char(c);
262 auto F = key_funcs_.find(k);
263 if(F != key_funcs_.end()) {
264 F->second();
265 }
266 }
267
268 void SimpleApplication::key_callback(
269 int key, int scancode, int action, int mods
270 ) {
271 Application::key_callback(key, scancode, action, mods);
272 if(action == 0) {
273 auto F = key_funcs_.find(std::string(key_to_string(key)));
274 if(F != key_funcs_.end()) {
275 F->second();
276 }
277 }
278 }
279
280 void SimpleApplication::set_style(const std::string& style) {
281 Application::set_style(style);
282 if(String::string_starts_with(style, "Light") || style == "Polyscope") {
283 background_color_ = vec4f(1.0f, 1.0f, 1.0f, 1.0f);
284 } else {
285 background_color_ = vec4f(0.0f, 0.0f, 0.0f, 1.0f);
286 }
287 }
288
289 void SimpleApplication::set_region_of_interest(
290 double xmin, double ymin, double zmin,
291 double xmax, double ymax, double zmax
292 ) {
293 roi_.xyz_min[0] = xmin;
294 roi_.xyz_min[1] = ymin;
295 roi_.xyz_min[2] = zmin;
296 roi_.xyz_max[0] = xmax;
297 roi_.xyz_max[1] = ymax;
298 roi_.xyz_max[2] = zmax;
299 roi_radius_ = sqrt(
300 0.25 * (xmax - xmin) * (xmax - xmin) +
301 0.25 * (ymax - ymin) * (ymax - ymin) +
302 0.25 * (zmax - zmin) * (zmax - zmin)
303 );
304 }
305
306 void SimpleApplication::get_region_of_interest(
307 double& xmin, double& ymin, double& zmin,
308 double& xmax, double& ymax, double& zmax
309 ) const {
310 xmin = roi_.xyz_min[0];
311 ymin = roi_.xyz_min[1];
312 zmin = roi_.xyz_min[2];
313 xmax = roi_.xyz_max[0];
314 ymax = roi_.xyz_max[1];
315 zmax = roi_.xyz_max[2];
316 }
317
318 void SimpleApplication::draw_gui() {
319 #ifdef GEO_OS_ANDROID
320 if(
321 supported_read_file_extensions() != "" ||
322 supported_write_file_extensions() != ""
323 ) {
324 static bool firsttime = true;
325 static int nb_perms = 2;
326 static const char* perms[] = {
327 "READ_EXTERNAL_STORAGE",
328 "WRITE_EXTERNAL_STORAGE"
329 };
330 if(firsttime) {
331 firsttime = false;
332 bool OK = true;
333 for(int i=0; i<nb_perms; ++i) {
334 OK = OK && AndroidUtils::has_permission(
335 CmdLine::get_android_app(), perms[i]
336 );
337 }
338 if(!OK) {
339 AndroidUtils::request_permissions(
340 CmdLine::get_android_app(), nb_perms, perms
341 );
342 }
343 }
344 }
345 #endif
346 draw_menu_bar();
347 draw_dock_space();
348 draw_viewer_properties_window();
349 draw_object_properties_window();
350 draw_console();
351 draw_command_window();
352 draw_command_line_editor();
353 if(text_editor_visible_) {
354 text_editor_.draw();
355 }
356 if(
357 ImGui::FileDialog("##load_dlg", filename_, geo_imgui_string_length)
358 ) {
359 load(filename_);
360 }
361 if(
362 ImGui::FileDialog("##save_dlg", filename_, geo_imgui_string_length)
363 ) {
364 save(filename_);
365 }
366 if(status_bar_->active()) {
367 float w = float(get_frame_buffer_width());
368 float h = float(get_frame_buffer_height());
369 float STATUS_HEIGHT = status_bar_->get_window_height();
370 if(STATUS_HEIGHT == 0.0f) {
371 STATUS_HEIGHT = float(get_font_size());
372 if(phone_screen_) {
373 STATUS_HEIGHT *= std::max(w,h)/600.f;
374 }
375 }
376 STATUS_HEIGHT *= 1.5f;
377 ImGui::SetNextWindowPos(
378 ImVec2(0.0f, h-STATUS_HEIGHT),
379 ImGuiCond_Always
380 );
381 ImGui::SetNextWindowSize(
382 ImVec2(w,STATUS_HEIGHT-1.0f),
383 ImGuiCond_Always
384 );
385 status_bar_->draw();
386 }
387 }
388
389 void SimpleApplication::draw_scene_begin() {
390
391 glClearColor(
392 background_color_.x,
393 background_color_.y,
394 background_color_.z,
395 background_color_.w
396 );
397 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
398
399 glEnable(GL_DEPTH_TEST);
400 glDepthFunc(GL_LEQUAL);
401
402 glViewport(
403 0, 0,
404 int(double(get_frame_buffer_width())),
405 int(double(get_frame_buffer_height()))
406 );
407
408 double zScreen = 5.0; // screen projection plane
409 {
410 glupMatrixMode(GLUP_PROJECTION_MATRIX);
411 glupLoadIdentity();
412
413 double aspect = double(get_width()) / double(get_height());
414 double zNear = 1.0; // near clipping plane.
415 double zFar = 10.0; // far clipping plane.
416 if(three_D_) {
417 double camera_aperture = 9.0; // field of view in degrees.
418 double view_max_size = zScreen * tan(
419 (camera_aperture * M_PI / 180.0) / 2.0
420 );
421 double right;
422 double top;
423 if(aspect < 1.0) {
424 top = view_max_size;
425 right = top * aspect;
426 } else {
427 right = view_max_size;
428 top = right / aspect;
429 }
430 right /= zoom_;
431 top /= zoom_;
432 glupFrustum(-right, right, -top, top, zNear, zFar);
433 } else {
434 double x = 1.0 / zoom_;
435 double y = 1.0 / zoom_;
436 if(aspect > 1.0) {
437 x *= aspect;
438 } else {
439 y /= aspect;
440 }
441 glupOrtho(-x, x, -y, y, zNear, zFar);
442 }
443 }
444
445 static vec3 light0 = normalize(vec3(1.0, 1.0, 1.0));
446 vec3 light = vec3(vec4(light0,0.0) * light_rotation_.get_value());
447
448 glupLightVector3f(float(light.x), float(light.y), float(light.z));
449
450 glupMatrixMode(GLUP_MODELVIEW_MATRIX);
451 glupLoadIdentity();
452 glupTranslated(0.0, 0.0, -zScreen);
453
454 // Clipping
455 {
456 static double clip_eqn[4];
457 clip_eqn[0] = 0.0;
458 clip_eqn[1] = 0.0;
459 clip_eqn[2] = 0.0;
460 clip_eqn[3] = 0.0;
461 glupClipPlane(clip_eqn);
462 glupDisable(GLUP_CLIPPING);
463
464 if(clipping_) {
465 glupPushMatrix();
466 glupTranslated(
467 clip_translation_.x,
468 clip_translation_.y,
469 clip_translation_.z
470 );
471 glupMultMatrix(clip_rotation_.get_value());
472
473 {
474 if(effect_ != 0) {
475 glDepthMask(GL_FALSE);
476 }
477 glupSetMeshWidth(1.0f);
478 glupSetColor3f(
479 GLUP_FRONT_AND_BACK_COLOR,
480 1.0f - background_color_.x,
481 1.0f - background_color_.y,
482 1.0f - background_color_.z
483 );
484 float sq_w = 1.25f / float(zoom_);
485
486 GLboolean vertex_colors_save =
487 glupIsEnabled(GLUP_VERTEX_COLORS);
488 GLboolean texturing_save = glupIsEnabled(GLUP_TEXTURING);
489 glupDisable(GLUP_VERTEX_COLORS);
490 glupDisable(GLUP_TEXTURING);
491
492 // Draw the cross
493 glupBegin(GLUP_LINES);
494 glupVertex3f(-sq_w, 0.0f, 0.0f);
495 glupVertex3f(sq_w, 0.0f, 0.0f);
496 glupVertex3f(0.0f, -sq_w, 0.0f);
497 glupVertex3f(0.0f, sq_w, 0.0f);
498
499 // Draw the square around the cross
500 for(index_t i=0; i<3; ++i) {
501 glupVertex3f(sq_w, -sq_w, 0.0f);
502 glupVertex3f(sq_w, sq_w, 0.0f);
503 glupVertex3f(sq_w, sq_w, 0.0f);
504 glupVertex3f(-sq_w, sq_w, 0.0f);
505 glupVertex3f(-sq_w, sq_w, 0.0f);
506 glupVertex3f(-sq_w, -sq_w, 0.0f);
507 glupVertex3f(-sq_w, -sq_w, 0.0f);
508 glupVertex3f(sq_w, -sq_w, 0.0f);
509 sq_w = sq_w * 1.01f;
510 }
511 glupEnd();
512 if(vertex_colors_save) {
513 glupEnable(GLUP_VERTEX_COLORS);
514 }
515 if(texturing_save) {
516 glupEnable(GLUP_TEXTURING);
517 }
518 }
519 clip_eqn[0] = 0.0;
520 clip_eqn[1] = 0.0;
521 clip_eqn[2] = -1.0;
522 clip_eqn[3] = 0.0;
523 glupEnable(GLUP_CLIPPING);
524 glupClipPlane(clip_eqn);
525 glupClipMode(clip_mode_);
526 glupPopMatrix();
527 if(effect_ != 0) {
528 glDepthMask(GL_TRUE);
529 }
530 }
531 }
532
533 glupTranslate(object_translation_);
534 glupMultMatrix(object_rotation_.get_value());
535
536 glupScaled(
537 1.5 / roi_radius_, 1.5 / roi_radius_, 1.5 / roi_radius_
538 );
539 glupTranslated(
540 -0.5 * (roi_.xyz_min[0] + roi_.xyz_max[0]),
541 -0.5 * (roi_.xyz_min[1] + roi_.xyz_max[1]),
542 -0.5 * (roi_.xyz_min[2] + roi_.xyz_max[2])
543 );
544
545 if(lighting_) {
546 glupEnable(GLUP_LIGHTING);
547 } else {
548 glupDisable(GLUP_LIGHTING);
549 }
550
551 // Save transform for picking
552 {
553 glGetIntegerv(GL_VIEWPORT, viewport_);
554 // Note: OpenGL uses column-major order for matrices
555 // (thus what we get is the transpose of each matrix)
556 glupGetMatrixdv(GLUP_MODELVIEW_MATRIX, modelview_transpose_.data());
557 glupGetMatrixdv(GLUP_PROJECTION_MATRIX, project_transpose_.data());
558 }
559 }
560
561 vec3 SimpleApplication::project(const vec3& p) {
562 vec3 result;
563 glupProject(
564 p.x, p.y, p.z,
565 modelview_transpose_.data(), project_transpose_.data(), viewport_,
566 &result.x, &result.y, &result.z
567 );
568 return result;
569 }
570
571 vec3 SimpleApplication::unproject(const vec3& p) {
572 vec3 result;
573 glupUnProject(
574 p.x, p.y, p.z,
575 modelview_transpose_.data(), project_transpose_.data(), viewport_,
576 &result.x, &result.y, &result.z
577 );
578 return result;
579 }
580
581 vec2 SimpleApplication::unproject_2d(const vec2& p) {
582 double z = project(vec3(0.0, 0.0, 0.0)).z;
583 vec3 result3d = unproject(vec3(p.x, p.y, z));
584 return vec2(result3d.x, result3d.y);
585 }
586
587 void SimpleApplication::draw_scene_end() {
588 }
589
590 void SimpleApplication::draw_scene() {
591 }
592
593 void SimpleApplication::draw_graphics() {
594 if(!full_screen_effect_.is_null()) {
595 // Note: on retina, we use window resolution
596 // rather than frame buffer resolution
597 // (we do not need fullres for shadows)
598 full_screen_effect_->pre_render(
599 get_frame_buffer_width(), get_frame_buffer_height()
600 );
601 }
602 draw_scene_begin();
603 draw_scene();
604 draw_scene_end();
605 if(!full_screen_effect_.is_null()) {
606 full_screen_effect_->post_render();
607 }
608 }
609
610 void SimpleApplication::draw_viewer_properties_window() {
611 if(!viewer_properties_visible_) {
612 return;
613 }
614 if(ImGui::Begin(
615 (icon_UTF8("camera")+" Viewer").c_str(),
616 &viewer_properties_visible_)
617 ) {
618 draw_viewer_properties();
619 }
620 ImGui::End();
621 }
622
623 void SimpleApplication::draw_viewer_properties() {
624 if(phone_screen_ && get_height() >= get_width()) {
625 if(props_pinned_) {
626 if(ImGui::SimpleButton(
627 icon_UTF8("dot-circle") + "##props_pin"
628 )) {
629 props_pinned_ = !props_pinned_;
630 }
631 } else {
632 if(ImGui::SimpleButton(
633 icon_UTF8("thumbtack") + "##props_pin"
634 )) {
635 props_pinned_ = !props_pinned_;
636 }
637 }
638 ImGui::SameLine();
639 }
640
641 if(ImGui::Button(
642 (icon_UTF8("home")).c_str(),
643 ImVec2(-1.0, 0.0)
644 )) {
645 home();
646 }
647 ImGui::Separator();
648 ImGui::Checkbox("Animate", animate_ptr());
649 if(three_D_) {
650 ImGui::Checkbox("Lighting", &lighting_);
651 if(lighting_) {
652 ImGui::Checkbox("Edit light", &edit_light_);
653 }
654 ImGui::Separator();
655 ImGui::Checkbox("Clipping", &clipping_);
656 if(clipping_) {
657 ImGui::Combo(
658 "##mode", (int*)&clip_mode_,
659 "std. GL\0cells\0stradd.\0slice\0\0"
660 );
661 ImGui::Checkbox(
662 "edit clip", &edit_clip_
663 );
664 ImGui::Checkbox(
665 "fixed clip", &fixed_clip_
666 );
667 }
668 ImGui::Separator();
669 }
670 ImGui::ColorEdit3WithPalette("Backgnd", background_color_.data());
671 // Full-screen effects are for now deactivated under Android:
672 // SSAO has stripes (and is damned slow)
673 // unsharp masking gives a black screen
674 if(!phone_screen_) {
675 if(three_D_) {
676 if(ImGui::Combo(
677 "sfx",
678 (int*)&effect_,
679 "none\0SSAO\0cartoon\0\0"
680 )) {
681 switch(effect_) {
682 case 0:
683 full_screen_effect_.reset();
684 break;
685 case 1:
686 full_screen_effect_ = new AmbientOcclusionImpl();
687 break;
688 case 2:
689 full_screen_effect_ = new UnsharpMaskingImpl();
690 break;
691 }
692 }
693 }
694 }
695 }
696
697 void SimpleApplication::draw_object_properties_window() {
698 if(!object_properties_visible_) {
699 return;
700 }
701 if(ImGui::Begin(
702 (icon_UTF8("edit")+" Object").c_str(),
703 &object_properties_visible_)
704 ) {
705 draw_object_properties();
706 }
707 ImGui::End();
708 }
709
710 void SimpleApplication::draw_object_properties() {
711 }
712
713 void SimpleApplication::draw_command_window() {
714 if(Command::current() == nullptr) {
715 return;
716 }
717 if(!Command::current()->is_visible()) {
718 Command::reset_current();
719 return;
720 }
721 if(ImGui::Begin(
722 "Command",
723 Command::current()->is_visible_ptr()
724 )) {
725 Command::current()->draw();
726 }
727 ImGui::End();
728 }
729
730 void SimpleApplication::draw_console() {
731 console_->draw(&console_visible_);
732 }
733
734 void SimpleApplication::draw_menu_bar() {
735 if(!menubar_visible_) {
736 return;
737 }
738
739 if(phone_screen_) {
740 if(ImGui::BeginMainMenuBar()) {
741
742 float w = ImGui::GetContentRegionAvail().x;
743 if(ImGui::BeginMenu(icon_UTF8("ellipsis-v"))) {
744 draw_application_menus();
745 draw_fileops_menu();
746 draw_about();
747 ImGui::EndMenu();
748 }
749 w -= ImGui::GetContentRegionAvail().x; // gets btn size
750
751 ImGui::Dummy(ImVec2(0.5f*w, 1.0));
752
753 if(supported_read_file_extensions() != "") {
754 if(ImGui::SimpleButton(
755 icon_UTF8("folder-open") + "##menubar_open")
756 ) {
757 ImGui::OpenFileDialog(
758 "##load_dlg",
759 supported_read_file_extensions().c_str(),
760 filename_,
761 ImGuiExtFileDialogFlags_Load
762 );
763 }
764 }
765
766 if(supported_write_file_extensions() != "") {
767 if(ImGui::SimpleButton(
768 icon_UTF8("save") + "##menubar_save")
769 ) {
770 ImGui::OpenFileDialog(
771 "##save_dlg",
772 supported_write_file_extensions().c_str(),
773 filename_,
774 ImGuiExtFileDialogFlags_Save
775 );
776 }
777 }
778
779 draw_application_icons();
780
781 if(use_text_editor_) {
782 ImGui::Dummy(ImVec2(0.5f*w, 1.0));
783 if(ImGui::SimpleButton(icon_UTF8("keyboard"))) {
784 #ifdef GEO_OS_ANDROID
785 AndroidUtils::show_soft_keyboard(
786 CmdLine::get_android_app()
787 );
788 #endif
789 }
790 }
791
792 ImGui::Dummy(ImVec2(ImGui::GetContentRegionAvail().x - w, 1.0));
793
794 if(ImGui::BeginMenu(icon_UTF8("bars"))) {
795 draw_windows_menu();
796 ImGui::EndMenu();
797 }
798
799 ImGui::EndMainMenuBar();
800 }
801 return;
802 }
803
804 if(ImGui::BeginMainMenuBar()) {
805 if(ImGui::BeginMenu("File")) {
806 if(supported_read_file_extensions() != "") {
807 draw_load_menu();
808 }
809 #ifndef GEO_OS_EMSCRIPTEN
810 if(current_file_ != "") {
811 if(ImGui::MenuItem(icon_UTF8("save") + " Save")) {
812 if(save(current_file_)) {
813 Logger::out("I/O") << "Saved "
814 << current_file_ << std::endl;
815 } else {
816 Logger::out("I/O") << "Could not save "
817 << current_file_ << std::endl;
818 }
819 }
820 }
821 #endif
822 if(supported_write_file_extensions() != "") {
823 draw_save_menu();
824 }
825 draw_fileops_menu();
826 ImGui::Separator();
827 if(
828 ImGui::MenuItem(
829 icon_UTF8("camera") + " snapshot",
830 "[F11]", false, true
831 )
832 ) {
833 this->snapshot("",true);
834 }
835 #ifndef GEO_OS_EMSCRIPTEN
836 ImGui::Separator();
837 if(
838 ImGui::MenuItem(
839 icon_UTF8("door-open") + " quit",
840 "[q]", false, true
841 )
842 ) {
843 this->stop();
844 }
845 #endif
846 draw_about();
847 // Key shortcuts not really relevant on Android
848 if(!phone_screen_) {
849 draw_help();
850 }
851 ImGui::EndMenu();
852 }
853 if(ImGui::BeginMenu("Windows")) {
854 draw_windows_menu();
855 ImGui::EndMenu();
856 }
857 draw_application_menus();
858
859 ImGui::EndMainMenuBar();
860 }
861 }
862
863 void SimpleApplication::draw_load_menu() {
864 #ifdef GEO_OS_EMSCRIPTEN
865 ImGui::Text("To load a file,");
866 ImGui::Text("use the \"Browse\"");
867 ImGui::Text("button on the top");
868 ImGui::Text("(or \"recent files\"");
869 ImGui::Text("below)");
870 ImGui::Separator();
871 if(ImGui::BeginMenu("Recent files...")) {
872 browse(path_);
873 ImGui::EndMenu();
874 }
875 #else
876 if(ImGui::MenuItem(icon_UTF8("folder-open") + " Load...")) {
877 ImGui::OpenFileDialog(
878 "##load_dlg",
879 supported_read_file_extensions().c_str(),
880 filename_,
881 ImGuiExtFileDialogFlags_Load
882 );
883 }
884 #endif
885 }
886
887 void SimpleApplication::draw_save_menu() {
888 #ifdef GEO_OS_EMSCRIPTEN
889 if(ImGui::BeginMenu(icon_UTF8("save") + " Save as...")) {
890 ImGui::MenuItem("Supported extensions:", nullptr, false, false);
891 std::vector<std::string> extensions;
892 String::split_string(
893 supported_write_file_extensions(), ';', extensions
894 );
895 for(index_t i=0; i<extensions.size(); ++i) {
896 ImGui::MenuItem(
897 " ." + extensions[i], nullptr, false, false
898 );
899 }
900 ImGui::Separator();
901 static char buff[geo_imgui_string_length];
902 if(current_file_ != "") {
903 strcpy(buff, current_file_.c_str());
904 } else if (extensions.size() != 0) {
905 strcpy(buff, ("out." + extensions[0]).c_str());
906 }
907
908 if(ImGui::InputText(
909 "##MenuFileName",buff,geo_imgui_string_length,
910 ImGuiInputTextFlags_EnterReturnsTrue)
911 ) {
912 current_file_ = buff;
913 if(String::string_starts_with(current_file_, "/")) {
914 current_file_ = current_file_.substr(
915 1,current_file_.length()-1
916 );
917 }
918 if(save(current_file_)) {
919 std::string command =
920 "saveFileFromMemoryFSToDisk(\'" +
921 current_file_ +
922 "\');" ;
923 emscripten_run_script(command.c_str());
924 }
925 }
926 ImGui::EndMenu();
927 }
928 #else
929 if(ImGui::MenuItem(icon_UTF8("save") + " Save as...")) {
930 ImGui::OpenFileDialog(
931 "##save_dlg",
932 supported_write_file_extensions().c_str(),
933 filename_,
934 ImGuiExtFileDialogFlags_Save
935 );
936 }
937 #endif
938 }
939
940 void SimpleApplication::draw_fileops_menu() {
941 }
942
943 void SimpleApplication::draw_about() {
944 ImGui::Separator();
945 if(ImGui::BeginMenu(icon_UTF8("info") + " About...")) {
946 ImGui::Text("%s : a GEOGRAM application", name().c_str());
947 float sz = float(280.0 * std::min(scaling(), 2.0));
948 ImGui::Image(
949 static_cast<ImTextureID>(geogram_logo_texture_),
950 ImVec2(sz, sz)
951 );
952 ImGui::Text("\n");
953 ImGui::Separator();
954 ImGui::Text("\n");
955 ImGui::Text("GEOGRAM website: ");
956 ImGui::Text("https://github.com/BrunoLevy/geogram");
957
958 ImGui::Text("\n");
959 ImGui::Separator();
960 ImGui::Text(
961 "%s",
962 (
963 "GEOGRAM version:" +
964 Environment::instance()->get_value("version")
965 ).c_str()
966 );
967 ImGui::EndMenu();
968 }
969 }
970
971 void SimpleApplication::draw_help() {
972 if(ImGui::BeginMenu(icon_UTF8("circle-question") + " help")) {
973 ImGui::Text("Key shortcuts");
974 ImGui::Separator();
975 std::vector<std::string> helps;
976 for(auto it: key_funcs_help_) {
977 helps.push_back(it.first + " : " + it.second);
978 }
979 std::sort(helps.begin(), helps.end());
980 for(const std::string& s: helps) {
981 ImGui::Text("%s",s.c_str());
982 }
983 ImGui::EndMenu();
984 }
985 }
986
987 void SimpleApplication::draw_windows_menu() {
988 if(phone_screen_) {
989 ImGui::MenuItem(
990 " " + icon_UTF8("window-restore") + " Windows",
991 nullptr,
992 false, false
993 );
994 }
995 if(use_text_editor_) {
996 ImGui::MenuItem(
997 icon_UTF8("code") + " text editor",
998 phone_screen_ ? nullptr : "[F6]",
999 &text_editor_visible_
1000 );
1001 }
1002 ImGui::MenuItem(
1003 icon_UTF8("eye") + " viewer properties",
1004 phone_screen_ ? nullptr : "[F7]",
1005 &viewer_properties_visible_
1006 );
1007 ImGui::MenuItem(
1008 icon_UTF8("edit") + " object properties",
1009 phone_screen_ ? nullptr : "[F8]",
1010 &object_properties_visible_
1011 );
1012 ImGui::MenuItem(
1013 icon_UTF8("terminal") + " console",
1014 phone_screen_ ? nullptr : "[F9]",
1015 &console_visible_
1016 );
1017 if(!phone_screen_) {
1018 ImGui::MenuItem(
1019 icon_UTF8("bars") + " menubar", "[F12]", &menubar_visible_
1020 );
1021 }
1022 ImGui::Separator();
1023
1024 {
1025 bool needs_to_close = false;
1026 if(phone_screen_) {
1027 ImGui::MenuItem(
1028 " " + icon_UTF8("font") + " Font size",
1029 nullptr,
1030 false, false
1031 );
1032 } else {
1033 needs_to_close =
1034 ImGui::BeginMenu(icon_UTF8("font") + " Font size");
1035 }
1036
1037 if(phone_screen_ || needs_to_close) {
1038 static index_t font_sizes[] = {10, 12, 14, 16, 18, 22};
1039 for(index_t i=0; i<sizeof(font_sizes)/sizeof(int); ++i) {
1040 bool selected = (get_font_size() == font_sizes[i]);
1041 if(ImGui::MenuItem(
1042 String::to_string(font_sizes[i]),
1043 nullptr,
1044 &selected
1045 )) {
1046 set_font_size(font_sizes[i]);
1047 }
1048 }
1049 if(needs_to_close) {
1050 ImGui::EndMenu();
1051 }
1052 }
1053 }
1054 if(phone_screen_) {
1055 ImGui::Separator();
1056 }
1057 {
1058 bool needs_to_close = false;
1059 if(phone_screen_) {
1060 ImGui::MenuItem(
1061 " " + icon_UTF8("palette") + " Style",
1062 nullptr,
1063 false, false
1064 );
1065 } else {
1066 needs_to_close = ImGui::BeginMenu(icon_UTF8("cog") + " Style");
1067 }
1068 if(phone_screen_ || needs_to_close) {
1069 std::vector<std::string> styles;
1070 String::split_string(get_styles(), ';', styles);
1071 for(index_t i=0; i<styles.size(); ++i) {
1072 bool selected = (get_style() == styles[i]);
1073 if(ImGui::MenuItem(styles[i], nullptr, &selected)) {
1074 set_style(styles[i]);
1075 }
1076 }
1077 if(needs_to_close) {
1078 ImGui::EndMenu();
1079 }
1080 }
1081 }
1082 ImGui::Separator();
1083 if(ImGui::MenuItem(icon_UTF8("undo") + " Restore layout")) {
1084 set_default_layout();
1085 }
1086 if(CmdLine::get_arg_bool("gui:expert")) {
1087 if(ImGui::MenuItem("Test android vertical layout")) {
1088 set_gui_state(default_layout_android_vertical());
1089 }
1090 if(ImGui::MenuItem("Test android horizontal layout")) {
1091 set_gui_state(default_layout_android_horizontal());
1092 }
1093 if(ImGui::MenuItem("Export gui state to C++")) {
1094 std::string filename =
1095 FileSystem::get_current_working_directory() +
1096 "/gui_state.h";
1097 Logger::out("GUI")
1098 << "Exporting current GUI state to C++ file: "
1099 << filename
1100 << std::endl;
1101 std::string state = get_gui_state();
1102 std::ofstream out("gui_state.h");
1103 out << "// Serialized ImGui windows docking configuration"
1104 << std::endl;
1105 out << "// generated using <geogram_program> gui:expert=true"
1106 << std::endl;
1107 out << "// then Windows->Export gui state to C++"
1108 << std::endl;
1109 out << "const unsigned char gui_state[] = {";
1110 for(size_t i=0; i<state.length(); ++i) {
1111 if((i%10) == 0) {
1112 out << std::endl;
1113 }
1114 int x = int((unsigned char)state[i]);
1115 out << x << ",";
1116 }
1117 out << " 0 };" << std::endl;
1118 }
1119 }
1120 }
1121
1122 void SimpleApplication::set_default_layout() {
1123 set_gui_state(default_layout());
1124 }
1125
1126 const char* SimpleApplication::default_layout_android_vertical() const {
1127 return (const char*)gui_state_v;
1128 }
1129
1130 const char* SimpleApplication::default_layout_android_horizontal() const {
1131 return (const char*)gui_state_h;
1132 }
1133
1134 void SimpleApplication::command_line_arg_changed(
1135 const std::string& argname
1136 ) {
1137 geo_argused(argname);
1138 }
1139
1140 const char* SimpleApplication::default_layout() const {
1141 const char* result = nullptr;
1142 if(phone_screen_) {
1143 if(get_height() >= get_width()) {
1144 result = default_layout_android_vertical();
1145 } else {
1146 result = default_layout_android_horizontal();
1147 }
1148 } else {
1149 result = (const char*)gui_state;
1150 }
1151 return result;
1152 }
1153
1154
1155 void SimpleApplication::resize(
1156 index_t w, index_t h, index_t fb_w, index_t fb_h
1157 ) {
1158 Application::resize(w,h,fb_w,fb_h);
1159 if(phone_screen_) {
1160 set_default_layout();
1161 }
1162 }
1163
1164 void SimpleApplication::draw_application_menus() {
1165 }
1166
1167 void SimpleApplication::draw_application_icons() {
1168 if(phone_screen_) {
1169 if(ImGui::SimpleButton(icon_UTF8("sliders-h"))) {
1170 if(object_properties_visible_) {
1171 object_properties_visible_ = false;
1172 viewer_properties_visible_ = false;
1173 } else {
1174 object_properties_visible_ = true;
1175 viewer_properties_visible_ = true;
1176 }
1177 }
1178 }
1179 }
1180
1181 void SimpleApplication::draw_command_line_editor() {
1182 if(!command_line_editor_visible_) {
1183 return;
1184 }
1185 ImGui::SetNextWindowSize(ImVec2(600,400),ImGuiCond_FirstUseEver);
1186 ImGui::Begin("Parameters...",&command_line_editor_visible_);
1187 std::vector<std::string> groups;
1188 CmdLine::get_arg_groups(groups);
1189 ImGui::BeginTabBar("##tabs");
1190 for(auto group: groups) {
1191 if(ImGui::BeginTabItem(group.c_str())) {
1192 std::vector<std::string> args;
1193 CmdLine::get_arg_names_in_group(group, args);
1194 for(auto full_arg_name: args) {
1195 std::string arg_name = full_arg_name;
1196 std::string help = CmdLine::get_arg_desc(full_arg_name);
1197 if(String::string_starts_with(arg_name, group + ":")) {
1198 arg_name = arg_name.substr(group.length()+1);
1199 }
1200 CmdLine::ArgType type = CmdLine::get_arg_type(full_arg_name);
1201 switch(type) {
1202 case CmdLine::ARG_UNDEFINED: {
1203 } break;
1204 case CmdLine::ARG_INT: {
1205 ImGui::Text("%s",arg_name.c_str());
1206 ImGui::SameLine();
1207 ImGui::SetNextItemWidth(-1.0f);
1208 int val = CmdLine::get_arg_int(full_arg_name);
1209 if(ImGui::InputInt(
1210 ("##" + arg_name + "##" + group).c_str(), &val
1211 )) {
1212 CmdLine::set_arg(full_arg_name, val);
1213 }
1214 ImGui::Tooltip(help);
1215 } break;
1216 case CmdLine::ARG_DOUBLE: {
1217 ImGui::Text("%s",arg_name.c_str());
1218 ImGui::SameLine();
1219 ImGui::SetNextItemWidth(-1.0f);
1220 double val = CmdLine::get_arg_double(full_arg_name);
1221 if(ImGui::InputDouble(
1222 ("##" + arg_name + "##" + group).c_str(), &val
1223 )) {
1224 CmdLine::set_arg(full_arg_name, val);
1225 }
1226 ImGui::Tooltip(help);
1227 } break;
1228 case CmdLine::ARG_STRING: {
1229 std::string val = CmdLine::get_arg(full_arg_name);
1230 ImGui::Text("%s",arg_name.c_str());
1231 ImGui::SameLine();
1232 ImGui::SetNextItemWidth(-1.0f);
1233 ImGui::Tooltip(help);
1234 if(
1235 ImGui::InputText(("##" + full_arg_name).c_str(),&val)
1236 ) {
1237 CmdLine::set_arg(full_arg_name, val);
1238 }
1239 ImGui::Tooltip(help);
1240 } break;
1241 case CmdLine::ARG_BOOL: {
1242 bool val = CmdLine::get_arg_bool(full_arg_name);
1243 if(ImGui::Checkbox(
1244 (arg_name + "##" + group).c_str(), &val
1245 )) {
1246 CmdLine::set_arg(full_arg_name, val);
1247 }
1248 ImGui::Tooltip(help);
1249 } break;
1250 case CmdLine::ARG_PERCENT: {
1251 ImGui::Text("%s",arg_name.c_str());
1252 ImGui::SameLine();
1253 ImGui::SetNextItemWidth(-1.0f);
1254 double val = CmdLine::get_arg_percent(
1255 full_arg_name,100.0
1256 );
1257 if(ImGui::InputDouble(
1258 ("##" + arg_name + "##" + group).c_str(), &val
1259 )) {
1260 CmdLine::set_arg(full_arg_name, val);
1261 }
1262 ImGui::Tooltip(help + " (percent)");
1263 } break;
1264 }
1265 }
1266 ImGui::EndTabItem();
1267 }
1268 }
1269 ImGui::EndTabBar();
1270 ImGui::End();
1271 }
1272
1273 void SimpleApplication::post_draw() {
1274 if(locked_ || Command::queued() == nullptr) {
1275 return;
1276 }
1277 locked_ = true;
1278 Logger::out("Command") << Command::queued()->name() << " start..."
1279 << std::endl;
1280 instance()->draw(); // so that we can see the logger message
1281 Stopwatch W("Command");
1282 Command::flush_queue();
1283 locked_ = false;
1284 }
1285
1286 void SimpleApplication::mouse_button_callback(
1287 int button, int action, int mods, int source
1288 ) {
1289 geo_argused(mods);
1290
1291 // Hide object and viewer properties if in phone
1292 // mode and user clicked elsewhere.
1293 if(phone_screen_ &&
1294 !ImGui::GetIO().WantCaptureMouse &&
1295 get_height() >= get_width()
1296 ) {
1297 if(!props_pinned_) {
1298 object_properties_visible_ = false;
1299 viewer_properties_visible_ = false;
1300 }
1301 }
1302
1303
1304 // Swap "buttons" if using fingers (it is more
1305 // natural to do the rotation with one finger,
1306 // zoom with two fingers and then translation)
1307 // Same thing if editing light and event source
1308 // is stylus.
1309 if(
1310 source == EVENT_SOURCE_FINGER ||
1311 (lighting_ && source == EVENT_SOURCE_STYLUS && edit_light_)
1312 ) {
1313 if(button == 0) {
1314 button = 1;
1315 } else if(button == 1) {
1316 button = 0;
1317 }
1318 }
1319
1320 if(action == EVENT_ACTION_DOWN) {
1321 mouse_down_xy_ = mouse_xy_;
1322 if(button == 1) {
1323 if(three_D_) {
1324 mouse_op_ = MOUSE_ROTATE;
1325 } else {
1326 mouse_op_ = MOUSE_TRANSLATE;
1327 }
1328 } else if(button == 0) {
1329 mouse_op_ = MOUSE_TRANSLATE;
1330 } else if(button == 2) {
1331 mouse_op_ = MOUSE_ZOOM;
1332 zoom_down_ = zoom_;
1333 }
1334 if(clipping_ && edit_clip_) {
1335 mouse_target_ = MOUSE_CLIP;
1336 } else if(lighting_ && edit_light_) {
1337 mouse_target_ = MOUSE_LIGHT;
1338 } else {
1339 mouse_target_ = MOUSE_OBJECT;
1340 }
1341 if(three_D_ && mouse_op_ == MOUSE_ROTATE) {
1342 switch(mouse_target_) {
1343 case MOUSE_NOTARGET:
1344 break;
1345 case MOUSE_OBJECT:
1346 object_rotation_.grab(mouse_xy_);
1347 if(fixed_clip_) {
1348 clip_rotation_.grab(mouse_xy_);
1349 }
1350 break;
1351 case MOUSE_LIGHT:
1352 light_rotation_.grab(mouse_xy_);
1353 break;
1354 case MOUSE_CLIP:
1355 clip_rotation_.grab(mouse_xy_);
1356 break;
1357 }
1358 }
1359 } else if(action == EVENT_ACTION_UP) {
1360 if(three_D_ && mouse_op_ == MOUSE_ROTATE) {
1361 switch(mouse_target_) {
1362 case MOUSE_NOTARGET:
1363 break;
1364 case MOUSE_OBJECT:
1365 object_rotation_.release(mouse_xy_);
1366 if(fixed_clip_) {
1367 clip_rotation_.release(mouse_xy_);
1368 }
1369 break;
1370 case MOUSE_LIGHT:
1371 light_rotation_.release(mouse_xy_);
1372 break;
1373 case MOUSE_CLIP:
1374 clip_rotation_.release(mouse_xy_);
1375 break;
1376 }
1377 }
1378 mouse_op_ = MOUSE_NOOP;
1379 mouse_target_ = MOUSE_NOTARGET;
1380 }
1381 }
1382
1383 void SimpleApplication::cursor_pos_callback(
1384 double x, double y, int source
1385 ) {
1386 geo_argused(source);
1387 mouse_xy_ = vec2(
1388 double(x) / double(get_width()),
1389 double(y) / double(get_height())
1390 );
1391 mouse_xy_ *= 2.0;
1392 mouse_xy_ -= vec2(1.0, 1.0);
1393 if(three_D_ && mouse_op_ == MOUSE_ROTATE) {
1394 switch(mouse_target_) {
1395 case MOUSE_NOTARGET:
1396 break;
1397 case MOUSE_OBJECT:
1398 object_rotation_.drag(mouse_xy_);
1399 if(fixed_clip_) {
1400 clip_rotation_.drag(mouse_xy_);
1401 }
1402 break;
1403 case MOUSE_LIGHT:
1404 light_rotation_.drag(mouse_xy_);
1405 break;
1406 case MOUSE_CLIP:
1407 clip_rotation_.drag(mouse_xy_);
1408 break;
1409 }
1410 } else if(mouse_op_ == MOUSE_ZOOM && mouse_target_ == MOUSE_OBJECT) {
1411 double R = mouse_xy_.y - mouse_down_xy_.y;
1412 double fact = (1.0 + R);
1413 fact = std::min(fact, 2.0);
1414 fact = std::max(fact, 0.1);
1415 zoom_ = zoom_down_ * fact;
1416 } else if( mouse_op_ == MOUSE_TRANSLATE) {
1417 double dx = mouse_xy_.x - mouse_down_xy_.x;
1418 double dy = mouse_xy_.y - mouse_down_xy_.y;
1419 if(mouse_target_ == MOUSE_OBJECT) {
1420 object_translation_.x += 2.0 * dx / zoom_;
1421 object_translation_.y -= 2.0 * dy / zoom_;
1422 if(fixed_clip_) {
1423 clip_translation_.x += 2.0 * dx / zoom_;
1424 clip_translation_.y -= 2.0 * dy / zoom_;
1425 }
1426 } else if(mouse_target_ == MOUSE_CLIP) {
1427 clip_translation_.x += 2.0 * dx / zoom_;
1428 clip_translation_.y -= 2.0 * dy / zoom_;
1429 }
1430 mouse_down_xy_ = mouse_xy_;
1431 }
1432 }
1433
1434 void SimpleApplication::scroll_callback(double xoffset, double yoffset) {
1435 geo_argused(xoffset);
1436 double dy = -40.0*double(yoffset) / double(get_height());
1437 zoom_ *= (1.0 + dy);
1438 }
1439
1440 bool SimpleApplication::save(const std::string& filename) {
1441 Logger::warn("GLUP")
1442 << "Could not save " << filename << std::endl;
1443 Logger::warn("GLUP")
1444 << "SimpleApplication::save() needs to be overloaded"
1445 << std::endl;
1446 return false;
1447 }
1448
1449 bool SimpleApplication::load(const std::string& filename) {
1450 Logger::warn("GLUP")
1451 << "Could not load " << filename << std::endl;
1452 Logger::warn("GLUP")
1453 << "SimpleApplication::load() needs to be overloaded"
1454 << std::endl;
1455 return false;
1456 }
1457
1458 bool SimpleApplication::can_load(const std::string& filename) {
1459 std::string extensions_str = supported_read_file_extensions();
1460 if(extensions_str == "") {
1461 return false;
1462 }
1463 if(extensions_str == "*") {
1464 return true;
1465 }
1466 std::string extension = FileSystem::extension(filename);
1467 std::vector<std::string> extensions;
1468 String::split_string(extensions_str, ';', extensions);
1469 for(index_t i=0; i<extensions.size(); ++i) {
1470 if(extensions[i] == extension) {
1471 return true;
1472 }
1473 }
1474 return false;
1475 }
1476
1477 std::string SimpleApplication::supported_read_file_extensions() {
1478 return "";
1479 }
1480
1481 std::string SimpleApplication::supported_write_file_extensions() {
1482 return "";
1483 }
1484
1485 void SimpleApplication::GL_initialize() {
1486 Application::GL_initialize();
1487 glGenTextures(1, &geogram_logo_texture_);
1488 glActiveTexture(GL_TEXTURE0 + GLUP_TEXTURE_2D_UNIT);
1489 glBindTexture(GL_TEXTURE_2D, geogram_logo_texture_);
1490 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1491 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1492 glTexImage2Dxpm(geogram_logo_256_xpm);
1493 glEnable(GL_POLYGON_OFFSET_FILL);
1494 glPolygonOffset (1.,1.);
1495
1496 init_colormaps();
1497 std::string keys = CmdLine::get_arg("gfx:keypress");
1498 for(index_t i=0; i<index_t(keys.length()); ++i) {
1499 char_callback((unsigned int)(keys[i]));
1500 }
1501 }
1502
1503 void SimpleApplication::GL_terminate() {
1504 for(index_t i=0; i<colormaps_.size(); ++i) {
1505 if(colormaps_[i].texture != 0) {
1506 glDeleteTextures(1, &colormaps_[i].texture);
1507 colormaps_[i].texture = 0;
1508 }
1509 }
1510 colormaps_.resize(0);
1511 if(geogram_logo_texture_ != 0) {
1512 glDeleteTextures(1, &geogram_logo_texture_);
1513 geogram_logo_texture_ = 0;
1514 }
1515 Application::GL_terminate();
1516 }
1517
1518
1519 void SimpleApplication::browse(const std::string& path, bool subdirs) {
1520 std::vector<std::string> files;
1521 FileSystem::get_directory_entries(path,files);
1522
1523 for(index_t i=0; i<files.size(); ++i) {
1524 if(FileSystem::is_directory(files[i]) && subdirs) {
1525 if(ImGui::BeginMenu(path_to_label(path_,files[i]))) {
1526 browse(files[i]);
1527 ImGui::EndMenu();
1528 }
1529 } else {
1530 if(can_load(files[i])) {
1531 if(ImGui::MenuItem(path_to_label(path_,files[i]))) {
1532 load(files[i]);
1533 }
1534 }
1535 }
1536 }
1537 }
1538
1539 void SimpleApplication::geogram_initialize(int argc, char** argv) {
1540 GEO::Application::geogram_initialize(argc, argv);
1541 if(filenames().size() == 1 &&
1542 FileSystem::is_directory(filenames()[0])
1543 ) {
1544 path_ = filenames()[0];
1545 } else if(filenames().size() > 0) {
1546 for(index_t i=0; i<filenames().size(); ++i) {
1547 load(filenames()[i]);
1548 }
1549 if(filenames().size() > 0) {
1550 path_ = FileSystem::dir_name(filenames()[filenames().size()-1]);
1551 }
1552 } else {
1553 path_ = FileSystem::documents_directory();
1554 }
1555 Logger::instance()->register_client(console_);
1556 Progress::set_client(status_bar_);
1557 set_default_layout();
1558 if(phone_screen_) {
1559 object_properties_visible_ = false;
1560 viewer_properties_visible_ = false;
1561 }
1562 }
1563
1564 void SimpleApplication::init_colormap(
1565 const std::string& name, const char** xpm_data
1566 ) {
1567 colormaps_.push_back(ColormapInfo());
1568 colormaps_.rbegin()->name = name;
1569 glGenTextures(1, &colormaps_.rbegin()->texture);
1570 glBindTexture(GL_TEXTURE_2D, colormaps_.rbegin()->texture);
1571 glTexImage2Dxpm(xpm_data);
1572 glGenerateMipmap(GL_TEXTURE_2D);
1573 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1574 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1575 glTexParameteri(
1576 GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR
1577 );
1578 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1579 glBindTexture(GL_TEXTURE_2D, 0);
1580 }
1581
1582 void SimpleApplication::init_colormaps() {
1583 init_colormap("french", french_xpm);
1584 init_colormap("black_white", black_white_xpm);
1585 init_colormap("viridis", viridis_xpm);
1586 init_colormap("rainbow", rainbow_xpm);
1587 init_colormap("cei_60757", cei_60757_xpm);
1588 init_colormap("inferno", inferno_xpm);
1589 init_colormap("magma", magma_xpm);
1590 init_colormap("parula", parula_xpm);
1591 init_colormap("plasma", plasma_xpm);
1592 init_colormap("blue_red", blue_red_xpm);
1593 }
1594
1595 bool SimpleApplication::exec_command(const char* command) {
1596 #ifdef GEOGRAM_WITH_LUA
1597 if(luaL_dostring(lua_state_,command)) {
1598 adjust_lua_glup_state(lua_state_);
1599 const char* msg = lua_tostring(lua_state_,-1);
1600 const char* msg2 = strchr(msg,']');
1601 if(msg2 != nullptr) {
1602 msg = msg2+2;
1603 }
1604 Logger::err("LUA") << "line " << msg << std::endl;
1605 lua_error_occured_ = true;
1606 } else {
1607 lua_error_occured_ = false;
1608 }
1609 return !lua_error_occured_;
1610 #else
1611 geo_argused(command);
1612 Logger::err("LUA") << "Compiled without LUA support"
1613 << std::endl;
1614 return false;
1615 #endif
1616 }
1617
1618 void SimpleApplication::ImGui_initialize() {
1619 if(phone_screen_ && !ImGui_firsttime_init_) {
1620 console_visible_ = false;
1621 // Tooltips do not play well with touch screens.
1622 ImGui::DisableTooltips();
1623 #ifndef GEO_OS_ANDROID
1624 set_default_layout();
1625 #endif
1626 }
1627 CmdLine::set_arg("gui:style","Light");
1628 Application::ImGui_initialize();
1629 }
1630
1631 void SimpleApplication::drop_callback(int nb, const char** f) {
1632 for(int i=0; i<nb; ++i) {
1633 load(f[i]);
1634 }
1635 }
1636
1637 void SimpleApplication::replay_latest_command() {
1638 if(instance()->locked_ || Command::latest() == nullptr) {
1639 return;
1640 }
1641 instance()->locked_ = true;
1642 Logger::out("Command") << Command::latest()->name() << " start..."
1643 << std::endl;
1644 instance()->draw(); // so that we can see the logger message
1645 Stopwatch W("Command");
1646 Command::replay_latest();
1647 instance()->locked_ = false;
1648 }
1649
1650
1651 void SimpleApplication::snapshot(
1652 Image* image,
1653 index_t x0, index_t y0, index_t width, index_t height,
1654 bool redraw
1655 ) {
1656 #ifdef GEO_OS_EMSCRIPTEN
1657 // GL_PACK_ROW_LENGTH undefined under emscripten
1658 // (to be fixed, deactivated for now)
1659 geo_argused(image);
1660 geo_argused(x0);
1661 geo_argused(y0);
1662 geo_argused(width);
1663 geo_argused(height);
1664 geo_argused(redraw);
1665 #else
1666 if(redraw) {
1667 one_frame(false); // false: do not draw GUI
1668 }
1669
1670 // hidpi_scaling to take "logical pixels" into account on MacOS
1671 index_t window_width = index_t(double(get_width()) * pixel_ratio());
1672 index_t window_height = index_t(double(get_height()) * pixel_ratio());
1673
1674 if(width == 0) {
1675 width = window_width;
1676 }
1677
1678 if(height == 0) {
1679 height = window_height;
1680 }
1681
1682 if(image->base_mem() == nullptr) {
1683 image->initialize(Image::RGB, Image::BYTE, width, height);
1684 }
1685
1686 width = std::min(image->width(), window_width-x0);
1687 height = std::min(image->height(), window_height-y0);
1688
1689 glPixelStorei(GL_PACK_ALIGNMENT, 1);
1690 glPixelStorei(GL_PACK_ROW_LENGTH, int(image->width()));
1691
1692 if(image->component_encoding() != Image::BYTE) {
1693 Logger::err("RenderingContext")
1694 << "snapshot(): wrong image component encoding"
1695 << std::endl;
1696 return;
1697 }
1698
1699 switch (image->color_encoding()) {
1700 case Image::RGB:
1701 glReadPixels(
1702 GLint(x0), GLint(y0), GLsizei(width), GLsizei(height),
1703 GL_RGB, GL_UNSIGNED_BYTE, image->base_mem()
1704 );
1705 break;
1706
1707 case Image::BGR:
1708 glReadPixels(
1709 GLint(x0), GLint(y0), GLsizei(width), GLsizei(height),
1710 GL_BGR, GL_UNSIGNED_BYTE, image->base_mem()
1711 );
1712 break;
1713
1714 case Image::RGBA:
1715 glReadPixels(
1716 GLint(x0), GLint(y0), GLsizei(width), GLsizei(height),
1717 GL_RGBA, GL_UNSIGNED_BYTE, image->base_mem()
1718 );
1719 break;
1720
1721 case Image::GRAY:
1722 case Image::INDEXED:
1723 case Image::YUV:
1724 Logger::err("RenderingContext")
1725 << "snapshot(): wrong image color encoding"
1726 << std::endl;
1727 break;
1728 }
1729
1730 // Restore default
1731 glPixelStorei(GL_PACK_ROW_LENGTH, 0);
1732
1733 if(redraw) {
1734 one_frame(true); // redraw with GUI
1735 }
1736 #endif
1737 }
1738
1739 void SimpleApplication::snapshot(std::string filename, bool verbose) {
1740 if(filename == "") {
1741 filename = CmdLine::get_arg("gui:snapshot_filename");
1742 }
1743 Image_var image = new Image;
1744 snapshot(image);
1745 bool ok = ImageLibrary::instance()->save_image(filename,image);
1746 if(verbose && ok) {
1747 Logger::out("Snapshot") << "Saved " << filename << std::endl;
1748 }
1749 }
1750
1751 void SimpleApplication::declare_args() {
1752 Application::declare_args();
1753 CmdLine::declare_arg(
1754 "gui:snapshot_filename", "snapshot.png",
1755 "default snapshot image filename"
1756 );
1757 }
1758
1759 }
1760