Skip to content

Commit

Permalink
Demo: Reworked "Property Editor" demo in a manner that more ressemble…
Browse files Browse the repository at this point in the history
… the tree data and struct description data that a real application would want to use.
  • Loading branch information
ocornut committed Jul 15, 2024
1 parent bc9e5b6 commit 46691d1
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 43 deletions.
2 changes: 2 additions & 0 deletions docs/CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ Other changes:
Debug log entries add a imgui frame counter prefix + are redirected to ShowDebugLogWindow() and
other configurable locations. Always call IMGUI_DEBUG_LOG() for maximum stripping in caller code.
- Debug Tools: Debug Log: Added "Configure Outputs.." button. (#5855)
- Demo: Reworked "Property Editor" demo in a manner that more ressemble the tree data and
struct description data that a real application would want to use.
- Backends: Win32: Fixed ImGuiMod_Super being mapped to VK_APPS instead of VK_LWIN||VK_RWIN.
(#7768, #4858, #2622) [@Aemony]
- Backends: SDL3: Update for API changes: SDLK_x renames and SDLK_KP_x removals (#7761, #7762)
Expand Down
182 changes: 139 additions & 43 deletions imgui_demo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7749,53 +7749,162 @@ static void ShowExampleAppLayout(bool* p_open)
// [SECTION] Example App: Property Editor / ShowExampleAppPropertyEditor()
//-----------------------------------------------------------------------------

static void ShowPlaceholderObject(const char* prefix, int uid)
// Simple representation for a tree
// (this is designed to be simple to understand for our demos, not to be efficient etc.)
struct ExampleTreeNode
{
// Use object uid as identifier. Most commonly you could also use the object pointer as a base ID.
ImGui::PushID(uid);
char Name[28];
ImGuiID UID = 0;
ExampleTreeNode* Parent = NULL;
ImVector<ExampleTreeNode*> Childs;

// Data
bool HasData = false; // All leaves have data
bool DataIsEnabled = false;
int DataInt = 128;
ImVec2 DataVec2 = ImVec2(0.0f, 3.141592f);
};

// Simple representation of struct metadata/serialization data.
// (this is a minimal version of what a typical advanced application may provide)
struct ExampleMemberInfo
{
const char* Name;
ImGuiDataType DataType;
int DataCount;
int Offset;
};

// Metadata description of ExampleTreeNode struct.
static const ExampleMemberInfo ExampleTreeNodeMemberInfos[]
{
{ "Enabled", ImGuiDataType_Bool, 1, offsetof(ExampleTreeNode, DataIsEnabled) },
{ "MyInt", ImGuiDataType_S32, 1, offsetof(ExampleTreeNode, DataInt) },
{ "MyVec2", ImGuiDataType_Float, 2, offsetof(ExampleTreeNode, DataVec2) },
};

static ExampleTreeNode* ExampleTree_CreateNode(const char* name, const ImGuiID uid, ExampleTreeNode* parent)
{
ExampleTreeNode* node = IM_NEW(ExampleTreeNode);
snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name);
node->UID = uid;
node->Parent = parent;
if (parent)
parent->Childs.push_back(node);
return node;
}

// Text and Tree nodes are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing to make the tree lines equal high.
static ExampleTreeNode* ExampleTree_CreateDemoTree()
{
static const char* root_names[] = { "Apple", "Banana", "Cherry", "Kiwi", "Mango", "Orange", "Pineapple", "Strawberry", "Watermelon" };
char name_buf[32];
ImGuiID uid = 0;
ExampleTreeNode* node_L0 = ExampleTree_CreateNode("<ROOT>", ++uid, NULL);
for (int idx_L0 = 0; idx_L0 < IM_ARRAYSIZE(root_names) * 2; idx_L0++)
{
snprintf(name_buf, 32, "%s %d", root_names[idx_L0 / 2], idx_L0 % 2);
ExampleTreeNode* node_L1 = ExampleTree_CreateNode(name_buf, ++uid, node_L0);
const int number_of_childs = (int)strlen(node_L1->Name);
for (int idx_L1 = 0; idx_L1 < number_of_childs; idx_L1++)
{
snprintf(name_buf, 32, "Child %d", idx_L1);
ExampleTreeNode* node_L2 = ExampleTree_CreateNode(name_buf, ++uid, node_L1);
node_L2->HasData = true;
if (idx_L1 == 0)
{
snprintf(name_buf, 32, "Sub-child %d", 0);
ExampleTreeNode* node_L3 = ExampleTree_CreateNode(name_buf, ++uid, node_L2);
node_L3->HasData = true;
}
}
}
return node_L0;
}

static void PropertyEditor_ShowTreeNode(ExampleTreeNode* node)
{
// Object tree node
ImGui::PushID((int)node->UID);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
bool node_open = ImGui::TreeNode("##Object", "%s_%u", prefix, uid);
ImGuiTreeNodeFlags tree_flags = ImGuiTreeNodeFlags_None;
tree_flags |= ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_AllowOverlap; // Highlight whole row for visibility
tree_flags |= ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; // Standard opening mode as we are likely to want to add selection afterwards
tree_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Left arrow support
bool node_open = ImGui::TreeNodeEx("##Object", tree_flags, "%s", node->Name);
ImGui::TableSetColumnIndex(1);
ImGui::Text("my sailor is rich");
ImGui::TextDisabled("UID: 0x%08X", node->UID);

// Display child and data
if (node_open)
for (ExampleTreeNode* child : node->Childs)
PropertyEditor_ShowTreeNode(child);
if (node_open && node->HasData)
{
static float placeholder_members[8] = { 0.0f, 0.0f, 1.0f, 3.1416f, 100.0f, 999.0f };
for (int i = 0; i < 8; i++)
// In a typical application, the structure description would be derived from a data-driven system.
// - We try to mimic this with our ExampleMemberInfo structure and the ExampleTreeNodeMemberInfos[] array.
// - Limits and some details are hard-coded to simplify the demo.
// - Text and Selectable are less high than framed widgets, using AlignTextToFramePadding() we add vertical spacing to make the selectable lines equal high.
for (const ExampleMemberInfo& field_desc : ExampleTreeNodeMemberInfos)
{
ImGui::PushID(i); // Use field index as identifier.
if (i < 2)
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGui::PushTabStop(false); // We didn't expose ImGuiItemFlags_NoNav yet, so arrow navigation will still pass through this
ImGui::Selectable(field_desc.Name, false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
ImGui::PopTabStop();
ImGui::TableSetColumnIndex(1);
ImGui::PushID(field_desc.Name);
void* field_ptr = (void*)(((unsigned char*)node) + field_desc.Offset);
switch (field_desc.DataType)
{
ShowPlaceholderObject("Child", 424242);
case ImGuiDataType_Bool:
{
IM_ASSERT(field_desc.DataCount == 1);
ImGui::Checkbox("##Editor", (bool*)field_ptr);
break;
}
else
case ImGuiDataType_S32:
{
// Here we use a TreeNode to highlight on hover (we could use e.g. Selectable as well)
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::AlignTextToFramePadding();
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Bullet;
ImGui::TreeNodeEx("Field", flags, "Field_%d", i);

ImGui::TableSetColumnIndex(1);
int v_min = INT_MIN, v_max = INT_MAX;
ImGui::SetNextItemWidth(-FLT_MIN);
if (i >= 5)
ImGui::InputFloat("##value", &placeholder_members[i], 1.0f);
else
ImGui::DragFloat("##value", &placeholder_members[i], 0.01f);
ImGui::NextColumn();
ImGui::DragScalarN("##Editor", field_desc.DataType, field_ptr, field_desc.DataCount, 1.0f, &v_min, &v_max);
break;
}
case ImGuiDataType_Float:
{
float v_min = 0.0f, v_max = 1.0f;
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::SliderScalarN("##Editor", field_desc.DataType, field_ptr, field_desc.DataCount, &v_min, &v_max);
break;
}
}
ImGui::PopID();
}
ImGui::TreePop();
}
if (node_open)
ImGui::TreePop();
ImGui::PopID();
}

static void PropertyEditor_ShowTree(ExampleTreeNode* root_node)
{
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
ImGuiTableFlags table_flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg;
if (ImGui::BeginTable("##split", 2, table_flags))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("Object", ImGuiTableColumnFlags_WidthStretch, 1.0f);
ImGui::TableSetupColumn("Contents", ImGuiTableColumnFlags_WidthStretch, 2.0f); // Default twice larger
ImGui::TableHeadersRow();
for (ExampleTreeNode* node : root_node->Childs)
PropertyEditor_ShowTreeNode(node);
ImGui::EndTable();
}
ImGui::PopStyleVar();
}

// Demonstrate create a simple property editor.
// This demo is a bit lackluster nowadays, would be nice to improve.
static void ShowExampleAppPropertyEditor(bool* p_open)
Expand All @@ -7808,25 +7917,12 @@ static void ShowExampleAppPropertyEditor(bool* p_open)
}

IMGUI_DEMO_MARKER("Examples/Property Editor");
HelpMarker(
"This example shows how you may implement a property editor using two columns.\n"
"All objects/fields data are dummies here.\n");
static ExampleTreeNode* tree_data = NULL;
if (tree_data == NULL)
tree_data = ExampleTree_CreateDemoTree();

ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
if (ImGui::BeginTable("##split", 2, ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("Object");
ImGui::TableSetupColumn("Contents");
ImGui::TableHeadersRow();

// Iterate placeholder objects (all the same data)
for (int obj_i = 0; obj_i < 4; obj_i++)
ShowPlaceholderObject("Object", obj_i);
PropertyEditor_ShowTree(tree_data);

ImGui::EndTable();
}
ImGui::PopStyleVar();
ImGui::End();
}

Expand Down

0 comments on commit 46691d1

Please sign in to comment.