038450a1ca
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>
280 lines
8.3 KiB
C++
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
|