Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I add a separator line for dragging and dropping selectables? #7803

Closed
TheSlugInTub opened this issue Jul 19, 2024 · 2 comments
Closed
Labels
drag drop drag and drop

Comments

@TheSlugInTub
Copy link

Version/Branch of Dear ImGui:

docking

Back-ends:

imgui_impl_opengl3.cpp + imgui_impl_glfw.cpp

Compiler, OS:

Windows 10 + MSVC

Full config/build information:

No response

Details:

I am trying to make a list of objects, and you can drag and drop them around to change their position in the std::vector of objects.
This is my code for doing this:

    for (size_t i = 0; i < objects.size(); ++i) {
        auto& object = objects[i];
        ImGui::PushID(static_cast<int>(i));

        bool selected = selectedObjectIndex == static_cast<int>(i);
        if (ImGui::Selectable(objects[i]->name.c_str(), selected)) {
            selectedObjectIndex = static_cast<int>(i);
        }

        if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) {
            ImGui::SetDragDropPayload("DND_DEMO_CELL", &i, sizeof(size_t));
            ImGui::Text("Dragging Object %d", i);
            ImGui::EndDragDropSource();
        }

        if (ImGui::BeginDragDropTarget()) {
            if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_DEMO_CELL")) {
                IM_ASSERT(payload->DataSize == sizeof(size_t));
                size_t payload_n = *(const size_t*)payload->Data;
                if (payload_n != i) {
                    std::swap(objects[payload_n], objects[i]);
                    if (selectedObjectIndex == static_cast<int>(i))
                        selectedObjectIndex = static_cast<int>(payload_n);
                    else if (selectedObjectIndex == static_cast<int>(payload_n))
                        selectedObjectIndex = static_cast<int>(i);
                }
            }
            ImGui::EndDragDropTarget();
        }

        ImGui::PopID();
    }

    ImGui::End();

Currently, I have to drag object selectables into each other, but I want there to be a line between the objects that is invisible and acts as a drag target that I can drag the object selectables into. But how would I do that?

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

No response

@ocornut ocornut added backends drag drop drag and drop and removed backends labels Jul 19, 2024
@ocornut
Copy link
Owner

ocornut commented Jul 19, 2024

(A)
One solution may be to call GetDragDropPayload() to see if there is a payload, then IsDataType() on said payload to check the type, and when you have the right payload you may need to submit dummy "in-between" items between your Selectables.

(B)
Another alternative which may be simpler is:

  • use existing Selectable() as regular drop target, but call AcceptDragDropPayload() with the ImGuiDragDropFlags_AcceptNoDrawDefaultRect flag.
  • arbitrary decide that near the top and bottom edge of each selectable, based on mouse position.
  • based on this decision draw a rect or a separating line.
  • based on this decision during the drop you can decide where to move the item.

Also see #2823

@GamingMinds-DanielC
Copy link
Contributor

GamingMinds-DanielC commented Jul 19, 2024

My implementation for a scene outliner had to solve the same problem. Maybe my solution will help you, it has this basic structure:

Outliner window
  local menu
  filter(s)
  child window acting as container for drop targets
    tree view items for scene nodes
      drop source per tree view item
      drop target per tree view item
      construct a drop rect for insertion spanning the width of the item and the vertical gap to the previous item
      if mouse is in that rect (between items), memorize item as "drop before", also memorize drop rect
    child window for scene root with single empty line to act as a placeholder
      after child window is closed, use as drop target to drop into scene root
    after container child window is closed, use as drop target if a "drop before" item is marked

The drop target for the child window that contains basically everything is the most interesting part. That is where I draw the insertion line manually like this:

		// dropBeforeItem  = memorized item for reordering
		// dropRectMin/Max = x spans dropBeforeItem (with same indentation),
		//                   y spans space between previous item and dropBeforeItem

		// handle dropping items in front of other items
		if ( ( dropBeforeItem != nullptr ) && ImGui::BeginDragDropTarget() )
		{
			ImGuiDragDropFlags flags = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect;

			if ( const ImGuiPayload* payload = ImGui::AcceptDragDropPayload( Outliner_Payload, flags ) )
			{
				ImDrawList* drawList = ImGui::GetWindowDrawList();

				dropRectMin.y = dropRectMax.y = 0.5f * ( dropRectMin.y + dropRectMax.y );
				drawList->AddLine( dropRectMin, dropRectMax, ImGui::GetColorU32( ImGuiCol_DragDropTarget ), 3.5f );

				if ( payload->IsDelivery() )
				{
					// handle actual editor specific stuff
					// - filter circular dependencies
					// - make selected items children of parent of dropBeforeItem
					// - reorder selected items to be before dropBeforeItem
					// - record all changes for the undo/redo system
				}
			}

			ImGui::EndDragDropTarget();
		}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
drag drop drag and drop
Projects
None yet
Development

No branches or pull requests

3 participants