I confirm that this indeed happens, for us 4.17.2. Quick workaround - on BeginPlay in PlayerController refresh the cursor and call the WINAPI functions again. I have a custom solution to the entire issue, but basically what I did was calling UGameViewportClient::RebuildCursors, only for hardware type, only for the one single type I needed. More or less what I am actually calling (from within child class of UGameViewportClient):
TSharedPtr<FHardwareCursor> HardwareCursor = HardwareCursors.FindRef(EMouseCursor::Type::Default);
TSharedPtr<ICursor> PlatformCursor = FSlateApplication::Get().GetPlatformCursor();
if ( ICursor* Cursor = PlatformCursor.Get() )
{
Cursor->SetTypeShape(EMouseCursor::Type::Default, HardwareCursor->GetHandle());
}
EDIT: Too fast This partially fixes the issue, since something else calls refresh again and clears the custom texture leaving you with windows default hardware cursor again. Generally, the issue is that “bIsMouseOverClient” is initialized to false and when the first time the hardware cursor is set this is still false (and thus if’ed out the actual set call from within UGameViewportClient::SetHardwareCursor) because MouseEnter never comes - since it was always on the viewport. From my understanding this bool should be set appropriately upon viewport Init() call. I’ll dig deeper.
EDIT2: Ok, got it. This code:
#if WITH_EDITOR
// NOTE: Only do this in editor builds where the editor is running.
// We don't care about bothering to clear them otherwise, and it may negatively impact
// things like drag/drop, since those would 'leave' the viewport.
if ( !FSlateApplication::Get().IsDragDropping() )
{
bIsMouseOverClient = false;
ResetHardwareCursorStates();
}
#endif
Causes the cursor to reset to regular windows one. Why? because upon slate tick, the app thinks that the cursor left the viewport, despite the coordinates being exactly the same. It gets a bit more complicated the deeper I venture into Slate, but it seems as if the viewport is created once with some size and has the mouse on itself (without calling MouseEnter), then it gets destructed and a new one is created, with 0x0 size, therefore during this SlateTick it seems as if the widget really lost mouse. I’m not fixing this, I’ll just comment out the entire code I pasted above. I believe someone more competent at Epic should look into this. This is as far as I’ve got (SlateApplication.cpp)
TOptional<FArrangedWidget> FoundWidget = WidgetsUnderPointer.FindArrangedWidget( SomeWidgetPreviouslyUnderCursor.ToSharedRef() );
It does not find the viewport anymore, so the previous viewport gets mouseleave, resetting the textures to windows default ones. Commenting the code (or just using binary without editor) works flawlessly. @OP - cook it and see how it works. of course, you still need to initialize the bIsMouseOverClient property to true, which you cannot do without source. Works flawlessly.
LASTEDIT: As a side note, it would be nice to make a Getter for bUseSoftwareCursorWidgets. There’s only setter