ocornut / imgui

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

Docking: Detect window close vs tab close #4186

Closed scottmcnab closed 9 months ago

scottmcnab commented 3 years ago

Version/Branch of Dear ImGui:

Version: 1.83 Branch: docking

Back-end/Renderer/Compiler/OS

Back-ends: Custom (Unigine) Compiler: Visual Studio 2019 v16.1.6 Operating System: Windows 10

My Issue/Question:

Is it possible to detect when a docking window is closed by the window close button, versus an individual tab close button?

close-window-vs-tab

Specifically, I've setup a dockspace with a similar layout to the above using 3 tabs as follows:

auto dockspaceId = ImGui::GetID("MyDockSpace");
// Don't clear previous layout if it exists
auto node = ImGui::DockBuilderGetNode(dockspaceId);
if (node == nullptr) {
    ImGui::DockBuilderAddNode(dockspaceId);

    const auto size = ImVec2(520.0f, 600.0f);
    ImGui::DockBuilderSetNodeSize(dockspaceId, size);

    // Center the window on the screen
    const auto viewport = ImGui::GetMainViewport();
    const auto pos = ImVec2(
        std::max(0.0f, (viewport->WorkSize.x - size.x) / 2.0f),
        std::max(0.0f, (viewport->WorkSize.y - size.y) / 2.0f));
    ImGui::DockBuilderSetNodePos(dockspaceId, pos);

    // Split the dockspace into 2 nodes
    ImGuiID dockspaceA = 0;
    ImGuiID dockspaceB = 0;
    ImGui::DockBuilderSplitNode(dockspaceId, ImGuiDir_Down, 0.60f, &dockspaceB, &dockspaceA);

    // Dock our windows into the docking nodes we made above
    ImGui::DockBuilderDockWindow("Tab A", dockspaceA);
    ImGui::DockBuilderDockWindow("Tab B", dockspaceB);
    ImGui::DockBuilderDockWindow("Tab C", dockspaceB);

    ImGui::DockBuilderFinish(dockspaceId);
}

I need the ability for tabs to be closed individually, but also have the option of closing the entire window. At the moment the draw code looks like:

// All tabs initially open at construction
bool tab_a_open = true;
bool tab_b_open = true;
bool tab_c_open = true;

// ...

// Draw function here
if (tab_a_open) {
    if (ImGui::Begin("Tab A", &tab_a_open)) {
        // Draw tab A here...
    }
    ImGui::End();
    if (!tab_a_open) {
        // FIXME: only close the other tabs if window closed!
        tab_b_open = tab_c_open = false;
    }
}
if (tab_b_open) {
    if (ImGui::Begin("Tab B", &tab_b_open)) {
        // Draw tab B here...
    }
    ImGui::End();
    if (!tab_b_open) {
        // FIXME: only close the other tabs if window closed!
        tab_a_open = tab_c_open = false;
    }
}
if (tab_c_open) {
    if (ImGui::Begin("Tab C", &tab_c_open)) {
        // Draw tab C here...
    }
    ImGui::End();
    if (!tab_c_open) {
        // FIXME: only close the other tabs if window closed!
        tab_a_open = tab_b_open = false;
    }
}

This code detects that a tab has been closed by checking that the bool* p_open in ImGui::Begin() has been set to false. However, p_open is set to false both when the close button on the tab is clicked, and also when the close button on the window is clicked.

Is there some way I can detect the difference between the window vs tab being closed? I'd like to replace the FIXMEs above with conditional code that only closes the other tabs if the window was closed, and not if just a tab was closed.

Is this possible? Thanks!

ocornut commented 3 years ago

Hello,

Presently it is not possible for the reason that both of those buttons are doing the same thing. The fact that it is doing the same thing is probably a topic we would reevaluate and investigate.

I seemingly recall there was a reason for that but right now I remember why, other that: the problem with "Closing all" is that we cannot really honor it for windows that don't have a close button. So if you have Windows A, B, and C docked together but B doesn't have a close button, clicking an hypothetical "Close all" button wouldn't close B.

ocornut commented 3 years ago

I looked at this and reverted to initial (pre-docking-branch) behavior that the close button on a dock node will close all windows of a node.

I believe this essentially solve the problem you had in the first place?

scottmcnab commented 3 years ago

Thanks for the fix. It certainly makes more sense that closing the tab bar closes all the tabs in the bar, rather than just one.

While this is close, the effect I'm trying to achieve is to emulate a more "conventional" window behaviour, whereby clicking the top-right close button will close ALL tabs in the host window. If you consider the layout in the picture above, clicking the top-right close removes the top tab bar, and the bottom two tabs expand to fill the space. It then requires the user to click a second time to close the window completely:

https://user-images.githubusercontent.com/7407015/121277185-a4fb9200-c902-11eb-8192-ade7f52c405f.mp4

Is there some way to detect that a tab bar has been closed, then programmatically close the other tab bar in the same host window, thereby closing all tabs with one click?

ocornut commented 3 years ago

It's not currently possible to do. Whether it is more "conventional" or not is ambiguous, in your initial situation with the floating node you could see it as two windows stuck together (similar to photoshop docking style), in which case the current behavior is more consistent.

If you were to want to hack something, you might be able to:

Either way your app will behave inconsistently with dear imgui behaviors. I don't think we should provide that sort of thing as a configuration option as those varying behaviors tend to have many knock-on effects on other features.

scottmcnab commented 3 years ago

Thanks for the pointers on where to start looking 👍 I'll dig into it and see what I can do.

I agree - this action is arguably inconsistent with the default docking behaviour, although it would perhaps appear less-so if I can work out how to hide the close button on the lower tab bar - Is there a way to determine this?

FWIW I am trying to do this in order to make new features written using Dear ImGui resemble an existing retained-mode UI as closely as possible. Since this is for a fairly large application, both GUIs realistically need to coexist side-by-side until there is time to port all the legacy code. The more consistently I can match the behaviours of the two systems the better, as the more seamless the transition will be for the users.

I expect a small amount of hacky code to be a necessary evil in this situation, but it is a very big testament to the flexibility of Dear ImGui that I have not had to hack much at all yet, so thank you! 👏

ocornut commented 9 months ago

Closing this as normally you have the info you want for the small patch.