/***************************************************************************************** * * * OpenSpace * * * * Copyright (c) 2014-2017 * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include "gui_lua.inl" //#define SHOW_IMGUI_HELPERS namespace { const std::string _loggerCat = "GUI"; const char* configurationFile = "imgui.ini"; const char* GuiFont = "${FONTS}/Roboto/Roboto-Regular.ttf"; const ImVec2 size = ImVec2(350, 500); //GLuint fontTex = 0; // A VBO max size of 0 will cause a lazy instantiation of the buffer size_t vboMaxSize = 0; GLuint vao = 0; GLuint vbo = 0; GLuint vboElements = 0; std::unique_ptr _program; std::unique_ptr _fontTexture; char* iniFileBuffer = nullptr; static void RenderDrawLists(ImDrawData* drawData) { // Avoid rendering when minimized, scale coordinates for retina displays // (screen coordinates != framebuffer coordinates) ImGuiIO& io = ImGui::GetIO(); int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); if (fb_width == 0 || fb_height == 0) return; drawData->ScaleClipRects(io.DisplayFramebufferScale); // Setup render state: // alpha-blending enabled, no face culling, no depth testing, scissor enabled glEnable(GL_BLEND); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glEnable(GL_SCISSOR_TEST); ghoul::opengl::TextureUnit unit; unit.activate(); _fontTexture->bind(); // Setup orthographic projection matrix const float width = ImGui::GetIO().DisplaySize.x; const float height = ImGui::GetIO().DisplaySize.y; glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); const glm::mat4 ortho( 2.f / width, 0.0f, 0.0f, 0.f, 0.0f, 2.0f / -height, 0.0f, 0.f, 0.0f, 0.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f ); _program->activate(); _program->setUniform("tex", unit); _program->setUniform("ortho", ortho); glBindVertexArray(vao); for (int i = 0; i < drawData->CmdListsCount; ++i) { const ImDrawList* cmdList = drawData->CmdLists[i]; const ImDrawIdx* indexBufferOffset = 0; glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData( GL_ARRAY_BUFFER, (GLsizeiptr)cmdList->VtxBuffer.size() * sizeof(ImDrawVert), (GLvoid*)&cmdList->VtxBuffer.front(), GL_STREAM_DRAW ); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboElements); glBufferData( GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)cmdList->IdxBuffer.size() * sizeof(ImDrawIdx), (GLvoid*)&cmdList->IdxBuffer.front(), GL_STREAM_DRAW ); for (const ImDrawCmd* pcmd = cmdList->CmdBuffer.begin(); pcmd != cmdList->CmdBuffer.end(); pcmd++) { if (pcmd->UserCallback) { pcmd->UserCallback(cmdList, pcmd); } else { glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); glScissor( (int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y) ); glDrawElements( GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, indexBufferOffset ); } indexBufferOffset += pcmd->ElemCount; } } //// Grow our buffer according to what we need //size_t totalVertexCount = 0; //for (int i = 0; i < nCommandLists; ++i) // totalVertexCount += commandLists[i]->vtx_buffer.size(); //glBindBuffer(GL_ARRAY_BUFFER, vbo); //size_t neededBufferSize = totalVertexCount * sizeof(ImDrawVert); //if (neededBufferSize > vboMaxSize) { // // Grow buffer // vboMaxSize = neededBufferSize * 1.25f; // glBufferData(GL_ARRAY_BUFFER, vboMaxSize, NULL, GL_STREAM_DRAW); //} //// Copy and convert all vertices into a single contiguous buffer //unsigned char* bufferData = reinterpret_cast( // glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY) // ); //if (!bufferData) { // LFATAL("Error mapping ImGui buffer"); // return; //} //for (int i = 0; i < nCommandLists; ++i) { // const ImDrawList* cmd_list = commandLists[i]; // memcpy( // bufferData, // &cmd_list->vtx_buffer[0], // cmd_list->vtx_buffer.size() * sizeof(ImDrawVert) // ); // bufferData += (cmd_list->vtx_buffer.size() * sizeof(ImDrawVert)); //} //glUnmapBuffer(GL_ARRAY_BUFFER); //glBindBuffer(GL_ARRAY_BUFFER, 0); //glBindVertexArray(vao); //int cmdOffset = 0; //for (int i = 0; i < nCommandLists; ++i) { // const ImDrawList* cmd_list = commandLists[i]; // int vtxOffset = cmdOffset; // for (const auto& pcmd : cmd_list->commands) { // glScissor( // static_cast(pcmd.clip_rect.x), // static_cast(height - pcmd.clip_rect.w), // static_cast(pcmd.clip_rect.z - pcmd.clip_rect.x), // static_cast(pcmd.clip_rect.w - pcmd.clip_rect.y) // ); // glDrawArrays(GL_TRIANGLES, vtxOffset, pcmd.vtx_count); // vtxOffset += pcmd.vtx_count; // } // cmdOffset = vtxOffset; //} glBindVertexArray(0); _program->deactivate(); glDisable(GL_SCISSOR_TEST); } void addScreenSpaceRenderable(std::string texturePath) { if (!FileSys.fileExists(texturePath)) { LWARNING("Could not find image '" << texturePath << "'"); return; } texturePath = absPath(texturePath); texturePath = FileSys.convertPathSeparator(texturePath, '/'); std::string luaTable = "{Type = 'ScreenSpaceImage', TexturePath = '" + texturePath + "' }"; std::string script = "openspace.registerScreenSpaceRenderable(" + luaTable + ");"; OsEng.scriptEngine().queueScript(script, openspace::scripting::ScriptEngine::RemoteScripting::Yes); } } // namespace namespace openspace { namespace gui { GUI::GUI() : GuiComponent("Main") , _globalProperty("Global") , _property("Properties") , _screenSpaceProperty("ScreenSpace Properties") , _virtualProperty("Virtual Properties") , _currentVisibility(properties::Property::Visibility::All) { addPropertySubOwner(_help); addPropertySubOwner(_origin); addPropertySubOwner(_performance); addPropertySubOwner(_globalProperty); addPropertySubOwner(_property); addPropertySubOwner(_screenSpaceProperty); addPropertySubOwner(_virtualProperty); addPropertySubOwner(_time); addPropertySubOwner(_iswa); } void GUI::initialize() { std::string cachedFile = FileSys.cacheManager()->cachedFilename( configurationFile, "", ghoul::filesystem::CacheManager::Persistent::Yes ); iniFileBuffer = new char[cachedFile.size() + 1]; #ifdef WIN32 strcpy_s(iniFileBuffer, cachedFile.size() + 1, cachedFile.c_str()); #else strcpy(iniFileBuffer, cachedFile.c_str()); #endif ImGuiIO& io = ImGui::GetIO(); io.IniFilename = iniFileBuffer; io.DeltaTime = 1.f / 60.f; io.KeyMap[ImGuiKey_Tab] = static_cast(Key::Tab); io.KeyMap[ImGuiKey_LeftArrow] = static_cast(Key::Left); io.KeyMap[ImGuiKey_RightArrow] = static_cast(Key::Right); io.KeyMap[ImGuiKey_UpArrow] = static_cast(Key::Up); io.KeyMap[ImGuiKey_DownArrow] = static_cast(Key::Down); io.KeyMap[ImGuiKey_Home] = static_cast(Key::Home); io.KeyMap[ImGuiKey_End] = static_cast(Key::End); io.KeyMap[ImGuiKey_Delete] = static_cast(Key::Delete); io.KeyMap[ImGuiKey_Backspace] = static_cast(Key::BackSpace); io.KeyMap[ImGuiKey_Enter] = static_cast(Key::Enter); io.KeyMap[ImGuiKey_Escape] = static_cast(Key::Escape); io.KeyMap[ImGuiKey_A] = static_cast(Key::A); io.KeyMap[ImGuiKey_C] = static_cast(Key::C); io.KeyMap[ImGuiKey_V] = static_cast(Key::V); io.KeyMap[ImGuiKey_X] = static_cast(Key::X); io.KeyMap[ImGuiKey_Y] = static_cast(Key::Y); io.KeyMap[ImGuiKey_Z] = static_cast(Key::Z); io.RenderDrawListsFn = RenderDrawLists; io.Fonts->AddFontFromFileTTF( absPath(GuiFont).c_str(), 16.f ); ImGuiStyle& style = ImGui::GetStyle(); style.WindowPadding = { 4.f, 4.f }; style.WindowRounding = 0.f; style.FramePadding = { 3.f, 3.f }; style.FrameRounding = 0.f; style.ScrollbarSize = 15.f; style.ScrollbarRounding = 0.f; style.IndentSpacing = 25; style.ItemSpacing = { 4.f, 2.f }; style.Colors[ImGuiCol_WindowBg] = ImVec4(0.0f, 0.21f, 0.24f, 1.0f); style.Colors[ImGuiCol_Border] = ImVec4(0.1f, 0.39f, 0.42f, 0.59f); style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); style.Colors[ImGuiCol_TitleBg] = ImVec4(0.5f, 0.94f, 1.0f, 0.45f); style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.5f, 0.94f, 1.0f, 0.45f); style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.12f, 0.71f, 0.8f, 0.43f); style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.4f, 0.75f, 0.8f, 0.65f); style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.4f, 0.75f, 0.8f, 0.65f); style.Colors[ImGuiCol_ComboBg] = ImVec4(0.18f, 0.51f, 0.78f, 1.f); style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.5f, 0.8f, 0.76f, 1.0f); style.Colors[ImGuiCol_Button] = ImVec4(0.0f, 0.36f, 0.67f, 0.6f); style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.0f, 0.51f, 0.94f, 1.0f); style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.0f, 0.43f, 0.8f, 1.0f); style.Colors[ImGuiCol_Header] = ImVec4(0.f, 0.36f, 0.67f, 0.45f); style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.f, 0.54f, 1.0f, 0.8f); style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.53f, 0.63f, 0.87f, 0.8f); style.Colors[ImGuiCol_ResizeGrip] = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); style.Colors[ImGuiCol_CloseButton] = ImVec4(0.75f, 0.75f, 0.75f, 1.0f); style.Colors[ImGuiCol_CloseButtonHovered] = ImVec4(0.52f, 0.52f, 0.52f, 0.6f); style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.52f, 0.52f, 0.52f, 1.0f); _property.initialize(); _property.setHasRegularProperties(true); _screenSpaceProperty.initialize(); _screenSpaceProperty.setHasRegularProperties(true); _globalProperty.initialize(); _globalProperty.setHasRegularProperties(true); _virtualProperty.initialize(); _performance.initialize(); _help.initialize(); _iswa.initialize(); } void GUI::deinitialize() { ImGui::Shutdown(); _iswa.deinitialize(); _help.deinitialize(); _performance.deinitialize(); _globalProperty.deinitialize(); _screenSpaceProperty.deinitialize(); _virtualProperty.deinitialize(); _property.deinitialize(); delete iniFileBuffer; } void GUI::initializeGL() { _program = ghoul::opengl::ProgramObject::Build( "GUI", "${MODULE_ONSCREENGUI}/shaders/gui_vs.glsl", "${MODULE_ONSCREENGUI}/shaders/gui_fs.glsl" ); if (!_program) { return; } unsigned char* pngData; glm::ivec2 textureSize; ImGui::GetIO().Fonts->GetTexDataAsRGBA32(&pngData, &textureSize.x, &textureSize.y); _fontTexture = std::make_unique( pngData, glm::uvec3(textureSize.x, textureSize.y, 1) ); _fontTexture->setName("Gui Text"); _fontTexture->setDataOwnership(ghoul::opengl::Texture::TakeOwnership::No); _fontTexture->uploadTexture(); GLuint id = *_fontTexture; ImGui::GetIO().Fonts->TexID = (void*)(intptr_t)id; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, vboMaxSize, NULL, GL_DYNAMIC_DRAW); glGenBuffers(1, &vboElements); glGenVertexArrays(1, &vao); glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); glEnableVertexAttribArray(_program->attributeLocation("in_position")); glEnableVertexAttribArray(_program->attributeLocation("in_uv")); glEnableVertexAttribArray(_program->attributeLocation("in_color")); glVertexAttribPointer( _program->attributeLocation("in_position"), 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, pos)) ); glVertexAttribPointer( _program->attributeLocation("in_uv"), 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, uv)) ); glVertexAttribPointer( _program->attributeLocation("in_color"), 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), reinterpret_cast(offsetof(ImDrawVert, col)) ); glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); _property.initializeGL(); _screenSpaceProperty.initializeGL(); _globalProperty.initializeGL(); _performance.initializeGL(); _help.initializeGL(); _iswa.initializeGL(); } void GUI::deinitializeGL() { _program = nullptr; _fontTexture = nullptr; if (vao) { glDeleteVertexArrays(1, &vao); } if (vbo) { glDeleteBuffers(1, &vbo); } if (vboElements) { glDeleteBuffers(1, &vboElements); } _iswa.deinitializeGL(); _help.deinitializeGL(); _performance.deinitializeGL(); _globalProperty.deinitializeGL(); _screenSpaceProperty.deinitializeGL(); _property.deinitializeGL(); } void GUI::startFrame(float deltaTime, const glm::vec2& windowSize, const glm::vec2& dpiScaling, const glm::vec2& mousePos, uint32_t mouseButtonsPressed) { ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(windowSize.x, windowSize.y); io.DisplayFramebufferScale = ImVec2(dpiScaling.x, dpiScaling.y); io.DeltaTime = deltaTime; io.MousePos = ImVec2(mousePos.x, mousePos.y); io.MouseDown[0] = mouseButtonsPressed & (1 << 0); io.MouseDown[1] = mouseButtonsPressed & (1 << 1); ImGui::NewFrame(); } void GUI::endFrame() { if (_program->isDirty()) { _program->rebuildFromFile(); } if (OsEng.renderEngine().doesPerformanceMeasurements()) { _performance.render(); } if (_isEnabled) { render(); if (_globalProperty.isEnabled()) { _globalProperty.render(); } if (_property.isEnabled()) { _property.render(); } if (_screenSpaceProperty.isEnabled()) { _screenSpaceProperty.render(); } if (_virtualProperty.isEnabled()) { _virtualProperty.render(); } if (_help.isEnabled()) { _help.render(); } if (_iswa.isEnabled()) { _iswa.render(); } } ImGui::Render(); } bool GUI::mouseButtonCallback(MouseButton button, MouseAction action) { ImGuiIO& io = ImGui::GetIO(); bool consumeEvent = io.WantCaptureMouse; return consumeEvent; } bool GUI::mouseWheelCallback(double position) { ImGuiIO& io = ImGui::GetIO(); bool consumeEvent = io.WantCaptureMouse; if (consumeEvent) { io.MouseWheel = static_cast(position); } return consumeEvent; } bool GUI::keyCallback(Key key, KeyModifier modifier, KeyAction action) { const int keyIndex = static_cast(key); if (keyIndex < 0) { LERROR("Key of index '" << keyIndex << "' was negative"); return false; } const bool hasShift = hasKeyModifier(modifier, KeyModifier::Shift); const bool hasCtrl = hasKeyModifier(modifier, KeyModifier::Control); const bool hasAlt = hasKeyModifier(modifier, KeyModifier::Alt); ImGuiIO& io = ImGui::GetIO(); const bool consumeEvent = io.WantCaptureKeyboard; if (consumeEvent) { if (action == KeyAction::Press) { io.KeysDown[keyIndex] = true; } io.KeyShift = hasShift; io.KeyCtrl = hasCtrl; io.KeyAlt = hasAlt; } // Even if the event is not consumed, // set keys and modifiers to false when they are released. if (action == KeyAction::Release) { io.KeysDown[keyIndex] = false; } if (!hasShift) { io.KeyShift = false; } if (!hasCtrl) { io.KeyCtrl = false; } if (!hasAlt) { io.KeyAlt = false; } return consumeEvent; } bool GUI::charCallback(unsigned int character, KeyModifier modifier) { ImGuiIO& io = ImGui::GetIO(); bool consumeEvent = io.WantCaptureKeyboard; if (consumeEvent) { io.AddInputCharacter(static_cast(character)); } return consumeEvent; } void GUI::render() { ImGui::Begin("OpenSpace GUI", nullptr); bool property = _property.isEnabled(); ImGui::Checkbox("Scene Graph Properties", &property); _property.setEnabled(property); bool screenSpaceProperty = _screenSpaceProperty.isEnabled(); ImGui::Checkbox("ScreenSpace Properties", &screenSpaceProperty); _screenSpaceProperty.setEnabled(screenSpaceProperty); bool globalProperty = _globalProperty.isEnabled(); ImGui::Checkbox("Global Properties", &globalProperty); _globalProperty.setEnabled(globalProperty); bool virtualProperty = _virtualProperty.isEnabled(); ImGui::Checkbox("Virtual Properties", &virtualProperty); _virtualProperty.setEnabled(virtualProperty); #ifdef OPENSPACE_MODULE_ISWA_ENABLED bool iswa = _iswa.isEnabled(); ImGui::Checkbox("iSWA", &iswa); _iswa.setEnabled(iswa); #endif _origin.render(); _time.render(); bool help = _help.isEnabled(); ImGui::Checkbox("Help", &help); _help.setEnabled(help); renderAndUpdatePropertyVisibility(); static const int addImageBufferSize = 256; static char addImageBuffer[addImageBufferSize]; bool addImage = ImGui::InputText( "addImage", addImageBuffer, addImageBufferSize, ImGuiInputTextFlags_EnterReturnsTrue ); if (addImage) { addScreenSpaceRenderable(std::string(addImageBuffer)); } #ifdef SHOW_IMGUI_HELPERS ImGui::Begin("Style Editor"); ImGui::ShowStyleEditor(); ImGui::End(); ImGui::Begin("Test Window"); ImGui::ShowTestWindow(); ImGui::End(); ImGui::Begin("Metrics Window"); ImGui::ShowMetricsWindow(); ImGui::End(); #endif ImGui::End(); } void GUI::renderAndUpdatePropertyVisibility() { // Fragile! Keep this in sync with properties::Property::Visibility using V = properties::Property::Visibility; int t = static_cast>(_currentVisibility); // Array is sorted by importance std::array items = { "None", "User", "Developer", "All"}; ImGui::Combo("PropertyVisibility", &t, items.data(), static_cast(items.size())); _currentVisibility = static_cast(t); _globalProperty.setVisibility(_currentVisibility); _property.setVisibility(_currentVisibility); _screenSpaceProperty.setVisibility(_currentVisibility); _virtualProperty.setVisibility(_currentVisibility); } } // namespace gui } // namespace openspace