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:
@@ -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>(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user