#include "ImGuiPresenter.h" #include #include "imgui_impl_ax.h" #include "imgui_internal.h" // TODO: mac metal #if (defined(AX_USE_GL) || defined(AX_USE_GLES)) # define AX_IMGUI_ENABLE_MULTI_VIEWPORT 1 #else # define AX_IMGUI_ENABLE_MULTI_VIEWPORT 0 #endif NS_AX_EXT_BEGIN static uint32_t fourccValue(std::string_view str) { if (str.empty() || str[0] != '#') return (uint32_t)-1; uint32_t value = 0; memcpy(&value, str.data() + 1, std::min(sizeof(value), str.size() - 1)); return value; } class ImGuiEventTracker { public: virtual ~ImGuiEventTracker() {} }; // Track scene event and check whether routed to the scene graph class ImGuiSceneEventTracker : public ImGuiEventTracker { public: bool initWithScene(Scene* scene) { #ifdef AX_PLATFORM_PC _trackLayer = utils::newInstance(&Node::initLayer); // note: when at the first click to focus the window, this will not take effect auto listener = EventListenerTouchOneByOne::create(); listener->setSwallowTouches(true); listener->onTouchBegan = [this](Touch* touch, Event*) -> bool { return ImGui::GetIO().WantCaptureMouse; }; _trackLayer->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, _trackLayer); // add by halx99 auto stopAnyMouse = [=](EventMouse* event) { if (ImGui::GetIO().WantCaptureMouse) { event->stopPropagation(); } }; auto mouseListener = EventListenerMouse::create(); mouseListener->onMouseDown = mouseListener->onMouseUp = stopAnyMouse; _trackLayer->getEventDispatcher()->addEventListenerWithSceneGraphPriority(mouseListener, _trackLayer); scene->addChild(_trackLayer, INT_MAX); #endif // add an empty sprite to avoid render problem // const auto sp = Sprite::create(); // sp->setGlobalZOrder(1); // sp->setOpacity(0); // addChild(sp, 1); /* * There a 3 choice for schedule frame for ImGui render loop * a. at visit/draw to call beginFrame/endFrame, but at ImGui loop, we can't game object and add to Scene * directly, will cause damage iterator b. scheduleUpdate at onEnter to call beginFrame, at visit/draw to call * endFrame, it's solve iterator damage problem, but when director is paused the director will stop call * 'update' function of Scheduler And need modify engine code to call _scheduler->update(_deltaTime) even * director is paused, pass 0 for update c. Director::EVENT_BEFORE_DRAW call beginFrame, EVENT_AFTER_VISIT call * endFrame */ return true; } ~ImGuiSceneEventTracker() { #ifdef AX_PLATFORM_PC if (_trackLayer) { if (_trackLayer->getParent()) _trackLayer->removeFromParent(); _trackLayer->release(); } #endif } private: Node* _trackLayer = nullptr; }; class ImGuiGlobalEventTracker : public ImGuiEventTracker { static const int highestPriority = (std::numeric_limits::min)(); public: bool init() { #ifdef AX_PLATFORM_PC // note: when at the first click to focus the window, this will not take effect auto eventDispatcher = Director::getInstance()->getEventDispatcher(); _touchListener = utils::newInstance(); _touchListener->setSwallowTouches(true); _touchListener->onTouchBegan = [this](Touch* touch, Event*) -> bool { return ImGui::GetIO().WantCaptureMouse; }; eventDispatcher->addEventListenerWithFixedPriority(_touchListener, highestPriority); // add by halx99 auto stopAnyMouse = [=](EventMouse* event) { if (ImGui::GetIO().WantCaptureMouse) { event->stopPropagation(); } }; _mouseListener = utils::newInstance(); _mouseListener->onMouseDown = _mouseListener->onMouseUp = stopAnyMouse; eventDispatcher->addEventListenerWithFixedPriority(_mouseListener, highestPriority); #endif return true; } ~ImGuiGlobalEventTracker() { #ifdef AX_PLATFORM_PC auto eventDispatcher = Director::getInstance()->getEventDispatcher(); eventDispatcher->removeEventListener(_mouseListener); eventDispatcher->removeEventListener(_touchListener); _mouseListener->release(); _touchListener->release(); #endif } EventListenerTouchOneByOne* _touchListener = nullptr; EventListenerMouse* _mouseListener = nullptr; }; static ImGuiPresenter* _instance = nullptr; std::function ImGuiPresenter::_onInit; ImGuiPresenter* ImGuiPresenter::getInstance() { if (_instance == nullptr) { _instance = new ImGuiPresenter(); _instance->init(); if (_onInit) _onInit(_instance); } return _instance; } void ImGuiPresenter::destroyInstance() { if (_instance) { _instance->cleanup(); delete _instance; _instance = nullptr; } } void ImGuiPresenter::init() { // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking #if AX_IMGUI_ENABLE_MULTI_VIEWPORT io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows #endif // io.ConfigViewportsNoAutoMerge = true; // io.ConfigViewportsNoTaskBarIcon = true; // io.ConfigViewportsNoDefaultParent = true; // io.ConfigDockingAlwaysTabBar = true; // io.ConfigDockingTransparentPayload = true; // io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleFonts; // FIXME-DPI: Experimental. THIS CURRENTLY DOESN'T // WORK AS EXPECTED. DON'T USE IN USER APP! io.ConfigFlags |= ImGuiConfigFlags_DpiEnableScaleViewports; // // FIXME-DPI: Experimental. // Setup Dear ImGui style ImGui::StyleColorsDark(); // ImGui::StyleColorsClassic(); // When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular // ones. ImGuiStyle& style = ImGui::GetStyle(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { style.WindowRounding = 0.0f; style.Colors[ImGuiCol_WindowBg].w = 1.0f; } auto window = static_cast(Director::getInstance()->getOpenGLView())->getWindow(); ImGui_ImplGlfw_InitForAx(window, true); ImGui_ImplAx_Init(); ImGui_ImplAx_SetCustomFontLoader(&ImGuiPresenter::loadCustomFonts, this); ImGui::StyleColorsClassic(); auto eventDispatcher = Director::getInstance()->getEventDispatcher(); eventDispatcher->addCustomEventListener(Director::EVENT_BEFORE_DRAW, [=](EventCustom*) { beginFrame(); }); eventDispatcher->addCustomEventListener(Director::EVENT_AFTER_VISIT, [=](EventCustom*) { endFrame(); }); } void ImGuiPresenter::cleanup() { auto eventDispatcher = Director::getInstance()->getEventDispatcher(); eventDispatcher->removeCustomEventListeners(Director::EVENT_AFTER_VISIT); eventDispatcher->removeCustomEventListeners(Director::EVENT_BEFORE_DRAW); ImGui_ImplAx_SetCustomFontLoader(nullptr, nullptr); ImGui_ImplAx_Shutdown(); ImGui_ImplGlfw_Shutdown(); AX_SAFE_RELEASE_NULL(_fontsTexture); ImGui::DestroyContext(); } void ImGuiPresenter::setOnInit(const std::function& callBack) { _onInit = callBack; } void ImGuiPresenter::loadCustomFonts(void* ud) { auto thiz = (ImGuiPresenter*)ud; auto imFonts = ImGui::GetIO().Fonts; imFonts->Clear(); auto contentZoomFactor = thiz->_contentZoomFactor; for (auto& fontInfo : thiz->_fontsInfoMap) { const ImWchar* imChars = nullptr; switch (fontInfo.second.glyphRange) { case CHS_GLYPH_RANGE::GENERAL: imChars = imFonts->GetGlyphRangesChineseSimplifiedCommon(); break; case CHS_GLYPH_RANGE::FULL: imChars = imFonts->GetGlyphRangesChineseFull(); break; default:; } auto fontData = FileUtils::getInstance()->getDataFromFile(fontInfo.first); AXASSERT(!fontData.isNull(), "Cannot load font for IMGUI"); ssize_t bufferSize = 0; auto* buffer = fontData.takeBuffer(&bufferSize); // Buffer automatically freed by IMGUI imFonts->AddFontFromMemoryTTF(buffer, bufferSize, fontInfo.second.fontSize * contentZoomFactor, nullptr, imChars); } } float ImGuiPresenter::scaleAllByDPI(float userScale) { // Gets scale float xscale = 1.0f; glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, nullptr); auto zoomFactor = userScale * xscale; auto imFonts = ImGui::GetIO().Fonts; // clear before add new font auto fontConf = imFonts->ConfigData; // copy font config data if (zoomFactor != _contentZoomFactor) { for (auto& fontConf : imFonts->ConfigData) { fontConf.SizePixels = (fontConf.SizePixels / _contentZoomFactor) * zoomFactor; } // Destory font informations, let implcocos2dx recreate at newFrame ImGui_ImplAx_SetDeviceObjectsDirty(); ImGui::GetStyle().ScaleAllSizes(zoomFactor); _contentZoomFactor = zoomFactor; } return zoomFactor; } void ImGuiPresenter::addFont(std::string_view fontFile, float fontSize, CHS_GLYPH_RANGE glyphRange) { if (FileUtils::getInstance()->isFileExistInternal(fontFile)) { if (_fontsInfoMap.emplace(fontFile, FontInfo{fontSize, glyphRange}).second) ImGui_ImplAx_SetDeviceObjectsDirty(); } } void ImGuiPresenter::removeFont(std::string_view fontFile) { auto count = _fontsInfoMap.size(); _fontsInfoMap.erase(fontFile); if (count != _fontsInfoMap.size()) ImGui_ImplAx_SetDeviceObjectsDirty(); } void ImGuiPresenter::clearFonts() { bool haveCustomFonts = !_fontsInfoMap.empty(); _fontsInfoMap.clear(); if (haveCustomFonts) ImGui_ImplAx_SetDeviceObjectsDirty(); // auto drawData = ImGui::GetDrawData(); // if(drawData) drawData->Clear(); } void ImGuiPresenter::end() { _purgeNextLoop = true; } /* * begin ImGui frame and draw ImGui stubs */ void ImGuiPresenter::beginFrame() { // drived by event Director::EVENT_BEFORE_DRAW from engine mainLoop if (_purgeNextLoop) { Director::getInstance()->end(); return; } if (!_renderPiplines.empty()) { // create frame ImGui_ImplAx_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); // move to endFrame? _fontsTexture = (Texture2D*)ImGui_ImplAx_GetFontsTexture(); assert(_fontsTexture != nullptr); _fontsTexture->retain(); // draw all gui this->update(); ++_beginFrames; } } /* * flush ImGui draw data to engine */ void ImGuiPresenter::endFrame() { if (_beginFrames > 0) { // render ImGui::Render(); auto drawData = ImGui::GetDrawData(); if (drawData) ImGui_ImplAx_RenderDrawData(drawData); ImGui_ImplAx_RenderPlatform(); --_beginFrames; AX_SAFE_RELEASE_NULL(_fontsTexture); } } void ImGuiPresenter::update() { // clear things from last frame usedCCRefIdMap.clear(); usedCCRef.clear(); // drawing commands for (auto& pipline : _renderPiplines) pipline.second.frame(); // commands will be processed after update } bool ImGuiPresenter::addRenderLoop(std::string_view id, std::function func, Scene* target) { // TODO: check whether exist auto fourccId = fourccValue(id); if (_renderPiplines.find(fourccId) != _renderPiplines.end()) { return false; } ImGuiEventTracker* tracker; if (target) tracker = utils::newInstance(&ImGuiSceneEventTracker::initWithScene, target); else tracker = utils::newInstance(); if (tracker) { _renderPiplines.emplace(fourccId, RenderPipline{tracker, std::move(func)}); return true; } return false; } void ImGuiPresenter::removeRenderLoop(std::string_view id) { auto fourccId = fourccValue(id); const auto iter = _renderPiplines.find(fourccId); if (iter != _renderPiplines.end()) { auto tracker = iter->second.tracker; delete tracker; _renderPiplines.erase(iter); } if (_renderPiplines.empty()) deactiveImGuiViewports(); } void ImGuiPresenter::deactiveImGuiViewports() { ImGuiContext& g = *GImGui; if (!(g.ConfigFlagsCurrFrame & ImGuiConfigFlags_ViewportsEnable)) return; // Create/resize/destroy platform windows to match each active viewport. // Skip the main viewport (index 0), which is always fully handled by the application! for (int i = 1; i < g.Viewports.Size; i++) { ImGuiViewportP* viewport = g.Viewports[i]; viewport->Window->Active = false; } } static std::tuple getTextureUV(Sprite* sp) { ImVec2 uv0, uv1; if (!sp || !sp->getTexture()) return std::tuple{uv0, uv1}; const auto& rect = sp->getTextureRect(); const auto tex = sp->getTexture(); const float atlasWidth = (float)tex->getPixelsWide(); const float atlasHeight = (float)tex->getPixelsHigh(); uv0.x = rect.origin.x / atlasWidth; uv0.y = rect.origin.y / atlasHeight; uv1.x = (rect.origin.x + rect.size.width) / atlasWidth; uv1.y = (rect.origin.y + rect.size.height) / atlasHeight; return std::tuple{uv0, uv1}; } void ImGuiPresenter::image(Texture2D* tex, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) { if (!tex) return; auto size_ = size; if (size_.x <= 0.f) size_.x = tex->getPixelsWide(); if (size_.y <= 0.f) size_.y = tex->getPixelsHigh(); ImGui::PushID(getCCRefId(tex)); ImGui::Image((ImTextureID)tex, size_, uv0, uv1, tint_col, border_col); ImGui::PopID(); } void ImGuiPresenter::image(Sprite* sprite, const ImVec2& size, const ImVec4& tint_col, const ImVec4& border_col) { if (!sprite || !sprite->getTexture()) return; auto size_ = size; const auto& rect = sprite->getTextureRect(); if (size_.x <= 0.f) size_.x = rect.size.width; if (size_.y <= 0.f) size_.y = rect.size.height; ImVec2 uv0, uv1; std::tie(uv0, uv1) = getTextureUV(sprite); ImGui::PushID(getCCRefId(sprite)); ImGui::Image((ImTextureID)sprite->getTexture(), size_, uv0, uv1, tint_col, border_col); ImGui::PopID(); } bool ImGuiPresenter::imageButton(Texture2D* tex, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) { if (!tex) return false; auto size_ = size; if (size_.x <= 0.f) size_.x = tex->getPixelsWide(); if (size_.y <= 0.f) size_.y = tex->getPixelsHigh(); ImGui::PushID(getCCRefId(tex)); const auto ret = ImGui::ImageButton((ImTextureID)tex, size_, uv0, uv1, frame_padding, bg_col, tint_col); ImGui::PopID(); return ret; } bool ImGuiPresenter::imageButton(Sprite* sprite, const ImVec2& size, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) { if (!sprite || !sprite->getTexture()) return false; auto size_ = size; const auto& rect = sprite->getTextureRect(); if (size_.x <= 0.f) size_.x = rect.size.width; if (size_.y <= 0.f) size_.y = rect.size.height; ImVec2 uv0, uv1; std::tie(uv0, uv1) = getTextureUV(sprite); ImGui::PushID(getCCRefId(sprite)); const auto ret = ImGui::ImageButton((ImTextureID)sprite->getTexture(), size_, uv0, uv1, frame_padding, bg_col, tint_col); ImGui::PopID(); return ret; } void ImGuiPresenter::node(Node* node, const ImVec4& tint_col, const ImVec4& border_col) { if (!node) return; const auto size = node->getContentSize(); const auto pos = ImGui::GetCursorScreenPos(); Mat4 tr; tr.m[5] = -1; tr.m[12] = pos.x; tr.m[13] = pos.y + size.height; if (border_col.w > 0.f) { tr.m[12] += 1; tr.m[13] += 1; } node->setNodeToParentTransform(tr); ImGui::PushID(getCCRefId(node)); ImGui::Image((ImTextureID)node, ImVec2(size.width, size.height), ImVec2(0, 0), ImVec2(1, 1), tint_col, border_col); ImGui::PopID(); } bool ImGuiPresenter::nodeButton(Node* node, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col) { if (!node) return false; const auto size = node->getContentSize(); const auto pos = ImGui::GetCursorScreenPos(); Mat4 tr; tr.m[5] = -1; tr.m[12] = pos.x; tr.m[13] = pos.y + size.height; if (frame_padding >= 0) { tr.m[12] += (float)frame_padding; tr.m[13] += (float)frame_padding; } else { tr.m[12] += ImGui::GetStyle().FramePadding.x; tr.m[13] += ImGui::GetStyle().FramePadding.y; } node->setNodeToParentTransform(tr); ImGui::PushID(getCCRefId(node)); const auto ret = ImGui::ImageButton((ImTextureID)node, ImVec2(size.width, size.height), ImVec2(0, 0), ImVec2(1, 1), frame_padding, bg_col, tint_col); ImGui::PopID(); return ret; } std::tuple ImGuiPresenter::useTexture(Texture2D* texture) { if (!texture) return std::tuple{nullptr, 0}; return std::tuple{(ImTextureID)texture, getCCRefId(texture)}; } std::tuple ImGuiPresenter::useSprite(Sprite* sprite) { if (!sprite || !sprite->getTexture()) return std::tuple{nullptr, {}, {}, 0}; ImVec2 uv0, uv1; std::tie(uv0, uv1) = getTextureUV(sprite); return std::tuple{(ImTextureID)sprite->getTexture(), uv0, uv1, getCCRefId(sprite)}; } std::tuple ImGuiPresenter::useNode(Node* node, const ImVec2& pos) { if (!node) return std::tuple{nullptr, {}, {}, 0}; const auto size = node->getContentSize(); Mat4 tr; tr.m[5] = -1; tr.m[12] = pos.x; tr.m[13] = pos.y + size.height; node->setNodeToParentTransform(tr); return std::tuple{ (ImTextureID)node, pos, ImVec2(pos.x + size.width, pos.y + size.height), getCCRefId(node)}; } void ImGuiPresenter::setNodeColor(Node* node, const ImVec4& col) { if (node) { node->setColor({uint8_t(col.x * 255), uint8_t(col.y * 255), uint8_t(col.z * 255)}); node->setOpacity(uint8_t(col.w * 255)); } } void ImGuiPresenter::setNodeColor(Node* node, ImGuiCol col) { if (node && 0 <= col && col < ImGuiCol_COUNT) setNodeColor(node, ImGui::GetStyleColorVec4(col)); } void ImGuiPresenter::setLabelColor(Label* label, const ImVec4& col) { if (label) { label->setTextColor({uint8_t(col.x * 255), uint8_t(col.y * 255), uint8_t(col.z * 255), uint8_t(col.w * 255)}); } } void ImGuiPresenter::setLabelColor(Label* label, bool disabled) { if (label) setLabelColor(label, ImGui::GetStyleColorVec4(disabled ? ImGuiCol_TextDisabled : ImGuiCol_Text)); } void ImGuiPresenter::setLabelColor(Label* label, ImGuiCol col) { if (label && 0 <= col && col < ImGuiCol_COUNT) setLabelColor(label, ImGui::GetStyleColorVec4(col)); } ImWchar* ImGuiPresenter::addGlyphRanges(std::string_view key, const std::vector& ranges) { auto it = glyphRanges.find(key); // the pointer must be persistant, do not replace if (it != glyphRanges.end()) return it->second.data(); it = glyphRanges.emplace(key, ranges).first; // glyphRanges[key] = ranges; if (ranges.empty()) it->second.push_back(0); return it->second.data(); } void ImGuiPresenter::mergeFontGlyphs(ImFont* dst, ImFont* src, ImWchar start, ImWchar end) { if (!dst || !src || start > end) return; for (auto i = start; i <= end; ++i) { const auto g = src->FindGlyphNoFallback(i); if (g) { dst->AddGlyph(nullptr, g->Codepoint, g->X0, g->Y0, g->X1, g->Y1, g->U0, g->V0, g->U1, g->V1, g->AdvanceX); } } dst->BuildLookupTable(); } int ImGuiPresenter::getCCRefId(Ref* p) { int id = 0; const auto it = usedCCRefIdMap.find(p); if (it == usedCCRefIdMap.end()) { usedCCRefIdMap[p] = 0; usedCCRef.pushBack(p); } else id = ++it->second; // BKDR hash constexpr unsigned int seed = 131; unsigned int hash = 0; for (auto i = 0u; i < sizeof(void*); ++i) hash = hash * seed + ((const char*)&p)[i]; for (auto i = 0u; i < sizeof(int); ++i) hash = hash * seed + ((const char*)&id)[i]; return (int)hash; } NS_AX_EXT_END