nuklear icon indicating copy to clipboard operation
nuklear copied to clipboard

Once every window is hidden all windows get reset

Open Benno1308 opened this issue 7 years ago • 4 comments

Hello there, I'm currently implementing nuklear into my project, but somehow window hiding is not working as it should.

This is what it looks like once I try to close windows: https://cloud.thebigben.me/s/mXmsoQiqf5ktTAa/download

Notice that every window position etc. gets reset.

Ive attached my code below.

void Menu::OnPresent(IDXGISwapChain* pSwapChain)
{
	static bool initDone = false;
	if(!initDone)
	{
		initDone = true;
		IDXGISwapChain_GetDevice(pSwapChain, IID_ID3D11Device, reinterpret_cast<void**>(&device));
		((device)->lpVtbl->GetImmediateContext(device, &context));
		swap_chain = pSwapChain;
		set_swap_chain_size(2560, 1440);
		ctx = nk_d3d11_init(device, 2560, 1440, 512 * 1024, 128 * 1024);

		struct nk_font_atlas *atlas;
		nk_d3d11_font_stash_begin(&atlas);
		nk_d3d11_font_stash_end();
	}

	if (nk_begin_titled(ctx, "Test", "Test", nk_rect(300, 300, 230, 260), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE))
	{
		nk_layout_row_static(ctx, 30, 200, 1);
		if (nk_button_label(ctx, "[DEV] Unload"))
		{
			this->FireEvent("btn_unload"); // FireEvent just connects to my internal event system.
		}
	}
	nk_end(ctx);

	if (nk_begin_titled(ctx, "Colors", "Colors", nk_rect(600, 300, 580, 550), NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_TITLE | NK_WINDOW_CLOSABLE))
	{
	}
	nk_end(ctx);

	// Rest of the Input gets handled in a hooked winproc (see below)
	nk_input_begin(ctx);
	nk_input_end(ctx);

	nk_d3d11_render(context, NK_ANTI_ALIASING_ON);
}

static LRESULT CALLBACK hookedWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if (nk_d3d11_handle_event(hwnd, uMsg, wParam, lParam))
		return 0;

	return CallWindowProc(m_PrevWndProc, hwnd, uMsg, wParam, lParam);
}

Benno1308 avatar Dec 10 '18 22:12 Benno1308

Im also having this problem. @vurtun can you help out?

SysRider1313 avatar Dec 17 '18 15:12 SysRider1313

I had the same problem, and ran into 2 more issues while testing.

However, my wild guess "fix" for one of the other issues, seems to fix OP's issue too, so I'm posting here.

Here's the long story.

  1. Build example/extended.c, but add the NK_WINDOW_CLOSABLE flag to each window.
  2. Click the close icon on any window.
  3. Then move the mouse to hover over each of the other windows' bodies, in turn. Upon cursor entry over one of the other windows (which one it is, varies), this causes the "closed" window to reappear:

Animation 1: gif1

Background:

/// - 2016/08/08 (1.07.0) - Nuklear now differentiates between hiding a window (NK_WINDOW_HIDDEN) and
///                        closing a window (NK_WINDOW_CLOSED). A window can be hidden/shown
///                        by using `nk_window_show` and closed by either clicking the close
///                        icon in a window or by calling `nk_window_close`. Only closed
///                        windows get removed at the end of the frame while hidden windows
///                        remain.

First, the above seems to be inaccurate. Clicking the close icon currently only hides the window:

Snippet A

/* window close button */
//...
if (win->flags & NK_WINDOW_CLOSABLE) {
    //...
    if (nk_do_button_symbol(...)  // clicking the close button...
    {
        layout->flags |= NK_WINDOW_HIDDEN;  // ...only hides the window
        layout->flags &= (nk_flags)~NK_WINDOW_MINIMIZED;
    }
}

I wondered if there was some magic supposed to happen outside of this, but no. You can see by printing nk_window_is_closed() after nk_clear(). The window is merely hidden.

((As a side-note, if you add the | NK_WINDOW_CLOSED flag to snippet (A), that will destroy the window, so next time you call nk_begin(), the window will just re-appear, because there is no record of it being closed, since the window was destroyed. I think you have to poll nk_window_is_closed() after every nk_end().))

But the main issue, is why the window is re-appearing upon the hover? This is actually because it's being kicked off the linked list when the hovered window activates (causing the next nk_begin() to assume it's a new window...and re-show it). This kickage occurs in nk_begin_titled(), at the 4th nk_insert_window call:

Snippet B

if (!iter && ctx->end != win) {
        if (!(win->flags & NK_WINDOW_BACKGROUND)) {
            /* current window is active in that position so transfer to top
             * at the highest priority in stack */
            nk_remove_window(ctx, win);
            nk_insert_window(ctx, win, NK_INSERT_BACK); // <--- deletes hidden window from list
        }
        win->flags &= ~(nk_flags)NK_WINDOW_ROM;
        ctx->active = win;
}

Snippet C

if (loc == NK_INSERT_BACK) {
	//...
        end->next = win; // <--- the culprit. hidden window was in end->next
	//...

The hidden window (which should have been closed, according to the changelog), is actually kept (with its hidden flag) at end->next, and is there being overwritten when a new window activates (by hovering).

((I would've expected end->next to be equal to NULL since it's the end of a linked list, but if you put NK_ASSERT(ctx->end->next == 0) above the nk_insert_window in snippet (B), it, of course, fails.))

Looking for the culprit i found this suspicious line in nk_clear():

Snippet D

/* remove hotness from hidden or closed windows*/
if (((iter->flags & NK_WINDOW_HIDDEN) ||
    (iter->flags & NK_WINDOW_CLOSED)) &&
    iter == ctx->active) {
    ctx->active = iter->prev;
    ctx->end = iter->prev;  // what is end->next, now? the hidden window......

The end pointer is being bumped to behind the active (and now hidden) window, so that end->next now points to the hidden window. Then, when snippet (C) gets executed, the address in end->next gets overwritten, meaning the hidden window is no longer linked in the list.

Someone might say, well you clicked the close box, so why shouldn't the window get removed from the list? Because the same error happens even if you manually hide a window with nk_window_show().

To see for yourself, add this snippet to example/extended.c after nk_input_end():

if (nk_input_is_key_pressed(&ctx.input, NK_KEY_TAB)) {
            static int shown = 0;
            nk_window_show(&ctx, "Button Demo", shown);
            shown = !shown;
}

Then follow this GIF:

Animation 2: gif2

My guess was maybe the hidden window (in snippet D) should be moved to the start of the linked list, instead of just dangled off the end.

So I replaced that line:

ctx->end = iter->prev;

..with these:

nk_remove_window(ctx, iter);
nk_insert_window(ctx, iter, NK_INSERT_BACK);

That fixed OP's problem for me, and the other one.

It does not address the issue in Snippet A.

What's bad is, I really have no grasp of nuklear's design- state machines are tricky to me- this might not even be a valid fix.

It would be hugely comforting if @Vurtun has any insight on these.

Thanks for your time, sorry this was so long.

ghost avatar Jan 16 '19 23:01 ghost

I had similar issue with Hidden windows, and isolated a (partial) fix to pretty much the same lines in nk_close() as jrodatus mentioned in snipped D.

The ctx structure contains a doubly linked list of nk_window structures bounded by its begin and end pointers. This bit of code is trying to remove the (last) window from the list if it is hidden or closed and active. It backs up the end pointer correctly. However, it doesn't set the next pointer in the doubly linked list for the second to last (now last) window. This means the nk_window list doesn't agree with it's bounds, leading later things to go wonky in ways I don't understand, but which could lead to some of the other problems jrodatus motions. Nulling out iter->prev->next pointer as shown below seems to fix the problem.

However, I don't know to know if this is a complete fix.

  • This only fixes the problem for the last window.
  • However, the active window is always the last window in all my tests, so this seems ok.

I did write a bit of test code that checked this doubly linked nk_window list and it's bounds in ctx after any modification. I didn't find any other corruption problems in that list. (It seems that image list ctx list is regenerated every frame, so bugs in it tend to be transitory and hence hard to find.)

A (partial) fix to the code is hence this one line (with a few additional brackets)

     /* remove hotness from hidden or closed windows*/
        if (((iter->flags & NK_WINDOW_HIDDEN) ||
            (iter->flags & NK_WINDOW_CLOSED)) &&
            iter == ctx->active) {
            ctx->active = iter->prev;
            ctx->end = iter->prev;
            if (!ctx->end) {
                ctx->begin = 0;
            } else {
                iter->prev->next = NULL;                // <-- fix
            }
            if (ctx->active)
                ctx->active->flags &= ~(unsigned)NK_WINDOW_ROM;
        }  

johnbrw avatar Aug 21 '19 20:08 johnbrw

However, it doesn't set the next pointer in the doubly linked list for the second to last (now last) window.

PRs more than welcome :wink:.

dumblob avatar Aug 21 '19 21:08 dumblob