diff --git a/src/ui/MaterialEditorPanel.cpp b/src/ui/MaterialEditorPanel.cpp index ed823ff..6173c33 100644 --- a/src/ui/MaterialEditorPanel.cpp +++ b/src/ui/MaterialEditorPanel.cpp @@ -182,6 +182,37 @@ void VerticalSplitter(const char* id, float* width, float minWidth, float maxWid ImGui::SameLine(0.0f, 0.0f); } +// Horizontal drag-splitter stacked between two vertically-arranged panels. +// Adjusts *ratio (fraction of totalHeight given to the panel above) by the +// mouse drag; colours match VerticalSplitter. +void HorizontalSplitter(const char* id, float* ratio, float totalHeight, + float minRatio, float maxRatio) { + const float width = std::max(1.0f, ImGui::GetContentRegionAvail().x); + ImGui::InvisibleButton(id, ImVec2(width, 6.0f)); + const bool hovered = ImGui::IsItemHovered(); + const bool active = ImGui::IsItemActive(); + if (hovered || active) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS); + if (active && totalHeight > 1.0f) + *ratio = std::clamp(*ratio + ImGui::GetIO().MouseDelta.y / totalHeight, minRatio, maxRatio); + ImVec2 rectMin = ImGui::GetItemRectMin(); + ImVec2 rectMax = ImGui::GetItemRectMax(); + ImGui::GetWindowDrawList()->AddRectFilled( + ImVec2(rectMin.x, rectMin.y + 2.0f), ImVec2(rectMax.x, rectMax.y - 2.0f), + (hovered || active) ? IM_COL32(250, 150, 66, 200) : IM_COL32(80, 80, 80, 180)); +} + +// Small glyph for a create-node list entry, keyed by the derived category +// (see MaterialManager::DeriveCategory: Material/Texture/Geometry/Light/Utility). +Icon IconForNodeCategory(const std::string& category) { + if (category == "Material") return Icon::Swatchbook; + if (category == "Texture") return Icon::Grid; + if (category == "Geometry") return Icon::Cube; + if (category == "Light") return Icon::Lightbulb; + if (category == "Utility") return Icon::Code; + return Icon::CircleDot; +} + // Networks referenced from .mtlx (or authored elsewhere) carry no uiPosition // custom data, so every node would seed at (0,0) in a pile. Give those nodes // layered left-to-right positions instead: each node sits one column left of @@ -227,8 +258,9 @@ void ApplyFallbackLayout(ShaderGraphSnapshot& graph) { MaterialEditorPanel::MaterialEditorPanel() { // Route the vendored editor's click-time hit-test dump into our log so - // it interleaves with the [NodeGraph] event trace. - g_AxNodeEditorDebugLog = [](const char* msg) { LOG_INFO(std::string(msg)); }; + // it interleaves with the [NodeGraph] event trace. Disabled: uncomment to + // re-enable the [ed] trace while diagnosing the drag regression. + // g_AxNodeEditorDebugLog = [](const char* msg) { LOG_INFO(std::string(msg)); }; NE::Config config; config.SettingsFile = nullptr; // layout persists as USD uiPosition custom data, not an on-disk .json @@ -612,25 +644,34 @@ void MaterialEditorPanel::CommitInputEdit(const ShaderGraphNode& node, const Sha } void MaterialEditorPanel::RenderToolbar() { - // Create Material icon button - bool createClicked; - if (m_iconManager) { - createClicked = ImGui::ImageButton("##createMaterial", - ImTextureRef(m_iconManager->Get(Icon::FilePlus)), - ImVec2(18.f, 18.f)); - } else { - createClicked = ImGui::Button("Create Material"); - } - if (ImGui::IsItemHovered()) - ImGui::SetTooltip("Create Material"); - if (createClicked) + // Every toolbar action is an icon button; the tooltip carries the label. + // The icon fills the full button height (standard frame height, zero frame + // padding) so the buttons stay a uniform, standard-row-height square. Falls + // back to a text button only when no icon set is loaded. + const float btnH = ImGui::GetFrameHeight(); + auto iconBtn = [&](const char* id, Icon icon, const char* fallback, const char* tip) -> bool { + bool clicked; + if (m_iconManager) { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f)); + clicked = ImGui::ImageButton(id, ImTextureRef(m_iconManager->Get(icon)), + ImVec2(btnH, btnH)); + ImGui::PopStyleVar(); + } else { + clicked = ImGui::Button(fallback); + } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("%s", tip); + return clicked; + }; + + if (iconBtn("##createMaterial", Icon::FilePlus, "Create Material", "Create Material")) CreateNewMaterial(); // Presets menu: entries appear per node source when the required shader // definitions are registered, and need an open material to apply to. ImGui::SameLine(); ImGui::BeginDisabled(m_materialPath.IsEmpty()); - if (ImGui::Button("Presets")) + if (iconBtn("##presets", Icon::Swatchbook, "Presets", "Presets")) ImGui::OpenPopup("MaterialPresets"); ImGui::EndDisabled(); if (ImGui::BeginPopup("MaterialPresets")) { @@ -657,11 +698,13 @@ void MaterialEditorPanel::RenderToolbar() { ImGui::SameLine(); ImGui::Text("Selected: %s", m_targetPrimPath.GetText()); ImGui::SameLine(); - if (ImGui::Button("Create + Bind Material")) + if (iconBtn("##createBind", Icon::ObjectGroup, "Create + Bind", + "Create + Bind Material to Selected")) CreateAndBindMaterialForTarget(); ImGui::SameLine(); ImGui::BeginDisabled(m_materialPath.IsEmpty()); - if (ImGui::Button("Bind Current Material to Selected")) + if (iconBtn("##bindCurrent", Icon::Link, "Bind Current", + "Bind Current Material to Selected")) BindMaterialToTarget(m_materialPath, m_targetPrimPath); ImGui::EndDisabled(); } @@ -698,9 +741,12 @@ void MaterialEditorPanel::RenderMaterialBrowser() { std::find(materials.begin(), materials.end(), m_browserSelection) == materials.end()) m_browserSelection = pxr::SdfPath(); - // Top ~45%: materials list. Below: persistent searchable create-node - // list (Hypershade's create bar, with Nuke-style filtering). - ImGui::BeginChild("MaterialBrowserList", ImVec2(0.0f, ImGui::GetContentRegionAvail().y * 0.45f), false); + // Materials list (top) and the persistent searchable create-node list + // (bottom, Hypershade's create bar with Nuke-style filtering), split by a + // draggable horizontal splitter; m_browserListRatio is the list's share. + const float browserAvail = ImGui::GetContentRegionAvail().y; + const float listHeight = std::max(1.0f, browserAvail * m_browserListRatio); + ImGui::BeginChild("MaterialBrowserList", ImVec2(0.0f, listHeight), false); for (const pxr::SdfPath& path : materials) { const bool isSelected = (path == m_browserSelection); const bool isOpen = (path == m_materialPath); @@ -721,7 +767,8 @@ void MaterialEditorPanel::RenderMaterialBrowser() { } ImGui::EndChild(); - ImGui::Separator(); + HorizontalSplitter("##BrowserListSplitter", &m_browserListRatio, browserAvail, 0.15f, 0.85f); + ImGui::TextUnformatted("Create Node"); ImGui::SetNextItemWidth(-FLT_MIN); ImGui::InputTextWithHint("##BrowserNodeSearch", "search...", @@ -773,6 +820,12 @@ void MaterialEditorPanel::RenderMaterialBrowser() { } if (!categoryOpen) continue; + if (m_iconManager) { + const float h = ImGui::GetTextLineHeight(); + ImGui::Image(ImTextureRef(m_iconManager->Get(IconForNodeCategory(info->category))), + ImVec2(h, h)); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + } std::string itemLabel = info->label + "##" + std::to_string(i); if (ImGui::Selectable(itemLabel.c_str(), false)) m_pendingCreateShaderId = info->identifier; // created at view center next canvas pass @@ -1225,6 +1278,9 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() { // event logs once, not per frame. Event order in the editor is: // LMB press -> node becomes active (and is brought to front) -> drag // moves it -> release -> "click" -> selection updates -> position save. + // Disabled: flip to #if 1 to re-enable the [NodeGraph] trace while + // diagnosing the node-editor drag regression. +#if 0 { // Logs the mouse position and every node's editor rect with a hit // verdict, and returns the hit paths. Topmost is unknown to us, but @@ -1378,6 +1434,7 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() { LOG_INFO(msg); } } +#endif // Mirror the editor's node selection (first selected node) for the // properties section under the preview. @@ -1611,8 +1668,8 @@ void MaterialEditorPanel::PersistNodePosition(NE::NodeId nodeId) { for (const auto& node : m_graph.nodes) { if (node.path == path) { if (node.uiPosition == newValue) { - LOG_INFO("[NodeGraph] position save for " + path.GetString() + - " ignored (echo of snapshot seed)"); + // LOG_INFO("[NodeGraph] position save for " + path.GetString() + + // " ignored (echo of snapshot seed)"); return; } break; @@ -1625,16 +1682,16 @@ void MaterialEditorPanel::PersistNodePosition(NE::NodeId nodeId) { : pxr::GfVec2f(0.0f, 0.0f); if (oldValue == newValue) { - LOG_INFO("[NodeGraph] position save for " + path.GetString() + - " ignored (customData already matches)"); + // LOG_INFO("[NodeGraph] position save for " + path.GetString() + + // " ignored (customData already matches)"); return; } - char moveMsg[256]; - std::snprintf(moveMsg, sizeof(moveMsg), - "[NodeGraph] move committed: %s (%.1f, %.1f) -> (%.1f, %.1f)", - path.GetText(), oldValue[0], oldValue[1], newValue[0], newValue[1]); - LOG_INFO(std::string(moveMsg)); + // char moveMsg[256]; + // std::snprintf(moveMsg, sizeof(moveMsg), + // "[NodeGraph] move committed: %s (%.1f, %.1f) -> (%.1f, %.1f)", + // path.GetText(), oldValue[0], oldValue[1], newValue[0], newValue[1]); + // LOG_INFO(std::string(moveMsg)); pxr::UsdStageRefPtr stage = m_stage; m_commandHistory->Push(std::make_unique( diff --git a/src/ui/MaterialEditorPanel.h b/src/ui/MaterialEditorPanel.h index bafe4e5..59d727f 100644 --- a/src/ui/MaterialEditorPanel.h +++ b/src/ui/MaterialEditorPanel.h @@ -157,6 +157,11 @@ private: float m_browserWidth = 220.0f; float m_previewWidth = 320.0f; + /// Fraction of the browser column's height given to the materials list; + /// the create-node list takes the rest. User-adjustable via the horizontal + /// splitter between them. + float m_browserListRatio = 0.45f; + /// Search text of the browser's persistent create-node list. char m_browserNodeSearchBuf[128] = ""; /// Shader id clicked in the browser's create-node list; created at the