mirror of
https://github.com/ocornut/imgui.git
synced 2026-01-17 01:04:19 +00:00
Merge branch 'master' into docking
This commit is contained in:
commit
6df1a06fc3
29 changed files with 393 additions and 137 deletions
239
imgui_demo.cpp
239
imgui_demo.cpp
|
|
@ -274,13 +274,14 @@ void* GImGuiDemoMarkerCallbackUserData = NULL;
|
|||
struct ExampleTreeNode
|
||||
{
|
||||
// Tree structure
|
||||
char Name[28];
|
||||
char Name[28] = "";
|
||||
int UID = 0;
|
||||
ExampleTreeNode* Parent = NULL;
|
||||
ImVector<ExampleTreeNode*> Childs;
|
||||
unsigned short IndexInParent = 0; // Maintaining this allows us to implement linear traversal more easily
|
||||
|
||||
// Leaf Data
|
||||
bool HasData = false; // All leaves have data
|
||||
bool HasData = false; // All leaves have data
|
||||
bool DataMyBool = true;
|
||||
int DataMyInt = 128;
|
||||
ImVec2 DataMyVec2 = ImVec2(0.0f, 3.141592f);
|
||||
|
|
@ -310,6 +311,7 @@ static ExampleTreeNode* ExampleTree_CreateNode(const char* name, int uid, Exampl
|
|||
snprintf(node->Name, IM_ARRAYSIZE(node->Name), "%s", name);
|
||||
node->UID = uid;
|
||||
node->Parent = parent;
|
||||
node->IndexInParent = parent ? (unsigned short)parent->Childs.Size : 0;
|
||||
if (parent)
|
||||
parent->Childs.push_back(node);
|
||||
return node;
|
||||
|
|
@ -1417,18 +1419,18 @@ static void ShowDemoWindowWidgets(DemoWindowData* demo_data)
|
|||
// (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively
|
||||
// stored in the object itself, etc.)
|
||||
const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
|
||||
static int item_current_idx = 0; // Here we store our selection data as an index.
|
||||
static int item_selected_idx = 0; // Here we store our selection data as an index.
|
||||
|
||||
// Pass in the preview value visible before opening the combo (it could technically be different contents or not pulled from items[])
|
||||
const char* combo_preview_value = items[item_current_idx];
|
||||
const char* combo_preview_value = items[item_selected_idx];
|
||||
|
||||
if (ImGui::BeginCombo("combo 1", combo_preview_value, flags))
|
||||
{
|
||||
for (int n = 0; n < IM_ARRAYSIZE(items); n++)
|
||||
{
|
||||
const bool is_selected = (item_current_idx == n);
|
||||
const bool is_selected = (item_selected_idx == n);
|
||||
if (ImGui::Selectable(items[n], is_selected))
|
||||
item_current_idx = n;
|
||||
item_selected_idx = n;
|
||||
|
||||
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
|
||||
if (is_selected)
|
||||
|
|
@ -1470,14 +1472,22 @@ static void ShowDemoWindowWidgets(DemoWindowData* demo_data)
|
|||
// (your selection data could be an index, a pointer to the object, an id for the object, a flag intrusively
|
||||
// stored in the object itself, etc.)
|
||||
const char* items[] = { "AAAA", "BBBB", "CCCC", "DDDD", "EEEE", "FFFF", "GGGG", "HHHH", "IIII", "JJJJ", "KKKK", "LLLLLLL", "MMMM", "OOOOOOO" };
|
||||
static int item_current_idx = 0; // Here we store our selection data as an index.
|
||||
static int item_selected_idx = 0; // Here we store our selected data as an index.
|
||||
|
||||
static bool item_highlight = false;
|
||||
int item_highlighted_idx = -1; // Here we store our highlighted data as an index.
|
||||
ImGui::Checkbox("Highlight hovered item in second listbox", &item_highlight);
|
||||
|
||||
if (ImGui::BeginListBox("listbox 1"))
|
||||
{
|
||||
for (int n = 0; n < IM_ARRAYSIZE(items); n++)
|
||||
{
|
||||
const bool is_selected = (item_current_idx == n);
|
||||
const bool is_selected = (item_selected_idx == n);
|
||||
if (ImGui::Selectable(items[n], is_selected))
|
||||
item_current_idx = n;
|
||||
item_selected_idx = n;
|
||||
|
||||
if (item_highlight && ImGui::IsItemHovered())
|
||||
item_highlighted_idx = n;
|
||||
|
||||
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
|
||||
if (is_selected)
|
||||
|
|
@ -1493,9 +1503,10 @@ static void ShowDemoWindowWidgets(DemoWindowData* demo_data)
|
|||
{
|
||||
for (int n = 0; n < IM_ARRAYSIZE(items); n++)
|
||||
{
|
||||
const bool is_selected = (item_current_idx == n);
|
||||
if (ImGui::Selectable(items[n], is_selected))
|
||||
item_current_idx = n;
|
||||
bool is_selected = (item_selected_idx == n);
|
||||
ImGuiSelectableFlags flags = (item_highlighted_idx == n) ? ImGuiSelectableFlags_Highlight : 0;
|
||||
if (ImGui::Selectable(items[n], is_selected, flags))
|
||||
item_selected_idx = n;
|
||||
|
||||
// Set the initial focus when opening the combo (scrolling + keyboard navigation focus)
|
||||
if (is_selected)
|
||||
|
|
@ -1542,8 +1553,8 @@ static void ShowDemoWindowWidgets(DemoWindowData* demo_data)
|
|||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
IMGUI_DEMO_MARKER("Widgets/Selectables/In columns");
|
||||
if (ImGui::TreeNode("In columns"))
|
||||
IMGUI_DEMO_MARKER("Widgets/Selectables/In Tables");
|
||||
if (ImGui::TreeNode("In Tables"))
|
||||
{
|
||||
static bool selected[10] = {};
|
||||
|
||||
|
|
@ -3372,6 +3383,52 @@ static void ShowDemoWindowMultiSelect(DemoWindowData* demo_data)
|
|||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// Demonstrate using the clipper with BeginMultiSelect()/EndMultiSelect()
|
||||
IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (in a table)");
|
||||
if (ImGui::TreeNode("Multi-Select (in a table)"))
|
||||
{
|
||||
static ImGuiSelectionBasicStorage selection;
|
||||
|
||||
const int ITEMS_COUNT = 10000;
|
||||
ImGui::Text("Selection: %d/%d", selection.Size, ITEMS_COUNT);
|
||||
if (ImGui::BeginTable("##Basket", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter))
|
||||
{
|
||||
ImGui::TableSetupColumn("Object");
|
||||
ImGui::TableSetupColumn("Action");
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGuiMultiSelectFlags flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect1d;
|
||||
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(flags, selection.Size, ITEMS_COUNT);
|
||||
selection.ApplyRequests(ms_io);
|
||||
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin(ITEMS_COUNT);
|
||||
if (ms_io->RangeSrcItem != -1)
|
||||
clipper.IncludeItemByIndex((int)ms_io->RangeSrcItem); // Ensure RangeSrc item is not clipped.
|
||||
while (clipper.Step())
|
||||
{
|
||||
for (int n = clipper.DisplayStart; n < clipper.DisplayEnd; n++)
|
||||
{
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
char label[64];
|
||||
sprintf(label, "Object %05d: %s", n, ExampleNames[n % IM_ARRAYSIZE(ExampleNames)]);
|
||||
bool item_is_selected = selection.Contains((ImGuiID)n);
|
||||
ImGui::SetNextItemSelectionUserData(n);
|
||||
ImGui::Selectable(label, item_is_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::SmallButton("hello");
|
||||
}
|
||||
}
|
||||
|
||||
ms_io = ImGui::EndMultiSelect();
|
||||
selection.ApplyRequests(ms_io);
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (checkboxes)");
|
||||
if (ImGui::TreeNode("Multi-Select (checkboxes)"))
|
||||
{
|
||||
|
|
@ -3462,6 +3519,160 @@ static void ShowDemoWindowMultiSelect(DemoWindowData* demo_data)
|
|||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// Demonstrate supporting multiple-selection in a tree.
|
||||
// - We don't use linear indices for selection user data, but our ExampleTreeNode* pointer directly!
|
||||
// This showcase how SetNextItemSelectionUserData() never assume indices!
|
||||
// - The difficulty here is to "interpolate" from RangeSrcItem to RangeDstItem in the SetAll/SetRange request.
|
||||
// We want this interpolation to match what the user sees: in visible order, skipping closed nodes.
|
||||
// This is implemented by our TreeGetNextNodeInVisibleOrder() user-space helper.
|
||||
// - Important: In a real codebase aiming to implement full-featured selectable tree with custom filtering, you
|
||||
// are more likely to build an array mapping sequential indices to visible tree nodes, since your
|
||||
// filtering/search + clipping process will benefit from it. Having this will make this interpolation much easier.
|
||||
// - Consider this a prototype: we are working toward simplifying some of it.
|
||||
IMGUI_DEMO_MARKER("Widgets/Selection State/Multi-Select (trees)");
|
||||
if (ImGui::TreeNode("Multi-Select (trees)"))
|
||||
{
|
||||
HelpMarker(
|
||||
"This is rather advanced and experimental. If you are getting started with multi-select,"
|
||||
"please don't start by looking at how to use it for a tree!\n\n"
|
||||
"Future versions will try to simplify and formalize some of this.");
|
||||
|
||||
struct ExampleTreeFuncs
|
||||
{
|
||||
static void DrawNode(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection)
|
||||
{
|
||||
ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
tree_node_flags |= ImGuiTreeNodeFlags_NavLeftJumpsBackHere; // Enable pressing left to jump to parent
|
||||
if (node->Childs.Size == 0)
|
||||
tree_node_flags |= ImGuiTreeNodeFlags_Bullet | ImGuiTreeNodeFlags_Leaf;
|
||||
if (selection->Contains((ImGuiID)node->UID))
|
||||
tree_node_flags |= ImGuiTreeNodeFlags_Selected;
|
||||
|
||||
// Using SetNextItemStorageID() to specify storage id, so we can easily peek into
|
||||
// the storage holding open/close stage, using our TreeNodeGetOpen/TreeNodeSetOpen() functions.
|
||||
ImGui::SetNextItemSelectionUserData((ImGuiSelectionUserData)(intptr_t)node);
|
||||
ImGui::SetNextItemStorageID((ImGuiID)node->UID);
|
||||
if (ImGui::TreeNodeEx(node->Name, tree_node_flags))
|
||||
{
|
||||
for (ExampleTreeNode* child : node->Childs)
|
||||
DrawNode(child, selection);
|
||||
ImGui::TreePop();
|
||||
}
|
||||
else if (ImGui::IsItemToggledOpen())
|
||||
{
|
||||
TreeCloseAndUnselectChildNodes(node, selection);
|
||||
}
|
||||
}
|
||||
|
||||
static bool TreeNodeGetOpen(ExampleTreeNode* node)
|
||||
{
|
||||
return ImGui::GetStateStorage()->GetBool((ImGuiID)node->UID);
|
||||
}
|
||||
|
||||
static void TreeNodeSetOpen(ExampleTreeNode* node, bool open)
|
||||
{
|
||||
ImGui::GetStateStorage()->SetBool((ImGuiID)node->UID, open);
|
||||
}
|
||||
|
||||
// When closing a node: 1) close and unselect all child nodes, 2) select parent if any child was selected.
|
||||
// FIXME: This is currently handled by user logic but I'm hoping to eventually provide tree node
|
||||
// features to do this automatically, e.g. a ImGuiTreeNodeFlags_AutoCloseChildNodes etc.
|
||||
static int TreeCloseAndUnselectChildNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, int depth = 0)
|
||||
{
|
||||
// Recursive close (the test for depth == 0 is because we call this on a node that was just closed!)
|
||||
int unselected_count = selection->Contains((ImGuiID)node->UID) ? 1 : 0;
|
||||
if (depth == 0 || TreeNodeGetOpen(node))
|
||||
{
|
||||
for (ExampleTreeNode* child : node->Childs)
|
||||
unselected_count += TreeCloseAndUnselectChildNodes(child, selection, depth + 1);
|
||||
TreeNodeSetOpen(node, false);
|
||||
}
|
||||
|
||||
// Select root node if any of its child was selected, otherwise unselect
|
||||
selection->SetItemSelected((ImGuiID)node->UID, (depth == 0 && unselected_count > 0));
|
||||
return unselected_count;
|
||||
}
|
||||
|
||||
// Apply multi-selection requests
|
||||
static void ApplySelectionRequests(ImGuiMultiSelectIO* ms_io, ExampleTreeNode* tree, ImGuiSelectionBasicStorage* selection)
|
||||
{
|
||||
for (ImGuiSelectionRequest& req : ms_io->Requests)
|
||||
{
|
||||
if (req.Type == ImGuiSelectionRequestType_SetAll)
|
||||
{
|
||||
if (req.Selected)
|
||||
TreeSetAllInOpenNodes(tree, selection, req.Selected);
|
||||
else
|
||||
selection->Clear();
|
||||
}
|
||||
else if (req.Type == ImGuiSelectionRequestType_SetRange)
|
||||
{
|
||||
ExampleTreeNode* first_node = (ExampleTreeNode*)(intptr_t)req.RangeFirstItem;
|
||||
ExampleTreeNode* last_node = (ExampleTreeNode*)(intptr_t)req.RangeLastItem;
|
||||
for (ExampleTreeNode* node = first_node; node != NULL; node = TreeGetNextNodeInVisibleOrder(node, last_node))
|
||||
selection->SetItemSelected((ImGuiID)node->UID, req.Selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void TreeSetAllInOpenNodes(ExampleTreeNode* node, ImGuiSelectionBasicStorage* selection, bool selected)
|
||||
{
|
||||
if (node->Parent != NULL) // Root node isn't visible nor selectable in our scheme
|
||||
selection->SetItemSelected((ImGuiID)node->UID, selected);
|
||||
if (node->Parent == NULL || TreeNodeGetOpen(node))
|
||||
for (ExampleTreeNode* child : node->Childs)
|
||||
TreeSetAllInOpenNodes(child, selection, selected);
|
||||
}
|
||||
|
||||
// Interpolate in *user-visible order* AND only *over opened nodes*.
|
||||
// If you have a sequential mapping tables (e.g. generated after a filter/search pass) this would be simpler.
|
||||
// Here the tricks are that:
|
||||
// - we store/maintain ExampleTreeNode::IndexInParent which allows implementing a linear iterator easily, without searches, without recursion.
|
||||
// this could be replaced by a search in parent, aka 'int index_in_parent = curr_node->Parent->Childs.find_index(curr_node)'
|
||||
// which would only be called when crossing from child to a parent, aka not too much.
|
||||
// - we call SetNextItemStorageID() before our TreeNode() calls with an ID which doesn't relate to UI stack,
|
||||
// making it easier to call TreeNodeGetOpen()/TreeNodeSetOpen() from any location.
|
||||
static ExampleTreeNode* TreeGetNextNodeInVisibleOrder(ExampleTreeNode* curr_node, ExampleTreeNode* last_node)
|
||||
{
|
||||
// Reached last node
|
||||
if (curr_node == last_node)
|
||||
return NULL;
|
||||
|
||||
// Recurse into childs. Query storage to tell if the node is open.
|
||||
if (curr_node->Childs.Size > 0 && TreeNodeGetOpen(curr_node))
|
||||
return curr_node->Childs[0];
|
||||
|
||||
// Next sibling, then into our own parent
|
||||
while (curr_node->Parent != NULL)
|
||||
{
|
||||
if (curr_node->IndexInParent + 1 < curr_node->Parent->Childs.Size)
|
||||
return curr_node->Parent->Childs[curr_node->IndexInParent + 1];
|
||||
curr_node = curr_node->Parent;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}; // ExampleTreeFuncs
|
||||
|
||||
static ImGuiSelectionBasicStorage selection;
|
||||
static ExampleTreeNode* tree = ExampleTree_CreateDemoTree(); // Create tree once
|
||||
ImGui::Text("Selection size: %d", selection.Size);
|
||||
|
||||
if (ImGui::BeginChild("##Tree", ImVec2(-FLT_MIN, ImGui::GetFontSize() * 20), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_ResizeY))
|
||||
{
|
||||
ImGuiMultiSelectFlags ms_flags = ImGuiMultiSelectFlags_ClearOnEscape | ImGuiMultiSelectFlags_BoxSelect2d;
|
||||
ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ms_flags, selection.Size, -1);
|
||||
ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection);
|
||||
for (ExampleTreeNode* node : tree->Childs)
|
||||
ExampleTreeFuncs::DrawNode(node, &selection);
|
||||
ms_io = ImGui::EndMultiSelect();
|
||||
ExampleTreeFuncs::ApplySelectionRequests(ms_io, tree, &selection);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
// Advanced demonstration of BeginMultiSelect()
|
||||
// - Showcase clipping.
|
||||
// - Showcase deletion.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue