Page 1 of 1

Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 12 Jun 2017, 09:23
by nikobarli
Hi,

I created two windows, each with its own XAML root element. I set different cursors to the two elements:

Element 1
<Grid Cursor="Hand">
...
</Grid>
Element 2
<Grid Cursor="Arrow">
...
</Grid>
Then I spawn the two windows (inside the same process) side by side, and move the mouse around.

1. First, I move the mouse to the first window -> the cursor changed to Hand cursor
2. Then I move the cursor to the second window -> the cursor changed to Arrow window
3. Then I move the cursor to the first window -> the cursor doesn't change (still Arrow).
Now, no matter what I do, the cursor keeps as Arrow.

I also observed that the callback to cursor handler (set by Noesis::GUI::SetCursorCallback) is only called twice at step 1 and 2 above, and never called again.

I suspect this is because NoesisGUI's View doesn't get MouseEnter/MouseLeave event forwarded by the client (there no API to forward them, I believe). Thus, it cannot detect that the mouse has leave or newly enter its view, which are the timing to call the callback to notify that the cursor may need to be set again.

Re: Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 12 Jun 2017, 18:33
by sfernandez
I've investigated this issue and it is a bug in our code, that is caching the current cursor on each view, instead of managing a global cursor.
Please report it in our bugtracker and we will fix it as soon as possible.

Thanks.

Re: Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 13 Jun 2017, 02:36
by nikobarli
Thanks for response. I have filed an issue here: https://bugs.noesisengine.com/view.php?id=1105

Re: Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 14 Jun 2017, 03:29
by nikobarli
Probably one easy fix is to provide an API to set cursor callback per view (instead of the current global callback). So each view can call its own callback (using the same logic as the current one), and client code can track the mapping of view -> current cursor and set the cursor appropriately.

Re: Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 14 Jun 2017, 13:38
by jsantos
Probably one easy fix is to provide an API to set cursor callback per view (instead of the current global callback). So each view can call its own callback (using the same logic as the current one), and client code can track the mapping of view -> current cursor and set the cursor appropriately.
We have debated here many times about moving those callbacks per view. But problem is that cursor is global, if we move the callback to the view then the view thinks that the cursor haven't changed but in fact it was changed by another view and it doesn't know about it. This is exactly the problem you are observing now. The alternative would be sending the cursor per frame, even if it is the same than the last frame. This way the client code would be in charge of caching. But this put an extra responsibility in client code. Although probably windows SetCursor is already caching internally.

I think managing the global cursor globally with a single callback will solve your problem. What do you think?

Re: Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 15 Jun 2017, 03:23
by nikobarli
We have debated here many times about moving those callbacks per view. But problem is that cursor is global, if we move the callback to the view then the view thinks that the cursor haven't changed but in fact it was changed by another view and it doesn't know about it. This is exactly the problem you are observing now.
The problem is that the cursor may be changed by not only the other NoesisGUI views, but also by other layers or elements. Easy steps to produce:
1. Move mouse to a NoesisGUI view -> Cursor callback is called, assume that I call ::SetCursor here to change the cursor
2. Move mouse to another arbitrary window (non NoesisGUI view)-> cursor is changed by the window, NoesisGUI cannot be aware of it
3. Move mouse back to the NoesisGUI view in step 1 -> Cursor callback is not called, I cannot change the cursor back

In my current integration code, I don't call ::SetCursor inside the cursor callback, because it's not sufficient as illustrated above. Rather I treat the call to the callback as "Hey, this is your current cursor when your mouse is inside this view. Set it when necessary".
        static auto cursorCallback = [](void* user, Cursor cursor) -> void {
            g_currentCursor = cursor; // remember the last cursor for the view
        };
        Noesis::GUI::SetCursorCallback(nullptr, cursorCallback);
Then I need to handle the WM_SETCURSOR sent by windows as follows. Note that I only set the cursor if the mouse is on client area (HTCLIENT). And let Windows to set the cursor of non-client area (e.g. resize cursor when the mouse is at the corner of the window).
            case WM_SETCURSOR:
            {
                if (LOWORD(lParam) == HTCLIENT) { // only set cursor when the mouse inside client area
                    HCURSOR hc = 0;
                    bool showCursor = true;
                    bool setCursor = true;
                    
                    switch (g_currentCursor) {
                    case Cursor_None: showCursor = false; setCursor = false;
                    case Cursor_No: hc = LoadCursor(NULL, IDC_NO); break;
                    case Cursor_Arrow: hc = LoadCursor(NULL, IDC_ARROW); break;
                    ...
                    default: setCursor = false; break;
                    }

                    if (!showCursor) ::SetCursor(NULL); // hide cursor
                    else if (setCursor) ::SetCursor(hc); // or set the cursor
                }
                break;
The alternative would be sending the cursor per frame, even if it is the same than the last frame. This way the client code would be in charge of caching. But this put an extra responsibility in client code. Although probably windows SetCursor is already caching internally.
I don't think it's a good idea. It's redundant to WM_SETCURSOR as well.
I think managing the global cursor globally with a single callback will solve your problem. What do you think?
Yes, it should also be OK. In that case, I can treat the call to the cursor callback as: "Hey, this is your current cursor when your mouse is inside ANY of NoesisGUI's views. Set it when necessary". It would also be more helpful if you can call the callback anytime the mouse re-enter any of the views. This way, I no longer need to handle WM_SETCURSOR and only need to call ::SetCursor inside the callback.

Re: Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 15 Jun 2017, 13:18
by jsantos
Yes, we are also using the WM_SETCURSOR in our internal window implementation. We plan to release open source all the application framework we have (operating system abstractions for the application loop, window class and render context for all our supported platforms) to ease the integration into client code.
Yes, it should also be OK. In that case, I can treat the call to the cursor callback as: "Hey, this is your current cursor when your mouse is inside ANY of NoesisGUI's views. Set it when necessary". It would also be more helpful if you can call the callback anytime the mouse re-enter any of the views. This way, I no longer need to handle WM_SETCURSOR and only need to call ::SetCursor inside the callback.
Problem is we don't know when the mouse is reentering a view. We would need to add new messages...

Re: Cannot switch cursor correctly between two windows (C++ SDK 2.0.2f2)

Posted: 16 Jun 2017, 01:27
by nikobarli
Yes, we are also using the WM_SETCURSOR in our internal window implementation. We plan to release open source all the application framework we have (operating system abstractions for the application loop, window class and render context for all our supported platforms) to ease the integration into client code.
Yes, that would be great.
Problem is we don't know when the mouse is reentering a view. We would need to add new messages...
Yes, I think you need to get MouseEnter/MouseLeave messages to be forwarded to the IView, which I wrote in the initial message of this topic
I suspect this is because NoesisGUI's View doesn't get MouseEnter/MouseLeave event forwarded by the client (there no API to forward them, I believe). Thus, it cannot detect that the mouse has leave or newly enter its view, which are the timing to call the callback to notify that the cursor may need to be set again.
I added a note about this to the bug report: https://bugs.noesisengine.com/view.php?id=1105