From 6e780808d2d7e03af90df2848bd17f75db26a49a Mon Sep 17 00:00:00 2001 From: michabr <82236305+michabr@users.noreply.github.com> Date: Wed, 28 Apr 2021 15:04:20 -0700 Subject: [PATCH] UI Canvas Editor fixes (#340) * Fix mouse input in UI Editor preview mode * Add textured rect outline drawing for Draw2d --- Gems/LyShine/Code/Editor/ViewportIcon.cpp | 108 +----------- Gems/LyShine/Code/Editor/ViewportWidget.cpp | 9 +- Gems/LyShine/Code/Include/LyShine/Draw2d.h | 50 ++++++ Gems/LyShine/Code/Source/Draw2d.cpp | 172 +++++++++++++++++++- 4 files changed, 229 insertions(+), 110 deletions(-) diff --git a/Gems/LyShine/Code/Editor/ViewportIcon.cpp b/Gems/LyShine/Code/Editor/ViewportIcon.cpp index 1e137fe91b..81ed5dbcb5 100644 --- a/Gems/LyShine/Code/Editor/ViewportIcon.cpp +++ b/Gems/LyShine/Code/Editor/ViewportIcon.cpp @@ -344,7 +344,7 @@ void ViewportIcon::DrawDistanceLineWithTransform(Draw2dHelper& draw2d, AZ::Vecto DrawDistanceLine(draw2d, start2, end2, value, suffix); } -void ViewportIcon::DrawElementRectOutline([[maybe_unused]] Draw2dHelper& draw2d, AZ::EntityId entityId, AZ::Color color) +void ViewportIcon::DrawElementRectOutline(Draw2dHelper& draw2d, AZ::EntityId entityId, AZ::Color color) { // get the transformed rect for the element UiTransformInterface::RectPoints points; @@ -380,109 +380,5 @@ void ViewportIcon::DrawElementRectOutline([[maybe_unused]] Draw2dHelper& draw2d, rightVec.NormalizeSafe(); downVec.NormalizeSafe(); - // calculate the transformed width and height of the rect - // (in case it is smaller than the texture height) - AZ::Vector2 widthVec = points.TopRight() - points.TopLeft(); - AZ::Vector2 heightVec = points.BottomLeft() - points.TopLeft(); - float rectWidth = widthVec.GetLength(); - float rectHeight = heightVec.GetLength(); - - // the outline "width" will be based on the texture height - float textureHeight = GetTextureSize().GetY(); - if (textureHeight <= 0) - { - return; // should never happen - avoiding possible divide by zero later - } - - // the outline is centered on the element rect so half the outline is outside - // the rect and half is inside the rect - float outerOffset = -textureHeight * 0.5f; - float innerOffset = textureHeight * 0.5f; - float outerV = 0.0f; - float innerV = 1.0f; - - // if the rect is small there may not be space for the half of the outline that - // is inside the rect. If this is the case reduce the innerOffset so the inner - // points are coincident. Adjust the UVs according to keep a 1-1 texel to pixel ratio. - float minDimension = min(rectWidth, rectHeight); - if (innerOffset > minDimension * 0.5f) - { - float oldInnerOffset = innerOffset; - innerOffset = minDimension * 0.5f; - // note oldInnerOffset can't be zero because of early return if textureHeight is zero - innerV = 0.5f + 0.5f * innerOffset / oldInnerOffset; - } - - // fill out the 8 verts to define the 2 rectangles - outer and inner - // The vertices are in the order of outer rect then inner rect. e.g.: - // 0 1 - // 4 5 - // 6 7 - // 2 3 - // - const int32 NUM_VERTS = 8; - AZ::Vector2 verts2d[NUM_VERTS] = { - // four verts of outer rect - points.pt[0] + rightVec * outerOffset + downVec * outerOffset, - points.pt[1] - rightVec * outerOffset + downVec * outerOffset, - points.pt[3] + rightVec * outerOffset - downVec * outerOffset, - points.pt[2] - rightVec * outerOffset - downVec * outerOffset, - // four verts of inner rect - points.pt[0] + rightVec * innerOffset + downVec * innerOffset, - points.pt[1] - rightVec * innerOffset + downVec * innerOffset, - points.pt[3] + rightVec * innerOffset - downVec * innerOffset, - points.pt[2] - rightVec * innerOffset - downVec * innerOffset, - }; - - // and define the UV coordinates for these 8 verts - AZ::Vector2 uvs[NUM_VERTS] = { - AZ::Vector2(0.0f, outerV), AZ::Vector2(1.0f, outerV), AZ::Vector2(1.0f, outerV), AZ::Vector2(0.0f, outerV), - AZ::Vector2(0.0f, innerV), AZ::Vector2(1.0f, innerV), AZ::Vector2(1.0f, innerV), AZ::Vector2(0.0f, innerV), - }; - - // Now create the 8 verts in the right vertex format for DrawDynVB - SVF_P3F_C4B_T2F vertices[NUM_VERTS]; - const float z = 1.0f; // depth test disabled, if writing Z this will write at far plane - uint32 packedColor = (color.GetA8() << 24) | (color.GetR8() << 16) | (color.GetG8() << 8) | color.GetB8(); - for (int i = 0; i < NUM_VERTS; ++i) - { - vertices[i].xyz = Vec3(verts2d[i].GetX(), verts2d[i].GetY(), z); - vertices[i].color.dcolor = packedColor; - vertices[i].st = Vec2(uvs[i].GetX(), uvs[i].GetY()); - } - - // The indicies are for four quads (one for each side of the rect). - // The quads are drawn using a triangle list (simpler than a tri-strip) - // We draw each quad in the same order that the image component draws quads to - // maximize chances of things lining up so each quad is drawn as two triangles: - // top-left, top-right, bottom-left / bottom-left, top-right, bottom-right - // e.g. for a quad like this: - // - // 0 1 - // |/| - // 2 3 - // - // The two triangles would be 0,1,2 and 2,1,3 - // - const int32 NUM_INDICES = 24; - uint16 indicies[NUM_INDICES] = - { - 0, 1, 4, 4, 1, 5, // top quad - 6, 7, 2, 2, 7, 3, // bottom quad - 0, 4, 2, 2, 4, 6, // left quad - 5, 1, 7, 1, 7, 3, // right quad - }; - -#ifdef LYSHINE_ATOM_TODO - IRenderer* renderer = gEnv->pRenderer; - renderer->SetTexture(m_texture->GetTextureID()); - - renderer->SetState(GS_BLSRC_SRCALPHA | GS_BLDST_ONEMINUSSRCALPHA | GS_NODEPTHTEST); - renderer->SetColorOp(eCO_MODULATE, eCO_MODULATE, DEF_TEXARG0, DEF_TEXARG0); - - // This will end up using DrawIndexedPrimitive to render the quad - renderer->DrawDynVB(vertices, indicies, NUM_VERTS, NUM_INDICES, prtTriangleList); -#else - // LYSHINE_ATOM_TODO - add option in Draw2d to draw indexed primitive for this textured element outline -#endif + draw2d.DrawRectOutlineTextured(m_image, points, rightVec, downVec, color); } diff --git a/Gems/LyShine/Code/Editor/ViewportWidget.cpp b/Gems/LyShine/Code/Editor/ViewportWidget.cpp index 9956a39647..8861763dcf 100644 --- a/Gems/LyShine/Code/Editor/ViewportWidget.cpp +++ b/Gems/LyShine/Code/Editor/ViewportWidget.cpp @@ -570,7 +570,8 @@ void ViewportWidget::mousePressEvent(QMouseEvent* ev) if (ev->button() == Qt::LeftButton) { // Send event to this canvas - const AZ::Vector2 viewportPosition(aznumeric_cast(ev->x()), aznumeric_cast(ev->y())); + QPointF scaledPos = WidgetToViewport(ev->localPos()); + const AZ::Vector2 viewportPosition(aznumeric_cast(scaledPos.x()), aznumeric_cast(scaledPos.y())); const AzFramework::InputChannel::Snapshot inputSnapshot(AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputDeviceMouse::Id, AzFramework::InputChannel::State::Began); @@ -604,7 +605,8 @@ void ViewportWidget::mouseMoveEvent(QMouseEvent* ev) AZ::EntityId canvasEntityId = m_editorWindow->GetPreviewModeCanvas(); if (canvasEntityId.IsValid()) { - const AZ::Vector2 viewportPosition(aznumeric_cast(ev->x()), aznumeric_cast(ev->y())); + QPointF scaledPos = WidgetToViewport(ev->localPos()); + const AZ::Vector2 viewportPosition(aznumeric_cast(scaledPos.x()), aznumeric_cast(scaledPos.y())); const AzFramework::InputChannelId& channelId = (ev->buttons() & Qt::LeftButton) ? AzFramework::InputDeviceMouse::Button::Left : AzFramework::InputDeviceMouse::SystemCursorPosition; @@ -640,7 +642,8 @@ void ViewportWidget::mouseReleaseEvent(QMouseEvent* ev) if (ev->button() == Qt::LeftButton) { // Send event to this canvas - const AZ::Vector2 viewportPosition(aznumeric_cast(ev->x()), aznumeric_cast(ev->y())); + QPointF scaledPos = WidgetToViewport(ev->localPos()); + const AZ::Vector2 viewportPosition(aznumeric_cast(scaledPos.x()), aznumeric_cast(scaledPos.y())); const AzFramework::InputChannel::Snapshot inputSnapshot(AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputDeviceMouse::Id, AzFramework::InputChannel::State::Ended); diff --git a/Gems/LyShine/Code/Include/LyShine/Draw2d.h b/Gems/LyShine/Code/Include/LyShine/Draw2d.h index ec636c3e3b..40383e5e08 100644 --- a/Gems/LyShine/Code/Include/LyShine/Draw2d.h +++ b/Gems/LyShine/Code/Include/LyShine/Draw2d.h @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -128,6 +129,19 @@ public: // member functions void DrawText(const char* textString, AZ::Vector2 position, float pointSize, float opacity = 1.0f, TextOptions* textOptions = nullptr); + //! Draw a rectangular outline with a texture + // + //! \param image The texture to be used for drawing the outline + //! \param points The rect's vertices (top left, top right, bottom right, bottom left) + //! \param rightVec Right vector. Specified because the rect's width/height could be 0 + //! \param downVec Down vector. Specified because the rect's width/height could be 0 + //! \param color The color of the outline + void DrawRectOutlineTextured(AZ::Data::Instance image, + UiTransformInterface::RectPoints points, + AZ::Vector2 rightVec, + AZ::Vector2 downVec, + AZ::Color color); + //! Get the width and height (in pixels) that would be used to draw the given text string. // //! Pass the same parameter values that would be used to draw the string @@ -243,6 +257,24 @@ protected: // types and constants std::string m_string; }; + class DeferredRectOutline + : public DeferredPrimitive + { + public: + ~DeferredRectOutline() override {}; + void Draw(AZ::RHI::Ptr dynamicDraw, + const Draw2dShaderData& shaderData, + AZ::RPI::ViewportContextPtr viewportContext) const override; + + AZ::Data::Instance m_image; + + static constexpr int32 NUM_VERTS = 8; + AZ::Vector2 m_verts2d[NUM_VERTS]; + AZ::Vector2 m_uvs[NUM_VERTS]; + + AZ::Color m_color; + }; + protected: // member functions //! Rotate an array of points around the z-axis at the pivot point. @@ -261,6 +293,9 @@ protected: // member functions //! Draw or defer a line void DrawOrDeferLine(const DeferredLine* line); + //! Draw or defer a rect outline + void DrawOrDeferRectOutline(const DeferredRectOutline* outlineRect); + //! Get specified viewport context or default viewport context if not specified AZ::RPI::ViewportContextPtr GetViewportContext() const; @@ -394,6 +429,21 @@ public: // member functions } } + //! Draw a rect outline with a texture + // + //! See IDraw2d:DrawRectOutlineTextured for parameter descriptions + void DrawRectOutlineTextured(AZ::Data::Instance image, + UiTransformInterface::RectPoints points, + AZ::Vector2 rightVec, + AZ::Vector2 downVec, + AZ::Color color) + { + if (m_draw2d) + { + m_draw2d->DrawRectOutlineTextured(image, points, rightVec, downVec, color); + } + } + //! Draw a text string. Only supports ASCII text. // //! See IDraw2d:DrawText for parameter descriptions diff --git a/Gems/LyShine/Code/Source/Draw2d.cpp b/Gems/LyShine/Code/Source/Draw2d.cpp index cd15475039..c8ee38cce4 100644 --- a/Gems/LyShine/Code/Source/Draw2d.cpp +++ b/Gems/LyShine/Code/Source/Draw2d.cpp @@ -305,6 +305,89 @@ void CDraw2d::DrawText(const char* textString, AZ::Vector2 position, float point actualTextOptions->baseState); } +void CDraw2d::DrawRectOutlineTextured(AZ::Data::Instance image, + UiTransformInterface::RectPoints points, + AZ::Vector2 rightVec, + AZ::Vector2 downVec, + AZ::Color color) +{ + // since the rect can be transformed we have to add the offsets by multiplying them + // by unit vectors parallel with the edges of the rect. However, the rect could be + // zero width and/or height so we can't use "points" to compute these unit vectors. + // So we instead get two transformed unit vectors and then normalize them + rightVec.NormalizeSafe(); + downVec.NormalizeSafe(); + + // calculate the transformed width and height of the rect + // (in case it is smaller than the texture height) + AZ::Vector2 widthVec = points.TopRight() - points.TopLeft(); + AZ::Vector2 heightVec = points.BottomLeft() - points.TopLeft(); + float rectWidth = widthVec.GetLength(); + float rectHeight = heightVec.GetLength(); + + // the outline thickness will be based on the texture height + float textureHeight = image ? aznumeric_cast(image->GetDescriptor().m_size.m_height) : 0.0f; + if (textureHeight <= 0.0f) + { + AZ_Assert(false, "Attempting to draw a textured rect outline with an image of zero height."); + return; // avoiding possible divide by zero later + } + + // the outline is centered on the element rect so half the outline is outside + // the rect and half is inside the rect + float outerOffset = -textureHeight * 0.5f; + float innerOffset = textureHeight * 0.5f; + float outerV = 0.0f; + float innerV = 1.0f; + + // if the rect is small there may not be space for the half of the outline that + // is inside the rect. If this is the case reduce the innerOffset so the inner + // points are coincident. Adjust the UVs according to keep a 1-1 texel to pixel ratio. + float minDimension = min(rectWidth, rectHeight); + if (innerOffset > minDimension * 0.5f) + { + float oldInnerOffset = innerOffset; + innerOffset = minDimension * 0.5f; + // note oldInnerOffset can't be zero because of early return if textureHeight is zero + innerV = 0.5f + 0.5f * innerOffset / oldInnerOffset; + } + + DeferredRectOutline rectOutline; + + // fill out the 8 verts to define the 2 rectangles - outer and inner + // The vertices are in the order of outer rect then inner rect. e.g.: + // 0 1 + // 4 5 + // 6 7 + // 2 3 + // + // four verts of outer rect + rectOutline.m_verts2d[0] = points.pt[0] + rightVec * outerOffset + downVec * outerOffset; + rectOutline.m_verts2d[1] = points.pt[1] - rightVec * outerOffset + downVec * outerOffset; + rectOutline.m_verts2d[2] = points.pt[3] + rightVec * outerOffset - downVec * outerOffset; + rectOutline.m_verts2d[3] = points.pt[2] - rightVec * outerOffset - downVec * outerOffset; + // four verts of inner rect + rectOutline.m_verts2d[4] = points.pt[0] + rightVec * innerOffset + downVec * innerOffset; + rectOutline.m_verts2d[5] = points.pt[1] - rightVec * innerOffset + downVec * innerOffset; + rectOutline.m_verts2d[6] = points.pt[3] + rightVec * innerOffset - downVec * innerOffset; + rectOutline.m_verts2d[7] = points.pt[2] - rightVec * innerOffset - downVec * innerOffset; + + // and define the UV coordinates for these 8 verts + rectOutline.m_uvs[0] = AZ::Vector2(0.0f, outerV); + rectOutline.m_uvs[1] = AZ::Vector2(1.0f, outerV); + rectOutline.m_uvs[2] = AZ::Vector2(1.0f, outerV); + rectOutline.m_uvs[3] = AZ::Vector2(0.0f, outerV); + rectOutline.m_uvs[4] = AZ::Vector2(0.0f, innerV); + rectOutline.m_uvs[5] = AZ::Vector2(1.0f, innerV); + rectOutline.m_uvs[6] = AZ::Vector2(1.0f, innerV); + rectOutline.m_uvs[7] = AZ::Vector2(0.0f, innerV); + + rectOutline.m_image = image; + rectOutline.m_color = color; + + DrawOrDeferRectOutline(&rectOutline); +} + //////////////////////////////////////////////////////////////////////////////////////////////////// // TBD should the size include the offset of the drop shadow (if any)? //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -592,6 +675,20 @@ void CDraw2d::DrawOrDeferLine(const DeferredLine* line) } } +void CDraw2d::DrawOrDeferRectOutline(const DeferredRectOutline* rectOutline) +{ + if (m_deferCalls) + { + DeferredRectOutline* newRectOutline = new DeferredRectOutline; + *newRectOutline = *rectOutline; + m_deferredPrimitives.push_back(newRectOutline); + } + else + { + rectOutline->Draw(m_dynamicDraw, m_shaderData, GetViewportContext()); + } +} + //////////////////////////////////////////////////////////////////////////////////////////////////// AZ::RPI::ViewportContextPtr CDraw2d::GetViewportContext() const { @@ -604,7 +701,6 @@ AZ::RPI::ViewportContextPtr CDraw2d::GetViewportContext() const // Return the user specified viewport context return m_viewportContext; - } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -729,6 +825,80 @@ void CDraw2d::DeferredLine::Draw(AZ::RHI::Ptr dynam dynamicDraw->DrawLinear(vertices, NUM_VERTS, drawSrg); } +void CDraw2d::DeferredRectOutline::Draw(AZ::RHI::Ptr dynamicDraw, + const Draw2dShaderData& shaderData, + AZ::RPI::ViewportContextPtr viewportContext) const +{ + // Create the 8 verts in the right vertex format for the dynamic draw context + SVF_P3F_C4B_T2F vertices[NUM_VERTS]; + const float z = 1.0f; // depth test disabled, if writing Z this will write at far plane + uint32 packedColor = (m_color.GetA8() << 24) | (m_color.GetR8() << 16) | (m_color.GetG8() << 8) | m_color.GetB8(); + for (int i = 0; i < NUM_VERTS; ++i) + { + vertices[i].xyz = Vec3(m_verts2d[i].GetX(), m_verts2d[i].GetY(), z); + vertices[i].color.dcolor = packedColor; + vertices[i].st = Vec2(m_uvs[i].GetX(), m_uvs[i].GetY()); + } + + // The indices are for four quads (one for each side of the rect). + // The quads are drawn using a triangle list (simpler than a tri-strip) + // We draw each quad in the same order that the image component draws quads to + // maximize chances of things lining up so each quad is drawn as two triangles: + // top-left, top-right, bottom-left / bottom-left, top-right, bottom-right + // e.g. for a quad like this: + // + // 0 1 + // |/| + // 2 3 + // + // The two triangles would be 0,1,2 and 2,1,3 + // + static constexpr int32 NUM_INDICES = 24; + uint16 indices[NUM_INDICES] = + { + 0, 1, 4, 4, 1, 5, // top quad + 6, 7, 2, 2, 7, 3, // bottom quad + 0, 4, 2, 2, 4, 6, // left quad + 5, 1, 7, 1, 7, 3, // right quad + }; + + // Set up per draw SRG + AZ::Data::Instance drawSrg = dynamicDraw->NewDrawSrg(); + + // Set texture + const AZ::RHI::ImageView* imageView = m_image ? m_image->GetImageView() : nullptr; + if (!imageView) + { + // Default to white texture + auto image = AZ::RPI::ImageSystemInterface::Get()->GetSystemImage(AZ::RPI::SystemImage::White); + imageView = image->GetImageView(); + } + + if (imageView) + { + drawSrg->SetImageView(shaderData.m_imageInputIndex, imageView, 0); + } + + // Set projection matrix + auto windowContext = viewportContext->GetWindowContext(); + const AZ::RHI::Viewport& viewport = windowContext->GetViewport(); + const float viewX = viewport.m_minX; + const float viewY = viewport.m_minY; + const float viewWidth = viewport.m_maxX - viewport.m_minX; + const float viewHeight = viewport.m_maxY - viewport.m_minY; + const float zf = viewport.m_minZ; + const float zn = viewport.m_maxZ; + AZ::Matrix4x4 modelViewProjMat; + AZ::MakeOrthographicMatrixRH(modelViewProjMat, viewX, viewX + viewWidth, viewY + viewHeight, viewY, zn, zf); + drawSrg->SetConstant(shaderData.m_viewProjInputIndex, modelViewProjMat); + + drawSrg->Compile(); + + // Add the primitive to the dynamic draw context for drawing + dynamicDraw->SetPrimitiveType(AZ::RHI::PrimitiveTopology::TriangleList); + dynamicDraw->DrawIndexed(vertices, NUM_VERTS, indices, NUM_INDICES, AZ::RHI::IndexFormat::Uint16, drawSrg); + +} //////////////////////////////////////////////////////////////////////////////////////////////////// // CDraw2d::DeferredText