Refine extension ImGui, and rename director to ImGuiEXT

This commit is contained in:
halx99 2020-09-05 17:10:09 +08:00
parent e3e3352440
commit cc2c524573
25 changed files with 256 additions and 124 deletions

View File

@ -15,7 +15,7 @@ option(BUILD_EXTENSION_COCOSTUDIO "Build extension cocostudio" ON)
option(BUILD_EXTENSION_FAIRYGUI "Build extension FairyGUI" ON) option(BUILD_EXTENSION_FAIRYGUI "Build extension FairyGUI" ON)
option(BUILD_EXTENSION_IMGUI "Build extension ImGui" OFF) option(BUILD_EXTENSION_IMGUIEXT "Build extension ImGuiEXT" OFF)
function(setup_cocos_extension_config target_name) function(setup_cocos_extension_config target_name)
if(ANDROID) if(ANDROID)
@ -73,8 +73,8 @@ if(BUILD_EXTENSION_FAIRYGUI)
add_subdirectory(fairygui) add_subdirectory(fairygui)
endif() endif()
if(BUILD_EXTENSION_IMGUI) if(BUILD_EXTENSION_IMGUIEXT)
add_subdirectory(ImGui) add_subdirectory(ImGuiEXT)
endif() endif()
message(STATUS "CC_EXTENSION_LIBS:${CC_EXTENSION_LIBS}") message(STATUS "CC_EXTENSION_LIBS:${CC_EXTENSION_LIBS}")

View File

@ -1,65 +0,0 @@
#include "CCImGuiLayer.h"
#include "imgui/imgui.h"
#include "imgui_impl_cocos2dx.h"
#include "CCImGuiEXT.h"
NS_CC_EXT_BEGIN
bool ImGuiLayer::init()
{
if (!Layer::init() || !ImGuiEXT::getInstance())
return false;
#ifdef CC_PLATFORM_PC
// 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 {
if (!_visible)
return false;
return ImGui::IsAnyWindowHovered();
};
getEventDispatcher()->addEventListenerWithSceneGraphPriority(listener, this);
// add by halx99
auto stopAnyMouse = [=](EventMouse* event) {
if (ImGui::IsAnyWindowHovered()) {
event->stopPropagation();
cocos2d::log("!!!EventMouse should stop by ImGuiLayer");
}
};
auto mouseListener = EventListenerMouse::create();
mouseListener->onMouseDown = mouseListener->onMouseUp = stopAnyMouse;
getEventDispatcher()->addEventListenerWithSceneGraphPriority(mouseListener, this);
#endif
// add an empty sprite to avoid render problem
const auto sp = Sprite::create();
sp->setGlobalZOrder(1);
sp->setOpacity(0);
addChild(sp, 1);
return true;
}
void ImGuiLayer::visit(Renderer* renderer, const Mat4 &parentTransform, uint32_t parentFlags)
{
Layer::visit(renderer, parentTransform, parentFlags);
if(_visible) frame();
}
void ImGuiLayer::frame()
{
// create frame
ImGui_ImplCocos2dx_NewFrame();
// draw all gui
ImGuiEXT::getInstance()->onDraw();
// render
ImGui::Render();
ImGui_ImplCocos2dx_RenderDrawData(ImGui::GetDrawData());
ImGui_ImplCocos2dx_RenderPlatform();
}
NS_CC_EXT_END

View File

@ -1,18 +0,0 @@
#pragma once
#include "cocos2d.h"
#include "ExtensionMacros.h"
NS_CC_EXT_BEGIN
class ImGuiLayer : public Layer
{
CC_CONSTRUCTOR_ACCESS:
virtual bool init() override;
protected:
virtual void visit(cocos2d::Renderer *renderer, const cocos2d::Mat4& parentTransform, uint32_t parentFlags) override;
void frame();
};
NS_CC_EXT_END

View File

@ -1,8 +0,0 @@
# egnx-imgui-ext
Sync from https://github.com/Xrysnow/cocos2d-x-imgui and do a little changes
## How to use
Same with https://github.com/Xrysnow/cocos2d-x-imgui but do a little changes:
* ```CCIMGUI``` --> ```cocos2d::extension::ImGuiEXT```
* ```ImGuiLayer``` --> ```cocos2d::extension::ImGuiLayer```
* And no lua bindings currently

View File

@ -3,6 +3,118 @@
NS_CC_EXT_BEGIN NS_CC_EXT_BEGIN
static uint32_t fourccValue(const std::string& str) {
if (str.empty() || str[0] != '#') return (uint32_t)-1;
uint32_t value = 0;
memcpy(&value, str.c_str() + 1, std::min(sizeof(value), str.size() - 1));
return value;
}
class ImGuiEXTRenderer : public Layer
{
CC_CONSTRUCTOR_ACCESS:
bool initWithImGuiEXT(ImGuiEXT* guiext)
{
if (!Layer::init())
return false;
_imguiext = guiext; // weak ref the singleton instance
#ifdef CC_PLATFORM_PC
// 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 {
if (!_visible)
return false;
return ImGui::IsAnyWindowHovered();
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
// add by halx99
auto stopAnyMouse = [=](EventMouse* event) {
if (ImGui::IsAnyWindowHovered()) {
event->stopPropagation();
}
};
auto mouseListener = EventListenerMouse::create();
mouseListener->onMouseDown = mouseListener->onMouseUp = stopAnyMouse;
_eventDispatcher->addEventListenerWithSceneGraphPriority(mouseListener, this);
#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
*/
/*
* !!!All of methods, we should calculate delta at imgui_impl_cocos2dx manually
*/
_eventDispatcher->addCustomEventListener(Director::EVENT_BEFORE_DRAW, [=](EventCustom*) { beginFrame(); });
_eventDispatcher->addCustomEventListener(Director::EVENT_AFTER_VISIT, [=](EventCustom*) { endFrame(); });
return true;
}
~ImGuiEXTRenderer()
{
_eventDispatcher->removeCustomEventListeners(Director::EVENT_AFTER_VISIT);
_eventDispatcher->removeCustomEventListeners(Director::EVENT_BEFORE_DRAW);
}
protected:
/*void onEnter() override
{
Layer::onEnter();
scheduleUpdate();
}
void update(float dt) {
}*/
/*virtual void draw(cocos2d::Renderer* renderer, const cocos2d::Mat4& parentTransform, uint32_t parentFlags) override
{
Layer::draw(renderer, parentTransform, parentFlags);
endFrame();
}*/
/*
* begin ImGui frame and draw ImGui stubs
*/
void beginFrame()
{
// create frame
ImGui_ImplCocos2dx_NewFrame();
// draw all gui
_imguiext->update();
// render
ImGui::Render();
}
/*
* flush ImGui draw data to engine
*/
void endFrame() {
ImGui_ImplCocos2dx_RenderDrawData(ImGui::GetDrawData());
ImGui_ImplCocos2dx_RenderPlatform();
}
ImGuiEXT* _imguiext = nullptr;
};
static ImGuiEXT* _instance = nullptr; static ImGuiEXT* _instance = nullptr;
std::function<void(ImGuiEXT*)> ImGuiEXT::_onInit; std::function<void(ImGuiEXT*)> ImGuiEXT::_onInit;
@ -27,8 +139,8 @@ void ImGuiEXT::destroyInstance()
{ {
if (_instance) if (_instance)
{ {
delete _instance;
ImGui_ImplCocos2dx_Shutdown(); ImGui_ImplCocos2dx_Shutdown();
delete _instance;
_instance = nullptr; _instance = nullptr;
} }
} }
@ -38,30 +150,46 @@ void ImGuiEXT::setOnInit(const std::function<void(ImGuiEXT*)>& callBack)
_onInit = callBack; _onInit = callBack;
} }
void ImGuiEXT::onDraw() void ImGuiEXT::update()
{ { // drived by ImGuiEXTRenderer
// clear things from last frame // clear things from last frame
usedCCRefIdMap.clear(); usedCCRefIdMap.clear();
usedCCRef.clear(); usedCCRef.clear();
// drawing commands // drawing commands
auto iter = _callPiplines.begin(); for (auto& pipline : _renderPiplines)
for (; iter != _callPiplines.end(); ++iter) pipline.second.frame();
{
iter->second();
}
// commands will be processed after update // commands will be processed after update
} }
void ImGuiEXT::addCallback(const std::function<void()>& callBack, const std::string& name) bool ImGuiEXT::addRenderLoop(const std::string& id, Scene* scene, std::function<void()> onFrame)
{ {
_callPiplines[name] = callBack; // TODO: check whether exist
auto fourccId = fourccValue(id);
if (_renderPiplines.find(fourccId) != _renderPiplines.end())
{
return false;
}
auto renderer = utils::newInstance<ImGuiEXTRenderer>(&ImGuiEXTRenderer::initWithImGuiEXT, this);
scene->addChild(renderer, INT_MAX, fourccId);
_renderPiplines.emplace(fourccId, RenderPipline{ renderer, std::move(onFrame) });
return true;
} }
void ImGuiEXT::removeCallback(const std::string& name) void ImGuiEXT::removeRenderLoop(const std::string& id)
{ {
const auto iter = _callPiplines.find(name); auto fourccId = fourccValue(id);
if (iter != _callPiplines.end()) const auto iter = _renderPiplines.find(fourccId);
_callPiplines.erase(iter); if (iter != _renderPiplines.end()) {
auto renderer = iter->second.renderer;
if (renderer->getParent())
renderer->removeFromParent();
renderer->release();
_renderPiplines.erase(iter);
}
} }
static std::tuple<ImVec2, ImVec2> getTextureUV(Sprite* sp) static std::tuple<ImVec2, ImVec2> getTextureUV(Sprite* sp)

View File

@ -10,18 +10,29 @@
NS_CC_EXT_BEGIN NS_CC_EXT_BEGIN
class ImGuiLayer; class ImGuiEXTRenderer;
class ImGuiEXT class ImGuiEXT
{ {
friend class ImGuiLayer; friend class ImGuiEXTRenderer;
void init(); void init();
public: public:
static ImGuiEXT* getInstance(); static ImGuiEXT* getInstance();
static void destroyInstance(); static void destroyInstance();
static void setOnInit(const std::function<void(ImGuiEXT*)>& callBack); static void setOnInit(const std::function<void(ImGuiEXT*)>& callBack);
void addCallback(const std::function<void()>& callBack, const std::string& name); /// <summary>
void removeCallback(const std::string& name); /// Add a ImGui render loop to specific scene
/// </summary>
/// <param name="id">FOURCC starts with '#', such as "#abcd"</id>
/// <param name="scene">the scene to render ImGui</param>
/// <param name="onFrame">the ImGui render loop</param>
bool addRenderLoop(const std::string& id, Scene* scene, std::function<void()> onFrame);
/// <summary>
/// Remove ImGui render loop
/// </summary>
/// <param name="id">FOURCC starts with '#', such as "#abcd"</id>
void removeRenderLoop(const std::string& id);
// imgui helper // imgui helper
void image( void image(
@ -90,12 +101,18 @@ public:
private: private:
// perform draw ImGui stubs // perform draw ImGui stubs
void onDraw(); void update();
private: private:
static std::function<void(ImGuiEXT*)> _onInit; static std::function<void(ImGuiEXT*)> _onInit;
std::unordered_map<std::string, std::function<void()>> _callPiplines; struct RenderPipline {
ImGuiEXTRenderer* renderer;
std::function<void()> frame;
};
std::unordered_map<uint32_t, RenderPipline> _renderPiplines;
std::unordered_map<Ref*, int> usedCCRefIdMap; std::unordered_map<Ref*, int> usedCCRefIdMap;
// cocos objects should be retained until next frame // cocos objects should be retained until next frame
Vector<Ref*> usedCCRef; Vector<Ref*> usedCCRef;

View File

@ -1,4 +1,4 @@
set(target_name ImGui) set(target_name ImGuiEXT)
#~ if(WINDOWS) #~ if(WINDOWS)
#~ include_directories(${COCOS2DX_ROOT_PATH}/external/win32-specific/gles/include/OGLES) #~ include_directories(${COCOS2DX_ROOT_PATH}/external/win32-specific/gles/include/OGLES)
@ -7,7 +7,6 @@ include_directories(imgui)
set(HEADER set(HEADER
CCImGuiEXT.h CCImGuiEXT.h
CCImGuiLayer.h
# CCImGuiColorTextEdit.h # CCImGuiColorTextEdit.h
imgui_impl_cocos2dx.h imgui_impl_cocos2dx.h
imgui/imconfig.h imgui/imconfig.h
@ -24,7 +23,6 @@ set(HEADER
set(SOURCE set(SOURCE
CCImGuiEXT.cpp CCImGuiEXT.cpp
CCImGuiLayer.cpp
# CCImGuiColorTextEdit.cpp # CCImGuiColorTextEdit.cpp
imgui_impl_cocos2dx.cpp imgui_impl_cocos2dx.cpp
imgui/imgui.cpp imgui/imgui.cpp

View File

@ -0,0 +1,37 @@
# ImGuiEXT of EGNX
Sync from https://github.com/Xrysnow/cocos2d-x-imgui and do a little changes
## Improvements
* Simple API, use add/remove renderLoop present ImGui GUI widgets
* Optimize call pipeline flow, support add/remove Node to Scene at ImGui render loop without container iterator damage
* Calculate deltaTime at ```ImGui_ImplCocos2dx_NewFrame``` to avoid error when ```cc.Director``` paused
* Refine ```Init/Shutdown```, Restore all callbacks for glfw to solve recreate ```ImGuiEXT``` instance support
* Use FOURCC for key of ImGui render loop
## How to use
1. Enable by cmake option -DBUILD_EXTENSION_IMGUIEXT=ON
2.
```cpp
#include "ImGuiEXT/CCImGuiEXT.h"
USING_NS_CC;
USING_NS_CC_EXT;
class GameScene : public Scene {
public:
void onEnter() override
{
ImGuiEXT::getInstance()->addRenderLoop("#im01", this, CC_CALLBACK_0(GameScene::onImGuiDraw, this));
}
void onExit() override
{
ImGuiEXT::getInstance()->removeRenderLoop("#im01");
}
void onImGuiDraw()
{
ImGui::Begin("window");
ImGui::Text("FPS=%.1f", 1.f / ImGui::GetIO().DeltaTime);
ImGui::End();
}
}
```

View File

@ -45,6 +45,9 @@ static GLFWmousebuttonfun g_PrevUserCallbackMousebutton = nullptr;
static GLFWscrollfun g_PrevUserCallbackScroll = nullptr; static GLFWscrollfun g_PrevUserCallbackScroll = nullptr;
static GLFWkeyfun g_PrevUserCallbackKey = nullptr; static GLFWkeyfun g_PrevUserCallbackKey = nullptr;
static GLFWcharfun g_PrevUserCallbackChar = nullptr; static GLFWcharfun g_PrevUserCallbackChar = nullptr;
static GLFWmonitorfun g_PrevUserCallbackMonitor = nullptr;
static bool g_WantUpdateMonitors = true; static bool g_WantUpdateMonitors = true;
// Forward Declarations // Forward Declarations
@ -54,6 +57,11 @@ static void ImGui_ImplGlfw_UpdateMonitors();
#endif // CC_PLATFORM_PC #endif // CC_PLATFORM_PC
// fps macro
#define CC_IMGUI_DEFAULT_DELTA (1 / 60.f)
#define CC_IMGUI_MIN_DELTA (1 / 1000.f)
#define CC_IMGUI_MAX_DELTA (1 / 30.f)
struct ProgramInfo struct ProgramInfo
{ {
Program* program = nullptr; Program* program = nullptr;
@ -487,7 +495,7 @@ static void ImGui_ImplOpenGL2_RenderWindow(ImGuiViewport* viewport, void*)
ImGui_ImplCocos2dx_RenderDrawData(viewport->DrawData); ImGui_ImplCocos2dx_RenderDrawData(viewport->DrawData);
} }
bool ImGui_ImplCocos2dx_Init(bool install_callbacks) bool ImGui_ImplCocos2dx_Init(bool install_callbacks /*TODO: need check whether callbacks installed at shutdown*/)
{ {
g_Time = 0.0; g_Time = 0.0;
ImGui::CreateContext(); ImGui::CreateContext();
@ -589,18 +597,21 @@ bool ImGui_ImplCocos2dx_Init(bool install_callbacks)
g_PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplCocos2dx_CharCallback); g_PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplCocos2dx_CharCallback);
} }
// Update monitors the first time (note: monitor callback are broken in GLFW 3.2 and earlier, see github.com/glfw/glfw/issues/784)
ImGui_ImplGlfw_UpdateMonitors();
glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback);
// Our mouse update function expect PlatformHandle to be filled for the main viewport // Our mouse update function expect PlatformHandle to be filled for the main viewport
ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGuiViewport* main_viewport = ImGui::GetMainViewport();
main_viewport->PlatformHandle = (void*)window; main_viewport->PlatformHandle = (void*)window;
#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 #if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32
main_viewport->PlatformHandleRaw = glfwGetWin32Window(window); main_viewport->PlatformHandleRaw = glfwGetWin32Window(window);
#endif #endif
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
ImGui_ImplGlfw_InitPlatformInterface(); ImGui_ImplGlfw_InitPlatformInterface();
else {
// Update monitors the first time (note: monitor callback are broken in GLFW 3.2 and earlier, see github.com/glfw/glfw/issues/784)
ImGui_ImplGlfw_UpdateMonitors();
g_PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback);
}
#else #else
/* /*
auto e = cocos2d::EventListenerMouse::create(); auto e = cocos2d::EventListenerMouse::create();
@ -688,14 +699,38 @@ bool ImGui_ImplCocos2dx_Init(bool install_callbacks)
void ImGui_ImplCocos2dx_Shutdown() void ImGui_ImplCocos2dx_Shutdown()
{ {
const auto window = ImGui_ImplCocos2dx_GetWindow();
ImGui::DestroyPlatformWindows(); ImGui::DestroyPlatformWindows();
#ifdef CC_PLATFORM_PC #ifdef CC_PLATFORM_PC
glfwSetMonitorCallback(g_PrevUserCallbackMonitor);
g_PrevUserCallbackMonitor = nullptr;
// Restore mouse and char callback
glfwSetMouseButtonCallback(window, g_PrevUserCallbackMousebutton);
glfwSetScrollCallback(window, g_PrevUserCallbackScroll);
glfwSetKeyCallback(window, g_PrevUserCallbackKey);
glfwSetCharCallback(window, g_PrevUserCallbackChar);
g_PrevUserCallbackMousebutton = nullptr;
g_PrevUserCallbackScroll = nullptr;
g_PrevUserCallbackKey = nullptr;
g_PrevUserCallbackChar = nullptr;
// Restore monitor callback
if (g_PrevUserCallbackMonitor) {
glfwSetMonitorCallback(g_PrevUserCallbackMonitor);
g_PrevUserCallbackChar = nullptr;
}
// Destroy cursors
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++)
{ {
glfwDestroyCursor(g_MouseCursors[cursor_n]); glfwDestroyCursor(g_MouseCursors[cursor_n]);
g_MouseCursors[cursor_n] = nullptr; g_MouseCursors[cursor_n] = nullptr;
} }
#endif // CC_PLATFORM_PC #endif // CC_PLATFORM_PC
ImGui_ImplCocos2dx_DestroyDeviceObjects(); ImGui_ImplCocos2dx_DestroyDeviceObjects();
ImGui::DestroyContext(); ImGui::DestroyContext();
} }
@ -911,6 +946,14 @@ static void ImGui_ImplGlfw_UpdateMonitors()
void ImGui_ImplCocos2dx_NewFrame() void ImGui_ImplCocos2dx_NewFrame()
{ {
static std::chrono::steady_clock::time_point s_lastFrameTime;
// Calculate deltaTime by self to avoid error when pause cocos2d::Director
auto now = std::chrono::steady_clock::now();
auto deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(now - s_lastFrameTime).count() / 1000000.0f;
deltaTime = cocos2d::clampf(deltaTime, CC_IMGUI_MIN_DELTA, CC_IMGUI_MAX_DELTA);
s_lastFrameTime = now;
g_CallbackCommands.clear(); g_CallbackCommands.clear();
g_CustomCommands.clear(); g_CustomCommands.clear();
g_ProgramStates.clear(); g_ProgramStates.clear();
@ -945,7 +988,7 @@ void ImGui_ImplCocos2dx_NewFrame()
#endif // CC_PLATFORM_PC #endif // CC_PLATFORM_PC
// Setup time step // Setup time step
io.DeltaTime = Director::getInstance()->getDeltaTime(); io.DeltaTime = deltaTime;
ImGui_ImplCocos2dx_UpdateMousePosAndButtons(); ImGui_ImplCocos2dx_UpdateMousePosAndButtons();
ImGui_ImplCocos2dx_UpdateMouseCursor(); ImGui_ImplCocos2dx_UpdateMouseCursor();
@ -1307,7 +1350,7 @@ static void ImGui_ImplGlfw_InitPlatformInterface()
// Note: monitor callback are broken GLFW 3.2 and earlier (see github.com/glfw/glfw/issues/784) // Note: monitor callback are broken GLFW 3.2 and earlier (see github.com/glfw/glfw/issues/784)
ImGui_ImplGlfw_UpdateMonitors(); ImGui_ImplGlfw_UpdateMonitors();
glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback); g_PrevUserCallbackMonitor = glfwSetMonitorCallback(ImGui_ImplGlfw_MonitorCallback);
// Register main window handle (which is owned by the main application, not by us) // Register main window handle (which is owned by the main application, not by us)
// This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports. // This is mostly for simplicity and consistency, so that our code (e.g. mouse handling etc.) can use same logic for main and secondary viewports.