Icon toolbar and resizable browser lists in material editor; mute debug logs

Toolbar: every action is now an icon button (tooltip carries the label,
text fallback with no icon set) via a local iconBtn helper matching the
StageEditor/Timeline convention — Create Material, Presets, Create + Bind,
Bind Current. Icons fill the standard button height (GetFrameHeight, zero
frame padding) so the row stays uniform.

Browser: the materials list and the create-node list are now split by a
draggable HorizontalSplitter (sibling to the column VerticalSplitter)
instead of a fixed 45% / separator; m_browserListRatio holds the list's
share (clamped 0.15-0.85). Create-node entries get a per-category glyph
(Material/Texture/Geometry/Light/Utility) sized to the text line height.

Silence the temporary node-editor drag-regression tracing: the
[NodeGraph] event-trace block is #if 0'd, its PersistNodePosition logs
commented, and the [ed] g_AxNodeEditorDebugLog hook install is commented
out (the vendored editor's dumps are guarded on that hook).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 23:16:04 +08:00
parent 038450a1ca
commit 2ddeeed061
2 changed files with 92 additions and 30 deletions
+87 -30
View File
@@ -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<AttributeSetCommand>(
+5
View File
@@ -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