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);
|
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
|
// 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
|
// 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
|
// layered left-to-right positions instead: each node sits one column left of
|
||||||
@@ -227,8 +258,9 @@ void ApplyFallbackLayout(ShaderGraphSnapshot& graph) {
|
|||||||
|
|
||||||
MaterialEditorPanel::MaterialEditorPanel() {
|
MaterialEditorPanel::MaterialEditorPanel() {
|
||||||
// Route the vendored editor's click-time hit-test dump into our log so
|
// Route the vendored editor's click-time hit-test dump into our log so
|
||||||
// it interleaves with the [NodeGraph] event trace.
|
// it interleaves with the [NodeGraph] event trace. Disabled: uncomment to
|
||||||
g_AxNodeEditorDebugLog = [](const char* msg) { LOG_INFO(std::string(msg)); };
|
// re-enable the [ed] trace while diagnosing the drag regression.
|
||||||
|
// g_AxNodeEditorDebugLog = [](const char* msg) { LOG_INFO(std::string(msg)); };
|
||||||
|
|
||||||
NE::Config config;
|
NE::Config config;
|
||||||
config.SettingsFile = nullptr; // layout persists as USD uiPosition custom data, not an on-disk .json
|
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() {
|
void MaterialEditorPanel::RenderToolbar() {
|
||||||
// Create Material icon button
|
// Every toolbar action is an icon button; the tooltip carries the label.
|
||||||
bool createClicked;
|
// 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) {
|
if (m_iconManager) {
|
||||||
createClicked = ImGui::ImageButton("##createMaterial",
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.f, 0.f));
|
||||||
ImTextureRef(m_iconManager->Get(Icon::FilePlus)),
|
clicked = ImGui::ImageButton(id, ImTextureRef(m_iconManager->Get(icon)),
|
||||||
ImVec2(18.f, 18.f));
|
ImVec2(btnH, btnH));
|
||||||
|
ImGui::PopStyleVar();
|
||||||
} else {
|
} else {
|
||||||
createClicked = ImGui::Button("Create Material");
|
clicked = ImGui::Button(fallback);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered())
|
if (ImGui::IsItemHovered())
|
||||||
ImGui::SetTooltip("Create Material");
|
ImGui::SetTooltip("%s", tip);
|
||||||
if (createClicked)
|
return clicked;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (iconBtn("##createMaterial", Icon::FilePlus, "Create Material", "Create Material"))
|
||||||
CreateNewMaterial();
|
CreateNewMaterial();
|
||||||
|
|
||||||
// Presets menu: entries appear per node source when the required shader
|
// Presets menu: entries appear per node source when the required shader
|
||||||
// definitions are registered, and need an open material to apply to.
|
// definitions are registered, and need an open material to apply to.
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::BeginDisabled(m_materialPath.IsEmpty());
|
ImGui::BeginDisabled(m_materialPath.IsEmpty());
|
||||||
if (ImGui::Button("Presets"))
|
if (iconBtn("##presets", Icon::Swatchbook, "Presets", "Presets"))
|
||||||
ImGui::OpenPopup("MaterialPresets");
|
ImGui::OpenPopup("MaterialPresets");
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
if (ImGui::BeginPopup("MaterialPresets")) {
|
if (ImGui::BeginPopup("MaterialPresets")) {
|
||||||
@@ -657,11 +698,13 @@ void MaterialEditorPanel::RenderToolbar() {
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Text("Selected: %s", m_targetPrimPath.GetText());
|
ImGui::Text("Selected: %s", m_targetPrimPath.GetText());
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Create + Bind Material"))
|
if (iconBtn("##createBind", Icon::ObjectGroup, "Create + Bind",
|
||||||
|
"Create + Bind Material to Selected"))
|
||||||
CreateAndBindMaterialForTarget();
|
CreateAndBindMaterialForTarget();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::BeginDisabled(m_materialPath.IsEmpty());
|
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);
|
BindMaterialToTarget(m_materialPath, m_targetPrimPath);
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
}
|
}
|
||||||
@@ -698,9 +741,12 @@ void MaterialEditorPanel::RenderMaterialBrowser() {
|
|||||||
std::find(materials.begin(), materials.end(), m_browserSelection) == materials.end())
|
std::find(materials.begin(), materials.end(), m_browserSelection) == materials.end())
|
||||||
m_browserSelection = pxr::SdfPath();
|
m_browserSelection = pxr::SdfPath();
|
||||||
|
|
||||||
// Top ~45%: materials list. Below: persistent searchable create-node
|
// Materials list (top) and the persistent searchable create-node list
|
||||||
// list (Hypershade's create bar, with Nuke-style filtering).
|
// (bottom, Hypershade's create bar with Nuke-style filtering), split by a
|
||||||
ImGui::BeginChild("MaterialBrowserList", ImVec2(0.0f, ImGui::GetContentRegionAvail().y * 0.45f), false);
|
// 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) {
|
for (const pxr::SdfPath& path : materials) {
|
||||||
const bool isSelected = (path == m_browserSelection);
|
const bool isSelected = (path == m_browserSelection);
|
||||||
const bool isOpen = (path == m_materialPath);
|
const bool isOpen = (path == m_materialPath);
|
||||||
@@ -721,7 +767,8 @@ void MaterialEditorPanel::RenderMaterialBrowser() {
|
|||||||
}
|
}
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|
||||||
ImGui::Separator();
|
HorizontalSplitter("##BrowserListSplitter", &m_browserListRatio, browserAvail, 0.15f, 0.85f);
|
||||||
|
|
||||||
ImGui::TextUnformatted("Create Node");
|
ImGui::TextUnformatted("Create Node");
|
||||||
ImGui::SetNextItemWidth(-FLT_MIN);
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
ImGui::InputTextWithHint("##BrowserNodeSearch", "search...",
|
ImGui::InputTextWithHint("##BrowserNodeSearch", "search...",
|
||||||
@@ -773,6 +820,12 @@ void MaterialEditorPanel::RenderMaterialBrowser() {
|
|||||||
}
|
}
|
||||||
if (!categoryOpen)
|
if (!categoryOpen)
|
||||||
continue;
|
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);
|
std::string itemLabel = info->label + "##" + std::to_string(i);
|
||||||
if (ImGui::Selectable(itemLabel.c_str(), false))
|
if (ImGui::Selectable(itemLabel.c_str(), false))
|
||||||
m_pendingCreateShaderId = info->identifier; // created at view center next canvas pass
|
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:
|
// event logs once, not per frame. Event order in the editor is:
|
||||||
// LMB press -> node becomes active (and is brought to front) -> drag
|
// LMB press -> node becomes active (and is brought to front) -> drag
|
||||||
// moves it -> release -> "click" -> selection updates -> position save.
|
// 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
|
// 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
|
// verdict, and returns the hit paths. Topmost is unknown to us, but
|
||||||
@@ -1378,6 +1434,7 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
|
|||||||
LOG_INFO(msg);
|
LOG_INFO(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Mirror the editor's node selection (first selected node) for the
|
// Mirror the editor's node selection (first selected node) for the
|
||||||
// properties section under the preview.
|
// properties section under the preview.
|
||||||
@@ -1611,8 +1668,8 @@ void MaterialEditorPanel::PersistNodePosition(NE::NodeId nodeId) {
|
|||||||
for (const auto& node : m_graph.nodes) {
|
for (const auto& node : m_graph.nodes) {
|
||||||
if (node.path == path) {
|
if (node.path == path) {
|
||||||
if (node.uiPosition == newValue) {
|
if (node.uiPosition == newValue) {
|
||||||
LOG_INFO("[NodeGraph] position save for " + path.GetString() +
|
// LOG_INFO("[NodeGraph] position save for " + path.GetString() +
|
||||||
" ignored (echo of snapshot seed)");
|
// " ignored (echo of snapshot seed)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1625,16 +1682,16 @@ void MaterialEditorPanel::PersistNodePosition(NE::NodeId nodeId) {
|
|||||||
: pxr::GfVec2f(0.0f, 0.0f);
|
: pxr::GfVec2f(0.0f, 0.0f);
|
||||||
|
|
||||||
if (oldValue == newValue) {
|
if (oldValue == newValue) {
|
||||||
LOG_INFO("[NodeGraph] position save for " + path.GetString() +
|
// LOG_INFO("[NodeGraph] position save for " + path.GetString() +
|
||||||
" ignored (customData already matches)");
|
// " ignored (customData already matches)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char moveMsg[256];
|
// char moveMsg[256];
|
||||||
std::snprintf(moveMsg, sizeof(moveMsg),
|
// std::snprintf(moveMsg, sizeof(moveMsg),
|
||||||
"[NodeGraph] move committed: %s (%.1f, %.1f) -> (%.1f, %.1f)",
|
// "[NodeGraph] move committed: %s (%.1f, %.1f) -> (%.1f, %.1f)",
|
||||||
path.GetText(), oldValue[0], oldValue[1], newValue[0], newValue[1]);
|
// path.GetText(), oldValue[0], oldValue[1], newValue[0], newValue[1]);
|
||||||
LOG_INFO(std::string(moveMsg));
|
// LOG_INFO(std::string(moveMsg));
|
||||||
|
|
||||||
pxr::UsdStageRefPtr stage = m_stage;
|
pxr::UsdStageRefPtr stage = m_stage;
|
||||||
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
|
m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
|
||||||
|
|||||||
@@ -157,6 +157,11 @@ private:
|
|||||||
float m_browserWidth = 220.0f;
|
float m_browserWidth = 220.0f;
|
||||||
float m_previewWidth = 320.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.
|
/// Search text of the browser's persistent create-node list.
|
||||||
char m_browserNodeSearchBuf[128] = "";
|
char m_browserNodeSearchBuf[128] = "";
|
||||||
/// Shader id clicked in the browser's create-node list; created at the
|
/// Shader id clicked in the browser's create-node list; created at the
|
||||||
|
|||||||
Reference in New Issue
Block a user