ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
60.61k stars 10.23k forks source link

Docking branch available for testing #2109

Open ocornut opened 6 years ago

ocornut commented 6 years ago

I have pushed an experimental 'docking' branch: https://github.com/ocornut/imgui/tree/docking Effectively providing a long awaited official solution to the features discussed in #351 and #261.

TL;DR; You can benefit from good portion of docking features with no extra work or API call. Windows can be merged with each others, with or without partitioning the space.

Prefer creating separate New Issues for specific questions/issues, so they can more easily be closed when solved and not clutter this thread (which is already too big)

Please do not report issues without reading the 'Feedback' section of the next post and without reading the 'Contributing' document listed in there. The number of ambiguous, ill-formed questions and incomplete bug reports posted on this entire github is difficult to deal with. Thank you.

image

GIF (this is actually a gif from an older version which has some glitches, but you get the gist!) 20180809_docking

Quick demo

You may checkout docking and build one the example e.g. example_win32_dx11/, example_glfw_opengl3, example_sdl_vulkan to test this. Head to Demo>Configuration to see the global configuration flags. Note that multi-viewports are enabled by default in those demo but only well tested under Windows. If you have issues with them you can disable them via the Demo>Configuration pane or by commenting out the line that sets ImGuiConfigFlags_ViewportsEnable in the main.cpp of your example. Docking is functional without multi-viewports enabled.

ocornut commented 6 years ago

This is my second version of Docking (I developed a V1 during the year which didn't get released, and which I have completely scrapped, then a rebuilt a V2 from stratch. V2 has issues but is already very useful.). The development of this feature has been sponsored by Blizzard Entertainment and supporters on Patreon.

The current branch scheme is docking > viewport > master. This is because docking is best used with multi-viewport enabled (also a Beta feature) and it is less maintenance for me to follow this arrangement. It is however possible that Docking features may eventually be merged into master before viewport features. Right now I am hoping that both Docking and Viewport would become stable and useful enough to constitute a 1.70 release, but that will depend on your feedback.

The basic design is based on what Visual Studio do. When creating an explicit DockSpace() node, there is a Central Node which is a node that doesn't fold even when empty (when nothing is docked into it). It also uses the remaining/available space.

I'm also working on various features to manipulate dock nodes programmatically allowing to replicate what Unreal Engine is doing (each top-level editor have its own layout, and layout are copied/forked on demand). Some of those features are currently only exposed in imgui_internal.h but will slowly make it into the public API (they currently need a bit of rework).

Preamble

UPDATING

FEEDBACK

CONFIGURATION

* the current default is a little unexpected and may be changed in the future. I would recommend that you try it before toggling the option.

DESIRABLE (NOT STRICTLY DOCKING) FEATURES TO ENABLE ALONG WITH DOCKING

_Note: All of this is enabled if you try one of the standard examples e.g. example_win32_directx11/, example_glfw_opengl3/, example_glfwvulkan/, etc. Those paragraphs are useful if you want to integrate docking in your own application with your own back-end.

1) Mouse cursors Your back-end needs to honor the resizing mouse cursors (ImGui::GetMouseCursor() returning ImGuiMouseCursor_ResizeNS / ImGuiMouseCursor_ResizeEW). Most default back-ends are honoring this already, but if you have implemented your custom back-end, it would be preferable you add support for this. Without those cursors changes you will have less feedback when hovering the small splitter between docking nodes. See the back-ends in examples/ for reference code. Your back-end needs to set io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; to notify imgui that this is supported.

2) Resizing windows from their edge. You'll probably also want to enable io.ConfigResizeWindowsFromEdges = true; to allow resizing windows from their edge, providing a more consistent experience between docked and floating windows. This is not currently enabled by default because of (1). When you hover the edge of a window, the most noticeable feedback is the change of mouse cursor. This will eventually be enabled by default when ImGuiBackendFlags_HasMouseCursors is set by the back-end.

3) Multi-Viewports The Docking feature obviously works more nicely when multi-viewports are enabled (#1542), allowing you to take take anything outside the boundaries of your main OS window. Though it is not required, and Docking works without multi-viewports. You can play with viewports by compiling most of the examples/ app under Windows (with either viewport or docking branch). Depending on the back-end and/or VM, viewports appears to be broken under Linux, your help would be welcome if you are a Linux user. For your own application to support multi-viewport, please refer to #1542 + read the comments in imgui.h for ImGuiPlatformIO + read the main.cpp of the examples/ application and the platform and renderer example bindings (e.g. imgui_impl_win32.cpp + imgui_imgui_dx11.cpp). Please note that IF you enable viewports io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; the coordinate system changes to follow your OS natural coordinate system, so e.g. (0,0) will always be the top-left of your primary monitor under Windows, etc. If you are using any absolute coordinates when calling e.g. SetNextWindowPos(), you should use GetMainViewport()->Pos as a base position instead. If you are interested in enabling multi-viewports, be aware that unlike Docking which requires no changes in the bindings, the integration of Multi-Viewport into your back-end is a little complicated and involved. For reference: this is the showing the part that is handled by the multi-viewports feature: taking imgui windows out of your main os window (it is creating new os windows and graphical context on the fly. This is done using a single imgui context, you can drag and drop stuff between windows etc.): viewport_201810

(if you have any issue related to viewports please post in #1542)


Docking

The core Docking feature requires no new API. Whenever you are moving a window you can merge them into each others and split nodes. When io.ConfigDockingWithShift = true the docking happens when holding SHIFT (see the "Configuration" blurb above).

Merging windows: docking_201810_base-01

You can play around with that anywhere in the demo, the merging of windows itself is super useful (even without any of the "splitting" feature usually associated to Docking).

Settings and docking trees are persisting and references are tracked under the hood so when you restore a window it should be where you expect it to be:

docking_201810_base-02

There are various useful subtleties and complications associated to the persistence model which are not conveyed in the GIF. I'm not going to detail everything right now, but basically if you have any feedback or issue, please report them (there WILL be issues).

If you have keyboard navigation enabled, you can use CTRL+Tab to cycle through windows with the keyboard:

ctrl_tab

You can re-order tabs by dragging them horizontal. You can click the button on the upper-left corner to open a menu listing all the tabs by name. You can click and drag this button to undock an entire node when it is docking (so you can redock it elsewhere):

docking_201810_base-03

What should persist:

What doesn't persist on restart:

API

As pointed out above many operation can be done without any API.

(This is not a complete documentation, merely enough to get you started with basic uses. More advanced programmatic manipulation of docking are currently exposed only in imgui_internal.h as ImGui::DockBuilderXXX and will be reworked into a public-facing API)

There is ImGui::DockSpace() call which allows you to create an explicit docking node within an existing window. If you have a full-on editor this is useful to build your main application around a menu-bar etc. Simpler applications will be using docking functions without ever calling DockSpace(). This function comes with one fundamental constraint: anything you dock into an explicit DockSpace() node needs to be submitted after it, so you’ll have to structure your app around that one limitation. It is also generally expected that your dockspaces, if you have any, persist at all time.

DockSpace() create by default a "Central Node" which is a node that doesn't fold/disappear even if no windows are docked into it. There are a few properties associated to this which I'll detail later.

Using some combination of flag ImGuiDockNodeFlags_PassthruCentralNode (WAS ImGuiDockNodeFlags_PassthruDockspace) you can make the central node invisible and inputs pass-thru. See the Demo>Dockspace options for details and this comment.

image

As with regular window, if you call IsItemHovered() or other "Item" function right after your call to Begin(), the title-bar or tab is used as a reference item. You can use this to create context-menu over title bars or tabs:

image

There is a ImGuiWindowFlags_NoDocking to disable the ability for a window to be involved in docking. Menus, popups etc. are not dockable by default.

The SetNextWindowDockFamily() API, which doesn't have a demo yet, allows you to make some windows dockable into to another set of windows. In particular, if you have multiple dockspace with tools related to a given editor or document, you may want each dockspace to use a unique family so windows from one cannot be docked into the other.

There are 2 new demos Demo Window → "Examples" Menu → Documents Demo Window → "Examples" Menu → Dockspace However as pointed above many of the features do not need those demos. In fact, the “Dockspace” demo is pretty much only a call to DockSpace() + a bunch of comments.

Picture: This is a dockspace, the upper-right node is the empty document root node

image

// Docking 
// [BETA API] Enable with io.ConfigFlags |= ImGuiConfigFlags_DockingEnable.
// Note: you DO NOT need to call DockSpace() to use most Docking facilities! 
// To dock windows: hold SHIFT anywhere while moving windows (if io.ConfigDockingWithShift == true) or drag windows from their title bar (if io.ConfigDockingWithShift = false)
// Use DockSpace() to create an explicit dock node _within_ an existing window. See Docking demo for details.
void    DockSpace(ImGuiID id, const ImVec2& size = ImVec2(0, 0), ImGuiDockNodeFlags flags = 0, const ImGuiDockFamily* dock_family = NULL);
void    SetNextWindowDockId(ImGuiID dock_id, ImGuiCond cond = 0);           // set next window dock id (FIXME-DOCK)
void    SetNextWindowDockFamily(const ImGuiDockFamily* dock_family);        // set next window user type (docking filters by same user_type)
ImGuiID GetWindowDockId();
bool    IsWindowDocked();                                                   // is current window docked into another window? 
// Flags for ImGui::DockSpace()
enum ImGuiDockNodeFlags_
{
ImGuiDockNodeFlags_None                         = 0,
ImGuiDockNodeFlags_KeepAliveOnly                = 1 << 0,   // Don't display the dockspace node but keep it alive. Windows docked into this dockspace node won't be undocked.
//ImGuiDockNodeFlags_NoCentralNode              = 1 << 1,   // Disable Central Node (the node which can stay empty)
ImGuiDockNodeFlags_NoDockingInCentralNode       = 1 << 2,   // Disable docking inside the Central Node, which will be always kept empty. Note: when turned off, existing docked nodes will be preserved.
ImGuiDockNodeFlags_NoSplit                      = 1 << 3,   // Disable splitting the node into smaller nodes. Useful e.g. when embedding dockspaces into a main root one (the root one may have splitting disabled to reduce confusion). Note: when turned off, existing splits will be preserved.
ImGuiDockNodeFlags_NoResize                     = 1 << 4,   // Disable resizing child nodes using the splitter/separators. Useful with programatically setup dockspaces. 
ImGuiDockNodeFlags_PassthruCentralNode          = 1 << 5,   // Enable passthru dockspace: 1) DockSpace() will render a ImGuiCol_WindowBg background covering everything excepted the Central Node when empty. Meaning the host window should probably use SetNextWindowBgAlpha(0.0f) prior to Begin() when using this. 2) When Central Node is empty: let inputs pass-through + won't display a DockingEmptyBg background. See demo for details.
ImGuiDockNodeFlags_AutoHideTabBar               = 1 << 6    // Tab bar will automatically hide when there is a single window in the dock node.
};

If you have multiple dockspace each behind their own tabs (aka nested tabs), the ImGuiDockNodeFlags_KeepAliveOnly flag is useful to specify that a DockSpace is still alive even though it it is hidden and not being fully submitted. Otherwise, submitted windows that are docked into the hidden dockspace would normally undock themselves (as they lost their parent).

Tab Bars and Tabs

The docking system is in charge of creating tabs, but you can use the tab-bar/tabs system as regular low-level widgets, unrelated to Docking.

if (ImGui::BeginTabBar("blah"))
{
    if (ImGui::BeginTabItem("Video"))
    {
        ImGui::Text("Blah blah");
        ImGui::EndTabItem();
    }
    if (ImGui::BeginTabItem("Audio"))
    {
        ImGui::EndTabItem();
    }
    if (ImGui::BeginTabItem("Controls"))
    {
        ImGui::EndTabItem();
    }
    ImGui::EndTabBar();
}

image

API

bool  BeginTabBar(const char* str_id, ImGuiTabBarFlags flags = 0);        // create and append into a TabBar
void  EndTabBar();
bool  BeginTabItem(const char* label, bool* p_open = NULL, ImGuiTabItemFlags = 0);// create a Tab. Returns true if the Tab is selected.
void  EndTabItem();                                                       // only call EndTabItem() if BeginTabItem() returns true!
void  SetTabItemClosed(const char* tab_or_docked_window_label);           // notify TabBar or Docking system of a closed tab/window ahead (useful to reduce visual flicker on reorderable tab bars). For tab-bar: call after BeginTabBar() and before Tab submissions. Otherwise call with a window name.
// Flags for ImGui::BeginTabBar()
enum ImGuiTabBarFlags_
{
    ImGuiTabBarFlags_None                           = 0,
    ImGuiTabBarFlags_Reorderable                    = 1 << 0,   // Allow manually dragging tabs to re-order them + New tabs are appended at the end of list
    ImGuiTabBarFlags_AutoSelectNewTabs              = 1 << 1,   // Automatically select new tabs when they appear
    ImGuiTabBarFlags_NoCloseWithMiddleMouseButton   = 1 << 2,   // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false.
    ImGuiTabBarFlags_NoTabListPopupButton           = 1 << 3,
    ImGuiTabBarFlags_NoTabListScrollingButtons      = 1 << 4,
    ImGuiTabBarFlags_FittingPolicyResizeDown        = 1 << 5,   // Resize tabs when they don't fit
    ImGuiTabBarFlags_FittingPolicyScroll            = 1 << 6,   // Add scroll buttons when tabs don't fit
    ImGuiTabBarFlags_FittingPolicyMask_             = ImGuiTabBarFlags_FittingPolicyResizeDown | ImGuiTabBarFlags_FittingPolicyScroll,
    ImGuiTabBarFlags_FittingPolicyDefault_          = ImGuiTabBarFlags_FittingPolicyResizeDown
};  

// Flags for ImGui::BeginTabItem()
enum ImGuiTabItemFlags_
{
    ImGuiTabItemFlags_None                          = 0,
    ImGuiTabItemFlags_UnsavedDocument               = 1 << 0,   // Append '*' to title without affecting the ID, as a convenience to avoid using the ### operator. Also: tab is selected on closure and closure is deferred by one frame to allow code to undo it without flicker.
    ImGuiTabItemFlags_SetSelected                   = 1 << 1,   // Trigger flag to programatically make the tab selected when calling BeginTabItem()
    ImGuiTabItemFlags_NoCloseWithMiddleMouseButton  = 1 << 2,   // Disable behavior of closing tabs (that are submitted with p_open != NULL) with middle mouse button. You can still repro this behavior on user's side with if (IsItemHovered() && IsMouseClicked(2)) *p_open = false.
    ImGuiTabItemFlags_NoPushId                      = 1 << 3    // Don't call PushID(tab->ID)/PopID() on BeginTabItem()/EndTabItem()
};

See the demo under Demo->Layout->Tabs

image

And Demo->Examples Menu->Documents

image

image

Standalone tab-bars (not part of the docking system) are not currently saving their order/selected persistently.

TODO

not an exhaustive list, posted for informative pupose

Ylannl commented 6 years ago

The viewport part does not seem to be working properly on macOS with the glfw_opengl3 example. If I disable ImGuiConfigFlags_ViewportsEnable, the docking works fine. (commit 2cff3f6)

output

ocornut commented 6 years ago

@Ylannl Thanks! Anything related to viewport please report in the viewport thread #1542 (**edit: for Mac/Linux please report to #2117). By the look of it, it looks like the clipping rectangles are not projected from the global coordinates space to each window’s space. Will have to inspect but last time I tried the glfw+gl demo on Mac OSX it worked for me. Will try again when I have the chance.

rokups commented 6 years ago

Hey this is great news! Is there a way to set initial position of the docked window? I am looking to have some kind of default placement of newly opened windows.

bsviglo commented 6 years ago

Hey this is great news! Is there a way to set initial position of the docked window? I am looking to have some kind of default placement of newly opened windows.

Same question. Having ability to setup default layout either by default setting or programmatically will be really helpful. Other than that look awesome. Many Thanks.

ocornut commented 6 years ago

Hey this is great news! Is there a way to set initial position of the docked window? I am looking to have some kind of default placement of newly opened windows.

All the programmatic stuff are currently hidden in imgui_internal.h so please bear in mind that those API will evolve.

Given dockspace_id (same value passed to DockSpace()) Given dockspace_size (which currently cannot be zero, unlike the one passed to DockSpace()

EDITED Oct 6: Removed ImGuIContext* ctx parameter EDITED Feb 20: Updated to current internal api

You can create an initial layout, e.g.

ImGui::DockBuilderRemoveNode(dockspace_id); // Clear out existing layout
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_Dockspace); // Add empty node
ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size);

ImGuiID dock_main_id = dockspace_id; // This variable will track the document node, however we are not using it here as we aren't docking anything into it.
ImGuiID dock_id_prop = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Left, 0.20f, NULL, &dock_main_id);
ImGuiID dock_id_bottom = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Down, 0.20f, NULL, &dock_main_id);

ImGui::DockBuilderDockWindow("Log", dock_id_bottom);
ImGui::DockBuilderDockWindow("Properties", dock_id_prop);
ImGui::DockBuilderDockWindow("Mesh", dock_id_prop);
ImGui::DockBuilderDockWindow("Extra", dock_id_prop);
ImGui::DockBuilderFinish(dockspace_id);

In order to only call it once you may use e.g.

if (ImGui::DockBuilderGetNode(dockspace_id) == NULL)
   MyEditor_LayoutPreset();

(Note that I just pushed a fix for DockBuilderSplitNode for a bug I introduced yesterday)

image

Ylannl commented 6 years ago

I've just tested the GLFW + OpenGL3 example (branch docking, commit 2cff3f6) on macOS 10.12 and I have not the problem presented by @Ylannl as shown in the following GIF

@Alzathar Interesting! Do you have a retina screen? I do, perhaps that is related? I'm on macOS 10.14 btw, but also noticed this on the viewport branch on macOS 10.13.

@ocornut Thanks for the great work on ImGui. I'm very exited about these new features! Sorry for posting in the wrong thread.

rokups commented 6 years ago

@ocornut creating initial dock layout is clear, but what about setting initial dock placement of new window in already existing layout? Do we have to rebuild entire layout to dock new window into existing layout?

ocornut commented 6 years ago

creating initial dock layout is clear, but what about setting initial dock placement of new window in already existing layout? Do we have to rebuild entire layout to dock new window into existing layout?

@rokups You can use DockBuilderDockWindow() or SetNextWindowDockId(). The problem you will have is that you can not easily identify a docking node from an already split state. So the "where" parameter is tricky to define.

If you are building the whole hierarchy you have the ID, otherwise you can use e.g. GetWindowDockId() to get an ID from a window.

Another possibility (which I was using in Docking V1) would be to tag node on creation, and being later able to find a dock node id given the tag, The problem is that node that can merged/split by the user. To be able to track through merge we need node to be able to carry multiple tags.

Take some time to think about the problem and let me know if you have more ideas.

@Alzathar @Ylannl Please move viewport related discussion to the viewport thread.

rokups commented 6 years ago

Thanks, i got it working easily. Not sure how i missed that. And i hopefully last question: is there a way to set next window dock position to an empty undocked space? I suspect i might need to iterate through ImGuiDockNode* tree but i am not sure how to identify undocked space. 2018-10-02_19-28

ocornut commented 6 years ago

@rokups Do you actually want "an empty undocked space" (there can be only one) or do you want the DocRoot node (which happens to be the only node that can be empty, but it might have other windows docking). What would do do if there's already a window in there?

rokups commented 6 years ago

I think i want "empty undocked space". I tried SetNextWindowDockId(dockRoot, ..) but it just does not dock window anywhere so that must be wrong. What i want to do is dock window into empty space if there is any, if not - dock window as a tab to the largest docked tab. That second part is already working great.

ocornut commented 6 years ago

I tried SetNextWindowDockId(dockRoot, ..) but it just does not dock window anywhere

That's exactly what this function does, dock windows. If you believe you are running into an issue try to build a repro.

I think i want "empty undocked space". [...] What i want to do is dock window into empty space if there is any, if not - dock window as a tab to the largest docked tab.

The DocRoot node (the grey node) is essentially meant to represent that space (If there is an empty node it WILL be the DocRoot node) and it is up to the user to decide where it goes and how big it this. This matches VS behavior, rather than just looking for the "largest docked tab". It also behave differently sizing wise (if you resize your host OS windows you'll find that out).

You should probably either use the DocRoot node (which ID is not possible to query yet, haha) or track the current DockID of the LATEST focused document/tool (up to you how to define document/tool) and use this DockID to dock new windows, I think either would make more sense to the user.

EDIT You should never have to iterate the dock node hierarchy yourself, but we could provide helper functions to find DockID under different criteria: DocRoot node, largest node, last focused node, dock where a given window is hosted, with or without DockFamily filter, etc.

EDIT2: The DocRoot node is now called CentralNode

codz01 commented 6 years ago

great news :D , it works perfectly on windows Note : for mingw users , you have to add :

define WINVER 0x0601

define _WIN32_WINNT 0x0601

on top of include this is only to visible the function SetProcessDPIAware

ocornut commented 6 years ago

Note : for mingw users , you have to add :

define WINVER 0x0601

define _WIN32_WINNT 0x0601

on top of include

I think you need to add those defines are program-wide defines if you use Mingw.

Dingobloo commented 6 years ago

Took a bit of work going through the examples to update my application because I hadn't updated imgui in a while but docking and viewport seems to work just fine on OSX High Sierra (10.13.6) SDL2 + OpenGL2. The only slightly unintuitive bit is that I cannot interact with the translate gizmo if the window is undocked (it just drags the whole window) but that's likely my doing.

dockingsmall

rokups commented 6 years ago

@ocornut i created a small testcase of what i attempted. Please take a look.


        static int initialized = 0;
        static int new_window = 0;
        ImGuiWindowFlags flags = ImGuiWindowFlags_MenuBar;
        flags |= ImGuiWindowFlags_NoDocking;
        ImGuiViewport* viewport = ImGui::GetMainViewport();
        ImGui::SetNextWindowPos(viewport->Pos);
        ImGui::SetNextWindowSize(viewport->Size);
        ImGui::SetNextWindowViewport(viewport->ID);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
        flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
        flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
        ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
        ImGui::Begin("DockSpace Demo", 0, flags);
        ImGui::PopStyleVar();

        if (ImGui::BeginMenuBar())
        {
            if (initialized == 0)
            {
                if (ImGui::Button("1. Initialize"))
                    initialized = 1;
            }
            if (initialized > 0 && new_window == 0)
            {
                if (ImGui::Button("2. New Window"))
                    new_window = 1;
            }
            ImGui::EndMenuBar();
        }

        ImGuiIO& io = ImGui::GetIO();
        ImGuiID dockspace_id = ImGui::GetID("MyDockspace");

        if (initialized == 1)
        {
            initialized = 2;
            ImGuiContext* ctx = ImGui::GetCurrentContext();
            ImGui::DockBuilderRemoveNode(ctx, dockspace_id); // Clear out existing layout
            ImGui::DockBuilderAddNode(ctx, dockspace_id, ImGui::GetMainViewport()->Size); // Add empty node

            ImGuiID dock_main_id = dockspace_id; // This variable will track the document node, however we are not using it here as we aren't docking anything into it.
            ImGuiID dock_id_prop = ImGui::DockBuilderSplitNode(ctx, dock_main_id, ImGuiDir_Left, 0.20f, NULL, &dock_main_id);
            ImGuiID dock_id_bottom = ImGui::DockBuilderSplitNode(ctx, dock_main_id, ImGuiDir_Down, 0.20f, NULL, &dock_main_id);

            ImGui::DockBuilderDockWindow(ctx, "Log", dock_id_bottom);
            ImGui::DockBuilderDockWindow(ctx, "Properties", dock_id_prop);
            ImGui::DockBuilderFinish(ctx, dockspace_id);
        }

        ImGui::DockSpace(dockspace_id);
        if (initialized == 2)
        {
            ImGui::Begin("Properties");
            ImGui::End();

            ImGui::Begin("Log");
            ImGui::End();
        }

        if (new_window == 1)
        {
            // Should dock window to empty space, instead window is not docked anywhere.
            ImGui::SetNextWindowDockId(dockspace_id, ImGuiCond_Once);
            ImGui::Begin("New Window");
            ImGui::End();
        }

        ImGui::End();
        ImGui::PopStyleVar();

Click buttons in the menu bar to 1. initialize default layout and 2. create new window and dock it into empty space. Window is created but not docked anywhere. Do i do it wrong or is this a bug?

And another question: is there a way to detect presence of this free undocked space?

nem0 commented 6 years ago

I want to make a dockspace transparent, while windows docked in it should keep their style. I tried many ways, e.g.

    ImGui::SetNextWindowBgAlpha(0);
    ImGui::Begin("DockSpace Demo", 0, flags);

    ImGuiID dockspace_id = ImGui::GetID("MyDockspace");
    ImGui::DockSpace(dockspace_id);

    ImGui::End();

however, all I managed to do is that everything became transparent. Is there a way to this?

ocornut commented 6 years ago

I want to make a dockspace transparent, while windows docked in it should keep their style.

I am working on this right now, it should be available during the day. It also involve passing inputs through.

codz01 commented 6 years ago

the dockspace's scrollbar is visible , is it normal ? gif

ocornut commented 6 years ago

@codz01 Pushed a fix for this. Are your windows transparent? (I don't think it was normally noticeable with the default style).

codz01 commented 6 years ago

thanks. its transparent by default i think , i just used the default settings

ocornut commented 6 years ago

I have renamed what I used to called "Document Root" to "Central Node" (in comments, members, flags) which is clearer to talk about, and more consistent.

EDITED after API changes

I added a couple flag to DockSpace() to allow the creation of dockspace where the central node is transparent and let inputs pass-through (so in this mode, when you hover the central node, io.WantCaptureMouse will stay false, and you can even reach windows behind it).

// Disable docking inside the Central Node, which will be always kept empty.
ImGuiDockNodeFlags_NoDockingInCentralNode = 1 << 4,

// 1) DockSpace() will render a ImGuiCol_WindowBg background covering everything excepted the Central Node when empty. Meaning the host window should probably use SetNextWindowBgAlpha(0.0f) prior to Begin() when using this. 
// 2) When Central Node is empty: let inputs pass-through + won't display a DockingEmptyBg background.
ImGuiDockNodeFlags_PassthruDockspace      = 1 << 5

So you probably want to use the combined ImGuiDockNodeFlags_PassthruDockspace flag here. If you do it it's however important that you disable the background of the window hosting the dockspace.

This is how to create an invisible dockspace covering the whole screen:

ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(viewport->Size);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::SetNextWindowBgAlpha(0.0f);

ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;

ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("DockSpace Demo", p_open, window_flags);
ImGui::PopStyleVar(3);

ImGuiID dockspace_id = ImGui::GetID("MyDockspace");
ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_PassthruDockspace;
ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);

That's a lot of code (could consider creating a helper to do exactly that?)

EDITED: After DockSpaceOverViewport() function which does pretty much that.. the constraint is that you can't use it to create a MenuBar so I'm not so sure about it vs manual code being more flexible.

With this code nothing will initially be visible but you can dock windows on the side of your viewport, etc.

And this is the demo (which adds a menu-bar and allows you to test the different flags):

image

(cc: @nem0 @damqui)

JSandusky commented 6 years ago

Tried it out and it's the nicest DearImGui dock I've seen/used. If I hadn't sworn off docking forever I'd totally use it. Great work.

heroboy commented 6 years ago

I want restore the window size after undocking a window. https://github.com/ocornut/imgui/issues/2104

nem0 commented 6 years ago

I have following layout: image Is there a way to dock "CPU Markers" tab such way that it's only visible when "CPU Profiler" tab is active?

It works like that if I nest Begin End, however that has many other issues if "CPU Markers" is not docked.

ocornut commented 6 years ago

Normally you can treat them as regular windows, only Begin the second one if the first one is visible?

The docking should persist so when CPU Markers is not submitted its space will be given to the node on the left.

I’d need a repro to understand because normally there’s no reason that shouldn’t work by default.

nem0 commented 6 years ago

@ocornut If docked like in the image above it works fine, however if I dock it like this: image and activate CPU Markers tab, it starts blinking.

if (ImGui::Begin("CPU Profiler")) {
    ImGui::Begin("CPU Markers"); ImGui::End();
}
ImGui::End();
ocornut commented 6 years ago
if (ImGui::Begin("CPU Profiler")) {
    ImGui::Begin("CPU Markers"); ImGui::End();
}
ImGui::End();

Well that's going to create a visibility loop. CPU Markers only appears when CPU Profiler is selected, making CPU Profiler unselected, making CPU markers disappears, etc.

Is there a way to dock "CPU Markers" tab such way that it's only visible when "CPU Profiler" tab is active?

A) If you mean "active" = Begin was called, then you can just call both Begin when 1 bool is set (instead of testing the return value of the first Begin call). B) If you mean "selected" = Tab is currently selected, then you get the visibility loop above.

however that has many other issues if "CPU Markers" is not docked

It's not really "If CPU Markers is not docked", the problem arise only is CPU Markers is docked in the same node as CPU Profiler (it works if it is docked elsewhere). What would you ideally want in this case of them docked together? If you allow both tabs to appears simultaneously in the tab bar, you are contradicting the basic intent of B).

So I think intent A) is preferable and would be a non-issue, but there is perhaps another solution to this problem to be found.

nem0 commented 6 years ago

Background story: I quite often have two pieces of information (in this case CPU Profiler and CPU Markers) that ideally should be displayed together. I can do it in many way, e.g. with Columns. However user can change only width, not order, orientation, or tabify it.

One possible option is something like (Dockspace + DockFamily): image

problem is that I can end up with: image

sonoro1234 commented 6 years ago

Now trying docking without viewport on LuaJIT-imgui. It is fantastic it doesnt need code modification ( appart from adding ImGuiConfigFlags_DockingEnable) Great work!! Still investigating...

When is it expected to be included in master?

ngminteck commented 6 years ago

Hello, is possible to resize dockspace by the user?

also seem the auto fit in horizontal scroll is missing?

sonoro1234 commented 6 years ago

I fell that io.ConfigDockingWithShift = true should prevent the user to detach a window from a dock without pressing shift before. So io.ConfigDockingWithShift = true would be something as "lock docking" unless shift is pressed.

Alzathar commented 6 years ago

@sonoro1234 This rule may be true in case you do not enable the multi-viewport. When the multi-viewport is enabled (w or w/o the io.ConfigDockingWithShift parameter is set to true), the user might want to undock a window simply by dragging it outside the tab bar to create a new undocked window (like in Firefox, etc.).

Moreover, if you need to press the shift key to unlock a docked window, then the dock "gizmo" will appear instantaneously (because io.ConfigDockingWithShift is set to true). Again, this may not be the expected behavior when the multi-viewport is enabled.

What about another config parameter (e.g. io.ConfigDockingUnlockWithShift) to enable/disable this rule?

sonoro1234 commented 6 years ago

io.ConfigDockingUnlockWithShift could be a good idea for "lock docking"

When ImGuiConfigFlags_ViewportsEnable in the docking branch I get this assertion Expression: (g.FrameCount == 0 || g.FrameCount == g.FrameCountPlatformEnded) && "Forgot to call UpdatePlatform Windows() at the end of the previous frame?"

Any idea why?

ocornut commented 6 years ago

When ImGuiConfigFlags_ViewportsEnable in the docking branch I get this assertion

That's not happening in the example app.

Any idea why?

Well, did you "forget to call UpdatePlatformWindows() at the end of the previous frame?" What is the value of g.FrameCount ? Guys please make some effort providing details when asking questions. I'm working full-time on this and half of this github are incomplete questions addressed to me. It's really tough.

sonoro1234 commented 6 years ago

Yes, I forgot that, sorry.

aaronkirkham commented 6 years ago

Hi, is there any plan to bring back the BeginTabBar ImGuiTabBarFlags_SizingPolicyEqual flag which was available in the tabs branch?

ggerganov commented 6 years ago

I've posted an Emscripten port of the latest 'docking' branch on the following link. If anyone is interested in trying out the ImGui demo directly in the browser, feel free to check it out:

https://ggerganov.github.io/jekyll/update/2018/02/11/imgui-em.html?branch=docking

ngminteck commented 6 years ago

UpdatePlatform

just call UpdatePlatform()

before imgui render and imgui endframe (), put if the last of the update loop will do i guess

ocornut commented 6 years ago

@ggerganov: it works very well on the iphone, wow! For the docking branch you may want to enable the master Docking flag by default in there?

ggerganov commented 6 years ago

Done!

rokups commented 6 years ago

In case anyone bumps into my problem - well here is a solution:

ImGui has two "root dock" concepts actually.

  1. Root that is parent of all docks, can be obtained by DockNodeGetRootNode() call in imgui_internal.h.
  2. Root that is supposed to have remaining free space as in my screenshot.

In order to dock a window to existing free space we must find that second root.

// Find empty dockspace
std::function<ImGuiDockNode*(ImGuiDockNode*)> returnTargetDockspace = [&](ImGuiDockNode* dock) -> ImGuiDockNode* {
    if (dock == nullptr)
        return nullptr;
    if (dock->IsDocumentRoot)
        return dock;
    else if (auto* node = returnTargetDockspace(dock->ChildNodes[0]))
        return node;
    else if (auto* node = returnTargetDockspace(dock->ChildNodes[1]))
        return node;
    return nullptr;
};

ImGuiDockNode* dockspaceRoot = ui::DockBuilderGetNode(ui::GetCurrentContext(), dockspaceId);
ImGuiDockNode* currentRoot = returnTargetDockspace(dockspaceRoot);
if (currentRoot->Windows.empty())  // When dockspace has some free space current root will have no windows
    ui::SetNextWindowDockId(currentRoot->ID, ImGuiCond_Once);
Adria-F commented 6 years ago

I have renamed what I used to called "Document Root" to "Central Node" (in comments, members, flags) which is clearer to talk about, and more consistent. [...]

It works fine, however, it causes the input to also pass through the other docked windows. So, for example, if I have a scene which I can zoom in with the mouse wheel and I'm scrolling through a console window, it will also zoom the scene.

ocornut commented 6 years ago

@Adria-F

It works fine, however, it causes the input to also pass through the other docked windows. So, for example, if I have a scene which I can zoom in with the mouse wheel and I'm scrolling through a console window, it will also zoom the scene.

This doesn't happen in the demo. Is your app checking the io.WantMouseCapture flag correctly? Please provide a minimal, complete and verifiable example as instructed by https://github.com/ocornut/imgui/blob/master/.github/CONTRIBUTING.md.

xelatihy commented 6 years ago

@ocornut it would be great to have an example of how to do drawing in the central area of the image below.

image

In particular, should we use use a ImGui window with custom rendering, or have the application draw directly in the back buffer? If we use the latter, are there ImGui functions to get the central area size, framebuffer size, mouse coordinates (wrt that area, not the whole screen)?

bsviglo commented 6 years ago

@ocornut it would be great to have an example of how to do drawing in the central area of the image below.

It'll be cool to have an example how to perform it correctly because input handling in this situation is really tricky.

ocornut commented 6 years ago

@xelatihy

In particular, should we use use a ImGui window with custom rendering, or have the application draw directly in the back buffer? If we use the latter, are there ImGui functions to get the central area size, framebuffer size, mouse coordinates (wrt that area, not the whole screen)?

The point of that transparent central idea is mostly useful if you do NOT want to resize the underlying game/render, so you would render your game on the entire screen and imgui over it. Otherwise you can easily dock something into this area and render your game normally inside the imgui window that s docked. Up to you. This is not a problem specific to docking. You can create a window and either render a texture into it (with Image() or draw_list->AddImage()) either add a draw callback (with draw_list->AddCallback()) and draw directly. The later require more careful handling of your rendering state and is generally harder to use.

@bsviglo

input handling in this situation is really tricky.

You haven't answered my question. Is your code using the io.WantCaptureMouse flag correctly? Otherwise, please clarify precisely what is your problem and what is preventing you from achieving what you want. The problem you mentioned above suggest you are not using io.WantCaptureMouse properly.

ocornut commented 6 years ago

@rokups

In case anyone bumps into my problem - well here is a solution:

Your code wouldn't compile at the time you posted it, and even less so now. The terminology for this node is "Central Node". Beware if you are using those internal structures as they are bound to change under your feet from time to time. If you post code make sure you at the last version. What we need is only an API to retrieve the ID of the Central Node indeed.

damqui commented 6 years ago

@xelatihy

If we use the latter, are there ImGui functions to get the central area size, framebuffer size, mouse coordinates (wrt that area, not the whole screen)?

ImGuiDockNode* ImGui::DockNodeGetCentralNode(ImGuiDockNode* node) {
    if (!node)
        return nullptr;
    if (node->IsCentralNode) {
        return node;
    }
    for (int iChild = 0; iChild<2; iChild++) {
        ImGuiDockNode* central_node = DockNodeGetCentralNode(node->ChildNodes[iChild]);
        if (central_node)
            return central_node;
    }
    return nullptr;
}

@ocornut for some usage, I needed to programmatically be able to make a tabbed window selected. I didn't find something in the API to do this. I ended up writing this function :

void ImGui::SetTabbedWindowSelected(char* WindowName) {
    ImGuiWindow* Window = ImGui::FindWindowByName(WindowName);
    if (Window->DockNode && Window->DockNode->TabBar) {
        Window->DockNode->TabBar->NextSelectedTabId = Window->ID;
    }
}

is this "okay" ?

thanks !