bool FSpriteGeometryEditMode::InputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event) { bool bHandled = false; FInputEventState InputState(Viewport, Key, Event); // Handle marquee tracking in source region edit mode if (IsEditingGeometry()) { if (SpriteGeometryHelper.IsAddingPolygon()) { if (Key == EKeys::LeftMouseButton) { const int32 HitX = Viewport->GetMouseX(); const int32 HitY = Viewport->GetMouseY(); // Calculate the texture space position of the mouse click FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(Viewport, ViewportClient->GetScene(), ViewportClient->EngineShowFlags)); FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily); const FVector WorldPoint = View->PixelToWorld(HitX, HitY, 0); const FVector2D TexturePoint = SpriteGeometryHelper.GetEditorContext()->WorldSpaceToTextureSpace(WorldPoint); // Add or close the polygon (depending on where the click happened and how) const bool bMakeSubtractiveIfAllowed = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); SpriteGeometryHelper.HandleAddPolygonClick(TexturePoint, bMakeSubtractiveIfAllowed, *View, Event); } else if ((Key == EKeys::BackSpace) && (Event == IE_Pressed)) { SpriteGeometryHelper.DeleteLastVertexFromAddPolygonMode(); } else if (Key == EKeys::Enter) { SpriteGeometryHelper.ResetAddPolygonMode(); } else if (Key == EKeys::Escape) { SpriteGeometryHelper.AbandonAddPolygonMode(); } } else { if (ProcessMarquee(Viewport, Key, Event, true)) { const bool bAddingToSelection = InputState.IsShiftButtonPressed(); //@TODO: control button moves widget? Hopefully make this more consistent when that is changed SelectVerticesInMarquee(ViewportClient, Viewport, bAddingToSelection); } } } //@TODO: Support select-and-drag in a single operation (may involve InputAxis and StartTracking) // Pass keys to standard controls, if we didn't consume input return bHandled ? true : FEdMode::InputKey(ViewportClient, Viewport, Key, Event); }
void FSpriteEditorViewportClient::ProcessClick(FSceneView& View, HHitProxy* HitProxy, FKey Key, EInputEvent Event, uint32 HitX, uint32 HitY) { const FViewportClick Click(&View, this, Key, Event, HitX, HitY); const bool bIsCtrlKeyDown = Viewport->KeyState(EKeys::LeftControl) || Viewport->KeyState(EKeys::RightControl); const bool bIsShiftKeyDown = Viewport->KeyState(EKeys::LeftShift) || Viewport->KeyState(EKeys::RightShift); const bool bIsAltKeyDown = Viewport->KeyState(EKeys::LeftAlt) || Viewport->KeyState(EKeys::RightAlt); bool bHandled = false; HSpriteSelectableObjectHitProxy* SelectedItemProxy = HitProxyCast<HSpriteSelectableObjectHitProxy>(HitProxy); if (IsInSourceRegionEditMode()) { if ((Event == EInputEvent::IE_DoubleClick) && (Key == EKeys::LeftMouseButton)) { FVector4 WorldPoint = View.PixelToWorld(HitX, HitY, 0); UPaperSprite* Sprite = GetSpriteBeingEdited(); FVector2D TexturePoint = SourceTextureViewComponent->GetSprite()->ConvertWorldSpaceToTextureSpace(WorldPoint); if (bIsCtrlKeyDown) { const FVector2D StartingUV = Sprite->GetSourceUV(); const FVector2D StartingSize = Sprite->GetSourceSize(); if (UPaperSprite* NewSprite = CreateNewSprite(FIntPoint((int32)StartingUV.X, (int32)StartingUV.Y), FIntPoint((int32)StartingSize.X, (int32)StartingSize.Y))) { NewSprite->ExtractSourceRegionFromTexturePoint(TexturePoint); bHandled = true; } } else { Sprite->ExtractSourceRegionFromTexturePoint(TexturePoint); bHandled = true; } } else if ((Event == EInputEvent::IE_Released) && (Key == EKeys::LeftMouseButton)) { FVector4 WorldPoint = View.PixelToWorld(HitX, HitY, 0); FVector2D TexturePoint = SourceTextureViewComponent->GetSprite()->ConvertWorldSpaceToTextureSpace(WorldPoint); for (int32 RelatedSpriteIndex = 0; RelatedSpriteIndex < RelatedSprites.Num(); ++RelatedSpriteIndex) { FRelatedSprite& RelatedSprite = RelatedSprites[RelatedSpriteIndex]; if ((TexturePoint.X >= RelatedSprite.SourceUV.X) && (TexturePoint.Y >= RelatedSprite.SourceUV.Y) && (TexturePoint.X < (RelatedSprite.SourceUV.X + RelatedSprite.SourceDimension.X)) && (TexturePoint.Y < (RelatedSprite.SourceUV.Y + RelatedSprite.SourceDimension.Y))) { bHandled = true; // Select this sprite if (UPaperSprite* LoadedSprite = Cast<UPaperSprite>(RelatedSprite.AssetData.GetAsset())) { if (SpriteEditorPtr.IsValid()) { SpriteEditorPtr.Pin()->SetSpriteBeingEdited(LoadedSprite); break; } } } } } } if (!bHandled) { FPaperEditorViewportClient::ProcessClick(View, HitProxy, Key, Event, HitX, HitY); } }
void FSpriteGeometryEditingHelper::DrawGeometry_CanvasPass(FViewport& InViewport, const FSceneView& View, FCanvas& Canvas, /*inout*/ int32& YPos, const FLinearColor& GeometryVertexColor, const FLinearColor& NegativeGeometryVertexColor) { if (GeometryBeingEdited == nullptr) { return; } // Calculate the texture-space position of the mouse const FVector MousePositionWorldSpace = View.PixelToWorld(InViewport.GetMouseX(), InViewport.GetMouseY(), 0); const FVector2D MousePositionTextureSpace = EditorContext->WorldSpaceToTextureSpace(MousePositionWorldSpace); //@TODO: Move all of the line drawing to the PDI pass FSpriteGeometryCollection& Geometry = GetGeometryChecked(); // Display tool help { static const FText GeomHelpStr = LOCTEXT("GeomEditHelp", "Shift + click to insert a vertex.\nSelect one or more vertices and press Delete to remove them.\nDouble click a vertex to select a polygon\n"); static const FText GeomClickAddPolygon_NoSubtractive = LOCTEXT("GeomClickAddPolygon_NoSubtractive", "Click to start creating a polygon\n"); static const FText GeomClickAddPolygon_AllowSubtractive = LOCTEXT("GeomClickAddPolygon_AllowSubtractive", "Click to start creating a polygon\nCtrl + Click to start creating a subtractive polygon\n"); static const FText GeomAddVerticesHelpStr = LOCTEXT("GeomClickAddVertices", "Click to add points to the polygon\nDouble-click to add a point and close the shape\nClick again on the first point or press Enter to close the shape\nPress Backspace to remove the last added point or Escape to remove the shape\n"); FLinearColor ToolTextColor = FLinearColor::White; const FText* HelpStr; if (IsAddingPolygon()) { if (AddingPolygonIndex == INDEX_NONE) { HelpStr = bAllowSubtractivePolygons ? &GeomClickAddPolygon_AllowSubtractive : &GeomClickAddPolygon_NoSubtractive; } else { HelpStr = &GeomAddVerticesHelpStr; } ToolTextColor = FLinearColor::Yellow; } else { HelpStr = &GeomHelpStr; } FCanvasTextItem TextItem(FVector2D(6, YPos), *HelpStr, GEngine->GetSmallFont(), ToolTextColor); TextItem.EnableShadow(FLinearColor::Black); TextItem.Draw(&Canvas); YPos += 54; } const bool bIsHitTesting = Canvas.IsHitTesting(); // Run thru the geometry shapes and draw hit proxies for them for (int32 ShapeIndex = 0; ShapeIndex < Geometry.Shapes.Num(); ++ShapeIndex) { const FSpriteGeometryShape& Shape = Geometry.Shapes[ShapeIndex]; const bool bIsShapeSelected = IsGeometrySelected(FShapeVertexPair(ShapeIndex, INDEX_NONE)); const FLinearColor LineColorRaw = Shape.bNegativeWinding ? NegativeGeometryVertexColor : GeometryVertexColor; const FLinearColor VertexColor = Shape.bNegativeWinding ? NegativeGeometryVertexColor : GeometryVertexColor; const FLinearColor LineColor = Shape.IsShapeValid() ? LineColorRaw : FMath::Lerp(LineColorRaw, FLinearColor::Red, 0.8f); // Draw the circle shape if necessary if (Shape.ShapeType == ESpriteShapeType::Circle) { if (bIsHitTesting) { TSharedPtr<FSpriteSelectedShape> Data = MakeShareable(new FSpriteSelectedShape(EditorContext, Geometry, ShapeIndex, /*bIsBackground=*/ false)); Canvas.SetHitProxy(new HSpriteSelectableObjectHitProxy(Data)); } // Draw the circle const float RadiusX = Shape.BoxSize.X * 0.5f; const float RadiusY = Shape.BoxSize.Y * 0.5f; const float AngleDelta = 2.0f * PI / SpriteEditingConstantsEX::CircleShapeNumSides; const float LastX = Shape.BoxPosition.X + RadiusX; const float LastY = Shape.BoxPosition.Y; FVector2D LastVertexPos = TextureSpaceToScreenSpace(View, FVector2D(LastX, LastY)); for (int32 SideIndex = 0; SideIndex < SpriteEditingConstantsEX::CircleShapeNumSides; SideIndex++) { const float X = Shape.BoxPosition.X + RadiusX * FMath::Cos(AngleDelta * (SideIndex + 1)); const float Y = Shape.BoxPosition.Y + RadiusY * FMath::Sin(AngleDelta * (SideIndex + 1)); const FVector2D ScreenPos = TextureSpaceToScreenSpace(View, FVector2D(X, Y)); FCanvasLineItem LineItem(LastVertexPos, ScreenPos); LineItem.SetColor(bIsShapeSelected ? SpriteEditingConstantsEX::GeometrySelectedColor : LineColor); LineItem.LineThickness = SpriteEditingConstantsEX::GeometryBorderLineThickness; Canvas.DrawItem(LineItem); LastVertexPos = ScreenPos; } if (bIsHitTesting) { Canvas.SetHitProxy(nullptr); } } // Draw lines connecting the vertices of the shape for (int32 VertexIndex = 0; VertexIndex < Shape.Vertices.Num(); ++VertexIndex) { const int32 NextVertexIndex = (VertexIndex + 1) % Shape.Vertices.Num(); const FVector2D ScreenPos = TextureSpaceToScreenSpace(View, Shape.ConvertShapeSpaceToTextureSpace(Shape.Vertices[VertexIndex])); const FVector2D NextScreenPos = TextureSpaceToScreenSpace(View, Shape.ConvertShapeSpaceToTextureSpace(Shape.Vertices[NextVertexIndex])); const bool bIsThisVertexSelected = IsGeometrySelected(FShapeVertexPair(ShapeIndex, VertexIndex)); const bool bIsNextVertexSelected = IsGeometrySelected(FShapeVertexPair(ShapeIndex, NextVertexIndex)); const bool bIsEdgeSelected = bIsShapeSelected || (bIsThisVertexSelected && bIsNextVertexSelected); // Draw the normal tick if (bShowNormals) { const FVector2D Direction = (NextScreenPos - ScreenPos).GetSafeNormal(); const FVector2D Normal = FVector2D(-Direction.Y, Direction.X); const FVector2D Midpoint = (ScreenPos + NextScreenPos) * 0.5f; const FVector2D NormalPoint = Midpoint - Normal * SpriteEditingConstantsEX::GeometryNormalLength; FCanvasLineItem LineItem(Midpoint, NormalPoint); LineItem.SetColor(SpriteEditingConstantsEX::GeometryNormalColor); Canvas.DrawItem(LineItem); } // Draw the edge { if (bIsHitTesting) { TSharedPtr<FSpriteSelectedEdge> Data = MakeShareable(new FSpriteSelectedEdge(EditorContext, Geometry, ShapeIndex, VertexIndex)); Canvas.SetHitProxy(new HSpriteSelectableObjectHitProxy(Data)); } FCanvasLineItem LineItem(ScreenPos, NextScreenPos); LineItem.SetColor(bIsEdgeSelected ? SpriteEditingConstantsEX::GeometrySelectedColor : LineColor); LineItem.LineThickness = SpriteEditingConstantsEX::GeometryBorderLineThickness; Canvas.DrawItem(LineItem); if (bIsHitTesting) { Canvas.SetHitProxy(nullptr); } } } // Draw the vertices for (int32 VertexIndex = 0; VertexIndex < Shape.Vertices.Num(); ++VertexIndex) { const FVector2D ScreenPos = TextureSpaceToScreenSpace(View, Shape.ConvertShapeSpaceToTextureSpace(Shape.Vertices[VertexIndex])); const float X = ScreenPos.X; const float Y = ScreenPos.Y; const bool bIsVertexSelected = IsGeometrySelected(FShapeVertexPair(ShapeIndex, VertexIndex)); const bool bIsVertexLastAdded = IsAddingPolygon() && (AddingPolygonIndex == ShapeIndex) && (VertexIndex == Shape.Vertices.Num() - 1); const bool bNeedHighlightVertex = bIsShapeSelected || bIsVertexSelected || bIsVertexLastAdded; if (bIsHitTesting) { TSharedPtr<FSpriteSelectedVertex> Data = MakeShareable(new FSpriteSelectedVertex(EditorContext, Geometry, ShapeIndex, VertexIndex)); Canvas.SetHitProxy(new HSpriteSelectableObjectHitProxy(Data)); } const float VertSize = SpriteEditingConstantsEX::GeometryVertexSize; Canvas.DrawTile(ScreenPos.X - VertSize*0.5f, ScreenPos.Y - VertSize*0.5f, VertSize, VertSize, 0.f, 0.f, 1.f, 1.f, bNeedHighlightVertex ? SpriteEditingConstantsEX::GeometrySelectedColor : VertexColor, GWhiteTexture); if (bIsHitTesting) { Canvas.SetHitProxy(nullptr); } } } // Draw a preview cursor for the add polygon tool if (IsAddingPolygon()) { // Figure out where the mouse is back in screen space const FVector2D PotentialVertexScreenPos = TextureSpaceToScreenSpace(View, MousePositionTextureSpace); bool bWillCloseByClicking = false; if (Geometry.Shapes.IsValidIndex(AddingPolygonIndex)) { const FSpriteGeometryShape& Shape = Geometry.Shapes[AddingPolygonIndex]; const FLinearColor LineColorRaw = Shape.bNegativeWinding ? NegativeGeometryVertexColor : GeometryVertexColor; const FLinearColor LineColorValidity = Shape.IsShapeValid() ? LineColorRaw : FMath::Lerp(LineColorRaw, FLinearColor::Red, 0.8f); const FLinearColor LineColor = FMath::Lerp(LineColorValidity, SpriteEditingConstantsEX::GeometrySelectedColor, 0.2f); if (Shape.Vertices.Num() > 0) { // Draw a line from the last vertex to the potential insertion point for the new one { const FVector2D LastScreenPos = TextureSpaceToScreenSpace(View, Shape.ConvertShapeSpaceToTextureSpace(Shape.Vertices[Shape.Vertices.Num() - 1])); FCanvasLineItem LineItem(LastScreenPos, PotentialVertexScreenPos); LineItem.SetColor(LineColor); LineItem.LineThickness = SpriteEditingConstantsEX::GeometryBorderLineThickness; Canvas.DrawItem(LineItem); } // And to the first vertex if there were at least 2 if (Shape.Vertices.Num() >= 2) { const FVector2D FirstScreenPos = TextureSpaceToScreenSpace(View, Shape.ConvertShapeSpaceToTextureSpace(Shape.Vertices[0])); FCanvasLineItem LineItem(PotentialVertexScreenPos, FirstScreenPos); LineItem.SetColor(LineColor); LineItem.LineThickness = SpriteEditingConstantsEX::GeometryBorderLineThickness; Canvas.DrawItem(LineItem); // Determine how close we are to the first vertex (will we close the shape by clicking)? bWillCloseByClicking = (Shape.Vertices.Num() >= 3) && (FVector2D::Distance(FirstScreenPos, PotentialVertexScreenPos) < SpriteEditingConstantsEX::AddPolygonVertexWeldScreenSpaceDistance); } } } // Draw the prospective vert const float VertSize = SpriteEditingConstantsEX::GeometryVertexSize; Canvas.DrawTile(PotentialVertexScreenPos.X - VertSize*0.5f, PotentialVertexScreenPos.Y - VertSize*0.5f, VertSize, VertSize, 0.f, 0.f, 1.f, 1.f, SpriteEditingConstantsEX::GeometrySelectedColor, GWhiteTexture); // Draw a prompt above and to the right of the cursor static const FText CloseButton(LOCTEXT("ClosePolygonPrompt", "Close")); static const FText AddButton(LOCTEXT("AddVertexToPolygonPrompt", "+")); const FText PromptText(bWillCloseByClicking ? CloseButton : AddButton); FCanvasTextItem PromptTextItem(FVector2D(PotentialVertexScreenPos.X + VertSize, PotentialVertexScreenPos.Y - VertSize), PromptText, GEngine->GetSmallFont(), FLinearColor::White); PromptTextItem.EnableShadow(FLinearColor::Black); PromptTextItem.Draw(&Canvas); } }
void FSpriteGeometryEditMode::SelectVerticesInMarquee(FEditorViewportClient* ViewportClient, FViewport* Viewport, bool bAddToSelection) { if (!bAddToSelection) { SpriteGeometryHelper.ClearSelectionSet(); } { // Calculate world space positions FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(Viewport, ViewportClient->GetScene(), ViewportClient->EngineShowFlags)); FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily); const FVector StartPos = View->PixelToWorld(MarqueeStartPos.X, MarqueeStartPos.Y, 0); const FVector EndPos = View->PixelToWorld(MarqueeEndPos.X, MarqueeEndPos.Y, 0); // Convert to source texture space to work out the pixels dragged FVector2D TextureSpaceStartPos = SpriteGeometryHelper.GetEditorContext()->WorldSpaceToTextureSpace(StartPos); FVector2D TextureSpaceEndPos = SpriteGeometryHelper.GetEditorContext()->WorldSpaceToTextureSpace(EndPos); if (TextureSpaceStartPos.X > TextureSpaceEndPos.X) { Swap(TextureSpaceStartPos.X, TextureSpaceEndPos.X); } if (TextureSpaceStartPos.Y > TextureSpaceEndPos.Y) { Swap(TextureSpaceStartPos.Y, TextureSpaceEndPos.Y); } const FBox2D QueryBounds(TextureSpaceStartPos, TextureSpaceEndPos); // Check geometry if (FSpriteGeometryCollection* Geometry = SpriteGeometryHelper.GetGeometryBeingEdited()) { for (int32 ShapeIndex = 0; ShapeIndex < Geometry->Shapes.Num(); ++ShapeIndex) { const FSpriteGeometryShape& Shape = Geometry->Shapes[ShapeIndex]; bool bSelectWholeShape = false; if ((Shape.ShapeType == ESpriteShapeType::Circle) || (Shape.ShapeType == ESpriteShapeType::Box)) { // First see if we are fully contained const FBox2D ShapeBoxBounds(Shape.BoxPosition - Shape.BoxSize * 0.5f, Shape.BoxPosition + Shape.BoxSize * 0.5f); if (QueryBounds.IsInside(ShapeBoxBounds)) { bSelectWholeShape = true; } } //@TODO: Try intersecting with the circle if it wasn't entirely enclosed if (bSelectWholeShape) { SpriteGeometryHelper.AddShapeToSelection(ShapeIndex); } else { // Try to select some subset of the vertices for (int32 VertexIndex = 0; VertexIndex < Shape.Vertices.Num(); ++VertexIndex) { const FVector2D TextureSpaceVertex = Shape.ConvertShapeSpaceToTextureSpace(Shape.Vertices[VertexIndex]); if (QueryBounds.IsInside(TextureSpaceVertex)) { SpriteGeometryHelper.AddPolygonVertexToSelection(ShapeIndex, VertexIndex); } } } } } //@TODO: Check other items (sockets/etc...) } }
bool FSpriteEditorViewportClient::ConvertMarqueeToSourceTextureSpace(/*out*/ FIntPoint& OutStartPos, /*out*/ FIntPoint& OutDimension) { FSpriteGeometryEditMode* GeometryEditMode = ModeTools->GetActiveModeTyped<FSpriteGeometryEditMode>(FSpriteGeometryEditMode::EM_SpriteGeometry); check(GeometryEditMode); const FVector2D MarqueeStartPos = GeometryEditMode->GetMarqueeStartPos(); const FVector2D MarqueeEndPos = GeometryEditMode->GetMarqueeEndPos(); bool bSuccessful = false; UPaperSprite* Sprite = SourceTextureViewComponent->GetSprite(); UTexture2D* SpriteSourceTexture = Sprite->GetSourceTexture(); if (SpriteSourceTexture != nullptr) { // Calculate world space positions FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(Viewport, GetScene(), EngineShowFlags)); FSceneView* View = CalcSceneView(&ViewFamily); const FVector StartPos = View->PixelToWorld(MarqueeStartPos.X, MarqueeStartPos.Y, 0); const FVector EndPos = View->PixelToWorld(MarqueeEndPos.X, MarqueeEndPos.Y, 0); // Convert to source texture space to work out the pixels dragged FVector2D TextureSpaceStartPos = Sprite->ConvertWorldSpaceToTextureSpace(StartPos); FVector2D TextureSpaceEndPos = Sprite->ConvertWorldSpaceToTextureSpace(EndPos); if (TextureSpaceStartPos.X > TextureSpaceEndPos.X) { Swap(TextureSpaceStartPos.X, TextureSpaceEndPos.X); } if (TextureSpaceStartPos.Y > TextureSpaceEndPos.Y) { Swap(TextureSpaceStartPos.Y, TextureSpaceEndPos.Y); } const FIntPoint SourceTextureSize(SpriteSourceTexture->GetImportedSize()); const int32 SourceTextureWidth = SourceTextureSize.X; const int32 SourceTextureHeight = SourceTextureSize.Y; FIntPoint TSStartPos; TSStartPos.X = FMath::Clamp<int32>((int32)TextureSpaceStartPos.X, 0, SourceTextureWidth - 1); TSStartPos.Y = FMath::Clamp<int32>((int32)TextureSpaceStartPos.Y, 0, SourceTextureHeight - 1); FIntPoint TSEndPos; TSEndPos.X = FMath::Clamp<int32>((int32)TextureSpaceEndPos.X, 0, SourceTextureWidth - 1); TSEndPos.Y = FMath::Clamp<int32>((int32)TextureSpaceEndPos.Y, 0, SourceTextureHeight - 1); const FIntPoint TextureSpaceDimensions = TSEndPos - TSStartPos; if ((TextureSpaceDimensions.X > 0) || (TextureSpaceDimensions.Y > 0)) { OutStartPos = TSStartPos; OutDimension = TextureSpaceDimensions; bSuccessful = true; } } return bSuccessful; }