-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: ImageElement can rotate the image #3039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
c11b8cc
feat: ImageElement can rotate the image
laske185 f00fcba
Fix typo: rename UpateValueFromFloat to UpdateValueFromFloat
laske185 3acfd99
refactor: Replace specialized rotation property template provider by …
laske185 73a7656
Rename template provider namename
laske185 e834138
Simplify floating-point comparisons in ImageElement
laske185 28273ea
Simplify angle display value conversion and rounding
laske185 b03f1e9
docs(license): update copyright headers for MIT license
laske185 c0bef49
fix(ui-controls): improve Rotation property precision check
laske185 a330e95
refactor(AngleEditor): simplify control template layout
laske185 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
...ide.Core.Assets.Editor/View/TemplateProviders/TypeAndPropertyNameMatchTemplateProvider.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) | ||
| // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. | ||
| using Stride.Core.Presentation.Quantum.ViewModels; | ||
|
|
||
| namespace Stride.Core.Assets.Editor.View.TemplateProviders | ||
| { | ||
| /// <summary> | ||
| /// A template provider that matches nodes based on both their type and property name. | ||
| /// </summary> | ||
| public class TypeAndPropertyNameMatchTemplateProvider : TypeMatchTemplateProvider | ||
| { | ||
| /// <inheritdoc/> | ||
| public override string Name => $"{base.Name}_{PropertyName}"; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the name of the property to match. | ||
| /// </summary> | ||
| public string PropertyName { get; set; } | ||
|
|
||
| public override bool MatchNode(NodeViewModel node) | ||
| { | ||
| return base.MatchNode(node) && node.Name == PropertyName; | ||
| } | ||
| } | ||
| } |
188 changes: 188 additions & 0 deletions
188
sources/engine/Stride.UI.Tests/Layering/ImageElementRotationTests.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) | ||
| // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. | ||
|
|
||
| using Stride.Core.Mathematics; | ||
| using Stride.Graphics; | ||
| using Stride.UI.Controls; | ||
| using Xunit; | ||
|
|
||
| namespace Stride.UI.Tests.Layering | ||
| { | ||
| /// <summary> | ||
| /// Tests for the <see cref="ImageElement.Rotation"/> property. | ||
| /// </summary> | ||
| [System.ComponentModel.Description("Tests for ImageElement rotation functionality")] | ||
| public class ImageElementRotationTests | ||
| { | ||
| [Fact] | ||
| [System.ComponentModel.Description("Test that the default rotation value is 0")] | ||
| public void TestDefaultRotation() | ||
| { | ||
| var image = new ImageElement(); | ||
| Assert.Equal(0f, image.Rotation); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test that the default LocalMatrix is Identity when rotation is 0")] | ||
| public void TestDefaultLocalMatrix() | ||
| { | ||
| var image = new ImageElement(); | ||
| Assert.Equal(Matrix.Identity, image.LocalMatrix); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test setting rotation to a positive value")] | ||
| public void TestSetPositiveRotation() | ||
| { | ||
| var image = new ImageElement(); | ||
| var angle = MathUtil.PiOverFour; // 45 degrees | ||
|
|
||
| image.Rotation = angle; | ||
|
|
||
| Assert.Equal(angle, image.Rotation); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test setting rotation to a negative value (counter-clockwise)")] | ||
| public void TestSetNegativeRotation() | ||
| { | ||
| var image = new ImageElement(); | ||
| var angle = -MathUtil.PiOverFour; // -45 degrees | ||
|
|
||
| image.Rotation = angle; | ||
|
|
||
| Assert.Equal(angle, image.Rotation); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test that LocalMatrix is updated when rotation changes")] | ||
| public void TestLocalMatrixUpdatesOnRotationChange() | ||
| { | ||
| var image = new ImageElement(); | ||
| var angle = MathUtil.PiOverTwo; // 90 degrees | ||
|
|
||
| image.Rotation = angle; | ||
|
|
||
| var expectedMatrix = Matrix.RotationZ(angle); | ||
| AssertMatrixEqual(expectedMatrix, image.LocalMatrix); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test that setting rotation to 0 resets LocalMatrix to Identity")] | ||
| public void TestRotationZeroResetsToIdentity() | ||
| { | ||
| var image = new ImageElement(); | ||
|
|
||
| // First set a non-zero rotation | ||
| image.Rotation = MathUtil.PiOverFour; | ||
| Assert.NotEqual(Matrix.Identity, image.LocalMatrix); | ||
|
|
||
| // Then reset to zero | ||
| image.Rotation = 0f; | ||
| Assert.Equal(Matrix.Identity, image.LocalMatrix); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test that very small rotation values (near zero) set LocalMatrix to Identity")] | ||
| public void TestVerySmallRotationSetsIdentity() | ||
| { | ||
| var image = new ImageElement(); | ||
|
|
||
| // Set a value smaller than float.Epsilon | ||
| image.Rotation = float.Epsilon / 2f; | ||
|
|
||
| // Should be treated as zero | ||
| Assert.Equal(Matrix.Identity, image.LocalMatrix); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test multiple rotation changes")] | ||
| public void TestMultipleRotationChanges() | ||
| { | ||
| var image = new ImageElement(); | ||
|
|
||
| // First rotation | ||
| image.Rotation = MathUtil.PiOverFour; | ||
| AssertMatrixEqual(Matrix.RotationZ(MathUtil.PiOverFour), image.LocalMatrix); | ||
|
|
||
| // Second rotation | ||
| image.Rotation = MathUtil.PiOverTwo; | ||
| AssertMatrixEqual(Matrix.RotationZ(MathUtil.PiOverTwo), image.LocalMatrix); | ||
|
|
||
| // Third rotation (negative) | ||
| image.Rotation = -MathUtil.PiOverFour; | ||
| AssertMatrixEqual(Matrix.RotationZ(-MathUtil.PiOverFour), image.LocalMatrix); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test that setting the same rotation value doesn't trigger unnecessary updates")] | ||
| public void TestSetSameRotationValue() | ||
| { | ||
| var image = new ImageElement(); | ||
| var angle = MathUtil.PiOverFour; | ||
|
|
||
| image.Rotation = angle; | ||
| var firstMatrix = image.LocalMatrix; | ||
|
|
||
| // Set the same value again | ||
| image.Rotation = angle; | ||
| var secondMatrix = image.LocalMatrix; | ||
|
|
||
| // Matrix should be the same | ||
| Assert.Equal(firstMatrix, secondMatrix); | ||
| } | ||
|
|
||
| [Fact] | ||
| [System.ComponentModel.Description("Test that rotation doesn't affect the image size or measurement")] | ||
| public void TestRotationDoesNotAffectMeasurement() | ||
| { | ||
| var sprite = new Sprite() | ||
| { | ||
| Region = new Rectangle(0, 0, 100, 50) | ||
| }; | ||
| var image = new ImageElement() | ||
| { | ||
| Source = (Rendering.Sprites.SpriteFromTexture)sprite, | ||
| StretchType = StretchType.None | ||
| }; | ||
|
|
||
| // Measure without rotation | ||
| image.Measure(new Vector3(200, 200, 0)); | ||
| var sizeWithoutRotation = image.DesiredSizeWithMargins; | ||
|
|
||
| // Apply rotation and measure again | ||
| image.Rotation = MathUtil.PiOverFour; | ||
| image.Measure(new Vector3(200, 200, 0)); | ||
| var sizeWithRotation = image.DesiredSizeWithMargins; | ||
|
|
||
| // Rotation should not change the measured size | ||
| Assert.Equal(sizeWithoutRotation, sizeWithRotation); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Helper method to assert that two matrices are approximately equal within a tolerance. | ||
| /// </summary> | ||
| private static void AssertMatrixEqual(Matrix expected, Matrix actual, int precision = 5) | ||
| { | ||
| Assert.Equal(expected.M11, actual.M11, precision); | ||
| Assert.Equal(expected.M12, actual.M12, precision); | ||
| Assert.Equal(expected.M13, actual.M13, precision); | ||
| Assert.Equal(expected.M14, actual.M14, precision); | ||
|
|
||
| Assert.Equal(expected.M21, actual.M21, precision); | ||
| Assert.Equal(expected.M22, actual.M22, precision); | ||
| Assert.Equal(expected.M23, actual.M23, precision); | ||
| Assert.Equal(expected.M24, actual.M24, precision); | ||
|
|
||
| Assert.Equal(expected.M31, actual.M31, precision); | ||
| Assert.Equal(expected.M32, actual.M32, precision); | ||
| Assert.Equal(expected.M33, actual.M33, precision); | ||
| Assert.Equal(expected.M34, actual.M34, precision); | ||
|
|
||
| Assert.Equal(expected.M41, actual.M41, precision); | ||
| Assert.Equal(expected.M42, actual.M42, precision); | ||
| Assert.Equal(expected.M43, actual.M43, precision); | ||
| Assert.Equal(expected.M44, actual.M44, precision); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this one however, I think the bot is wrong in supposing the local offset is lost,
The local matrix is taken in
UIElement.UpdateWorldMatrix(), where the offsets are applied, and the result is used to compute the world-space matrix.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The local position is stored in the
RenderOffsetsproperty of theUIElement. The world matrix is calculated in theUpdateWorldMatrixmethod that adds the local matrix with the render offsets and size.So I agree that the bot comment is not valid.