From 7d23bf793aa620a931f43f962337d674476f8c3c Mon Sep 17 00:00:00 2001 From: Fabio Pellacini Date: Sat, 15 Aug 2020 17:53:51 +0200 Subject: [PATCH] Split gui into two libraries (#1012) --- apps/yimageview/yimageview.cpp | 3 +- apps/yimageviews/yimageviews.cpp | 3 +- apps/ysceneitrace/ysceneitrace.cpp | 3 +- apps/ysceneitraces/ysceneitraces.cpp | 3 +- apps/ysceneview/ysceneview.cpp | 3 +- apps/ysceneviews/ysceneviews.cpp | 3 +- apps/yshapeview/yshapeview.cpp | 3 +- libs/yocto_gui/CMakeLists.txt | 6 +- libs/yocto_gui/yocto_imgui.cpp | 938 ++++++++++++++++++ libs/yocto_gui/yocto_imgui.h | 314 ++++++ .../{yocto_gui.cpp => yocto_opengl.cpp} | 885 +---------------- .../yocto_gui/{yocto_gui.h => yocto_opengl.h} | 266 +---- 12 files changed, 1276 insertions(+), 1154 deletions(-) create mode 100644 libs/yocto_gui/yocto_imgui.cpp create mode 100644 libs/yocto_gui/yocto_imgui.h rename libs/yocto_gui/{yocto_gui.cpp => yocto_opengl.cpp} (67%) rename libs/yocto_gui/{yocto_gui.h => yocto_opengl.h} (67%) diff --git a/apps/yimageview/yimageview.cpp b/apps/yimageview/yimageview.cpp index 650157edd..6f3511d8a 100644 --- a/apps/yimageview/yimageview.cpp +++ b/apps/yimageview/yimageview.cpp @@ -28,7 +28,8 @@ #include #include -#include +#include +#include using namespace yocto; #include diff --git a/apps/yimageviews/yimageviews.cpp b/apps/yimageviews/yimageviews.cpp index 37c46f5fc..8ed6b8901 100644 --- a/apps/yimageviews/yimageviews.cpp +++ b/apps/yimageviews/yimageviews.cpp @@ -28,7 +28,8 @@ #include #include -#include +#include +#include using namespace yocto; #include diff --git a/apps/ysceneitrace/ysceneitrace.cpp b/apps/ysceneitrace/ysceneitrace.cpp index 170613512..e5a64aab8 100644 --- a/apps/ysceneitrace/ysceneitrace.cpp +++ b/apps/ysceneitrace/ysceneitrace.cpp @@ -31,7 +31,8 @@ #include #include #include -#include +#include +#include using namespace yocto; #include diff --git a/apps/ysceneitraces/ysceneitraces.cpp b/apps/ysceneitraces/ysceneitraces.cpp index 13cc2a778..03205e60d 100644 --- a/apps/ysceneitraces/ysceneitraces.cpp +++ b/apps/ysceneitraces/ysceneitraces.cpp @@ -31,7 +31,8 @@ #include #include #include -#include +#include +#include using namespace yocto; #include diff --git a/apps/ysceneview/ysceneview.cpp b/apps/ysceneview/ysceneview.cpp index 5c407ff87..ddb9f8dd0 100644 --- a/apps/ysceneview/ysceneview.cpp +++ b/apps/ysceneview/ysceneview.cpp @@ -31,7 +31,8 @@ #include #include #include -#include +#include +#include using namespace yocto; #include diff --git a/apps/ysceneviews/ysceneviews.cpp b/apps/ysceneviews/ysceneviews.cpp index 89c5fed85..2f539d370 100644 --- a/apps/ysceneviews/ysceneviews.cpp +++ b/apps/ysceneviews/ysceneviews.cpp @@ -31,7 +31,8 @@ #include #include #include -#include +#include +#include using namespace yocto; #include diff --git a/apps/yshapeview/yshapeview.cpp b/apps/yshapeview/yshapeview.cpp index 1e259ff16..98dad3232 100644 --- a/apps/yshapeview/yshapeview.cpp +++ b/apps/yshapeview/yshapeview.cpp @@ -31,7 +31,8 @@ #include #include #include -#include +#include +#include using namespace yocto; #include diff --git a/libs/yocto_gui/CMakeLists.txt b/libs/yocto_gui/CMakeLists.txt index f40d69272..bcef87ef2 100644 --- a/libs/yocto_gui/CMakeLists.txt +++ b/libs/yocto_gui/CMakeLists.txt @@ -8,8 +8,10 @@ if(YOCTO_OPENGL) add_subdirectory(ext/glfw glfw) add_library(yocto_gui - yocto_gui.h - yocto_gui.cpp + yocto_opengl.h + yocto_opengl.cpp + yocto_imgui.h + yocto_imgui.cpp ext/imgui/imgui.cpp ext/imgui/imgui_draw.cpp ext/imgui/imgui_widgets.cpp diff --git a/libs/yocto_gui/yocto_imgui.cpp b/libs/yocto_gui/yocto_imgui.cpp new file mode 100644 index 000000000..ba5249b65 --- /dev/null +++ b/libs/yocto_gui/yocto_imgui.cpp @@ -0,0 +1,938 @@ +// +// Utilities to use OpenGL 3, GLFW and ImGui. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2020 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +#include "yocto_imgui.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ext/glad/glad.h" + +#ifdef __APPLE__ +#define GL_SILENCE_DEPRECATION +#endif +#include + +#include "ext/imgui/imgui.h" +#include "ext/imgui/imgui_impl_glfw.h" +#include "ext/imgui/imgui_impl_opengl3.h" +#include "ext/imgui/imgui_internal.h" + +#ifdef _WIN32 +#undef near +#undef far +#endif + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::mutex; +using std::unordered_map; +using std::unordered_set; +using std::filesystem::directory_iterator; +using std::filesystem::path; +using namespace std::string_literals; + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// UI APPLICATION +// ----------------------------------------------------------------------------- +namespace yocto { + +// run the user interface with the give callbacks +void run_ui(const vec2i& size, const string& title, + const gui_callbacks& callbacks, int widgets_width, bool widgets_left) { + auto win_guard = std::make_unique(); + auto win = win_guard.get(); + init_window(win, size, title, (bool)callbacks.widgets_cb, widgets_width, + widgets_left); + + set_init_callback(win, callbacks.init_cb); + set_clear_callback(win, callbacks.clear_cb); + set_draw_callback(win, callbacks.draw_cb); + set_widgets_callback(win, callbacks.widgets_cb); + set_drop_callback(win, callbacks.drop_cb); + set_key_callback(win, callbacks.key_cb); + set_char_callback(win, callbacks.char_cb); + set_click_callback(win, callbacks.click_cb); + set_scroll_callback(win, callbacks.scroll_cb); + set_update_callback(win, callbacks.update_cb); + set_uiupdate_callback(win, callbacks.uiupdate_cb); + + // run ui + run_ui(win); + + // clear + clear_window(win); +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// UI WINDOW +// ----------------------------------------------------------------------------- +namespace yocto { + +static void draw_window(gui_window* win) { + glClearColor(win->background.x, win->background.y, win->background.z, + win->background.w); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if (win->draw_cb) win->draw_cb(win, win->input); + if (win->widgets_cb) { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + auto window = zero2i; + glfwGetWindowSize(win->win, &window.x, &window.y); + if (win->widgets_left) { + ImGui::SetNextWindowPos({0, 0}); + ImGui::SetNextWindowSize({(float)win->widgets_width, (float)window.y}); + } else { + ImGui::SetNextWindowPos({(float)(window.x - win->widgets_width), 0}); + ImGui::SetNextWindowSize({(float)win->widgets_width, (float)window.y}); + } + ImGui::SetNextWindowCollapsed(false); + ImGui::SetNextWindowBgAlpha(1); + if (ImGui::Begin(win->title.c_str(), nullptr, + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | + ImGuiWindowFlags_NoSavedSettings)) { + win->widgets_cb(win, win->input); + } + ImGui::End(); + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } + glfwSwapBuffers(win->win); +} + +void init_window(gui_window* win, const vec2i& size, const string& title, + bool widgets, int widgets_width, bool widgets_left) { + // init glfw + if (!glfwInit()) + throw std::runtime_error("cannot initialize windowing system"); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +#if __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +#endif + + // create window + win->title = title; + win->win = glfwCreateWindow(size.x, size.y, title.c_str(), nullptr, nullptr); + if (!win->win) throw std::runtime_error("cannot initialize windowing system"); + glfwMakeContextCurrent(win->win); + glfwSwapInterval(1); // Enable vsync + + // set user data + glfwSetWindowUserPointer(win->win, win); + + // set callbacks + glfwSetWindowRefreshCallback(win->win, [](GLFWwindow* glfw) { + auto win = (gui_window*)glfwGetWindowUserPointer(glfw); + draw_window(win); + }); + glfwSetDropCallback( + win->win, [](GLFWwindow* glfw, int num, const char** paths) { + auto win = (gui_window*)glfwGetWindowUserPointer(glfw); + if (win->drop_cb) { + auto pathv = vector(); + for (auto i = 0; i < num; i++) pathv.push_back(paths[i]); + win->drop_cb(win, pathv, win->input); + } + }); + glfwSetKeyCallback(win->win, + [](GLFWwindow* glfw, int key, int scancode, int action, int mods) { + auto win = (gui_window*)glfwGetWindowUserPointer(glfw); + if (win->key_cb) win->key_cb(win, key, (bool)action, win->input); + }); + glfwSetCharCallback(win->win, [](GLFWwindow* glfw, unsigned int key) { + auto win = (gui_window*)glfwGetWindowUserPointer(glfw); + if (win->char_cb) win->char_cb(win, key, win->input); + }); + glfwSetMouseButtonCallback( + win->win, [](GLFWwindow* glfw, int button, int action, int mods) { + auto win = (gui_window*)glfwGetWindowUserPointer(glfw); + if (win->click_cb) + win->click_cb( + win, button == GLFW_MOUSE_BUTTON_LEFT, (bool)action, win->input); + }); + glfwSetScrollCallback( + win->win, [](GLFWwindow* glfw, double xoffset, double yoffset) { + auto win = (gui_window*)glfwGetWindowUserPointer(glfw); + if (win->scroll_cb) win->scroll_cb(win, (float)yoffset, win->input); + }); + glfwSetWindowSizeCallback( + win->win, [](GLFWwindow* glfw, int width, int height) { + auto win = (gui_window*)glfwGetWindowUserPointer(glfw); + glfwGetWindowSize( + win->win, &win->input.window_size.x, &win->input.window_size.y); + if (win->widgets_width) win->input.window_size.x -= win->widgets_width; + glfwGetFramebufferSize(win->win, &win->input.framebuffer_viewport.z, + &win->input.framebuffer_viewport.w); + win->input.framebuffer_viewport.x = 0; + win->input.framebuffer_viewport.y = 0; + if (win->widgets_width) { + auto win_size = zero2i; + glfwGetWindowSize(win->win, &win_size.x, &win_size.y); + auto offset = (int)(win->widgets_width * + (float)win->input.framebuffer_viewport.z / + win_size.x); + win->input.framebuffer_viewport.z -= offset; + if (win->widgets_left) win->input.framebuffer_viewport.x += offset; + } + }); + + // init gl extensions + if (!gladLoadGL()) + throw std::runtime_error("cannot initialize OpenGL extensions"); + + // widgets + if (widgets) { + ImGui::CreateContext(); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetStyle().WindowRounding = 0; + ImGui_ImplGlfw_InitForOpenGL(win->win, true); +#ifndef __APPLE__ + ImGui_ImplOpenGL3_Init(); +#else + ImGui_ImplOpenGL3_Init("#version 330"); +#endif + ImGui::StyleColorsDark(); + win->widgets_width = widgets_width; + win->widgets_left = widgets_left; + } +} + +void clear_window(gui_window* win) { + glfwDestroyWindow(win->win); + glfwTerminate(); + win->win = nullptr; +} + +// Run loop +void run_ui(gui_window* win) { + // init + if (win->init_cb) win->init_cb(win, win->input); + + // loop + while (!glfwWindowShouldClose(win->win)) { + // update input + win->input.mouse_last = win->input.mouse_pos; + auto mouse_posx = 0.0, mouse_posy = 0.0; + glfwGetCursorPos(win->win, &mouse_posx, &mouse_posy); + win->input.mouse_pos = vec2f{(float)mouse_posx, (float)mouse_posy}; + if (win->widgets_width && win->widgets_left) + win->input.mouse_pos.x -= win->widgets_width; + win->input.mouse_left = glfwGetMouseButton( + win->win, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; + win->input.mouse_right = + glfwGetMouseButton(win->win, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS; + win->input.modifier_alt = + glfwGetKey(win->win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || + glfwGetKey(win->win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS; + win->input.modifier_shift = + glfwGetKey(win->win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || + glfwGetKey(win->win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS; + win->input.modifier_ctrl = + glfwGetKey(win->win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || + glfwGetKey(win->win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS; + glfwGetWindowSize( + win->win, &win->input.window_size.x, &win->input.window_size.y); + if (win->widgets_width) win->input.window_size.x -= win->widgets_width; + glfwGetFramebufferSize(win->win, &win->input.framebuffer_viewport.z, + &win->input.framebuffer_viewport.w); + win->input.framebuffer_viewport.x = 0; + win->input.framebuffer_viewport.y = 0; + if (win->widgets_width) { + auto win_size = zero2i; + glfwGetWindowSize(win->win, &win_size.x, &win_size.y); + auto offset = (int)(win->widgets_width * + (float)win->input.framebuffer_viewport.z / + win_size.x); + win->input.framebuffer_viewport.z -= offset; + if (win->widgets_left) win->input.framebuffer_viewport.x += offset; + } + if (win->widgets_width) { + auto io = &ImGui::GetIO(); + win->input.widgets_active = io->WantTextInput || io->WantCaptureMouse || + io->WantCaptureKeyboard; + } + + // time + win->input.clock_last = win->input.clock_now; + win->input.clock_now = + std::chrono::high_resolution_clock::now().time_since_epoch().count(); + win->input.time_now = (double)win->input.clock_now / 1000000000.0; + win->input.time_delta = + (double)(win->input.clock_now - win->input.clock_last) / 1000000000.0; + + // update ui + if (win->uiupdate_cb && !win->input.widgets_active) + win->uiupdate_cb(win, win->input); + + // update + if (win->update_cb) win->update_cb(win, win->input); + + // draw + draw_window(win); + + // event hadling + glfwPollEvents(); + } + + // clear + if (win->clear_cb) win->clear_cb(win, win->input); +} + +void set_init_callback(gui_window* win, init_callback cb) { win->init_cb = cb; } +void set_clear_callback(gui_window* win, clear_callback cb) { + win->clear_cb = cb; +} +void set_draw_callback(gui_window* win, draw_callback cb) { win->draw_cb = cb; } +void set_widgets_callback(gui_window* win, widgets_callback cb) { + win->widgets_cb = cb; +} +void set_drop_callback(gui_window* win, drop_callback drop_cb) { + win->drop_cb = drop_cb; +} +void set_key_callback(gui_window* win, key_callback cb) { win->key_cb = cb; } +void set_char_callback(gui_window* win, char_callback cb) { win->char_cb = cb; } +void set_click_callback(gui_window* win, click_callback cb) { + win->click_cb = cb; +} +void set_scroll_callback(gui_window* win, scroll_callback cb) { + win->scroll_cb = cb; +} +void set_uiupdate_callback(gui_window* win, uiupdate_callback cb) { + win->uiupdate_cb = cb; +} +void set_update_callback(gui_window* win, update_callback cb) { + win->update_cb = cb; +} + +void set_close(gui_window* win, bool close) { + glfwSetWindowShouldClose(win->win, close ? GLFW_TRUE : GLFW_FALSE); +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// OPENGL WIDGETS +// ----------------------------------------------------------------------------- +namespace yocto { + +void init_glwidgets(gui_window* win, int width, bool left) { + // init widgets + ImGui::CreateContext(); + ImGui::GetIO().IniFilename = nullptr; + ImGui::GetStyle().WindowRounding = 0; + ImGui_ImplGlfw_InitForOpenGL(win->win, true); +#ifndef __APPLE__ + ImGui_ImplOpenGL3_Init(); +#else + ImGui_ImplOpenGL3_Init("#version 330"); +#endif + ImGui::StyleColorsDark(); + win->widgets_width = width; + win->widgets_left = left; +} + +bool begin_header(gui_window* win, const char* lbl) { + if (!ImGui::CollapsingHeader(lbl)) return false; + ImGui::PushID(lbl); + return true; +} +void end_header(gui_window* win) { ImGui::PopID(); } + +void open_glmodal(gui_window* win, const char* lbl) { ImGui::OpenPopup(lbl); } +void clear_glmodal(gui_window* win) { ImGui::CloseCurrentPopup(); } +bool begin_glmodal(gui_window* win, const char* lbl) { + return ImGui::BeginPopupModal(lbl); +} +void end_glmodal(gui_window* win) { ImGui::EndPopup(); } +bool is_glmodal_open(gui_window* win, const char* lbl) { + return ImGui::IsPopupOpen(lbl); +} + +// Utility to normalize a path +static inline string normalize_path(const string& filename_) { + auto filename = filename_; + for (auto& c : filename) + if (c == '\\') c = '/'; + if (filename.size() > 1 && filename[0] == '/' && filename[1] == '/') { + throw std::invalid_argument("absolute paths are not supported"); + return filename_; + } + if (filename.size() > 3 && filename[1] == ':' && filename[2] == '/' && + filename[3] == '/') { + throw std::invalid_argument("absolute paths are not supported"); + return filename_; + } + auto pos = (size_t)0; + while ((pos = filename.find("//")) != filename.npos) + filename = filename.substr(0, pos) + filename.substr(pos + 1); + return filename; +} + +// Get extension (not including '.'). +static string get_extension(const string& filename_) { + auto filename = normalize_path(filename_); + auto pos = filename.rfind('.'); + if (pos == string::npos) return ""; + return filename.substr(pos); +} + +struct filedialog_state { + string dirname = ""; + string filename = ""; + vector> entries = {}; + bool save = false; + bool remove_hidden = true; + string filter = ""; + vector extensions = {}; + + filedialog_state() {} + filedialog_state(const string& dirname, const string& filename, bool save, + const string& filter) { + this->save = save; + set_filter(filter); + set_dirname(dirname); + set_filename(filename); + } + void set_dirname(const string& name) { + dirname = name; + dirname = normalize_path(dirname); + if (dirname == "") dirname = "./"; + if (dirname.back() != '/') dirname += '/'; + refresh(); + } + void set_filename(const string& name) { + filename = name; + check_filename(); + } + void set_filter(const string& flt) { + auto globs = vector{""}; + for (auto i = 0; i < flt.size(); i++) { + if (flt[i] == ';') { + globs.push_back(""); + } else { + globs.back() += flt[i]; + } + } + filter = ""; + extensions.clear(); + for (auto pattern : globs) { + if (pattern == "") continue; + auto ext = get_extension(pattern); + if (ext != "") { + extensions.push_back(ext); + filter += (filter == "") ? ("*." + ext) : (";*." + ext); + } + } + } + void check_filename() { + if (filename.empty()) return; + auto ext = get_extension(filename); + if (std::find(extensions.begin(), extensions.end(), ext) == + extensions.end()) { + filename = ""; + return; + } + if (!save && !exists_file(dirname + filename)) { + filename = ""; + return; + } + } + void select_entry(int idx) { + if (entries[idx].second) { + set_dirname(dirname + entries[idx].first); + } else { + set_filename(entries[idx].first); + } + } + + void refresh() { + entries.clear(); + for (auto entry : directory_iterator(path{dirname})) { + if (remove_hidden && entry.path().stem().string()[0] == '.') continue; + if (entry.is_directory()) { + entries.push_back({entry.path().stem().string() + "/", true}); + } else { + entries.push_back({entry.path().stem().string(), false}); + } + } + std::sort(entries.begin(), entries.end(), [](auto& a, auto& b) { + if (a.second == b.second) return a.first < b.first; + return a.second; + }); + } + + string get_path() const { return dirname + filename; } + bool exists_file(const string& filename) { + auto f = fopen(filename.c_str(), "r"); + if (!f) return false; + fclose(f); + return true; + } +}; +bool draw_filedialog(gui_window* win, const char* lbl, string& path, bool save, + const string& dirname, const string& filename, const string& filter) { + static auto states = unordered_map{}; + ImGui::SetNextWindowSize({500, 300}, ImGuiCond_FirstUseEver); + if (ImGui::BeginPopupModal(lbl)) { + if (states.find(lbl) == states.end()) { + states[lbl] = filedialog_state{dirname, filename, save, filter}; + } + auto& state = states.at(lbl); + char dir_buffer[1024]; + snprintf(dir_buffer, sizeof(dir_buffer), "%s", state.dirname.c_str()); + if (ImGui::InputText("dir", dir_buffer, sizeof(dir_buffer))) { + state.set_dirname(dir_buffer); + } + auto current_item = -1; + if (ImGui::ListBox( + "entries", ¤t_item, + [](void* data, int idx, const char** out_text) -> bool { + auto& state = *(filedialog_state*)data; + *out_text = state.entries[idx].first.c_str(); + return true; + }, + &state, (int)state.entries.size())) { + state.select_entry(current_item); + } + char file_buffer[1024]; + snprintf(file_buffer, sizeof(file_buffer), "%s", state.filename.c_str()); + if (ImGui::InputText("file", file_buffer, sizeof(file_buffer))) { + state.set_filename(file_buffer); + } + char filter_buffer[1024]; + snprintf(filter_buffer, sizeof(filter_buffer), "%s", state.filter.c_str()); + if (ImGui::InputText("filter", filter_buffer, sizeof(filter_buffer))) { + state.set_filter(filter_buffer); + } + auto ok = false, exit = false; + if (ImGui::Button("Ok")) { + path = state.dirname + state.filename; + ok = true; + exit = true; + } + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + exit = true; + } + if (exit) { + ImGui::CloseCurrentPopup(); + states.erase(lbl); + } + ImGui::EndPopup(); + return ok; + } else { + return false; + } +} +bool draw_filedialog_button(gui_window* win, const char* button_lbl, + bool button_active, const char* lbl, string& path, bool save, + const string& dirname, const string& filename, const string& filter) { + if (is_glmodal_open(win, lbl)) { + return draw_filedialog(win, lbl, path, save, dirname, filename, filter); + } else { + if (draw_button(win, button_lbl, button_active)) { + open_glmodal(win, lbl); + } + return false; + } +} + +bool draw_button(gui_window* win, const char* lbl, bool enabled) { + if (enabled) { + return ImGui::Button(lbl); + } else { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + auto ok = ImGui::Button(lbl); + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + return ok; + } +} + +void draw_label(gui_window* win, const char* lbl, const string& label) { + ImGui::LabelText(lbl, "%s", label.c_str()); +} + +void draw_separator(gui_window* win) { ImGui::Separator(); } + +void continue_line(gui_window* win) { ImGui::SameLine(); } + +bool draw_textinput(gui_window* win, const char* lbl, string& value) { + char buffer[4096]; + auto num = 0; + for (auto c : value) buffer[num++] = c; + buffer[num] = 0; + auto edited = ImGui::InputText(lbl, buffer, sizeof(buffer)); + if (edited) value = buffer; + return edited; +} + +bool draw_slider( + gui_window* win, const char* lbl, float& value, float min, float max) { + return ImGui::SliderFloat(lbl, &value, min, max); +} +bool draw_slider( + gui_window* win, const char* lbl, vec2f& value, float min, float max) { + return ImGui::SliderFloat2(lbl, &value.x, min, max); +} +bool draw_slider( + gui_window* win, const char* lbl, vec3f& value, float min, float max) { + return ImGui::SliderFloat3(lbl, &value.x, min, max); +} +bool draw_slider( + gui_window* win, const char* lbl, vec4f& value, float min, float max) { + return ImGui::SliderFloat4(lbl, &value.x, min, max); +} + +bool draw_slider( + gui_window* win, const char* lbl, int& value, int min, int max) { + return ImGui::SliderInt(lbl, &value, min, max); +} +bool draw_slider( + gui_window* win, const char* lbl, vec2i& value, int min, int max) { + return ImGui::SliderInt2(lbl, &value.x, min, max); +} +bool draw_slider( + gui_window* win, const char* lbl, vec3i& value, int min, int max) { + return ImGui::SliderInt3(lbl, &value.x, min, max); +} +bool draw_slider( + gui_window* win, const char* lbl, vec4i& value, int min, int max) { + return ImGui::SliderInt4(lbl, &value.x, min, max); +} + +bool draw_dragger(gui_window* win, const char* lbl, float& value, float speed, + float min, float max) { + return ImGui::DragFloat(lbl, &value, speed, min, max); +} +bool draw_dragger(gui_window* win, const char* lbl, vec2f& value, float speed, + float min, float max) { + return ImGui::DragFloat2(lbl, &value.x, speed, min, max); +} +bool draw_dragger(gui_window* win, const char* lbl, vec3f& value, float speed, + float min, float max) { + return ImGui::DragFloat3(lbl, &value.x, speed, min, max); +} +bool draw_dragger(gui_window* win, const char* lbl, vec4f& value, float speed, + float min, float max) { + return ImGui::DragFloat4(lbl, &value.x, speed, min, max); +} + +bool draw_dragger(gui_window* win, const char* lbl, int& value, float speed, + int min, int max) { + return ImGui::DragInt(lbl, &value, speed, min, max); +} +bool draw_dragger(gui_window* win, const char* lbl, vec2i& value, float speed, + int min, int max) { + return ImGui::DragInt2(lbl, &value.x, speed, min, max); +} +bool draw_dragger(gui_window* win, const char* lbl, vec3i& value, float speed, + int min, int max) { + return ImGui::DragInt3(lbl, &value.x, speed, min, max); +} +bool draw_dragger(gui_window* win, const char* lbl, vec4i& value, float speed, + int min, int max) { + return ImGui::DragInt4(lbl, &value.x, speed, min, max); +} + +bool draw_checkbox(gui_window* win, const char* lbl, bool& value) { + return ImGui::Checkbox(lbl, &value); +} +bool draw_checkbox(gui_window* win, const char* lbl, bool& value, bool invert) { + if (!invert) { + return draw_checkbox(win, lbl, value); + } else { + auto inverted = !value; + auto edited = ImGui::Checkbox(lbl, &inverted); + if (edited) value = !inverted; + return edited; + } +} + +bool draw_coloredit(gui_window* win, const char* lbl, vec3f& value) { + auto flags = ImGuiColorEditFlags_Float; + return ImGui::ColorEdit3(lbl, &value.x, flags); +} + +bool draw_coloredit(gui_window* win, const char* lbl, vec4f& value) { + auto flags = ImGuiColorEditFlags_Float; + return ImGui::ColorEdit4(lbl, &value.x, flags); +} + +bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec3f& value) { + auto color = value; + auto exposure = 0.0f; + auto scale = max(color); + if (scale > 1) { + color /= scale; + exposure = log2(scale); + } + auto edit_exposure = draw_slider( + win, (lbl + " [exp]"s).c_str(), exposure, 0, 10); + auto edit_color = draw_coloredit(win, (lbl + " [col]"s).c_str(), color); + if (edit_exposure || edit_color) { + value = color * exp2(exposure); + return true; + } else { + return false; + } +} +bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec4f& value) { + auto color = value; + auto exposure = 0.0f; + auto scale = max(xyz(color)); + if (scale > 1) { + color.x /= scale; + color.y /= scale; + color.z /= scale; + exposure = log2(scale); + } + auto edit_exposure = draw_slider( + win, (lbl + " [exp]"s).c_str(), exposure, 0, 10); + auto edit_color = draw_coloredit(win, (lbl + " [col]"s).c_str(), color); + if (edit_exposure || edit_color) { + value.x = color.x * exp2(exposure); + value.y = color.y * exp2(exposure); + value.z = color.z * exp2(exposure); + value.w = color.w; + return true; + } else { + return false; + } +} + +bool draw_combobox(gui_window* win, const char* lbl, int& value, + const vector& labels) { + if (!ImGui::BeginCombo(lbl, labels[value].c_str())) return false; + auto old_val = value; + for (auto i = 0; i < labels.size(); i++) { + ImGui::PushID(i); + if (ImGui::Selectable(labels[i].c_str(), value == i)) value = i; + if (value == i) ImGui::SetItemDefaultFocus(); + ImGui::PopID(); + } + ImGui::EndCombo(); + return value != old_val; +} + +bool draw_combobox(gui_window* win, const char* lbl, string& value, + const vector& labels) { + if (!ImGui::BeginCombo(lbl, value.c_str())) return false; + auto old_val = value; + for (auto i = 0; i < labels.size(); i++) { + ImGui::PushID(i); + if (ImGui::Selectable(labels[i].c_str(), value == labels[i])) + value = labels[i]; + if (value == labels[i]) ImGui::SetItemDefaultFocus(); + ImGui::PopID(); + } + ImGui::EndCombo(); + return value != old_val; +} + +bool draw_combobox(gui_window* win, const char* lbl, int& idx, int num, + const function& labels, bool include_null) { + if (num <= 0) idx = -1; + if (!ImGui::BeginCombo(lbl, idx >= 0 ? labels(idx).c_str() : "")) + return false; + auto old_idx = idx; + if (include_null) { + ImGui::PushID(100000); + if (ImGui::Selectable("", idx < 0)) idx = -1; + if (idx < 0) ImGui::SetItemDefaultFocus(); + ImGui::PopID(); + } + for (auto i = 0; i < num; i++) { + ImGui::PushID(i); + if (ImGui::Selectable(labels(i).c_str(), idx == i)) idx = i; + if (idx == i) ImGui::SetItemDefaultFocus(); + ImGui::PopID(); + } + ImGui::EndCombo(); + return idx != old_idx; +} + +void draw_progressbar(gui_window* win, const char* lbl, float fraction) { + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5, 0.5, 1, 0.25)); + ImGui::ProgressBar(fraction, ImVec2(0.0f, 0.0f)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text(lbl, ImVec2(0.0f, 0.0f)); + ImGui::PopStyleColor(1); +} + +void draw_progressbar( + gui_window* win, const char* lbl, int current, int total) { + auto overlay = std::to_string(current) + "/" + std::to_string(total); + ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5, 0.5, 1, 0.25)); + ImGui::ProgressBar( + (float)current / (float)total, ImVec2(0.0f, 0.0f), overlay.c_str()); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text(lbl, ImVec2(0.0f, 0.0f)); + ImGui::PopStyleColor(1); +} + +void draw_histogram( + gui_window* win, const char* lbl, const float* values, int count) { + ImGui::PlotHistogram(lbl, values, count); +} +void draw_histogram( + gui_window* win, const char* lbl, const vector& values) { + ImGui::PlotHistogram(lbl, values.data(), (int)values.size(), 0, nullptr, + flt_max, flt_max, {0, 0}, 4); +} +void draw_histogram( + gui_window* win, const char* lbl, const vector& values) { + ImGui::PlotHistogram((lbl + " x"s).c_str(), (const float*)values.data() + 0, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec2f)); + ImGui::PlotHistogram((lbl + " y"s).c_str(), (const float*)values.data() + 1, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec2f)); +} +void draw_histogram( + gui_window* win, const char* lbl, const vector& values) { + ImGui::PlotHistogram((lbl + " x"s).c_str(), (const float*)values.data() + 0, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec3f)); + ImGui::PlotHistogram((lbl + " y"s).c_str(), (const float*)values.data() + 1, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec3f)); + ImGui::PlotHistogram((lbl + " z"s).c_str(), (const float*)values.data() + 2, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec3f)); +} +void draw_histogram( + gui_window* win, const char* lbl, const vector& values) { + ImGui::PlotHistogram((lbl + " x"s).c_str(), (const float*)values.data() + 0, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); + ImGui::PlotHistogram((lbl + " y"s).c_str(), (const float*)values.data() + 1, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); + ImGui::PlotHistogram((lbl + " z"s).c_str(), (const float*)values.data() + 2, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); + ImGui::PlotHistogram((lbl + " w"s).c_str(), (const float*)values.data() + 3, + (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); +} + +// https://github.com/ocornut/imgui/issues/300 +struct ImGuiAppLog { + ImGuiTextBuffer Buf; + ImGuiTextFilter Filter; + ImVector LineOffsets; // Index to lines offset + bool ScrollToBottom; + + void Clear() { + Buf.clear(); + LineOffsets.clear(); + } + + void AddLog(const char* msg, const char* lbl) { + int old_size = Buf.size(); + Buf.appendf("[%s] %s\n", lbl, msg); + for (int new_size = Buf.size(); old_size < new_size; old_size++) + if (Buf[old_size] == '\n') LineOffsets.push_back(old_size); + ScrollToBottom = true; + } + + void Draw() { + if (ImGui::Button("Clear")) Clear(); + ImGui::SameLine(); + bool copy = ImGui::Button("Copy"); + ImGui::SameLine(); + Filter.Draw("Filter", -100.0f); + ImGui::Separator(); + ImGui::BeginChild("scrolling"); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 1)); + if (copy) ImGui::LogToClipboard(); + + if (Filter.IsActive()) { + const char* buf_begin = Buf.begin(); + const char* line = buf_begin; + for (int line_no = 0; line != NULL; line_no++) { + const char* line_end = (line_no < LineOffsets.Size) + ? buf_begin + LineOffsets[line_no] + : NULL; + if (Filter.PassFilter(line, line_end)) + ImGui::TextUnformatted(line, line_end); + line = line_end && line_end[1] ? line_end + 1 : NULL; + } + } else { + ImGui::TextUnformatted(Buf.begin()); + } + + if (ScrollToBottom) ImGui::SetScrollHere(1.0f); + ScrollToBottom = false; + ImGui::PopStyleVar(); + ImGui::EndChild(); + } + void Draw(const char* title, bool* p_opened = NULL) { + ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiSetCond_FirstUseEver); + ImGui::Begin(title, p_opened); + Draw(); + ImGui::End(); + } +}; + +std::mutex _log_mutex; +ImGuiAppLog _log_widget; +void log_info(gui_window* win, const string& msg) { + _log_mutex.lock(); + _log_widget.AddLog(msg.c_str(), "info"); + _log_mutex.unlock(); +} +void log_error(gui_window* win, const string& msg) { + _log_mutex.lock(); + _log_widget.AddLog(msg.c_str(), "errn"); + _log_mutex.unlock(); +} +void clear_log(gui_window* win) { + _log_mutex.lock(); + _log_widget.Clear(); + _log_mutex.unlock(); +} +void draw_log(gui_window* win) { + _log_mutex.lock(); + _log_widget.Draw(); + _log_mutex.unlock(); +} + +} // namespace yocto diff --git a/libs/yocto_gui/yocto_imgui.h b/libs/yocto_gui/yocto_imgui.h new file mode 100644 index 000000000..20df0604e --- /dev/null +++ b/libs/yocto_gui/yocto_imgui.h @@ -0,0 +1,314 @@ +// +// Yocto/ImGui: Utilities for writing native GUIs with Dear ImGui and GLFW. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2020 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// + +#ifndef _YOCTO_IMGUI_ +#define _YOCTO_IMGUI_ + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include + +#include +#include +#include + +// forward declaration +struct GLFWwindow; + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::function; +using std::string; +using std::vector; + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// UI APPLICATION +// ----------------------------------------------------------------------------- +namespace yocto { + +// Forward declaration of OpenGL window +struct gui_window; + +// Input state +struct gui_input { + bool mouse_left = false; // left button + bool mouse_right = false; // right button + bool mouse_middle = false; // middle button + vec2f mouse_pos = {}; // position excluding widgets + vec2f mouse_last = {}; // last mouse position excluding widgets + vec2f mouse_delta = {}; // last mouse delta excluding widgets + bool modifier_alt = false; // alt modifier + bool modifier_ctrl = false; // ctrl modifier + bool modifier_shift = false; // shift modifier + bool widgets_active = false; // widgets are active + uint64_t clock_now = 0; // clock now + uint64_t clock_last = 0; // clock last + double time_now = 0; // time now + double time_delta = 0; // time delta + vec2i window_size = {0, 0}; // window size + vec4i framebuffer_viewport = {0, 0, 0, 0}; // framebuffer viewport +}; + +// Init callback called after the window has opened +using init_callback = function; +// Clear callback called after the window is cloased +using clear_callback = function; +// Draw callback called every frame and when resizing +using draw_callback = function; +// Draw callback for drawing widgets +using widgets_callback = function; +// Drop callback that returns that list of dropped strings. +using drop_callback = + function&, const gui_input& input)>; +// Key callback that returns key codes, pressed/released flag and modifier keys +using key_callback = + function; +// Char callback that returns ASCII key +using char_callback = + function; +// Mouse click callback that returns left/right button, pressed/released flag, +// modifier keys +using click_callback = function; +// Scroll callback that returns scroll amount +using scroll_callback = + function; +// Update functions called every frame +using uiupdate_callback = function; +// Update functions called every frame +using update_callback = function; + +// User interface callcaks +struct gui_callbacks { + init_callback init_cb = {}; + clear_callback clear_cb = {}; + draw_callback draw_cb = {}; + widgets_callback widgets_cb = {}; + drop_callback drop_cb = {}; + key_callback key_cb = {}; + char_callback char_cb = {}; + click_callback click_cb = {}; + scroll_callback scroll_cb = {}; + update_callback update_cb = {}; + uiupdate_callback uiupdate_cb = {}; +}; + +// run the user interface with the give callbacks +void run_ui(const vec2i& size, const string& title, + const gui_callbacks& callbaks, int widgets_width = 320, + bool widgets_left = true); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// UI WINDOW +// ----------------------------------------------------------------------------- +namespace yocto { + +// OpenGL window wrapper +struct gui_window { + GLFWwindow* win = nullptr; + string title = ""; + init_callback init_cb = {}; + clear_callback clear_cb = {}; + draw_callback draw_cb = {}; + widgets_callback widgets_cb = {}; + drop_callback drop_cb = {}; + key_callback key_cb = {}; + char_callback char_cb = {}; + click_callback click_cb = {}; + scroll_callback scroll_cb = {}; + update_callback update_cb = {}; + uiupdate_callback uiupdate_cb = {}; + int widgets_width = 0; + bool widgets_left = true; + gui_input input = {}; + vec4f background = {0.15f, 0.15f, 0.15f, 1.0f}; +}; + +// Windows initialization +void init_window(gui_window* win, const vec2i& size, const string& title, + bool widgets, int widgets_width = 320, bool widgets_left = true); + +// Window cleanup +void clear_window(gui_window* win); + +// Set callbacks +void set_init_callback(gui_window* win, init_callback init_cb); +void set_clear_callback(gui_window* win, clear_callback clear_cb); +void set_draw_callback(gui_window* win, draw_callback draw_cb); +void set_widgets_callback(gui_window* win, widgets_callback widgets_cb); +void set_drop_callback(gui_window* win, drop_callback drop_cb); +void set_key_callback(gui_window* win, key_callback cb); +void set_char_callback(gui_window* win, char_callback cb); +void set_click_callback(gui_window* win, click_callback cb); +void set_scroll_callback(gui_window* win, scroll_callback cb); +void set_uiupdate_callback(gui_window* win, uiupdate_callback cb); +void set_update_callback(gui_window* win, update_callback cb); + +// Run loop +void run_ui(gui_window* win); +void set_close(gui_window* win, bool close); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// OPENGL WIDGETS +// ----------------------------------------------------------------------------- +namespace yocto { + +bool begin_header(gui_window* win, const char* title); +void end_header(gui_window* win); + +void draw_label(gui_window* win, const char* lbl, const string& text); + +void draw_separator(gui_window* win); +void continue_line(gui_window* win); + +bool draw_button(gui_window* win, const char* lbl, bool enabled = true); + +bool draw_textinput(gui_window* win, const char* lbl, string& value); + +bool draw_slider( + gui_window* win, const char* lbl, float& value, float min, float max); +bool draw_slider( + gui_window* win, const char* lbl, vec2f& value, float min, float max); +bool draw_slider( + gui_window* win, const char* lbl, vec3f& value, float min, float max); +bool draw_slider( + gui_window* win, const char* lbl, vec4f& value, float min, float max); + +bool draw_slider( + gui_window* win, const char* lbl, int& value, int min, int max); +bool draw_slider( + gui_window* win, const char* lbl, vec2i& value, int min, int max); +bool draw_slider( + gui_window* win, const char* lbl, vec3i& value, int min, int max); +bool draw_slider( + gui_window* win, const char* lbl, vec4i& value, int min, int max); + +bool draw_dragger(gui_window* win, const char* lbl, float& value, + float speed = 1.0f, float min = 0.0f, float max = 0.0f); +bool draw_dragger(gui_window* win, const char* lbl, vec2f& value, + float speed = 1.0f, float min = 0.0f, float max = 0.0f); +bool draw_dragger(gui_window* win, const char* lbl, vec3f& value, + float speed = 1.0f, float min = 0.0f, float max = 0.0f); +bool draw_dragger(gui_window* win, const char* lbl, vec4f& value, + float speed = 1.0f, float min = 0.0f, float max = 0.0f); + +bool draw_dragger(gui_window* win, const char* lbl, int& value, float speed = 1, + int min = 0, int max = 0); +bool draw_dragger(gui_window* win, const char* lbl, vec2i& value, + float speed = 1, int min = 0, int max = 0); +bool draw_dragger(gui_window* win, const char* lbl, vec3i& value, + float speed = 1, int min = 0, int max = 0); +bool draw_dragger(gui_window* win, const char* lbl, vec4i& value, + float speed = 1, int min = 0, int max = 0); + +bool draw_checkbox(gui_window* win, const char* lbl, bool& value); +bool draw_checkbox(gui_window* win, const char* lbl, bool& value, bool invert); + +bool draw_coloredit(gui_window* win, const char* lbl, vec3f& value); +bool draw_coloredit(gui_window* win, const char* lbl, vec4f& value); + +bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec3f& value); +bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec4f& value); + +bool draw_combobox( + gui_window* win, const char* lbl, int& idx, const vector& labels); +bool draw_combobox(gui_window* win, const char* lbl, string& value, + const vector& labels); +bool draw_combobox(gui_window* win, const char* lbl, int& idx, int num, + const function& labels, bool include_null = false); + +template +inline bool draw_combobox(gui_window* win, const char* lbl, T*& value, + const vector& vals, bool include_null = false) { + auto idx = -1; + for (auto pos = 0; pos < vals.size(); pos++) + if (vals[pos] == value) idx = pos; + auto edited = draw_combobox( + win, lbl, idx, (int)vals.size(), [&](int idx) { return vals[idx]->name; }, + include_null); + if (edited) { + value = idx >= 0 ? vals[idx] : nullptr; + } + return edited; +} + +template +inline bool draw_combobox(gui_window* win, const char* lbl, T*& value, + const vector& vals, const vector& labels, + bool include_null = false) { + auto idx = -1; + for (auto pos = 0; pos < vals.size(); pos++) + if (vals[pos] == value) idx = pos; + auto edited = draw_combobox( + win, lbl, idx, (int)vals.size(), [&](int idx) { return labels[idx]; }, + include_null); + if (edited) { + value = idx >= 0 ? vals[idx] : nullptr; + } + return edited; +} + +void draw_progressbar(gui_window* win, const char* lbl, float fraction); +void draw_progressbar(gui_window* win, const char* lbl, int current, int total); + +void draw_histogram( + gui_window* win, const char* lbl, const vector& values); +void draw_histogram( + gui_window* win, const char* lbl, const vector& values); +void draw_histogram( + gui_window* win, const char* lbl, const vector& values); +void draw_histogram( + gui_window* win, const char* lbl, const vector& values); + +bool draw_filedialog(gui_window* win, const char* lbl, string& path, bool save, + const string& dirname, const string& filename, const string& filter); +bool draw_filedialog_button(gui_window* win, const char* button_lbl, + bool button_active, const char* lbl, string& path, bool save, + const string& dirname, const string& filename, const string& filter); + +void log_info(gui_window* win, const string& msg); +void log_error(gui_window* win, const string& msg); +void clear_log(gui_window* win); +void draw_log(gui_window* win); + +} // namespace yocto + +#endif diff --git a/libs/yocto_gui/yocto_gui.cpp b/libs/yocto_gui/yocto_opengl.cpp similarity index 67% rename from libs/yocto_gui/yocto_gui.cpp rename to libs/yocto_gui/yocto_opengl.cpp index 223d38184..a69b425bc 100644 --- a/libs/yocto_gui/yocto_gui.cpp +++ b/libs/yocto_gui/yocto_opengl.cpp @@ -27,12 +27,11 @@ // // -#include "yocto_gui.h" +#include "yocto_opengl.h" #include +#include #include -#include -#include #include #include #include @@ -40,16 +39,6 @@ #include "ext/glad/glad.h" -#ifdef __APPLE__ -#define GL_SILENCE_DEPRECATION -#endif -#include - -#include "ext/imgui/imgui.h" -#include "ext/imgui/imgui_impl_glfw.h" -#include "ext/imgui/imgui_impl_opengl3.h" -#include "ext/imgui/imgui_internal.h" - #ifdef _WIN32 #undef near #undef far @@ -61,11 +50,8 @@ namespace yocto { // using directives -using std::mutex; using std::unordered_map; using std::unordered_set; -using std::filesystem::directory_iterator; -using std::filesystem::path; using namespace std::string_literals; } // namespace yocto @@ -1884,870 +1870,3 @@ void draw_scene(ogl_scene* scene, ogl_camera* camera, const vec4i& viewport, } } // namespace yocto - -// ----------------------------------------------------------------------------- -// UI APPLICATION -// ----------------------------------------------------------------------------- -namespace yocto { - -// run the user interface with the give callbacks -void run_ui(const vec2i& size, const string& title, - const gui_callbacks& callbacks, int widgets_width, bool widgets_left) { - auto win_guard = std::make_unique(); - auto win = win_guard.get(); - init_window(win, size, title, (bool)callbacks.widgets_cb, widgets_width, - widgets_left); - - set_init_callback(win, callbacks.init_cb); - set_clear_callback(win, callbacks.clear_cb); - set_draw_callback(win, callbacks.draw_cb); - set_widgets_callback(win, callbacks.widgets_cb); - set_drop_callback(win, callbacks.drop_cb); - set_key_callback(win, callbacks.key_cb); - set_char_callback(win, callbacks.char_cb); - set_click_callback(win, callbacks.click_cb); - set_scroll_callback(win, callbacks.scroll_cb); - set_update_callback(win, callbacks.update_cb); - set_uiupdate_callback(win, callbacks.uiupdate_cb); - - // run ui - run_ui(win); - - // clear - clear_window(win); -} - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// UI WINDOW -// ----------------------------------------------------------------------------- -namespace yocto { - -static void draw_window(gui_window* win) { - glClearColor(win->background.x, win->background.y, win->background.z, - win->background.w); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - if (win->draw_cb) win->draw_cb(win, win->input); - if (win->widgets_cb) { - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - auto window = zero2i; - glfwGetWindowSize(win->win, &window.x, &window.y); - if (win->widgets_left) { - ImGui::SetNextWindowPos({0, 0}); - ImGui::SetNextWindowSize({(float)win->widgets_width, (float)window.y}); - } else { - ImGui::SetNextWindowPos({(float)(window.x - win->widgets_width), 0}); - ImGui::SetNextWindowSize({(float)win->widgets_width, (float)window.y}); - } - ImGui::SetNextWindowCollapsed(false); - ImGui::SetNextWindowBgAlpha(1); - if (ImGui::Begin(win->title.c_str(), nullptr, - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | - ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse | - ImGuiWindowFlags_NoSavedSettings)) { - win->widgets_cb(win, win->input); - } - ImGui::End(); - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - } - glfwSwapBuffers(win->win); -} - -void init_window(gui_window* win, const vec2i& size, const string& title, - bool widgets, int widgets_width, bool widgets_left) { - // init glfw - if (!glfwInit()) - throw std::runtime_error("cannot initialize windowing system"); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); -#if __APPLE__ - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); -#endif - - // create window - win->title = title; - win->win = glfwCreateWindow(size.x, size.y, title.c_str(), nullptr, nullptr); - if (!win->win) throw std::runtime_error("cannot initialize windowing system"); - glfwMakeContextCurrent(win->win); - glfwSwapInterval(1); // Enable vsync - - // set user data - glfwSetWindowUserPointer(win->win, win); - - // set callbacks - glfwSetWindowRefreshCallback(win->win, [](GLFWwindow* glfw) { - auto win = (gui_window*)glfwGetWindowUserPointer(glfw); - draw_window(win); - }); - glfwSetDropCallback( - win->win, [](GLFWwindow* glfw, int num, const char** paths) { - auto win = (gui_window*)glfwGetWindowUserPointer(glfw); - if (win->drop_cb) { - auto pathv = vector(); - for (auto i = 0; i < num; i++) pathv.push_back(paths[i]); - win->drop_cb(win, pathv, win->input); - } - }); - glfwSetKeyCallback(win->win, - [](GLFWwindow* glfw, int key, int scancode, int action, int mods) { - auto win = (gui_window*)glfwGetWindowUserPointer(glfw); - if (win->key_cb) win->key_cb(win, key, (bool)action, win->input); - }); - glfwSetCharCallback(win->win, [](GLFWwindow* glfw, unsigned int key) { - auto win = (gui_window*)glfwGetWindowUserPointer(glfw); - if (win->char_cb) win->char_cb(win, key, win->input); - }); - glfwSetMouseButtonCallback( - win->win, [](GLFWwindow* glfw, int button, int action, int mods) { - auto win = (gui_window*)glfwGetWindowUserPointer(glfw); - if (win->click_cb) - win->click_cb( - win, button == GLFW_MOUSE_BUTTON_LEFT, (bool)action, win->input); - }); - glfwSetScrollCallback( - win->win, [](GLFWwindow* glfw, double xoffset, double yoffset) { - auto win = (gui_window*)glfwGetWindowUserPointer(glfw); - if (win->scroll_cb) win->scroll_cb(win, (float)yoffset, win->input); - }); - glfwSetWindowSizeCallback( - win->win, [](GLFWwindow* glfw, int width, int height) { - auto win = (gui_window*)glfwGetWindowUserPointer(glfw); - glfwGetWindowSize( - win->win, &win->input.window_size.x, &win->input.window_size.y); - if (win->widgets_width) win->input.window_size.x -= win->widgets_width; - glfwGetFramebufferSize(win->win, &win->input.framebuffer_viewport.z, - &win->input.framebuffer_viewport.w); - win->input.framebuffer_viewport.x = 0; - win->input.framebuffer_viewport.y = 0; - if (win->widgets_width) { - auto win_size = zero2i; - glfwGetWindowSize(win->win, &win_size.x, &win_size.y); - auto offset = (int)(win->widgets_width * - (float)win->input.framebuffer_viewport.z / - win_size.x); - win->input.framebuffer_viewport.z -= offset; - if (win->widgets_left) win->input.framebuffer_viewport.x += offset; - } - }); - - // init gl extensions - if (!gladLoadGL()) - throw std::runtime_error("cannot initialize OpenGL extensions"); - - // widgets - if (widgets) { - ImGui::CreateContext(); - ImGui::GetIO().IniFilename = nullptr; - ImGui::GetStyle().WindowRounding = 0; - ImGui_ImplGlfw_InitForOpenGL(win->win, true); -#ifndef __APPLE__ - ImGui_ImplOpenGL3_Init(); -#else - ImGui_ImplOpenGL3_Init("#version 330"); -#endif - ImGui::StyleColorsDark(); - win->widgets_width = widgets_width; - win->widgets_left = widgets_left; - } -} - -void clear_window(gui_window* win) { - glfwDestroyWindow(win->win); - glfwTerminate(); - win->win = nullptr; -} - -// Run loop -void run_ui(gui_window* win) { - // init - if (win->init_cb) win->init_cb(win, win->input); - - // loop - while (!glfwWindowShouldClose(win->win)) { - // update input - win->input.mouse_last = win->input.mouse_pos; - auto mouse_posx = 0.0, mouse_posy = 0.0; - glfwGetCursorPos(win->win, &mouse_posx, &mouse_posy); - win->input.mouse_pos = vec2f{(float)mouse_posx, (float)mouse_posy}; - if (win->widgets_width && win->widgets_left) - win->input.mouse_pos.x -= win->widgets_width; - win->input.mouse_left = glfwGetMouseButton( - win->win, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; - win->input.mouse_right = - glfwGetMouseButton(win->win, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS; - win->input.modifier_alt = - glfwGetKey(win->win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || - glfwGetKey(win->win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS; - win->input.modifier_shift = - glfwGetKey(win->win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || - glfwGetKey(win->win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS; - win->input.modifier_ctrl = - glfwGetKey(win->win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || - glfwGetKey(win->win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS; - glfwGetWindowSize( - win->win, &win->input.window_size.x, &win->input.window_size.y); - if (win->widgets_width) win->input.window_size.x -= win->widgets_width; - glfwGetFramebufferSize(win->win, &win->input.framebuffer_viewport.z, - &win->input.framebuffer_viewport.w); - win->input.framebuffer_viewport.x = 0; - win->input.framebuffer_viewport.y = 0; - if (win->widgets_width) { - auto win_size = zero2i; - glfwGetWindowSize(win->win, &win_size.x, &win_size.y); - auto offset = (int)(win->widgets_width * - (float)win->input.framebuffer_viewport.z / - win_size.x); - win->input.framebuffer_viewport.z -= offset; - if (win->widgets_left) win->input.framebuffer_viewport.x += offset; - } - if (win->widgets_width) { - auto io = &ImGui::GetIO(); - win->input.widgets_active = io->WantTextInput || io->WantCaptureMouse || - io->WantCaptureKeyboard; - } - - // time - win->input.clock_last = win->input.clock_now; - win->input.clock_now = - std::chrono::high_resolution_clock::now().time_since_epoch().count(); - win->input.time_now = (double)win->input.clock_now / 1000000000.0; - win->input.time_delta = - (double)(win->input.clock_now - win->input.clock_last) / 1000000000.0; - - // update ui - if (win->uiupdate_cb && !win->input.widgets_active) - win->uiupdate_cb(win, win->input); - - // update - if (win->update_cb) win->update_cb(win, win->input); - - // draw - draw_window(win); - - // event hadling - glfwPollEvents(); - } - - // clear - if (win->clear_cb) win->clear_cb(win, win->input); -} - -void set_init_callback(gui_window* win, init_callback cb) { win->init_cb = cb; } -void set_clear_callback(gui_window* win, clear_callback cb) { - win->clear_cb = cb; -} -void set_draw_callback(gui_window* win, draw_callback cb) { win->draw_cb = cb; } -void set_widgets_callback(gui_window* win, widgets_callback cb) { - win->widgets_cb = cb; -} -void set_drop_callback(gui_window* win, drop_callback drop_cb) { - win->drop_cb = drop_cb; -} -void set_key_callback(gui_window* win, key_callback cb) { win->key_cb = cb; } -void set_char_callback(gui_window* win, char_callback cb) { win->char_cb = cb; } -void set_click_callback(gui_window* win, click_callback cb) { - win->click_cb = cb; -} -void set_scroll_callback(gui_window* win, scroll_callback cb) { - win->scroll_cb = cb; -} -void set_uiupdate_callback(gui_window* win, uiupdate_callback cb) { - win->uiupdate_cb = cb; -} -void set_update_callback(gui_window* win, update_callback cb) { - win->update_cb = cb; -} - -void set_close(gui_window* win, bool close) { - glfwSetWindowShouldClose(win->win, close ? GLFW_TRUE : GLFW_FALSE); -} - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// OPENGL WIDGETS -// ----------------------------------------------------------------------------- -namespace yocto { - -void init_glwidgets(gui_window* win, int width, bool left) { - // init widgets - ImGui::CreateContext(); - ImGui::GetIO().IniFilename = nullptr; - ImGui::GetStyle().WindowRounding = 0; - ImGui_ImplGlfw_InitForOpenGL(win->win, true); -#ifndef __APPLE__ - ImGui_ImplOpenGL3_Init(); -#else - ImGui_ImplOpenGL3_Init("#version 330"); -#endif - ImGui::StyleColorsDark(); - win->widgets_width = width; - win->widgets_left = left; -} - -bool begin_header(gui_window* win, const char* lbl) { - if (!ImGui::CollapsingHeader(lbl)) return false; - ImGui::PushID(lbl); - return true; -} -void end_header(gui_window* win) { ImGui::PopID(); } - -void open_glmodal(gui_window* win, const char* lbl) { ImGui::OpenPopup(lbl); } -void clear_glmodal(gui_window* win) { ImGui::CloseCurrentPopup(); } -bool begin_glmodal(gui_window* win, const char* lbl) { - return ImGui::BeginPopupModal(lbl); -} -void end_glmodal(gui_window* win) { ImGui::EndPopup(); } -bool is_glmodal_open(gui_window* win, const char* lbl) { - return ImGui::IsPopupOpen(lbl); -} - -// Utility to normalize a path -static inline string normalize_path(const string& filename_) { - auto filename = filename_; - for (auto& c : filename) - if (c == '\\') c = '/'; - if (filename.size() > 1 && filename[0] == '/' && filename[1] == '/') { - throw std::invalid_argument("absolute paths are not supported"); - return filename_; - } - if (filename.size() > 3 && filename[1] == ':' && filename[2] == '/' && - filename[3] == '/') { - throw std::invalid_argument("absolute paths are not supported"); - return filename_; - } - auto pos = (size_t)0; - while ((pos = filename.find("//")) != filename.npos) - filename = filename.substr(0, pos) + filename.substr(pos + 1); - return filename; -} - -// Get extension (not including '.'). -static string get_extension(const string& filename_) { - auto filename = normalize_path(filename_); - auto pos = filename.rfind('.'); - if (pos == string::npos) return ""; - return filename.substr(pos); -} - -struct filedialog_state { - string dirname = ""; - string filename = ""; - vector> entries = {}; - bool save = false; - bool remove_hidden = true; - string filter = ""; - vector extensions = {}; - - filedialog_state() {} - filedialog_state(const string& dirname, const string& filename, bool save, - const string& filter) { - this->save = save; - set_filter(filter); - set_dirname(dirname); - set_filename(filename); - } - void set_dirname(const string& name) { - dirname = name; - dirname = normalize_path(dirname); - if (dirname == "") dirname = "./"; - if (dirname.back() != '/') dirname += '/'; - refresh(); - } - void set_filename(const string& name) { - filename = name; - check_filename(); - } - void set_filter(const string& flt) { - auto globs = vector{""}; - for (auto i = 0; i < flt.size(); i++) { - if (flt[i] == ';') { - globs.push_back(""); - } else { - globs.back() += flt[i]; - } - } - filter = ""; - extensions.clear(); - for (auto pattern : globs) { - if (pattern == "") continue; - auto ext = get_extension(pattern); - if (ext != "") { - extensions.push_back(ext); - filter += (filter == "") ? ("*." + ext) : (";*." + ext); - } - } - } - void check_filename() { - if (filename.empty()) return; - auto ext = get_extension(filename); - if (std::find(extensions.begin(), extensions.end(), ext) == - extensions.end()) { - filename = ""; - return; - } - if (!save && !exists_file(dirname + filename)) { - filename = ""; - return; - } - } - void select_entry(int idx) { - if (entries[idx].second) { - set_dirname(dirname + entries[idx].first); - } else { - set_filename(entries[idx].first); - } - } - - void refresh() { - entries.clear(); - for (auto entry : directory_iterator(path{dirname})) { - if (remove_hidden && entry.path().stem().string()[0] == '.') continue; - if (entry.is_directory()) { - entries.push_back({entry.path().stem().string() + "/", true}); - } else { - entries.push_back({entry.path().stem().string(), false}); - } - } - std::sort(entries.begin(), entries.end(), [](auto& a, auto& b) { - if (a.second == b.second) return a.first < b.first; - return a.second; - }); - } - - string get_path() const { return dirname + filename; } - bool exists_file(const string& filename) { - auto f = fopen(filename.c_str(), "r"); - if (!f) return false; - fclose(f); - return true; - } -}; -bool draw_filedialog(gui_window* win, const char* lbl, string& path, bool save, - const string& dirname, const string& filename, const string& filter) { - static auto states = unordered_map{}; - ImGui::SetNextWindowSize({500, 300}, ImGuiCond_FirstUseEver); - if (ImGui::BeginPopupModal(lbl)) { - if (states.find(lbl) == states.end()) { - states[lbl] = filedialog_state{dirname, filename, save, filter}; - } - auto& state = states.at(lbl); - char dir_buffer[1024]; - snprintf(dir_buffer, sizeof(dir_buffer), "%s", state.dirname.c_str()); - if (ImGui::InputText("dir", dir_buffer, sizeof(dir_buffer))) { - state.set_dirname(dir_buffer); - } - auto current_item = -1; - if (ImGui::ListBox( - "entries", ¤t_item, - [](void* data, int idx, const char** out_text) -> bool { - auto& state = *(filedialog_state*)data; - *out_text = state.entries[idx].first.c_str(); - return true; - }, - &state, (int)state.entries.size())) { - state.select_entry(current_item); - } - char file_buffer[1024]; - snprintf(file_buffer, sizeof(file_buffer), "%s", state.filename.c_str()); - if (ImGui::InputText("file", file_buffer, sizeof(file_buffer))) { - state.set_filename(file_buffer); - } - char filter_buffer[1024]; - snprintf(filter_buffer, sizeof(filter_buffer), "%s", state.filter.c_str()); - if (ImGui::InputText("filter", filter_buffer, sizeof(filter_buffer))) { - state.set_filter(filter_buffer); - } - auto ok = false, exit = false; - if (ImGui::Button("Ok")) { - path = state.dirname + state.filename; - ok = true; - exit = true; - } - ImGui::SameLine(); - if (ImGui::Button("Cancel")) { - exit = true; - } - if (exit) { - ImGui::CloseCurrentPopup(); - states.erase(lbl); - } - ImGui::EndPopup(); - return ok; - } else { - return false; - } -} -bool draw_filedialog_button(gui_window* win, const char* button_lbl, - bool button_active, const char* lbl, string& path, bool save, - const string& dirname, const string& filename, const string& filter) { - if (is_glmodal_open(win, lbl)) { - return draw_filedialog(win, lbl, path, save, dirname, filename, filter); - } else { - if (draw_button(win, button_lbl, button_active)) { - open_glmodal(win, lbl); - } - return false; - } -} - -bool draw_button(gui_window* win, const char* lbl, bool enabled) { - if (enabled) { - return ImGui::Button(lbl); - } else { - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); - auto ok = ImGui::Button(lbl); - ImGui::PopItemFlag(); - ImGui::PopStyleVar(); - return ok; - } -} - -void draw_label(gui_window* win, const char* lbl, const string& label) { - ImGui::LabelText(lbl, "%s", label.c_str()); -} - -void draw_separator(gui_window* win) { ImGui::Separator(); } - -void continue_line(gui_window* win) { ImGui::SameLine(); } - -bool draw_textinput(gui_window* win, const char* lbl, string& value) { - char buffer[4096]; - auto num = 0; - for (auto c : value) buffer[num++] = c; - buffer[num] = 0; - auto edited = ImGui::InputText(lbl, buffer, sizeof(buffer)); - if (edited) value = buffer; - return edited; -} - -bool draw_slider( - gui_window* win, const char* lbl, float& value, float min, float max) { - return ImGui::SliderFloat(lbl, &value, min, max); -} -bool draw_slider( - gui_window* win, const char* lbl, vec2f& value, float min, float max) { - return ImGui::SliderFloat2(lbl, &value.x, min, max); -} -bool draw_slider( - gui_window* win, const char* lbl, vec3f& value, float min, float max) { - return ImGui::SliderFloat3(lbl, &value.x, min, max); -} -bool draw_slider( - gui_window* win, const char* lbl, vec4f& value, float min, float max) { - return ImGui::SliderFloat4(lbl, &value.x, min, max); -} - -bool draw_slider( - gui_window* win, const char* lbl, int& value, int min, int max) { - return ImGui::SliderInt(lbl, &value, min, max); -} -bool draw_slider( - gui_window* win, const char* lbl, vec2i& value, int min, int max) { - return ImGui::SliderInt2(lbl, &value.x, min, max); -} -bool draw_slider( - gui_window* win, const char* lbl, vec3i& value, int min, int max) { - return ImGui::SliderInt3(lbl, &value.x, min, max); -} -bool draw_slider( - gui_window* win, const char* lbl, vec4i& value, int min, int max) { - return ImGui::SliderInt4(lbl, &value.x, min, max); -} - -bool draw_dragger(gui_window* win, const char* lbl, float& value, float speed, - float min, float max) { - return ImGui::DragFloat(lbl, &value, speed, min, max); -} -bool draw_dragger(gui_window* win, const char* lbl, vec2f& value, float speed, - float min, float max) { - return ImGui::DragFloat2(lbl, &value.x, speed, min, max); -} -bool draw_dragger(gui_window* win, const char* lbl, vec3f& value, float speed, - float min, float max) { - return ImGui::DragFloat3(lbl, &value.x, speed, min, max); -} -bool draw_dragger(gui_window* win, const char* lbl, vec4f& value, float speed, - float min, float max) { - return ImGui::DragFloat4(lbl, &value.x, speed, min, max); -} - -bool draw_dragger(gui_window* win, const char* lbl, int& value, float speed, - int min, int max) { - return ImGui::DragInt(lbl, &value, speed, min, max); -} -bool draw_dragger(gui_window* win, const char* lbl, vec2i& value, float speed, - int min, int max) { - return ImGui::DragInt2(lbl, &value.x, speed, min, max); -} -bool draw_dragger(gui_window* win, const char* lbl, vec3i& value, float speed, - int min, int max) { - return ImGui::DragInt3(lbl, &value.x, speed, min, max); -} -bool draw_dragger(gui_window* win, const char* lbl, vec4i& value, float speed, - int min, int max) { - return ImGui::DragInt4(lbl, &value.x, speed, min, max); -} - -bool draw_checkbox(gui_window* win, const char* lbl, bool& value) { - return ImGui::Checkbox(lbl, &value); -} -bool draw_checkbox(gui_window* win, const char* lbl, bool& value, bool invert) { - if (!invert) { - return draw_checkbox(win, lbl, value); - } else { - auto inverted = !value; - auto edited = ImGui::Checkbox(lbl, &inverted); - if (edited) value = !inverted; - return edited; - } -} - -bool draw_coloredit(gui_window* win, const char* lbl, vec3f& value) { - auto flags = ImGuiColorEditFlags_Float; - return ImGui::ColorEdit3(lbl, &value.x, flags); -} - -bool draw_coloredit(gui_window* win, const char* lbl, vec4f& value) { - auto flags = ImGuiColorEditFlags_Float; - return ImGui::ColorEdit4(lbl, &value.x, flags); -} - -bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec3f& value) { - auto color = value; - auto exposure = 0.0f; - auto scale = max(color); - if (scale > 1) { - color /= scale; - exposure = log2(scale); - } - auto edit_exposure = draw_slider( - win, (lbl + " [exp]"s).c_str(), exposure, 0, 10); - auto edit_color = draw_coloredit(win, (lbl + " [col]"s).c_str(), color); - if (edit_exposure || edit_color) { - value = color * exp2(exposure); - return true; - } else { - return false; - } -} -bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec4f& value) { - auto color = value; - auto exposure = 0.0f; - auto scale = max(xyz(color)); - if (scale > 1) { - color.x /= scale; - color.y /= scale; - color.z /= scale; - exposure = log2(scale); - } - auto edit_exposure = draw_slider( - win, (lbl + " [exp]"s).c_str(), exposure, 0, 10); - auto edit_color = draw_coloredit(win, (lbl + " [col]"s).c_str(), color); - if (edit_exposure || edit_color) { - value.x = color.x * exp2(exposure); - value.y = color.y * exp2(exposure); - value.z = color.z * exp2(exposure); - value.w = color.w; - return true; - } else { - return false; - } -} - -bool draw_combobox(gui_window* win, const char* lbl, int& value, - const vector& labels) { - if (!ImGui::BeginCombo(lbl, labels[value].c_str())) return false; - auto old_val = value; - for (auto i = 0; i < labels.size(); i++) { - ImGui::PushID(i); - if (ImGui::Selectable(labels[i].c_str(), value == i)) value = i; - if (value == i) ImGui::SetItemDefaultFocus(); - ImGui::PopID(); - } - ImGui::EndCombo(); - return value != old_val; -} - -bool draw_combobox(gui_window* win, const char* lbl, string& value, - const vector& labels) { - if (!ImGui::BeginCombo(lbl, value.c_str())) return false; - auto old_val = value; - for (auto i = 0; i < labels.size(); i++) { - ImGui::PushID(i); - if (ImGui::Selectable(labels[i].c_str(), value == labels[i])) - value = labels[i]; - if (value == labels[i]) ImGui::SetItemDefaultFocus(); - ImGui::PopID(); - } - ImGui::EndCombo(); - return value != old_val; -} - -bool draw_combobox(gui_window* win, const char* lbl, int& idx, int num, - const function& labels, bool include_null) { - if (num <= 0) idx = -1; - if (!ImGui::BeginCombo(lbl, idx >= 0 ? labels(idx).c_str() : "")) - return false; - auto old_idx = idx; - if (include_null) { - ImGui::PushID(100000); - if (ImGui::Selectable("", idx < 0)) idx = -1; - if (idx < 0) ImGui::SetItemDefaultFocus(); - ImGui::PopID(); - } - for (auto i = 0; i < num; i++) { - ImGui::PushID(i); - if (ImGui::Selectable(labels(i).c_str(), idx == i)) idx = i; - if (idx == i) ImGui::SetItemDefaultFocus(); - ImGui::PopID(); - } - ImGui::EndCombo(); - return idx != old_idx; -} - -void draw_progressbar(gui_window* win, const char* lbl, float fraction) { - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5, 0.5, 1, 0.25)); - ImGui::ProgressBar(fraction, ImVec2(0.0f, 0.0f)); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text(lbl, ImVec2(0.0f, 0.0f)); - ImGui::PopStyleColor(1); -} - -void draw_progressbar( - gui_window* win, const char* lbl, int current, int total) { - auto overlay = std::to_string(current) + "/" + std::to_string(total); - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.5, 0.5, 1, 0.25)); - ImGui::ProgressBar( - (float)current / (float)total, ImVec2(0.0f, 0.0f), overlay.c_str()); - ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - ImGui::Text(lbl, ImVec2(0.0f, 0.0f)); - ImGui::PopStyleColor(1); -} - -void draw_histogram( - gui_window* win, const char* lbl, const float* values, int count) { - ImGui::PlotHistogram(lbl, values, count); -} -void draw_histogram( - gui_window* win, const char* lbl, const vector& values) { - ImGui::PlotHistogram(lbl, values.data(), (int)values.size(), 0, nullptr, - flt_max, flt_max, {0, 0}, 4); -} -void draw_histogram( - gui_window* win, const char* lbl, const vector& values) { - ImGui::PlotHistogram((lbl + " x"s).c_str(), (const float*)values.data() + 0, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec2f)); - ImGui::PlotHistogram((lbl + " y"s).c_str(), (const float*)values.data() + 1, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec2f)); -} -void draw_histogram( - gui_window* win, const char* lbl, const vector& values) { - ImGui::PlotHistogram((lbl + " x"s).c_str(), (const float*)values.data() + 0, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec3f)); - ImGui::PlotHistogram((lbl + " y"s).c_str(), (const float*)values.data() + 1, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec3f)); - ImGui::PlotHistogram((lbl + " z"s).c_str(), (const float*)values.data() + 2, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec3f)); -} -void draw_histogram( - gui_window* win, const char* lbl, const vector& values) { - ImGui::PlotHistogram((lbl + " x"s).c_str(), (const float*)values.data() + 0, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); - ImGui::PlotHistogram((lbl + " y"s).c_str(), (const float*)values.data() + 1, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); - ImGui::PlotHistogram((lbl + " z"s).c_str(), (const float*)values.data() + 2, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); - ImGui::PlotHistogram((lbl + " w"s).c_str(), (const float*)values.data() + 3, - (int)values.size(), 0, nullptr, flt_max, flt_max, {0, 0}, sizeof(vec4f)); -} - -// https://github.com/ocornut/imgui/issues/300 -struct ImGuiAppLog { - ImGuiTextBuffer Buf; - ImGuiTextFilter Filter; - ImVector LineOffsets; // Index to lines offset - bool ScrollToBottom; - - void Clear() { - Buf.clear(); - LineOffsets.clear(); - } - - void AddLog(const char* msg, const char* lbl) { - int old_size = Buf.size(); - Buf.appendf("[%s] %s\n", lbl, msg); - for (int new_size = Buf.size(); old_size < new_size; old_size++) - if (Buf[old_size] == '\n') LineOffsets.push_back(old_size); - ScrollToBottom = true; - } - - void Draw() { - if (ImGui::Button("Clear")) Clear(); - ImGui::SameLine(); - bool copy = ImGui::Button("Copy"); - ImGui::SameLine(); - Filter.Draw("Filter", -100.0f); - ImGui::Separator(); - ImGui::BeginChild("scrolling"); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 1)); - if (copy) ImGui::LogToClipboard(); - - if (Filter.IsActive()) { - const char* buf_begin = Buf.begin(); - const char* line = buf_begin; - for (int line_no = 0; line != NULL; line_no++) { - const char* line_end = (line_no < LineOffsets.Size) - ? buf_begin + LineOffsets[line_no] - : NULL; - if (Filter.PassFilter(line, line_end)) - ImGui::TextUnformatted(line, line_end); - line = line_end && line_end[1] ? line_end + 1 : NULL; - } - } else { - ImGui::TextUnformatted(Buf.begin()); - } - - if (ScrollToBottom) ImGui::SetScrollHere(1.0f); - ScrollToBottom = false; - ImGui::PopStyleVar(); - ImGui::EndChild(); - } - void Draw(const char* title, bool* p_opened = NULL) { - ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiSetCond_FirstUseEver); - ImGui::Begin(title, p_opened); - Draw(); - ImGui::End(); - } -}; - -std::mutex _log_mutex; -ImGuiAppLog _log_widget; -void log_info(gui_window* win, const string& msg) { - _log_mutex.lock(); - _log_widget.AddLog(msg.c_str(), "info"); - _log_mutex.unlock(); -} -void log_error(gui_window* win, const string& msg) { - _log_mutex.lock(); - _log_widget.AddLog(msg.c_str(), "errn"); - _log_mutex.unlock(); -} -void clear_log(gui_window* win) { - _log_mutex.lock(); - _log_widget.Clear(); - _log_mutex.unlock(); -} -void draw_log(gui_window* win) { - _log_mutex.lock(); - _log_widget.Draw(); - _log_mutex.unlock(); -} - -} // namespace yocto diff --git a/libs/yocto_gui/yocto_gui.h b/libs/yocto_gui/yocto_opengl.h similarity index 67% rename from libs/yocto_gui/yocto_gui.h rename to libs/yocto_gui/yocto_opengl.h index e744fcf9f..4f9c00916 100644 --- a/libs/yocto_gui/yocto_gui.h +++ b/libs/yocto_gui/yocto_opengl.h @@ -1,5 +1,5 @@ // -// Yocto/Gui: Utilities for writing simple native GUIs. +// Yocto/OpenGL: Utilities for writing native GUIs with OpenGL. // // @@ -27,8 +27,8 @@ // // -#ifndef _YOCTO_GUI_ -#define _YOCTO_GUI_ +#ifndef _YOCTO_OPENGL_ +#define _YOCTO_OPENGL_ // ----------------------------------------------------------------------------- // INCLUDES @@ -37,8 +37,7 @@ #include #include -#include -#include +#include #include // forward declaration @@ -50,7 +49,6 @@ struct GLFWwindow; namespace yocto { // using directives -using std::function; using std::string; using std::vector; @@ -624,260 +622,4 @@ void draw_scene(ogl_scene* scene, ogl_camera* camera, const vec4i& viewport, } // namespace yocto -// ----------------------------------------------------------------------------- -// UI APPLICATION -// ----------------------------------------------------------------------------- -namespace yocto { - -// Forward declaration of OpenGL window -struct gui_window; - -// Input state -struct gui_input { - bool mouse_left = false; // left button - bool mouse_right = false; // right button - bool mouse_middle = false; // middle button - vec2f mouse_pos = {}; // position excluding widgets - vec2f mouse_last = {}; // last mouse position excluding widgets - vec2f mouse_delta = {}; // last mouse delta excluding widgets - bool modifier_alt = false; // alt modifier - bool modifier_ctrl = false; // ctrl modifier - bool modifier_shift = false; // shift modifier - bool widgets_active = false; // widgets are active - uint64_t clock_now = 0; // clock now - uint64_t clock_last = 0; // clock last - double time_now = 0; // time now - double time_delta = 0; // time delta - vec2i window_size = {0, 0}; // window size - vec4i framebuffer_viewport = {0, 0, 0, 0}; // framebuffer viewport -}; - -// Init callback called after the window has opened -using init_callback = function; -// Clear callback called after the window is cloased -using clear_callback = function; -// Draw callback called every frame and when resizing -using draw_callback = function; -// Draw callback for drawing widgets -using widgets_callback = function; -// Drop callback that returns that list of dropped strings. -using drop_callback = - function&, const gui_input& input)>; -// Key callback that returns key codes, pressed/released flag and modifier keys -using key_callback = - function; -// Char callback that returns ASCII key -using char_callback = - function; -// Mouse click callback that returns left/right button, pressed/released flag, -// modifier keys -using click_callback = function; -// Scroll callback that returns scroll amount -using scroll_callback = - function; -// Update functions called every frame -using uiupdate_callback = function; -// Update functions called every frame -using update_callback = function; - -// User interface callcaks -struct gui_callbacks { - init_callback init_cb = {}; - clear_callback clear_cb = {}; - draw_callback draw_cb = {}; - widgets_callback widgets_cb = {}; - drop_callback drop_cb = {}; - key_callback key_cb = {}; - char_callback char_cb = {}; - click_callback click_cb = {}; - scroll_callback scroll_cb = {}; - update_callback update_cb = {}; - uiupdate_callback uiupdate_cb = {}; -}; - -// run the user interface with the give callbacks -void run_ui(const vec2i& size, const string& title, - const gui_callbacks& callbaks, int widgets_width = 320, - bool widgets_left = true); - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// UI WINDOW -// ----------------------------------------------------------------------------- -namespace yocto { - -// OpenGL window wrapper -struct gui_window { - GLFWwindow* win = nullptr; - string title = ""; - init_callback init_cb = {}; - clear_callback clear_cb = {}; - draw_callback draw_cb = {}; - widgets_callback widgets_cb = {}; - drop_callback drop_cb = {}; - key_callback key_cb = {}; - char_callback char_cb = {}; - click_callback click_cb = {}; - scroll_callback scroll_cb = {}; - update_callback update_cb = {}; - uiupdate_callback uiupdate_cb = {}; - int widgets_width = 0; - bool widgets_left = true; - gui_input input = {}; - vec4f background = {0.15f, 0.15f, 0.15f, 1.0f}; -}; - -// Windows initialization -void init_window(gui_window* win, const vec2i& size, const string& title, - bool widgets, int widgets_width = 320, bool widgets_left = true); - -// Window cleanup -void clear_window(gui_window* win); - -// Set callbacks -void set_init_callback(gui_window* win, init_callback init_cb); -void set_clear_callback(gui_window* win, clear_callback clear_cb); -void set_draw_callback(gui_window* win, draw_callback draw_cb); -void set_widgets_callback(gui_window* win, widgets_callback widgets_cb); -void set_drop_callback(gui_window* win, drop_callback drop_cb); -void set_key_callback(gui_window* win, key_callback cb); -void set_char_callback(gui_window* win, char_callback cb); -void set_click_callback(gui_window* win, click_callback cb); -void set_scroll_callback(gui_window* win, scroll_callback cb); -void set_uiupdate_callback(gui_window* win, uiupdate_callback cb); -void set_update_callback(gui_window* win, update_callback cb); - -// Run loop -void run_ui(gui_window* win); -void set_close(gui_window* win, bool close); - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// OPENGL WIDGETS -// ----------------------------------------------------------------------------- -namespace yocto { - -bool begin_header(gui_window* win, const char* title); -void end_header(gui_window* win); - -void draw_label(gui_window* win, const char* lbl, const string& text); - -void draw_separator(gui_window* win); -void continue_line(gui_window* win); - -bool draw_button(gui_window* win, const char* lbl, bool enabled = true); - -bool draw_textinput(gui_window* win, const char* lbl, string& value); - -bool draw_slider( - gui_window* win, const char* lbl, float& value, float min, float max); -bool draw_slider( - gui_window* win, const char* lbl, vec2f& value, float min, float max); -bool draw_slider( - gui_window* win, const char* lbl, vec3f& value, float min, float max); -bool draw_slider( - gui_window* win, const char* lbl, vec4f& value, float min, float max); - -bool draw_slider( - gui_window* win, const char* lbl, int& value, int min, int max); -bool draw_slider( - gui_window* win, const char* lbl, vec2i& value, int min, int max); -bool draw_slider( - gui_window* win, const char* lbl, vec3i& value, int min, int max); -bool draw_slider( - gui_window* win, const char* lbl, vec4i& value, int min, int max); - -bool draw_dragger(gui_window* win, const char* lbl, float& value, - float speed = 1.0f, float min = 0.0f, float max = 0.0f); -bool draw_dragger(gui_window* win, const char* lbl, vec2f& value, - float speed = 1.0f, float min = 0.0f, float max = 0.0f); -bool draw_dragger(gui_window* win, const char* lbl, vec3f& value, - float speed = 1.0f, float min = 0.0f, float max = 0.0f); -bool draw_dragger(gui_window* win, const char* lbl, vec4f& value, - float speed = 1.0f, float min = 0.0f, float max = 0.0f); - -bool draw_dragger(gui_window* win, const char* lbl, int& value, float speed = 1, - int min = 0, int max = 0); -bool draw_dragger(gui_window* win, const char* lbl, vec2i& value, - float speed = 1, int min = 0, int max = 0); -bool draw_dragger(gui_window* win, const char* lbl, vec3i& value, - float speed = 1, int min = 0, int max = 0); -bool draw_dragger(gui_window* win, const char* lbl, vec4i& value, - float speed = 1, int min = 0, int max = 0); - -bool draw_checkbox(gui_window* win, const char* lbl, bool& value); -bool draw_checkbox(gui_window* win, const char* lbl, bool& value, bool invert); - -bool draw_coloredit(gui_window* win, const char* lbl, vec3f& value); -bool draw_coloredit(gui_window* win, const char* lbl, vec4f& value); - -bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec3f& value); -bool draw_hdrcoloredit(gui_window* win, const char* lbl, vec4f& value); - -bool draw_combobox( - gui_window* win, const char* lbl, int& idx, const vector& labels); -bool draw_combobox(gui_window* win, const char* lbl, string& value, - const vector& labels); -bool draw_combobox(gui_window* win, const char* lbl, int& idx, int num, - const function& labels, bool include_null = false); - -template -inline bool draw_combobox(gui_window* win, const char* lbl, T*& value, - const vector& vals, bool include_null = false) { - auto idx = -1; - for (auto pos = 0; pos < vals.size(); pos++) - if (vals[pos] == value) idx = pos; - auto edited = draw_combobox( - win, lbl, idx, (int)vals.size(), [&](int idx) { return vals[idx]->name; }, - include_null); - if (edited) { - value = idx >= 0 ? vals[idx] : nullptr; - } - return edited; -} - -template -inline bool draw_combobox(gui_window* win, const char* lbl, T*& value, - const vector& vals, const vector& labels, - bool include_null = false) { - auto idx = -1; - for (auto pos = 0; pos < vals.size(); pos++) - if (vals[pos] == value) idx = pos; - auto edited = draw_combobox( - win, lbl, idx, (int)vals.size(), [&](int idx) { return labels[idx]; }, - include_null); - if (edited) { - value = idx >= 0 ? vals[idx] : nullptr; - } - return edited; -} - -void draw_progressbar(gui_window* win, const char* lbl, float fraction); -void draw_progressbar(gui_window* win, const char* lbl, int current, int total); - -void draw_histogram( - gui_window* win, const char* lbl, const vector& values); -void draw_histogram( - gui_window* win, const char* lbl, const vector& values); -void draw_histogram( - gui_window* win, const char* lbl, const vector& values); -void draw_histogram( - gui_window* win, const char* lbl, const vector& values); - -bool draw_filedialog(gui_window* win, const char* lbl, string& path, bool save, - const string& dirname, const string& filename, const string& filter); -bool draw_filedialog_button(gui_window* win, const char* button_lbl, - bool button_active, const char* lbl, string& path, bool save, - const string& dirname, const string& filename, const string& filter); - -void log_info(gui_window* win, const string& msg); -void log_error(gui_window* win, const string& msg); -void clear_log(gui_window* win); -void draw_log(gui_window* win); - -} // namespace yocto - #endif