Files
UsdLayerManager/src/ui/ImGuiContext.cpp
T
indigo 038450a1ca Vendor node editor in-tree; add material presets; debug drag regression
Move the imgui-node-editor subset the build actually compiles from
third_party/imgui-node-editor to src/ui/NodeEditor (version-controlled,
MIT LICENSE included). FindImguiNodeEditor.cmake points at the new
location; CMakeLists excludes src/ui/NodeEditor from UI_SOURCES so it
isn't compiled twice.

Material editor presets: a Presets menu (disabled with no open material)
builds a UsdPreviewSurface + UsdUVTexture graph fed by an st primvar
reader, or a MaterialX standard_surface + image graph fed by a texcoord
node. Each is one idempotent, undoable command that re-normalizes layout.
Create Material becomes an icon button. New nodes route through
FindFreeCanvasSpot so a creation never lands on top of an existing node
(overlapping nodes fight over the editor hit test and become undraggable).

Shader-ball preview: pick a previewable output (terminal or 3/4-component
color-like) instead of always the first output, so scalar-only nodes keep
the whole-material preview rather than failing Storm codegen. Per-shape
camera frame-fit margins and auto-clip framing.

Fixes: DeletePrimCommand::Undo recreates missing destination ancestors
before SdfCopySpec (parent material may have been deleted after the
command ran). ConfigWindowsMoveFromTitleBarOnly stops a content-area drag
in the node canvas from moving the whole Material Editor window.

Temporary (marked for removal once the node-editor drag regression is
diagnosed): main.cpp mirrors LOG_INFO to %APPDATA%\UsdLayerManager\
debug.log; a g_AxNodeEditorDebugLog hook in the vendored editor plus a
[NodeGraph] event-trace block in RenderNodeGraphCanvas dump click/drag/
selection/position-save state.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:35:23 +08:00

280 lines
8.3 KiB
C++

#include "ImGuiContext.h"
#include "../utils/Logger.h"
#include "../utils/GLExt.h"
#include "../utils/PathUtils.h"
#include <imgui.h>
#include <imgui_impl_win32.h>
#include <imgui_impl_opengl3.h>
#include <filesystem>
// Forward declare message handler from imgui_impl_win32.cpp
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
namespace UsdLayerManager {
ImGuiContext::ImGuiContext()
: m_hwnd(nullptr)
, m_hdc(nullptr)
, m_hglrc(nullptr)
, m_shouldClose(false)
, m_width(1280)
, m_height(720) {
}
ImGuiContext::~ImGuiContext() {
Shutdown();
}
bool ImGuiContext::Initialize(const std::string& windowTitle, int width, int height) {
m_width = width;
m_height = height;
LOG_INFO("Initializing ImGui context...");
// Create application window
WNDCLASSEXW wc = {
sizeof(wc),
CS_OWNDC,
WndProc,
0L,
0L,
GetModuleHandle(nullptr),
nullptr,
nullptr,
nullptr,
nullptr,
L"UsdLayerManager",
nullptr
};
::RegisterClassExW(&wc);
m_hwnd = ::CreateWindowW(
wc.lpszClassName,
L"USD Layer Manager",
WS_OVERLAPPEDWINDOW,
100, 100,
m_width, m_height,
nullptr,
nullptr,
wc.hInstance,
nullptr
);
if (!m_hwnd) {
LOG_ERROR("Failed to create window");
return false;
}
// Store this pointer in window user data
::SetWindowLongPtr(m_hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
// Initialize OpenGL
if (!CreateDeviceWGL()) {
LOG_ERROR("Failed to initialize OpenGL");
::DestroyWindow(m_hwnd);
::UnregisterClassW(wc.lpszClassName, wc.hInstance);
return false;
}
// Initialize OpenGL extensions
if (!GL::InitExtensions()) {
LOG_ERROR("Failed to initialize OpenGL extensions");
}
// Show the window
::ShowWindow(m_hwnd, SW_SHOWDEFAULT);
::UpdateWindow(m_hwnd);
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
// DockingEnable only — NavEnableKeyboard is intentionally omitted for a DCC
// viewport application that manages its own keyboard shortcuts.
// With NavEnableKeyboard set, io.WantCaptureKeyboard becomes true whenever any
// ImGui window is focused, which would block all viewport hotkeys (F, A, etc.).
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
// Dock only while Shift is held during a title-bar/tab drag (the dock
// position overlay appears then); a plain drag just moves the window,
// so casual rearranging can't accidentally re-dock panels.
io.ConfigDockingWithShift = true;
// Windows move only via their title bar/tab, never by dragging empty
// content. Without this, a press-drag that a content widget doesn't
// capture falls through to ImGui's "drag body to move window" behaviour —
// which, in the node-editor canvas (whose background button doesn't
// reliably claim the drag in this nested-child embedding), meant dragging
// in the graph moved the whole Material Editor window. Title-bar dragging
// and Shift-to-dock are unaffected; this is also the desired behaviour for
// every other content area (viewports, timelines) where a content drag
// must never move the host window.
io.ConfigWindowsMoveFromTitleBarOnly = true;
// Store imgui.ini in %APPDATA%\UsdLayerManager\ so it doesn't litter the CWD.
if (const char* appData = getenv("APPDATA")) {
std::filesystem::path iniDir =
std::filesystem::path(appData) / "UsdLayerManager";
std::filesystem::create_directories(iniDir);
m_iniFilePath = (iniDir / "imgui.ini").string();
io.IniFilename = m_iniFilePath.c_str();
}
// Load Inter font.
// The build directory copies the font as Inter.ttc; the install step renames
// it to Inter.ttf. Try .ttf first (install layout), fall back to .ttc (build).
{
ImFontConfig fontConfig;
fontConfig.FontNo = 0; // select Regular face from the .ttc collection
// Build exe-relative font paths so they work from both build and install dirs
std::string fontPath0 = ResourcePath("resources/font/Inter.ttf");
std::string fontPath1 = ResourcePath("resources/font/Inter.ttc");
const char* tryPaths[] = { fontPath0.c_str(), fontPath1.c_str() };
bool loaded = false;
for (const char* p : tryPaths) {
ImFont* f = io.Fonts->AddFontFromFileTTF(p, 15.0f, &fontConfig);
if (f) {
LOG_INFO(std::string("Loaded Inter font from ") + p);
loaded = true;
break;
}
}
if (!loaded) {
LOG_WARNING("Could not load Inter font; using built-in default");
io.Fonts->AddFontDefault();
}
}
// Setup Dear ImGui style
ImGui::StyleColorsDark();
// Setup Platform/Renderer backends
ImGui_ImplWin32_Init(m_hwnd);
ImGui_ImplOpenGL3_Init("#version 130");
LOG_INFO("ImGui context initialized successfully");
return true;
}
void ImGuiContext::Shutdown() {
if (m_hwnd) {
LOG_INFO("Shutting down ImGui context...");
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyPlatformWindows();
ImGui::DestroyContext();
CleanupDeviceWGL();
::DestroyWindow(m_hwnd);
::UnregisterClassW(L"UsdLayerManager", ::GetModuleHandle(nullptr));
m_hwnd = nullptr;
}
}
void ImGuiContext::NewFrame() {
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
}
void ImGuiContext::Render() {
ImGui::Render();
glViewport(0, 0, m_width, m_height);
glClearColor(0.45f, 0.55f, 0.60f, 1.00f);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
::SwapBuffers(m_hdc);
}
bool ImGuiContext::ProcessEvents() {
MSG msg;
while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (msg.message == WM_QUIT) {
m_shouldClose = true;
}
}
return !m_shouldClose;
}
bool ImGuiContext::CreateDeviceWGL() {
m_hdc = ::GetDC(m_hwnd);
PIXELFORMATDESCRIPTOR pfd = {};
pfd.nSize = sizeof(pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.cStencilBits = 8;
int pixelFormat = ::ChoosePixelFormat(m_hdc, &pfd);
if (pixelFormat == 0) {
LOG_ERROR("ChoosePixelFormat failed");
return false;
}
if (!::SetPixelFormat(m_hdc, pixelFormat, &pfd)) {
LOG_ERROR("SetPixelFormat failed");
return false;
}
m_hglrc = ::wglCreateContext(m_hdc);
if (!m_hglrc) {
LOG_ERROR("wglCreateContext failed");
return false;
}
if (!::wglMakeCurrent(m_hdc, m_hglrc)) {
LOG_ERROR("wglMakeCurrent failed");
return false;
}
return true;
}
void ImGuiContext::CleanupDeviceWGL() {
if (m_hglrc) {
::wglMakeCurrent(nullptr, nullptr);
::wglDeleteContext(m_hglrc);
m_hglrc = nullptr;
}
if (m_hdc) {
::ReleaseDC(m_hwnd, m_hdc);
m_hdc = nullptr;
}
}
LRESULT WINAPI ImGuiContext::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) {
return true;
}
ImGuiContext* context = reinterpret_cast<ImGuiContext*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
switch (msg) {
case WM_SIZE:
if (context && wParam != SIZE_MINIMIZED) {
context->m_width = LOWORD(lParam);
context->m_height = HIWORD(lParam);
}
return 0;
case WM_SYSCOMMAND:
if ((wParam & 0xfff0) == SC_KEYMENU) // Disable ALT application menu
return 0;
break;
case WM_DESTROY:
::PostQuitMessage(0);
return 0;
}
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
} // namespace UsdLayerManager