Add thumbnail toggle and set-all pin-display toolbar icons

Material editor toolbar gets an eye icon to hide/show node thumbnails
(skips the reserved space and decode/render work, not just the
visual) and a 3-dot icon that broadcasts a pin-display mode (All /
Connected / Collapsed) to every node in the open material at once.
The dot icon is shared with the per-node toggle via a parameterized
DrawPinDisplayModeIcon, with dot radius/spacing scaled to fit the
toolbar's fixed icon-button size instead of overflowing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 11:07:57 +08:00
parent a7314ff108
commit a877e8973c
2 changed files with 99 additions and 22 deletions
+88 -22
View File
@@ -45,6 +45,29 @@ uintptr_t HashId(const std::string& s) {
return static_cast<uintptr_t>(std::hash<std::string>{}(s));
}
// Draws the 3-dot pin-display-mode icon (filled-dot count = 3 - mode, so it
// visually "funnels down" as pins collapse further), centered within
// [min, max]. Shared by the per-node toggle in RenderNodeGraphCanvas (which
// sizes its button around the default dot geometry) and the toolbar's
// "set all nodes" button in RenderToolbar (which instead fits the dots into
// its fixed, already-square icon-button size via smaller dotRadius/dotSpacing).
void DrawPinDisplayModeIcon(ImDrawList* dl, const ImVec2& min, const ImVec2& max,
PinDisplayMode mode, ImU32 filledColor, ImU32 hollowColor,
float dotRadius = 3.0f, float dotSpacing = 9.0f) {
const int dotCount = 3;
const int filledDots = 3 - static_cast<int>(mode);
const float iconWidth = dotSpacing * (dotCount - 1) + dotRadius * 2.0f;
ImVec2 dotCenter(min.x + (max.x - min.x - iconWidth) * 0.5f + dotRadius,
min.y + (max.y - min.y) * 0.5f);
for (int i = 0; i < dotCount; ++i) {
if (i < filledDots)
dl->AddCircleFilled(dotCenter, dotRadius, filledColor);
else
dl->AddCircle(dotCenter, dotRadius, hollowColor, 0, 1.2f);
dotCenter.x += dotSpacing;
}
}
// Mirrors the file-local sanitizers in Application.cpp/SceneHierarchyPanel.cpp.
std::string SanitizeUsdName(const std::string& raw) {
std::string result;
@@ -788,6 +811,50 @@ void MaterialEditorPanel::RenderToolbar() {
ImGui::EndPopup();
}
ImGui::SameLine();
if (iconBtn("##toggleThumbs", m_showNodeThumbnails ? Icon::Eye : Icon::EyeSlash,
m_showNodeThumbnails ? "Hide Thumbnails" : "Show Thumbnails",
m_showNodeThumbnails ? "Hide node thumbnails" : "Show node thumbnails"))
m_showNodeThumbnails = !m_showNodeThumbnails;
// "Set all nodes" pin-display broadcast: same 3-dot icon language as the
// per-node toggle (DrawPinDisplayModeIcon), but hit-tested the normal
// ImGui way — this button lives in the toolbar window, not inside the
// node canvas, so none of RenderNodeGraphCanvas's geometric hit-test
// workarounds are needed here.
ImGui::SameLine();
ImGui::BeginDisabled(m_materialPath.IsEmpty());
{
ImGui::PushID("##allPinMode");
const ImVec2 pos = ImGui::GetCursorScreenPos();
const ImVec2 posMax(pos.x + btnH, pos.y + btnH);
const bool clicked = ImGui::InvisibleButton("##btn", ImVec2(btnH, btnH));
const bool hovered = ImGui::IsItemHovered();
ImDrawList* dl = ImGui::GetWindowDrawList();
const ImU32 bg = ImGui::GetColorU32(
hovered ? (ImGui::IsItemActive() ? ImGuiCol_ButtonActive : ImGuiCol_ButtonHovered)
: ImGuiCol_Button);
dl->AddRectFilled(pos, posMax, bg, ImGui::GetStyle().FrameRounding);
// Dot geometry scaled to btnH (unlike the per-node toggle's fixed
// size) so the icon fits inside this fixed, already-square button
// instead of overflowing it.
DrawPinDisplayModeIcon(dl, pos, posMax, m_toolbarPinDisplayMode,
ImGui::GetColorU32(ImGuiCol_Text), ImGui::GetColorU32(ImGuiCol_TextDisabled),
btnH * 0.09f, btnH * 0.30f);
static const char* kTips[3] = {
"All nodes: showing all pins - click to show connected pins only on every node",
"All nodes: showing connected pins only - click to collapse every node to one pin",
"All nodes: collapsed to one pin per side - click to show all pins on every node",
};
if (hovered) ImGui::SetTooltip("%s", kTips[static_cast<int>(m_toolbarPinDisplayMode)]);
if (clicked) {
m_toolbarPinDisplayMode = static_cast<PinDisplayMode>((static_cast<int>(m_toolbarPinDisplayMode) + 1) % 3);
ApplyPinDisplayModeToAllNodes(m_toolbarPinDisplayMode);
}
ImGui::PopID();
}
ImGui::EndDisabled();
if (!m_targetPrimPath.IsEmpty()) {
ImGui::SameLine();
ImGui::Text("Selected: %s", m_targetPrimPath.GetText());
@@ -1396,9 +1463,11 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
// Thumbnail below the title: the source image for Texture nodes, a
// shader-ball render for Material nodes. thumb is 0 until ready; the
// space is still reserved so the node doesn't jump when it appears.
// Toggled off entirely (no reserved space, no decode/render work) via
// the toolbar's thumbnail eye icon.
ImTextureID thumb = 0;
bool wantsThumb = false;
if (node.category == "Texture") {
if (m_showNodeThumbnails && node.category == "Texture") {
// Any texture node that exposes a file/asset input reserves a
// thumbnail slot. It stays black until a valid, on-disk image is
// decoded, so an unset or unresolvable path reads as a black chip.
@@ -1411,7 +1480,7 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
if (!file.empty())
thumb = m_thumbnails.GetTextureThumbnail(node.path, file);
}
} else if (node.category == "Material") {
} else if (m_showNodeThumbnails && node.category == "Material") {
wantsThumb = true;
// Preview the terminal (token-typed) output as the surface; fall
// back to the first output routed into diffuseColor.
@@ -1479,14 +1548,11 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
// or popups, since the canvas-space transform is unclamped.
{
const ImGuiStyle& style = ImGui::GetStyle();
// 3-dot icon: filled-dot count encodes how much is visible —
// All shows all 3 dots filled, Connected 2, Collapsed 1 — so the
// icon visually "funnels down" as the node collapses further.
const float dotRadius = 3.0f;
const float dotSpacing = 9.0f;
const int dotCount = 3;
const int filledDots = 3 - static_cast<int>(pinDisplay.mode);
const ImVec2 iconSize(dotSpacing * (dotCount - 1) + dotRadius * 2.0f, dotRadius * 2.0f);
// Icon area sized to match the 3-dot icon's fixed geometry (see
// DrawPinDisplayModeIcon); filled-dot count visually "funnels
// down" as the node collapses further (All=3, Connected=2,
// Collapsed=1).
const ImVec2 iconSize(9.0f * 2.0f + 3.0f * 2.0f, 3.0f * 2.0f);
const ImVec2 btnMin = ImGui::GetCursorScreenPos();
const ImVec2 btnMax(btnMin.x + iconSize.x + style.FramePadding.x * 2.0f,
btnMin.y + iconSize.y + style.FramePadding.y * 2.0f);
@@ -1501,18 +1567,8 @@ void MaterialEditorPanel::RenderNodeGraphCanvas() {
: ImGuiCol_Button);
ImDrawList* dl = ImGui::GetWindowDrawList();
dl->AddRectFilled(btnMin, btnMax, frameColor, style.FrameRounding);
const ImU32 dotColor = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 dotHollowColor = ImGui::GetColorU32(ImGuiCol_TextDisabled);
ImVec2 dotCenter(btnMin.x + style.FramePadding.x + dotRadius,
btnMin.y + style.FramePadding.y + dotRadius);
for (int i = 0; i < dotCount; ++i) {
if (i < filledDots)
dl->AddCircleFilled(dotCenter, dotRadius, dotColor);
else
dl->AddCircle(dotCenter, dotRadius, dotHollowColor, 0, 1.2f);
dotCenter.x += dotSpacing;
}
DrawPinDisplayModeIcon(dl, btnMin, btnMax, pinDisplay.mode,
ImGui::GetColorU32(ImGuiCol_Text), ImGui::GetColorU32(ImGuiCol_TextDisabled));
static const char* kTooltips[3] = {
"Showing all pins - click to show connected pins only",
@@ -1971,6 +2027,16 @@ void MaterialEditorPanel::CyclePinDisplayMode(const pxr::SdfPath& nodePath) {
PersistPinDisplayState(nodePath);
}
void MaterialEditorPanel::ApplyPinDisplayModeToAllNodes(PinDisplayMode mode) {
for (const auto& node : m_graph.nodes) {
PinDisplayOverride& ov = GetPinDisplayOverride(node.path);
if (ov.mode != mode) {
ov.mode = mode;
PersistPinDisplayState(node.path);
}
}
}
uintptr_t MaterialEditorPanel::EffectivePinId(const pxr::SdfPath& nodePath, const std::string& pinName, bool isOutput) const {
auto it = m_pinDisplayOverrides.find(nodePath.GetString());
if (it != m_pinDisplayOverrides.end() && it->second.mode == PinDisplayMode::Collapsed)
+11
View File
@@ -169,6 +169,8 @@ private:
bool IsPinVisible(const ShaderGraphNode& node, const std::string& pinName, bool isOutput) const;
/// Cycles a node's display mode: All -> Connected -> Collapsed -> All.
void CyclePinDisplayMode(const pxr::SdfPath& nodePath);
/// Sets every node currently in m_graph to mode (toolbar "set all" button).
void ApplyPinDisplayModeToAllNodes(PinDisplayMode mode);
/// Authors the node's current PinDisplayOverride into USD customData via
/// an undoable command, mirroring PersistNodePosition. No-op unless
/// m_keepGraphNodeViewSettingsInUsd is set.
@@ -249,6 +251,15 @@ private:
bool m_openPinPickMenu = false;
PendingPinPick m_pendingPinPick;
/// Toolbar toggle: whether node thumbnails (texture images / material
/// shader-ball renders) are shown at all. Off skips the reserved space
/// and the decode/render work entirely, not just the visual.
bool m_showNodeThumbnails = true;
/// Toolbar "set all nodes" pin-display button: the mode last broadcast
/// to every node (and the icon/tooltip shown), independent of what any
/// individual node is actually in after the user tweaks one by hand.
PinDisplayMode m_toolbarPinDisplayMode = PinDisplayMode::All;
/// Column widths of the browser (left) and preview (right) sections,
/// user-adjustable via the splitters between them and the canvas.
float m_browserWidth = 220.0f;