Property panel: token[] view/edit, uniform row heights
- Add token[] attribute display: inline InputText (or Combo when allowedTokens metadata is present), collapsible [N tokens] tree for arrays with up to 32 editable entries - Force all property table rows to GetFrameHeight() minimum via TableNextRow min_row_height so text-only and widget rows match - Add AlignTextToFramePadding() before every TextUnformatted / TextDisabled / TextColored in cols 0-2 so short text sits centred in the uniform-height row rather than pinned to the top - Wrap matrix attributes in TreeNodeEx [NxN] so they collapse to a single-height row; relationship targets use inline text / collapsible tree instead of a variable-height ListBox Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+105
-14
@@ -550,6 +550,7 @@ static void DrawPropertyMiniButton(const char* label, bool authored,
|
|||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Text, btnColor);
|
ImGui::PushStyleColor(ImGuiCol_Text, btnColor);
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.f, 0.f, 0.f, 0.f));
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::SmallButton(label);
|
ImGui::SmallButton(label);
|
||||||
ImGui::PopStyleColor(2);
|
ImGui::PopStyleColor(2);
|
||||||
|
|
||||||
@@ -841,19 +842,28 @@ static pxr::VtValue DrawVtValueWidget(pxr::UsdAttribute& attr,
|
|||||||
return VtValue();
|
return VtValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Matrices (multi-row, read-write) ----
|
// ---- Matrices: collapsed single row, expand to edit ----
|
||||||
auto drawMatrix = [](const char* id, ImGuiDataType dt, void* data, int rows, int cols) -> bool {
|
// Render rows inside a TreeNode so the table row stays one line tall
|
||||||
|
// when collapsed. Expanded rows are user-initiated so taller is fine.
|
||||||
|
auto drawMatrix = [](const char* id, ImGuiDataType dt, void* data,
|
||||||
|
int rows, int cols) -> bool {
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
char hdr[24]; snprintf(hdr, sizeof(hdr), "[%dx%d]##%s", rows, cols, id);
|
||||||
|
bool open = ImGui::TreeNodeEx(hdr,
|
||||||
|
ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_SpanAvailWidth);
|
||||||
|
if (open) {
|
||||||
ImGui::PushID(id);
|
ImGui::PushID(id);
|
||||||
for (int r = 0; r < rows; ++r) {
|
for (int r = 0; r < rows; ++r) {
|
||||||
char rowId[8]; snprintf(rowId, sizeof(rowId), "##r%d", r);
|
char rowId[8]; snprintf(rowId, sizeof(rowId), "##r%d", r);
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
ImGui::InputScalarN(rowId, dt,
|
ImGui::InputScalarN(rowId, dt,
|
||||||
static_cast<char*>(data) + r * cols * (dt == ImGuiDataType_Double ? 8 : 4),
|
static_cast<char*>(data) + r * cols * (dt == ImGuiDataType_Double ? 8 : 4),
|
||||||
cols, nullptr, nullptr,
|
cols, nullptr, nullptr, "%.4f");
|
||||||
dt == ImGuiDataType_Double ? "%.4f" : "%.4f");
|
|
||||||
changed |= ImGui::IsItemDeactivatedAfterEdit();
|
changed |= ImGui::IsItemDeactivatedAfterEdit();
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
return changed;
|
return changed;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -890,29 +900,92 @@ static pxr::VtValue DrawVtValueWidget(pxr::UsdAttribute& attr,
|
|||||||
|
|
||||||
// ---- Token ----
|
// ---- Token ----
|
||||||
if (val.IsHolding<TfToken>()) {
|
if (val.IsHolding<TfToken>()) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextUnformatted(val.UncheckedGet<TfToken>().GetText());
|
ImGui::TextUnformatted(val.UncheckedGet<TfToken>().GetText());
|
||||||
return VtValue();
|
return VtValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- String / AssetPath ----
|
// ---- String / AssetPath ----
|
||||||
if (val.IsHolding<std::string>()) {
|
if (val.IsHolding<std::string>()) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextUnformatted(val.UncheckedGet<std::string>().c_str());
|
ImGui::TextUnformatted(val.UncheckedGet<std::string>().c_str());
|
||||||
return VtValue();
|
return VtValue();
|
||||||
}
|
}
|
||||||
if (val.IsHolding<SdfAssetPath>()) {
|
if (val.IsHolding<SdfAssetPath>()) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextUnformatted(val.UncheckedGet<SdfAssetPath>().GetAssetPath().c_str());
|
ImGui::TextUnformatted(val.UncheckedGet<SdfAssetPath>().GetAssetPath().c_str());
|
||||||
return VtValue();
|
return VtValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Token array (editable inline) ----
|
// ---- Token array: view + edit ----
|
||||||
if (val.IsHolding<VtArray<TfToken>>()) {
|
if (val.IsHolding<VtArray<TfToken>>()) {
|
||||||
const auto& arr = val.UncheckedGet<VtArray<TfToken>>();
|
VtArray<TfToken> arr = val.UncheckedGet<VtArray<TfToken>>();
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
// Optional allowedTokens → show Combo per element instead of InputText.
|
||||||
|
VtValue allowedMeta;
|
||||||
|
attr.GetMetadata(TfToken("allowedTokens"), &allowedMeta);
|
||||||
|
const VtArray<TfToken>* allowed =
|
||||||
|
(!allowedMeta.IsEmpty() && allowedMeta.IsHolding<VtArray<TfToken>>())
|
||||||
|
? &allowedMeta.UncheckedGet<VtArray<TfToken>>()
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
|
// Widget for one element (combo or free-text).
|
||||||
|
auto drawOne = [&](int idx) {
|
||||||
|
ImGui::PushID(idx);
|
||||||
|
if (allowed) {
|
||||||
|
std::string cur = arr[idx].GetString();
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
if (ImGui::BeginCombo("##tc", cur.c_str())) {
|
||||||
|
for (const auto& tok : *allowed) {
|
||||||
|
bool sel = (tok == arr[idx]);
|
||||||
|
if (ImGui::Selectable(tok.GetText(), sel))
|
||||||
|
{ arr[idx] = tok; changed = true; }
|
||||||
|
if (sel) ImGui::SetItemDefaultFocus();
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char buf[256];
|
||||||
|
snprintf(buf, sizeof(buf), "%s", arr[idx].GetText());
|
||||||
|
ImGui::SetNextItemWidth(-FLT_MIN);
|
||||||
|
ImGui::InputText("##te", buf, sizeof(buf));
|
||||||
|
if (ImGui::IsItemDeactivatedAfterEdit())
|
||||||
|
{ arr[idx] = TfToken(buf); changed = true; }
|
||||||
|
}
|
||||||
|
ImGui::PopID();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (arr.empty()) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextDisabled("[empty]");
|
||||||
|
} else if (arr.size() == 1) {
|
||||||
|
drawOne(0);
|
||||||
|
} else {
|
||||||
|
bool open = ImGui::TreeNodeEx("##tarr",
|
||||||
|
ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_SpanAvailWidth);
|
||||||
|
ImGui::SameLine(0, 6);
|
||||||
ImGui::TextDisabled("[%zu tokens]", arr.size());
|
ImGui::TextDisabled("[%zu tokens]", arr.size());
|
||||||
|
if (open) {
|
||||||
|
static constexpr size_t kLimit = 32;
|
||||||
|
size_t lim = std::min(arr.size(), kLimit);
|
||||||
|
for (size_t i = 0; i < lim; ++i) {
|
||||||
|
ImGui::Text("[%d]", (int)i);
|
||||||
|
ImGui::SameLine();
|
||||||
|
drawOne((int)i);
|
||||||
|
}
|
||||||
|
if (arr.size() > kLimit)
|
||||||
|
ImGui::TextDisabled(" ... %zu more", arr.size() - kLimit);
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) return VtValue(arr);
|
||||||
return VtValue();
|
return VtValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Large arrays: just show size ----
|
// ---- Large arrays: just show size ----
|
||||||
if (val.IsArrayValued() && val.GetArraySize() > 5) {
|
if (val.IsArrayValued() && val.GetArraySize() > 5) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextDisabled("[array %zu]", val.GetArraySize());
|
ImGui::TextDisabled("[array %zu]", val.GetArraySize());
|
||||||
return VtValue();
|
return VtValue();
|
||||||
}
|
}
|
||||||
@@ -923,6 +996,7 @@ static pxr::VtValue DrawVtValueWidget(pxr::UsdAttribute& attr,
|
|||||||
oss << val;
|
oss << val;
|
||||||
std::string s = oss.str();
|
std::string s = oss.str();
|
||||||
if (s.size() > 120) { s.resize(120); s += " ..."; }
|
if (s.size() > 120) { s.resize(120); s += " ..."; }
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextUnformatted(s.c_str());
|
ImGui::TextUnformatted(s.c_str());
|
||||||
}
|
}
|
||||||
return VtValue();
|
return VtValue();
|
||||||
@@ -1056,11 +1130,12 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
|
|||||||
// No TableHeadersRow – matches usdtweak "no header" intent
|
// No TableHeadersRow – matches usdtweak "no header" intent
|
||||||
|
|
||||||
const pxr::UsdEditTarget& editTarget = prim.GetStage()->GetEditTarget();
|
const pxr::UsdEditTarget& editTarget = prim.GetStage()->GetEditTarget();
|
||||||
|
const float rowH = ImGui::GetFrameHeight();
|
||||||
int uid = 0;
|
int uid = 0;
|
||||||
|
|
||||||
// ---- Attributes (mirrors usdtweak attribute loop) ----
|
// ---- Attributes (mirrors usdtweak attribute loop) ----
|
||||||
for (auto& attr : prim.GetAttributes()) {
|
for (auto& attr : prim.GetAttributes()) {
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH);
|
||||||
|
|
||||||
// Col 0 – mini button "(a)"
|
// Col 0 – mini button "(a)"
|
||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
@@ -1073,6 +1148,7 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
|
|||||||
|
|
||||||
// Col 1 – display name (namespace:basename)
|
// Col 1 – display name (namespace:basename)
|
||||||
ImGui::TableSetColumnIndex(1);
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextUnformatted(GetPropDisplayName(attr).c_str());
|
ImGui::TextUnformatted(GetPropDisplayName(attr).c_str());
|
||||||
|
|
||||||
// Col 2 – value widget with PushItemWidth(-FLT_MIN) (label hidden)
|
// Col 2 – value widget with PushItemWidth(-FLT_MIN) (label hidden)
|
||||||
@@ -1109,9 +1185,12 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
|
|||||||
} else if (attr.HasAuthoredConnections()) {
|
} else if (attr.HasAuthoredConnections()) {
|
||||||
SdfPathVector conns;
|
SdfPathVector conns;
|
||||||
attr.GetConnections(&conns);
|
attr.GetConnections(&conns);
|
||||||
if (!conns.empty())
|
if (!conns.empty()) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextDisabled("-> %s", conns[0].GetString().c_str());
|
ImGui::TextDisabled("-> %s", conns[0].GetString().c_str());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextDisabled("no value");
|
ImGui::TextDisabled("no value");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1121,7 +1200,7 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
|
|||||||
|
|
||||||
// ---- Relationships (mirrors usdtweak relationship loop) ----
|
// ---- Relationships (mirrors usdtweak relationship loop) ----
|
||||||
for (const auto& rel : prim.GetRelationships()) {
|
for (const auto& rel : prim.GetRelationships()) {
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow(ImGuiTableRowFlags_None, rowH);
|
||||||
|
|
||||||
// Col 0 – mini button "(r)"
|
// Col 0 – mini button "(r)"
|
||||||
ImGui::TableSetColumnIndex(0);
|
ImGui::TableSetColumnIndex(0);
|
||||||
@@ -1136,24 +1215,36 @@ void PropertyPanel::RenderPropertiesTable(const pxr::UsdPrim& prim) {
|
|||||||
ImVec4 relColor = rel.IsAuthored()
|
ImVec4 relColor = rel.IsAuthored()
|
||||||
? ImVec4(0.6f, 0.9f, 1.f, 1.f)
|
? ImVec4(0.6f, 0.9f, 1.f, 1.f)
|
||||||
: ImVec4(0.5f, 0.5f, 0.5f, 1.f);
|
: ImVec4(0.5f, 0.5f, 0.5f, 1.f);
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextColored(relColor, "%s", GetPropDisplayName(rel).c_str());
|
ImGui::TextColored(relColor, "%s", GetPropDisplayName(rel).c_str());
|
||||||
|
|
||||||
// Col 2 – target list (mirrors DrawUsdRelationshipList in usdtweak)
|
// Col 2 – target list: single row when collapsed, expand for multiple
|
||||||
ImGui::TableSetColumnIndex(2);
|
ImGui::TableSetColumnIndex(2);
|
||||||
SdfPathVector targets;
|
SdfPathVector targets;
|
||||||
rel.GetTargets(&targets);
|
rel.GetTargets(&targets);
|
||||||
if (targets.empty()) {
|
if (targets.empty()) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
ImGui::TextDisabled("no targets");
|
ImGui::TextDisabled("no targets");
|
||||||
|
} else if (targets.size() == 1) {
|
||||||
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextDisabled("%s", targets[0].GetString().c_str());
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("%s", targets[0].GetString().c_str());
|
||||||
} else {
|
} else {
|
||||||
ImGui::PushID(rel.GetPath().GetString().c_str());
|
ImGui::PushID(rel.GetPath().GetString().c_str());
|
||||||
float listH = static_cast<float>(targets.size()) * 25.f;
|
bool open = ImGui::TreeNodeEx("##rtarr",
|
||||||
if (ImGui::BeginListBox("##rl", ImVec2(-FLT_MIN, listH))) {
|
ImGuiTreeNodeFlags_AllowOverlap | ImGuiTreeNodeFlags_SpanAvailWidth);
|
||||||
|
ImGui::SameLine(0, 6);
|
||||||
|
ImGui::TextDisabled("[%zu targets]", targets.size());
|
||||||
|
if (open) {
|
||||||
for (const auto& path : targets) {
|
for (const auto& path : targets) {
|
||||||
ImGui::PushID(path.GetString().c_str());
|
ImGui::PushID(path.GetString().c_str());
|
||||||
ImGui::TextUnformatted(path.GetString().c_str());
|
ImGui::TextDisabled("%s", path.GetString().c_str());
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("%s", path.GetString().c_str());
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
ImGui::EndListBox();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user