Parcourir la source

Reset UI cursor shape during BeginFrame event, which allows custom logic to set it per frame. Apply OS cursor shape only once during UI rendering to avoid flicker (and potential loss of performance)

Lasse Öörni il y a 12 ans
Parent
commit
d2a7173e5f

+ 1 - 1
Bin/Data/Scripts/Editor/EditorView.as

@@ -959,7 +959,7 @@ void UpdateView(float timeStep)
             UpdateNodeAttributes();
     }
 
-    // if not dragging
+    // If not dragging
     if (resizingBorder == 0)
     {
         UIElement@ uiElement = ui.GetElementAt(ui.cursorPosition);

+ 68 - 58
Source/Engine/UI/Cursor.cpp

@@ -67,7 +67,8 @@ extern const char* UI_CATEGORY;
 Cursor::Cursor(Context* context) :
     BorderImage(context),
     shape_(CS_NORMAL),
-    useSystemShapes_(false)
+    useSystemShapes_(false),
+    osShapeDirty_(false)
 {
     // Subscribe to OS mouse cursor visibility changes to be able to reapply the cursor shape
     SubscribeToEvent(E_MOUSEVISIBLECHANGED, HANDLER(Cursor, HandleMouseVisibleChanged));
@@ -96,14 +97,17 @@ void Cursor::RegisterObject(Context* context)
     ACCESSOR_ATTRIBUTE(Cursor, VAR_VARIANTVECTOR, "Shapes", GetShapesAttr, SetShapesAttr, VariantVector, Variant::emptyVariantVector, AM_FILE);
 }
 
-void Cursor::SetUseSystemShapes(bool enable)
+void Cursor::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
 {
-    if (enable != useSystemShapes_)
-    {
-        useSystemShapes_ = enable;
+    unsigned initialSize = vertexData.Size();
+    const IntVector2& offset = shapeInfos_[shape_].hotSpot_;
+    Vector2 floatOffset(-(float)offset.x_, -(float)offset.y_);
 
-        // Reapply current shape
-        ApplyShape();
+    BorderImage::GetBatches(batches, vertexData, currentScissor);
+    for (unsigned i = initialSize; i < vertexData.Size(); i += 6)
+    {
+        vertexData[i] += floatOffset.x_;
+        vertexData[i + 1] += floatOffset.y_;
     }
 }
 
@@ -144,7 +148,10 @@ void Cursor::DefineShape(CursorShape shape, Image* image, const IntRect& imageRe
 
     // Reset current shape if it was edited
     if (shape == shape_)
-        ApplyShape();
+    {
+        shape_ = CS_MAX_SHAPES;
+        SetShape(shape);
+    }
 }
 
 void Cursor::SetShape(CursorShape shape)
@@ -153,7 +160,27 @@ void Cursor::SetShape(CursorShape shape)
         return;
 
     shape_ = shape;
-    ApplyShape();
+    
+    CursorShapeInfo& info = shapeInfos_[shape_];
+    texture_ = info.texture_;
+    imageRect_ = info.imageRect_;
+    SetSize(info.imageRect_.Size());
+    
+    // To avoid flicker, the UI subsystem will apply the OS shape once per frame. Exception: if we are using the
+    // busy shape, set it immediately as we may block before that
+    osShapeDirty_ = true;
+    if (shape_ == CS_BUSY)
+        ApplyOSCursorShape();
+}
+
+void Cursor::SetUseSystemShapes(bool enable)
+{
+    if (enable != useSystemShapes_)
+    {
+        useSystemShapes_ = enable;
+        // Reapply current shape
+        osShapeDirty_ = true;
+    }
 }
 
 void Cursor::SetShapesAttr(VariantVector value)
@@ -204,73 +231,56 @@ VariantVector Cursor::GetShapesAttr() const
     return ret;
 }
 
-void Cursor::GetBatches(PODVector<UIBatch>& batches, PODVector<float>& vertexData, const IntRect& currentScissor)
+void Cursor::ApplyOSCursorShape()
 {
-    unsigned initialSize = vertexData.Size();
-    const IntVector2& offset = shapeInfos_[shape_].hotSpot_;
-    Vector2 floatOffset(-(float)offset.x_, -(float)offset.y_);
+    if (!osShapeDirty_ || !GetSubsystem<Input>()->IsMouseVisible() || GetSubsystem<UI>()->GetCursor() != this)
+        return;
 
-    BorderImage::GetBatches(batches, vertexData, currentScissor);
-    for (unsigned i = initialSize; i < vertexData.Size(); i += 6)
+    CursorShapeInfo& info = shapeInfos_[shape_];
+    
+    // Remove existing SDL cursor if is not a system shape while we should be using those, or vice versa
+    if (info.osCursor_ && info.systemDefined_ != useSystemShapes_)
     {
-        vertexData[i] += floatOffset.x_;
-        vertexData[i + 1] += floatOffset.y_;
+        SDL_FreeCursor(info.osCursor_);
+        info.osCursor_ = 0;
     }
-}
-
-void Cursor::ApplyShape()
-{
-    CursorShapeInfo& info = shapeInfos_[shape_];
-    texture_ = info.texture_;
-    imageRect_ = info.imageRect_;
-    SetSize(info.imageRect_.Size());
 
-    // If the OS cursor is being shown, define/set SDL cursor shape if necessary
-    // Only do this when we are the active UI cursor
-    if (GetSubsystem<Input>()->IsMouseVisible() && GetSubsystem<UI>()->GetCursor() == this)
+    // Create SDL cursor now if necessary
+    if (!info.osCursor_)
     {
-        // Remove existing SDL cursor if is not a system shape while we should be using those, or vice versa
-        if (info.osCursor_ && info.systemDefined_ != useSystemShapes_)
+        // Create a system default shape
+        if (useSystemShapes_)
         {
-            SDL_FreeCursor(info.osCursor_);
-            info.osCursor_ = 0;
+            info.osCursor_ = SDL_CreateSystemCursor((SDL_SystemCursor)osCursorLookup[shape_]);
+            info.systemDefined_ = true;
+            if (!info.osCursor_)
+                LOGERROR("Could not create system cursor");
         }
-
-        // Create SDL cursor now if necessary
-        if (!info.osCursor_)
+        // Create from image
+        else if (info.image_)
         {
-            // Create a system default shape
-            if (useSystemShapes_)
+            SDL_Surface* surface = info.image_->GetSDLSurface(info.imageRect_);
+            
+            if (surface)
             {
-                info.osCursor_ = SDL_CreateSystemCursor((SDL_SystemCursor)osCursorLookup[shape_]);
-                info.systemDefined_ = true;
+                info.osCursor_ = SDL_CreateColorCursor(surface, info.hotSpot_.x_, info.hotSpot_.y_);
+                info.systemDefined_ = false;
                 if (!info.osCursor_)
-                    LOGERROR("Could not create system cursor");
-            }
-            // Create from image
-            else if (info.image_)
-            {
-                SDL_Surface* surface = info.image_->GetSDLSurface(info.imageRect_);
-                
-                if (surface)
-                {
-                    info.osCursor_ = SDL_CreateColorCursor(surface, info.hotSpot_.x_, info.hotSpot_.y_);
-                    info.systemDefined_ = false;
-                    if (!info.osCursor_)
-                        LOGERROR("Could not create cursor from image " + info.image_->GetName());
-                    SDL_FreeSurface(surface);
-                }
+                    LOGERROR("Could not create cursor from image " + info.image_->GetName());
+                SDL_FreeSurface(surface);
             }
         }
-
-        if (info.osCursor_)
-            SDL_SetCursor(info.osCursor_);
     }
+
+    if (info.osCursor_)
+        SDL_SetCursor(info.osCursor_);
+    
+    osShapeDirty_ = false;
 }
 
 void Cursor::HandleMouseVisibleChanged(StringHash eventType, VariantMap& eventData)
 {
-    ApplyShape();
+    ApplyOSCursorShape();
 }
 
 }

+ 5 - 3
Source/Engine/UI/Cursor.h

@@ -102,10 +102,10 @@ public:
     void SetShapesAttr(VariantVector value);
     /// Return shapes attribute.
     VariantVector GetShapesAttr() const;
-
+    /// Apply pending OS cursor shape. Called by UI. No-op when the OS mouse pointer is not used.
+    void ApplyOSCursorShape();
+    
 protected:
-    /// Apply the current shape.
-    void ApplyShape();
     /// Handle operating system mouse cursor visibility change event.
     void HandleMouseVisibleChanged(StringHash eventType, VariantMap& eventData);
     
@@ -115,6 +115,8 @@ protected:
     CursorShapeInfo shapeInfos_[CS_MAX_SHAPES];
     /// Use system default shapes flag.
     bool useSystemShapes_;
+    /// OS cursor shape needs update flag.
+    bool osShapeDirty_;
 };
 
 }

+ 14 - 4
Source/Engine/UI/UI.cpp

@@ -367,6 +367,11 @@ void UI::Render()
 {
     PROFILE(RenderUI);
 
+    // If the OS cursor is visible, apply its shape now if changed
+    bool osCursorVisible = GetSubsystem<Input>()->IsMouseVisible();
+    if (cursor_ && osCursorVisible)
+        cursor_->ApplyOSCursorShape();
+    
     SetVertexData(vertexBuffer_, vertexData_);
     SetVertexData(debugVertexBuffer_, debugVertexData_);
 
@@ -613,6 +618,7 @@ void UI::Initialize()
 
     initialized_ = true;
 
+    SubscribeToEvent(E_BEGINFRAME, HANDLER(UI, HandleBeginFrame));
     SubscribeToEvent(E_POSTUPDATE, HANDLER(UI, HandlePostUpdate));
     SubscribeToEvent(E_RENDERUPDATE, HANDLER(UI, HandleRenderUpdate));
 
@@ -905,8 +911,6 @@ void UI::ProcessHover(const IntVector2& cursorPos, int buttons, int qualifiers,
         if (!dragElement_ || dragElement_ == element || dragDropTest)
             element->OnHover(element->ScreenToElement(cursorPos), cursorPos, buttons, qualifiers, cursor);
     }
-    else if (cursor)
-        cursor->SetShape(CS_NORMAL);
 
     // Drag and drop test
     if (dragDropTest)
@@ -1006,8 +1010,6 @@ void UI::ProcessClickEnd(const IntVector2& cursorPos, int button, int buttons, i
             {
                 dragElement_->OnDragEnd(dragElement_->ScreenToElement(cursorPos), cursorPos, cursor);
                 SendDragEvent(E_DRAGEND, dragElement_, cursorPos);
-                if (cursor)
-                    cursor->SetShape(CS_NORMAL);
 
                 bool dragSource = dragElement_ && (dragElement_->GetDragDropMode() & DD_SOURCE) != 0;
                 if (dragSource)
@@ -1350,6 +1352,14 @@ void UI::HandleChar(StringHash eventType, VariantMap& eventData)
         element->OnChar(eventData[P_CHAR].GetInt(), mouseButtons_, qualifiers_);
 }
 
+void UI::HandleBeginFrame(StringHash eventType, VariantMap& eventData)
+{
+    // If have a cursor, and a drag is not going on, reset the cursor shape. Application logic that wants to apply
+    // custom shapes can do it after this, but needs to do it each frame
+    if (cursor_ && !dragElement_)
+        cursor_->SetShape(CS_NORMAL);
+}
+
 void UI::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
 {
     using namespace PostUpdate;

+ 2 - 0
Source/Engine/UI/UI.h

@@ -192,6 +192,8 @@ private:
     void HandleKeyDown(StringHash eventType, VariantMap& eventData);
     /// Handle character event.
     void HandleChar(StringHash eventType, VariantMap& eventData);
+    /// Handle frame begin event.
+    void HandleBeginFrame(StringHash eventType, VariantMap& eventData);
     /// Handle logic post-update event.
     void HandlePostUpdate(StringHash eventType, VariantMap& eventData);
     /// Handle render update event.

+ 0 - 3
Source/Engine/UI/UIElement.cpp

@@ -480,9 +480,6 @@ const IntVector2& UIElement::GetScreenPosition() const
 
 void UIElement::OnHover(const IntVector2& position, const IntVector2& screenPosition, int buttons, int qualifiers, Cursor* cursor)
 {
-    // Reset cursor shape on hover, but do not do that unnecessarily during drag
-    if (cursor && cursor->IsVisible() && !GetSubsystem<UI>()->GetDragElement())
-        cursor->SetShape(CS_NORMAL);
     hovering_ = true;
 }