#include "ImGuiWrapper.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "libslic3r/libslic3r.h" #include "libslic3r/Utils.hpp" #include "GUI.hpp" namespace Slic3r { namespace GUI { ImGuiWrapper::ImGuiWrapper(float font_size) : m_glyph_ranges(nullptr) , m_font_size(font_size) , m_font_texture(0) , m_style_scaling(1.0) , m_mouse_buttons(0) , m_disabled(false) , m_new_frame_open(false) { ImGui::CreateContext(); init_default_font(); init_input(); init_style(); ImGui::GetIO().IniFilename = nullptr; } ImGuiWrapper::~ImGuiWrapper() { destroy_device_objects(); ImGui::DestroyContext(); } void ImGuiWrapper::set_language(const std::string &language) { if (m_new_frame_open) { // ImGUI internally locks the font between NewFrame() and EndFrame() // NewFrame() might've been called here because of input from the 3D scene; // call EndFrame() ImGui::EndFrame(); m_new_frame_open = false; } const ImWchar *ranges = nullptr; size_t idx = language.find('_'); std::string lang = (idx == std::string::npos) ? language : language.substr(0, idx); static const ImWchar ranges_latin2[] = { 0x0020, 0x00FF, // Basic Latin + Latin Supplement 0x0100, 0x017F, // Latin Extended-A 0, }; if (lang == "cs" || lang == "pl") { ranges = ranges_latin2; } else if (lang == "ru" || lang == "uk") { ranges = ImGui::GetIO().Fonts->GetGlyphRangesCyrillic(); } else if (lang == "jp") { ranges = ImGui::GetIO().Fonts->GetGlyphRangesJapanese(); } else if (lang == "kr") { ranges = ImGui::GetIO().Fonts->GetGlyphRangesKorean(); } else if (lang == "zh") { ranges = ImGui::GetIO().Fonts->GetGlyphRangesChineseSimplifiedCommon(); } if (ranges != m_glyph_ranges) { m_glyph_ranges = ranges; init_default_font(); } } void ImGuiWrapper::set_display_size(float w, float h) { ImGuiIO& io = ImGui::GetIO(); io.DisplaySize = ImVec2(w, h); io.DisplayFramebufferScale = ImVec2(1.0f, 1.0f); } void ImGuiWrapper::set_style_scaling(float scaling) { if (!std::isnan(scaling) && !std::isinf(scaling) && scaling != m_style_scaling) { ImGui::GetStyle().ScaleAllSizes(scaling / m_style_scaling); m_style_scaling = scaling; init_default_font(); } } bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) { if (! display_initialized()) { return false; } ImGuiIO& io = ImGui::GetIO(); io.MousePos = ImVec2((float)evt.GetX(), (float)evt.GetY()); io.MouseDown[0] = evt.LeftIsDown(); io.MouseDown[1] = evt.RightIsDown(); io.MouseDown[2] = evt.MiddleIsDown(); unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; new_frame(); return want_mouse(); } bool ImGuiWrapper::update_key_data(wxKeyEvent &evt) { if (! display_initialized()) { return false; } ImGuiIO& io = ImGui::GetIO(); if (evt.GetEventType() == wxEVT_CHAR) { // Char event const auto key = evt.GetUnicodeKey(); if (key != 0) { io.AddInputCharacter(key); } new_frame(); return want_keyboard() || want_text_input(); } else { // Key up/down event int key = evt.GetKeyCode(); wxCHECK_MSG(key >= 0 && key < IM_ARRAYSIZE(io.KeysDown), false, "Received invalid key code"); io.KeysDown[key] = evt.GetEventType() == wxEVT_KEY_DOWN; io.KeyShift = evt.ShiftDown(); io.KeyCtrl = evt.ControlDown(); io.KeyAlt = evt.AltDown(); io.KeySuper = evt.MetaDown(); new_frame(); return want_keyboard() || want_text_input(); } } void ImGuiWrapper::new_frame() { if (m_new_frame_open) { return; } if (m_font_texture == 0) create_device_objects(); ImGui::NewFrame(); m_new_frame_open = true; } void ImGuiWrapper::render() { ImGui::Render(); render_draw_data(ImGui::GetDrawData()); m_new_frame_open = false; } void ImGuiWrapper::set_next_window_pos(float x, float y, int flag) { ImGui::SetNextWindowPos(ImVec2(x, y), (ImGuiCond)flag); ImGui::SetNextWindowSize(ImVec2(0.0, 0.0)); } void ImGuiWrapper::set_next_window_bg_alpha(float alpha) { ImGui::SetNextWindowBgAlpha(alpha); } bool ImGuiWrapper::begin(const std::string &name, int flags) { return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags); } bool ImGuiWrapper::begin(const wxString &name, int flags) { return begin(into_u8(name), flags); } void ImGuiWrapper::end() { ImGui::End(); } bool ImGuiWrapper::button(const wxString &label) { auto label_utf8 = into_u8(label); return ImGui::Button(label_utf8.c_str()); } bool ImGuiWrapper::radio_button(const wxString &label, bool active) { auto label_utf8 = into_u8(label); return ImGui::RadioButton(label_utf8.c_str(), active); } bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) { return ImGui::InputDouble(label.c_str(), const_cast(&value), 0.0f, 0.0f, format.c_str()); } bool ImGuiWrapper::input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format) { bool value_changed = false; ImGui::BeginGroup(); for (int i = 0; i < 3; ++i) { std::string item_label = (i == 0) ? "X" : ((i == 1) ? "Y" : "Z"); ImGui::PushID(i); ImGui::PushItemWidth(width); value_changed |= ImGui::InputDouble(item_label.c_str(), const_cast(&value(i)), 0.0f, 0.0f, format.c_str()); ImGui::PopID(); } ImGui::EndGroup(); return value_changed; } bool ImGuiWrapper::checkbox(const wxString &label, bool &value) { auto label_utf8 = into_u8(label); return ImGui::Checkbox(label_utf8.c_str(), &value); } void ImGuiWrapper::text(const char *label) { ImGui::Text(label, NULL); } void ImGuiWrapper::text(const std::string &label) { this->text(label.c_str()); } void ImGuiWrapper::text(const wxString &label) { this->text(into_u8(label).c_str()); } bool ImGuiWrapper::combo(const wxString& label, const std::vector& options, int& selection) { // this is to force the label to the left of the widget: text(label); ImGui::SameLine(); int selection_out = -1; bool res = false; const char *selection_str = selection < options.size() ? options[selection].c_str() : ""; if (ImGui::BeginCombo("", selection_str)) { for (int i = 0; i < options.size(); i++) { if (ImGui::Selectable(options[i].c_str(), i == selection)) { selection_out = i; } } ImGui::EndCombo(); res = true; } selection = selection_out; return res; } void ImGuiWrapper::disabled_begin(bool disabled) { wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call"); if (disabled) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); m_disabled = true; } } void ImGuiWrapper::disabled_end() { if (m_disabled) { ImGui::PopItemFlag(); ImGui::PopStyleVar(); m_disabled = false; } } bool ImGuiWrapper::want_mouse() const { return ImGui::GetIO().WantCaptureMouse; } bool ImGuiWrapper::want_keyboard() const { return ImGui::GetIO().WantCaptureKeyboard; } bool ImGuiWrapper::want_text_input() const { return ImGui::GetIO().WantTextInput; } bool ImGuiWrapper::want_any_input() const { const auto io = ImGui::GetIO(); return io.WantCaptureMouse || io.WantCaptureKeyboard || io.WantTextInput; } void ImGuiWrapper::init_default_font() { const float font_size = m_font_size * m_style_scaling; destroy_fonts_texture(); ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); ImFont* font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/NotoSans-Regular.ttf").c_str(), font_size, nullptr, m_glyph_ranges); if (font == nullptr) { font = io.Fonts->AddFontDefault(); if (font == nullptr) { throw std::runtime_error("ImGui: Could not load deafult font"); } } } void ImGuiWrapper::create_device_objects() { create_fonts_texture(); } void ImGuiWrapper::create_fonts_texture() { // Build texture atlas ImGuiIO& io = ImGui::GetIO(); unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. // Upload texture to graphics system GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); glGenTextures(1, &m_font_texture); glBindTexture(GL_TEXTURE_2D, m_font_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); // Store our identifier io.Fonts->TexID = (ImTextureID)(intptr_t)m_font_texture; // Restore state glBindTexture(GL_TEXTURE_2D, last_texture); } void ImGuiWrapper::init_input() { ImGuiIO& io = ImGui::GetIO(); // Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array. io.KeyMap[ImGuiKey_Tab] = WXK_TAB; io.KeyMap[ImGuiKey_LeftArrow] = WXK_LEFT; io.KeyMap[ImGuiKey_RightArrow] = WXK_RIGHT; io.KeyMap[ImGuiKey_UpArrow] = WXK_UP; io.KeyMap[ImGuiKey_DownArrow] = WXK_DOWN; io.KeyMap[ImGuiKey_PageUp] = WXK_PAGEUP; io.KeyMap[ImGuiKey_PageDown] = WXK_PAGEDOWN; io.KeyMap[ImGuiKey_Home] = WXK_HOME; io.KeyMap[ImGuiKey_End] = WXK_END; io.KeyMap[ImGuiKey_Insert] = WXK_INSERT; io.KeyMap[ImGuiKey_Delete] = WXK_DELETE; io.KeyMap[ImGuiKey_Backspace] = WXK_BACK; io.KeyMap[ImGuiKey_Space] = WXK_SPACE; io.KeyMap[ImGuiKey_Enter] = WXK_RETURN; io.KeyMap[ImGuiKey_Escape] = WXK_ESCAPE; io.KeyMap[ImGuiKey_A] = 'A'; io.KeyMap[ImGuiKey_C] = 'C'; io.KeyMap[ImGuiKey_V] = 'V'; io.KeyMap[ImGuiKey_X] = 'X'; io.KeyMap[ImGuiKey_Y] = 'Y'; io.KeyMap[ImGuiKey_Z] = 'Z'; // Don't let imgui special-case Mac, wxWidgets already do that io.ConfigMacOSXBehaviors = false; // Setup clipboard interaction callbacks io.SetClipboardTextFn = clipboard_set; io.GetClipboardTextFn = clipboard_get; io.ClipboardUserData = this; } void ImGuiWrapper::init_style() { ImGuiStyle &style = ImGui::GetStyle(); auto set_color = [&](ImGuiCol_ col, unsigned hex_color) { style.Colors[col] = ImVec4( ((hex_color >> 24) & 0xff) / 255.0f, ((hex_color >> 16) & 0xff) / 255.0f, ((hex_color >> 8) & 0xff) / 255.0f, (hex_color & 0xff) / 255.0f); }; static const unsigned COL_GREY_DARK = 0x444444ff; static const unsigned COL_GREY_LIGHT = 0x666666ff; static const unsigned COL_ORANGE_DARK = 0xc16737ff; static const unsigned COL_ORANGE_LIGHT = 0xff7d38ff; // Generics set_color(ImGuiCol_TitleBgActive, COL_ORANGE_DARK); set_color(ImGuiCol_FrameBg, COL_GREY_DARK); set_color(ImGuiCol_FrameBgHovered, COL_GREY_LIGHT); set_color(ImGuiCol_FrameBgActive, COL_GREY_LIGHT); // Text selection set_color(ImGuiCol_TextSelectedBg, COL_ORANGE_DARK); // Buttons set_color(ImGuiCol_Button, COL_ORANGE_DARK); set_color(ImGuiCol_ButtonHovered, COL_ORANGE_LIGHT); set_color(ImGuiCol_ButtonActive, COL_ORANGE_LIGHT); // Checkbox set_color(ImGuiCol_CheckMark, COL_ORANGE_LIGHT); // ComboBox items set_color(ImGuiCol_Header, COL_ORANGE_DARK); set_color(ImGuiCol_HeaderHovered, COL_ORANGE_LIGHT); set_color(ImGuiCol_HeaderActive, COL_ORANGE_LIGHT); // Slider set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK); set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT); } void ImGuiWrapper::render_draw_data(ImDrawData *draw_data) { // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) ImGuiIO& io = ImGui::GetIO(); int fb_width = (int)(draw_data->DisplaySize.x * io.DisplayFramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * io.DisplayFramebufferScale.y); if (fb_width == 0 || fb_height == 0) return; draw_data->ScaleClipRects(io.DisplayFramebufferScale); // We are using the OpenGL fixed pipeline to make the example code simpler to read! // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill. GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_CULL_FACE); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); glEnable(GL_SCISSOR_TEST); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnable(GL_TEXTURE_2D); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); GLint texture_env_mode = GL_MODULATE; glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &texture_env_mode); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(draw_data->DisplayPos.x, draw_data->DisplayPos.x + draw_data->DisplaySize.x, draw_data->DisplayPos.y + draw_data->DisplaySize.y, draw_data->DisplayPos.y, -1.0f, +1.0f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); // Render command lists ImVec2 pos = draw_data->DisplayPos; for (int n = 0; n < draw_data->CmdListsCount; n++) { const ImDrawList* cmd_list = draw_data->CmdLists[n]; const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos))); glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv))); glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col))); for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; if (pcmd->UserCallback) { // User callback (registered via ImDrawList::AddCallback) pcmd->UserCallback(cmd_list, pcmd); } else { ImVec4 clip_rect = ImVec4(pcmd->ClipRect.x - pos.x, pcmd->ClipRect.y - pos.y, pcmd->ClipRect.z - pos.x, pcmd->ClipRect.w - pos.y); if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) { // Apply scissor/clipping rectangle glScissor((int)clip_rect.x, (int)(fb_height - clip_rect.w), (int)(clip_rect.z - clip_rect.x), (int)(clip_rect.w - clip_rect.y)); // Bind texture, Draw glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); } } idx_buffer += pcmd->ElemCount; } } // Restore modified state glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texture_env_mode); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopAttrib(); glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]); glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); } bool ImGuiWrapper::display_initialized() const { const ImGuiIO& io = ImGui::GetIO(); return io.DisplaySize.x >= 0.0f && io.DisplaySize.y >= 0.0f; } void ImGuiWrapper::destroy_device_objects() { destroy_fonts_texture(); } void ImGuiWrapper::destroy_fonts_texture() { if (m_font_texture) { ImGuiIO& io = ImGui::GetIO(); io.Fonts->TexID = 0; glDeleteTextures(1, &m_font_texture); m_font_texture = 0; } } const char* ImGuiWrapper::clipboard_get(void* user_data) { ImGuiWrapper *self = reinterpret_cast(user_data); const char* res = ""; if (wxTheClipboard->Open()) { if (wxTheClipboard->IsSupported(wxDF_TEXT)) { wxTextDataObject data; wxTheClipboard->GetData(data); if (data.GetTextLength() > 0) { self->m_clipboard_text = into_u8(data.GetText()); res = self->m_clipboard_text.c_str(); } } wxTheClipboard->Close(); } return res; } void ImGuiWrapper::clipboard_set(void* /* user_data */, const char* text) { if (wxTheClipboard->Open()) { wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(text))); // object owned by the clipboard wxTheClipboard->Close(); } } } // namespace GUI } // namespace Slic3r