From 5a44e5fbe813395e966b4b9ce91f780f42302ff6 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 29 Jan 2020 20:56:26 +0100 Subject: [PATCH] Refactored the edit tool Fixed the fill shape but it might need some rethinking --- src/Artemis.Core/Models/Profile/Layer.cs | 32 +- .../Models/Profile/LayerShapes/Fill.cs | 6 +- .../Models/Profile/LayerShapes/LayerShape.cs | 35 +- .../ColorBrush.cs | 6 +- .../NoiseBrush.cs | 4 +- src/Artemis.UI/Artemis.UI.csproj | 38 +- .../Events/ShapeControlEventArgs.cs | 31 + .../Properties/Resources.Designer.cs | 42 +- src/Artemis.UI/Properties/Resources.resx | 15 +- src/Artemis.UI/Resources/aero_rotate.cur | Bin 0 -> 4286 bytes .../Resources/aero_rotate_bl_ico.ico | Bin 1826 -> 0 bytes .../Resources/aero_rotate_br_ico.ico | Bin 1826 -> 0 bytes .../Resources/aero_rotate_tl_ico.ico | Bin 1826 -> 0 bytes .../Resources/aero_rotate_tr_ico.ico | Bin 1826 -> 0 bytes .../Visualization/ProfileLayerViewModel.cs | 11 +- .../Visualization/ProfileView.xaml | 7 +- .../Visualization/ProfileViewModel.cs | 15 +- .../Visualization/Tools/EditToolView.xaml | 146 +---- .../Visualization/Tools/EditToolViewModel.cs | 614 ++++++++---------- .../Tools/EllipseToolViewModel.cs | 3 +- .../Visualization/Tools/FillToolViewModel.cs | 13 +- .../Tools/SelectionAddToolView.xaml | 10 - .../Tools/SelectionAddToolView.xaml.cs | 15 - .../Tools/SelectionAddToolViewModel.cs | 36 - .../Tools/SelectionRemoveToolView.xaml | 39 +- .../Tools/SelectionRemoveToolViewModel.cs | 69 +- .../Tools/SelectionToolView.xaml | 18 - .../Tools/SelectionToolViewModel.cs | 16 +- .../UserControls/LayerShapeControl.xaml | 123 ++++ .../UserControls/LayerShapeControl.xaml.cs | 392 +++++++++++ src/Artemis.UI/Services/LayerShapeService.cs | 41 +- src/Artemis.UI/Utilities/CursorRotator.cs | 86 --- 32 files changed, 1037 insertions(+), 826 deletions(-) create mode 100644 src/Artemis.UI/Events/ShapeControlEventArgs.cs create mode 100644 src/Artemis.UI/Resources/aero_rotate.cur delete mode 100644 src/Artemis.UI/Resources/aero_rotate_bl_ico.ico delete mode 100644 src/Artemis.UI/Resources/aero_rotate_br_ico.ico delete mode 100644 src/Artemis.UI/Resources/aero_rotate_tl_ico.ico delete mode 100644 src/Artemis.UI/Resources/aero_rotate_tr_ico.ico delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml.cs delete mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolViewModel.cs create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/UserControls/LayerShapeControl.xaml create mode 100644 src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/UserControls/LayerShapeControl.xaml.cs delete mode 100644 src/Artemis.UI/Utilities/CursorRotator.cs diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index a5c323295..af76b73f3 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -81,17 +81,17 @@ namespace Artemis.Core.Models.Profile public ReadOnlyCollection Leds => _leds.AsReadOnly(); /// - /// A rectangle relative to the surface that contains all the LEDs in this layer. - /// For rendering, use the RenderRectangle on . - /// - public SKRect Rectangle { get; private set; } - - /// - /// A zero-based absolute rectangle that contains all the LEDs in this layer. + /// An absolute rectangle to the surface that contains all the LEDs in this layer. /// For rendering, use the RenderRectangle on . /// public SKRect AbsoluteRectangle { get; private set; } + /// + /// A zero-based rectangle that contains all the LEDs in this layer. + /// For rendering, use the RenderRectangle on . + /// + public SKRect Rectangle { get; private set; } + /// /// A path containing all the LEDs this layer is applied to. /// For rendering, use the RenderPath on . @@ -177,8 +177,8 @@ namespace Artemis.Core.Models.Profile var relativeAnchor = GetLayerAnchor(false); // Translation originates from the unscaled center of the shape and is tied to the anchor - var x = position.X * Rectangle.Width - LayerShape.RenderRectangle.Width / 2 - relativeAnchor.X; - var y = position.Y * Rectangle.Height - LayerShape.RenderRectangle.Height / 2 - relativeAnchor.Y; + var x = position.X * AbsoluteRectangle.Width - LayerShape.RenderRectangle.Width / 2 - relativeAnchor.X; + var y = position.Y * AbsoluteRectangle.Height - LayerShape.RenderRectangle.Height / 2 - relativeAnchor.Y; canvas.RotateDegrees(rotation, anchor.X, anchor.Y); @@ -213,14 +213,14 @@ namespace Artemis.Core.Models.Profile if (!absolute) { var anchor = AnchorPointProperty.CurrentValue; - anchor.X = anchor.X * Rectangle.Width; - anchor.Y = anchor.Y * Rectangle.Height; + anchor.X = anchor.X * AbsoluteRectangle.Width; + anchor.Y = anchor.Y * AbsoluteRectangle.Height; return new SKPoint(anchor.X, anchor.Y); } var position = PositionProperty.CurrentValue; - position.X = position.X * Rectangle.Width; - position.Y = position.Y * Rectangle.Height; + position.X = position.X * AbsoluteRectangle.Width; + position.Y = position.Y * AbsoluteRectangle.Height; return new SKPoint(position.X + LayerShape.RenderRectangle.Left, position.Y + LayerShape.RenderRectangle.Top); } @@ -326,8 +326,8 @@ namespace Artemis.Core.Models.Profile { if (!Leds.Any()) { - Rectangle = SKRect.Empty; AbsoluteRectangle = SKRect.Empty; + Rectangle = SKRect.Empty; Path = new SKPath(); OnRenderPropertiesUpdated(); return; @@ -339,8 +339,8 @@ namespace Artemis.Core.Models.Profile var maxX = Leds.Max(l => l.AbsoluteRenderRectangle.Right); var maxY = Leds.Max(l => l.AbsoluteRenderRectangle.Bottom); - Rectangle = SKRect.Create(minX, minY, maxX - minX, maxY - minY); - AbsoluteRectangle = SKRect.Create(0, 0, maxX - minX, maxY - minY); + AbsoluteRectangle = SKRect.Create(minX, minY, maxX - minX, maxY - minY); + Rectangle = SKRect.Create(0, 0, maxX - minX, maxY - minY); var path = new SKPath {FillType = SKPathFillType.Winding}; foreach (var artemisLed in Leds) diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs b/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs index f1d437ab9..86340bdaf 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/Fill.cs @@ -16,7 +16,11 @@ namespace Artemis.Core.Models.Profile.LayerShapes public override void CalculateRenderProperties() { RenderRectangle = GetUnscaledRectangle(); - RenderPath = Layer.Path; + + // Shape originates from the center so compensate the path for that + var renderPath = new SKPath(Layer.Path); + renderPath.Transform(SKMatrix.MakeTranslation(RenderRectangle.Left - Layer.Path.Bounds.Left, RenderRectangle.Top - Layer.Path.Bounds.Top)); + RenderPath = renderPath; } public override void ApplyToEntity() diff --git a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs index 78e29f8ef..f24949591 100644 --- a/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs +++ b/src/Artemis.Core/Models/Profile/LayerShapes/LayerShape.cs @@ -58,37 +58,10 @@ namespace Artemis.Core.Models.Profile.LayerShapes return SKRect.Empty; return SKRect.Create( - Layer.Rectangle.Left + Layer.Rectangle.Width * ScaledRectangle.Left, - Layer.Rectangle.Top + Layer.Rectangle.Height * ScaledRectangle.Top, - Layer.Rectangle.Width * ScaledRectangle.Width, - Layer.Rectangle.Height * ScaledRectangle.Height - ); - } - - public void SetFromUnscaledAnchor(SKPoint anchor, TimeSpan? time) - { - if (!Layer.Leds.Any()) - { - Layer.PositionProperty.SetCurrentValue(SKPoint.Empty, time); - Layer.SizeProperty.SetCurrentValue(SKSize.Empty, time); - return; - } - - Layer.AnchorPointProperty.SetCurrentValue(new SKPoint( - 100f / Layer.Rectangle.Width * (anchor.X - Layer.Rectangle.Left - Layer.PositionProperty.CurrentValue.X) / 100f, - 100f / Layer.Rectangle.Height * (anchor.Y - Layer.Rectangle.Top - Layer.PositionProperty.CurrentValue.Y) / 100f - ), time); - CalculateRenderProperties(); - } - - public SKPoint GetUnscaledAnchor() - { - if (!Layer.Leds.Any()) - return SKPoint.Empty; - - return new SKPoint( - Layer.Rectangle.Left + Layer.Rectangle.Width * (Layer.AnchorPointProperty.CurrentValue.X + Layer.PositionProperty.CurrentValue.X), - Layer.Rectangle.Top + Layer.Rectangle.Height * (Layer.AnchorPointProperty.CurrentValue.Y + Layer.PositionProperty.CurrentValue.Y) + Layer.AbsoluteRectangle.Left + Layer.AbsoluteRectangle.Width * ScaledRectangle.Left, + Layer.AbsoluteRectangle.Top + Layer.AbsoluteRectangle.Height * ScaledRectangle.Top, + Layer.AbsoluteRectangle.Width * ScaledRectangle.Width, + Layer.AbsoluteRectangle.Height * ScaledRectangle.Height ); } } diff --git a/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs b/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs index 5b743516f..d0d6f7d0a 100644 --- a/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs +++ b/src/Artemis.Plugins.LayerBrushes.Color/ColorBrush.cs @@ -35,7 +35,7 @@ namespace Artemis.Plugins.LayerBrushes.Color private void CreateShader() { - var center = new SKPoint(Layer.Rectangle.MidX, Layer.Rectangle.MidY); + var center = new SKPoint(Layer.AbsoluteRectangle.MidX, Layer.AbsoluteRectangle.MidY); SKShader shader; switch (Settings.GradientType) { @@ -43,10 +43,10 @@ namespace Artemis.Plugins.LayerBrushes.Color shader = SKShader.CreateColor(_testColors.First()); break; case GradientType.LinearGradient: - shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(Layer.Rectangle.Width, 0), _testColors.ToArray(), SKShaderTileMode.Repeat); + shader = SKShader.CreateLinearGradient(new SKPoint(0, 0), new SKPoint(Layer.AbsoluteRectangle.Width, 0), _testColors.ToArray(), SKShaderTileMode.Repeat); break; case GradientType.RadialGradient: - shader = SKShader.CreateRadialGradient(center, Math.Min(Layer.Rectangle.Width, Layer.Rectangle.Height), _testColors.ToArray(), SKShaderTileMode.Repeat); + shader = SKShader.CreateRadialGradient(center, Math.Min(Layer.AbsoluteRectangle.Width, Layer.AbsoluteRectangle.Height), _testColors.ToArray(), SKShaderTileMode.Repeat); break; case GradientType.SweepGradient: shader = SKShader.CreateSweepGradient(center, _testColors.ToArray(), null, SKShaderTileMode.Clamp, 0, 360); diff --git a/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs b/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs index 094b9d2a4..173c46bbe 100644 --- a/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs +++ b/src/Artemis.Plugins.LayerBrushes.Noise/NoiseBrush.cs @@ -43,8 +43,8 @@ namespace Artemis.Plugins.LayerBrushes.Noise public override void Render(SKCanvas canvas) { // Scale down the render path to avoid computing a value for every pixel - var width = (int) (Math.Max(Layer.Rectangle.Width, Layer.Rectangle.Height) / Scale); - var height = (int) (Math.Max(Layer.Rectangle.Width, Layer.Rectangle.Height) / Scale); + var width = (int) (Math.Max(Layer.AbsoluteRectangle.Width, Layer.AbsoluteRectangle.Height) / Scale); + var height = (int) (Math.Max(Layer.AbsoluteRectangle.Width, Layer.AbsoluteRectangle.Height) / Scale); var opacity = (float) Math.Round(Settings.Color.Alpha / 255.0, 2, MidpointRounding.AwayFromZero); using (var bitmap = new SKBitmap(new SKImageInfo(width, height))) { diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index 0837eb5af..607dff520 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -155,8 +155,14 @@ + + + True + True + Resources.resx + LayerPropertiesView.xaml @@ -199,10 +205,6 @@ RectangleToolView.xaml - - SelectionAddToolView.xaml - - SelectionRemoveToolView.xaml @@ -216,6 +218,9 @@ + + LayerShapeControl.xaml + SidebarView.xaml @@ -224,7 +229,6 @@ - @@ -234,11 +238,6 @@ - - True - True - Resources.resx - ProfileElementRenameView.xaml @@ -390,10 +389,6 @@ Designer MSBuild:Compile - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -406,6 +401,10 @@ Designer MSBuild:Compile + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -572,16 +571,7 @@ - - - - - - - - - - + diff --git a/src/Artemis.UI/Events/ShapeControlEventArgs.cs b/src/Artemis.UI/Events/ShapeControlEventArgs.cs new file mode 100644 index 000000000..b8be02160 --- /dev/null +++ b/src/Artemis.UI/Events/ShapeControlEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using System.Windows.Input; + +namespace Artemis.UI.Events +{ + public class ShapeControlEventArgs : EventArgs + { + public ShapeControlEventArgs(MouseEventArgs mouseEventArgs, ShapeControlPoint shapeControlPoint) + { + MouseEventArgs = mouseEventArgs; + ShapeControlPoint = shapeControlPoint; + } + + public MouseEventArgs MouseEventArgs { get; set; } + public ShapeControlPoint ShapeControlPoint { get; } + } + + public enum ShapeControlPoint + { + TopLeft, + TopRight, + BottomRight, + BottomLeft, + TopCenter, + RightCenter, + BottomCenter, + LeftCenter, + LayerShape, + Anchor + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Properties/Resources.Designer.cs b/src/Artemis.UI/Properties/Resources.Designer.cs index 2c6653c37..39c8ffdae 100644 --- a/src/Artemis.UI/Properties/Resources.Designer.cs +++ b/src/Artemis.UI/Properties/Resources.Designer.cs @@ -143,20 +143,20 @@ namespace Artemis.UI.Properties { /// /// Looks up a localized resource of type System.Byte[]. /// - internal static byte[] aero_rotate_bl { + internal static byte[] aero_rotate { get { - object obj = ResourceManager.GetObject("aero_rotate_bl", resourceCulture); + object obj = ResourceManager.GetObject("aero_rotate", resourceCulture); return ((byte[])(obj)); } } /// - /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// Looks up a localized resource of type System.Byte[]. /// - internal static System.Drawing.Icon aero_rotate_bl_ico { + internal static byte[] aero_rotate_bl { get { - object obj = ResourceManager.GetObject("aero_rotate_bl_ico", resourceCulture); - return ((System.Drawing.Icon)(obj)); + object obj = ResourceManager.GetObject("aero_rotate_bl", resourceCulture); + return ((byte[])(obj)); } } @@ -170,16 +170,6 @@ namespace Artemis.UI.Properties { } } - /// - /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). - /// - internal static System.Drawing.Icon aero_rotate_br_ico { - get { - object obj = ResourceManager.GetObject("aero_rotate_br_ico", resourceCulture); - return ((System.Drawing.Icon)(obj)); - } - } - /// /// Looks up a localized resource of type System.Byte[]. /// @@ -190,16 +180,6 @@ namespace Artemis.UI.Properties { } } - /// - /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). - /// - internal static System.Drawing.Icon aero_rotate_tl_ico { - get { - object obj = ResourceManager.GetObject("aero_rotate_tl_ico", resourceCulture); - return ((System.Drawing.Icon)(obj)); - } - } - /// /// Looks up a localized resource of type System.Byte[]. /// @@ -210,16 +190,6 @@ namespace Artemis.UI.Properties { } } - /// - /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). - /// - internal static System.Drawing.Icon aero_rotate_tr_ico { - get { - object obj = ResourceManager.GetObject("aero_rotate_tr_ico", resourceCulture); - return ((System.Drawing.Icon)(obj)); - } - } - /// /// Looks up a localized resource of type System.Byte[]. /// diff --git a/src/Artemis.UI/Properties/Resources.resx b/src/Artemis.UI/Properties/Resources.resx index b89137361..e75834b49 100644 --- a/src/Artemis.UI/Properties/Resources.resx +++ b/src/Artemis.UI/Properties/Resources.resx @@ -142,30 +142,21 @@ ..\Resources\aero_pen_plus.cur;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\aero_rotate.cur;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\aero_rotate_bl.cur;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\aero_rotate_bl_ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\Resources\aero_rotate_br.cur;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\aero_rotate_br_ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\Resources\aero_rotate_tl.cur;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\aero_rotate_tl_ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\Resources\aero_rotate_tr.cur;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\aero_rotate_tr_ico.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - ..\Resources\bow.svg;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/src/Artemis.UI/Resources/aero_rotate.cur b/src/Artemis.UI/Resources/aero_rotate.cur new file mode 100644 index 0000000000000000000000000000000000000000..1eabacba9b35d1cea009cdc203ad58be00494a13 GIT binary patch literal 4286 zcmeH}O-Ni<6vywp8J*Ed{lsb{H3LGh8!b`=72;511QoinXc0#nX~8NerHksKwrb4E zC>B?WF$N7Tj0@|+Dz!ucMzT=B!Ytyb3|g%^8myx;^G^Tg=_@KGQD-KLk^}#D?|bLI z^E>Z;y(@$yG{R&O!XWhGahMPv3Lz38JQ8LIUR;H!hwr`ltp|*VTn2pyRY9LaI>_H< z)M~Yl3xy#J%_!n>xU_fHowAt50&Jw2_&?CtHPqM{-yD=VYX(NX2uz`y|E-@`d* zp--_EU2AJ=)Y{tWiHeGHZ)|K3?+f_fyM~5_gi3Xk+6)8n@io@-3ohre4O-pJ@b#x z4PWPRJ3BjD;i>DN%l`g;iin6{c7d;fr{Wp-8{692Xm4+iaAu>Vq$K7(hB8mJ_1Epk z2uu|f73%nZ;xEt};!5y+3-SE_-x~dA6R3$ylErm$i%@b3JvokKLwT0i?>>~{iqPA1 kLO&ry=!7IJe>6z+^eNFZEs+y)u=G$U?lXzpKOTbr1$ehrUjP6A literal 0 HcmV?d00001 diff --git a/src/Artemis.UI/Resources/aero_rotate_bl_ico.ico b/src/Artemis.UI/Resources/aero_rotate_bl_ico.ico deleted file mode 100644 index ec5dce6658f8f0878c7368e2703159fe3d9e2fed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1826 zcmb`H!AiqG5I`r9wjNT6X|44l9>r5{f)sM5n z#2Ob>madpaNj_dfGhbQ-z^CyT|I5ifjsi}ViZTeF2oy>BP-N+COP1~vp_ubRRaG<$ z1AX7eAP7>$uQ9E2z9Gvue58Y3uZNS#1j8^y$8oUJ>7ce*!Rm&Lg7pnd(@bh`T^DP$ z8j{~3I>a7PPWSJHes9H@oOiq3#AR6)dtBm#SPJ&VQMAgXP~d1ZLeKNqtS4K?F~EiPA^t!MuPC^e#fd7bw0&KnV5~yb6Uv zTBNaoUR0FvpV)=4q=BXr{!Mmf_S? z99q*XnU}U{Jd3o@#$CmFpUt>R#2W)r1JW#pZNt-9!rL>AH_J4>EW`N4v?1g}UC4%V zP4-Iuah-)>$iDA$x7%ggwq+msdBAn+tL~Uw+pVzUI6N2(c(GXUd_Lz+rz6ZqNRBlRA=G7vYY-3Q*q4ms4k%Q%Zia zs5Q-|JXP;mIAKXVb0Im-EM`%bnkLQMmZdZGf1GekBd8D2K2(8>m^^3{#55V-635VI SNr5{f)sM(fLxk6qlWxkDFEb(9Gq68_ zFRO3y*%eip!M9y3u=MMTwLI5lRe6UGInVrrInc4L>uXt`_@5=%Fbw#Ngy&`Lx|U$u zw&An0PEHtqD#2kGa--3Jvz0I=OIKNf$Kx@3o(G>N>?R4Yk3fpEe%|SHqVHl^7M!kd zvh2kE41*x;xg40L8980o8RL0L{Gj<$@+wC^% zn`+ltWm18F+{Slhfd%{rg%Zlg$S#pyd&Zw@&sj-BDV_j3OH?&Z#5hRDE^J0y&!WTlAmM05t_pmRw4V)sd+=aZ?F zV~Zi5FfVP>c#?Swj4IVEhHc9XvxFxWml?&H65}!D9J)SgmiQ)jDfww47VVkFi!f!@ zzD;?dy)y^NggRo{G)5tR?4@NIp8}LQHe_CyfvNMs#dCM;)eVe?!=bQ-u_^PyI~e=E zFFb$ljqs3{S}dO1)o=| z)zo_^7K_5VjmRU-dLIl_uJkAJ@HL`i$blN!Y?eLGQ}JrGD)9$s!8Gfw-&eUZpyyxH zJ0@p#Q|venHyRBdjYd4cex*`Lv*fe5U~*Ss)-QCn=yto@Y&O|-UAApoSoH-+$g%8v z==%@LFusx6g_@8H+0a`1%beZc+PYN#{ebEmPz&lnKT(B7<^TWy diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs index b7d7a1015..7c0e4de71 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileLayerViewModel.cs @@ -90,7 +90,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization var layerGeometry = group.GetOutlinedPathGeometry(); var opacityGeometry = Geometry.Combine(Geometry.Empty, layerGeometry, GeometryCombineMode.Exclude, new TranslateTransform()); - + LayerGeometry = layerGeometry; OpacityGeometry = opacityGeometry; } @@ -113,7 +113,12 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization shapeGeometry = new EllipseGeometry(rect); break; case Fill _: - shapeGeometry = LayerGeometry; + // Shape originates from the center so compensate the geometry for that, create a copy + shapeGeometry = LayerGeometry.Clone(); + // Add a transformation + shapeGeometry.Transform = new TranslateTransform(rect.Left - shapeGeometry.Bounds.Left, rect.Top - shapeGeometry.Bounds.Top); + // Apply the transformation so that it won't be overridden + shapeGeometry = shapeGeometry.GetOutlinedPathGeometry(); break; case Polygon _: // TODO @@ -123,7 +128,7 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization shapeGeometry = new RectangleGeometry(rect); break; } - + shapeGeometry.Transform = _layerEditorService.GetLayerTransformGroup(Layer); ShapeGeometry = shapeGeometry; }); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml index 995a01b6a..ba3502f41 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileView.xaml @@ -59,14 +59,11 @@ - + - - - - + diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs index 7a6e16806..64472dc4b 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/ProfileViewModel.cs @@ -263,22 +263,19 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization ActiveToolViewModel = new SelectionToolViewModel(this, _profileEditorService, _layerEditorService); break; case 3: - ActiveToolViewModel = new SelectionAddToolViewModel(this, _profileEditorService); + ActiveToolViewModel = new SelectionRemoveToolViewModel(this, _profileEditorService, _layerEditorService); break; - case 4: - ActiveToolViewModel = new SelectionRemoveToolViewModel(this, _profileEditorService); - break; - case 6: + case 5: ActiveToolViewModel = new EllipseToolViewModel(this, _profileEditorService, _layerEditorService); break; - case 7: + case 6: ActiveToolViewModel = new RectangleToolViewModel(this, _profileEditorService, _layerEditorService); break; - case 8: + case 7: ActiveToolViewModel = new PolygonToolViewModel(this, _profileEditorService); break; - case 9: - ActiveToolViewModel = new FillToolViewModel(this, _profileEditorService); + case 8: + ActiveToolViewModel = new FillToolViewModel(this, _profileEditorService, _layerEditorService); break; } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml index 6f3166fbf..f1b5563d0 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolView.xaml @@ -4,143 +4,25 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools" + xmlns:userControls="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization.UserControls" xmlns:s="https://github.com/canton7/Stylet" - xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" d:DataContext="{d:DesignInstance {x:Type local:EditToolViewModel}}"> - - - - - - - - - - - - - - - - - - - - - - - + - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs index 635f43d19..f2cef267e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EditToolViewModel.cs @@ -1,13 +1,11 @@ using System; -using System.Diagnostics; using System.Windows; using System.Windows.Input; using System.Windows.Media; using Artemis.Core.Models.Profile; -using Artemis.UI.Properties; +using Artemis.UI.Events; using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; -using Artemis.UI.Utilities; using SkiaSharp; using SkiaSharp.Views.WPF; using Stylet; @@ -17,14 +15,10 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools public class EditToolViewModel : VisualizationToolViewModel { private readonly ILayerEditorService _layerEditorService; - private bool _draggingHorizontally; - private bool _draggingVertically; private SKPoint _dragOffset; private SKPoint _dragStart; - private SKPoint _dragStartAnchor; - private float _previousDragAngle; private SKSize _dragStartScale; - private bool _isDragging; + private SKPoint _topLeft; public EditToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService) : base(profileViewModel, profileEditorService) @@ -32,66 +26,24 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools _layerEditorService = layerEditorService; Cursor = Cursors.Arrow; Update(); - UpdateControls(); - ProfileViewModel.PanZoomViewModel.PropertyChanged += (sender, args) => UpdateControls(); profileEditorService.SelectedProfileChanged += (sender, args) => Update(); profileEditorService.SelectedProfileElementChanged += (sender, args) => Update(); profileEditorService.SelectedProfileElementUpdated += (sender, args) => Update(); profileEditorService.ProfilePreviewUpdated += (sender, args) => Update(); } - - public double ControlSize { get; set; } - public double RotateSize { get; set; } - public Thickness ControlOffset { get; set; } - public Thickness RotateOffset { get; set; } - public double OutlineThickness { get; set; } - - public SKRect ShapeRectangle { get; set; } + public SKPath ShapePath { get; set; } public SKPoint ShapeAnchor { get; set; } public RectangleGeometry ShapeGeometry { get; set; } - public TransformCollection ShapeTransformCollection { get; set; } - - public SKPoint TopLeft { get; set; } - public SKPoint TopRight { get; set; } - public SKPoint BottomRight { get; set; } - public SKPoint BottomLeft { get; set; } - public SKPoint TopCenter { get; set; } - public SKPoint RightCenter { get; set; } - public SKPoint BottomCenter { get; set; } - public SKPoint LeftCenter { get; set; } - - public Cursor TopLeftRotateCursor { get; set; } - public Cursor TopRightRotateCursor { get; set; } - public Cursor BottomRightRotateCursor { get; set; } - public Cursor BottomLeftRotateCursor { get; set; } private void Update() { if (!(ProfileEditorService.SelectedProfileElement is Layer layer)) return; - ShapeRectangle = _layerEditorService.GetShapeRenderRect(layer.LayerShape).ToSKRect(); + ShapePath = _layerEditorService.GetLayerPath(layer, true, true, true); ShapeAnchor = _layerEditorService.GetLayerAnchor(layer, true); - - // Get a square path to use for mutation point placement - var path = _layerEditorService.GetLayerPath(layer, true, true, true); - TopLeft = path.Points[0]; - TopRight = path.Points[1]; - BottomRight = path.Points[2]; - BottomLeft = path.Points[3]; - - TopCenter = new SKPoint((TopLeft.X + TopRight.X) / 2, (TopLeft.Y + TopRight.Y) / 2); - RightCenter = new SKPoint((TopRight.X + BottomRight.X) / 2, (TopRight.Y + BottomRight.Y) / 2); - BottomCenter = new SKPoint((BottomLeft.X + BottomRight.X) / 2, (BottomLeft.Y + BottomRight.Y) / 2); - LeftCenter = new SKPoint((TopLeft.X + BottomLeft.X) / 2, (TopLeft.Y + BottomLeft.Y) / 2); - - TopLeftRotateCursor = CursorUtilities.GetRotatedCursor(Resources.aero_rotate_tl_ico, layer.RotationProperty.CurrentValue); - TopRightRotateCursor = CursorUtilities.GetRotatedCursor(Resources.aero_rotate_tr_ico, layer.RotationProperty.CurrentValue); - BottomRightRotateCursor = CursorUtilities.GetRotatedCursor(Resources.aero_rotate_br_ico, layer.RotationProperty.CurrentValue); - BottomLeftRotateCursor = CursorUtilities.GetRotatedCursor(Resources.aero_rotate_bl_ico, layer.RotationProperty.CurrentValue); - Execute.PostToUIThread(() => { var shapeGeometry = new RectangleGeometry(_layerEditorService.GetShapeRenderRect(layer.LayerShape)) @@ -100,283 +52,39 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools }; shapeGeometry.Freeze(); ShapeGeometry = shapeGeometry; - ShapeTransformCollection = _layerEditorService.GetLayerTransformGroup(layer).Children; - ShapeTransformCollection.Freeze(); }); + + // Store the last top-left for easy later on + _topLeft = _layerEditorService.GetLayerPath(layer, true, true, true).Points[0]; } - - private void UpdateControls() - { - ControlSize = Math.Max(10 / ProfileViewModel.PanZoomViewModel.Zoom, 4); - RotateSize = ControlSize * 8; - ControlOffset = new Thickness(ControlSize / 2 * -1, ControlSize / 2 * -1, 0, 0); - RotateOffset = new Thickness(RotateSize / 2 * -1, RotateSize / 2 * -1, 0, 0); - OutlineThickness = Math.Max(2 / ProfileViewModel.PanZoomViewModel.Zoom, 1); - } - - public void ShapeEditMouseDown(object sender, MouseButtonEventArgs e) - { - if (_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - // The path starts at 0,0 so there's no simple way to get the position relative to the top-left of the path - _dragStart = GetRelativePosition(sender, e).ToSKPoint(); - _dragStartScale = layer.SizeProperty.CurrentValue; - _previousDragAngle = CalculateAngle(_layerEditorService.GetLayerAnchor(layer, true), _dragStart); - - // Store the original position and do a test to figure out the mouse offset - var originalPosition = layer.PositionProperty.CurrentValue; - var scaledDragStart = _layerEditorService.GetScaledPoint(layer, _dragStart, true); - layer.PositionProperty.SetCurrentValue(scaledDragStart, ProfileEditorService.CurrentTime); - - // TopLeft is not updated yet and acts as a snapshot of the top-left before changing the position - // GetLayerPath will return the updated position with all transformations applied, the difference is the offset - _dragOffset = TopLeft - _layerEditorService.GetLayerPath(layer, true, true, true).Points[0]; - _dragStart += _dragOffset; - - // Restore the position back to before the test was done - layer.PositionProperty.SetCurrentValue(originalPosition, ProfileEditorService.CurrentTime); - - _isDragging = true; - ((IInputElement) sender).CaptureMouse(); - e.Handled = true; - } - - public void ShapeEditMouseUp(object sender, MouseButtonEventArgs e) - { - ProfileEditorService.UpdateSelectedProfileElement(); - - _dragOffset = SKPoint.Empty; - _dragStartAnchor = SKPoint.Empty; - - _isDragging = false; - _draggingHorizontally = false; - _draggingVertically = false; - - ((IInputElement) sender).ReleaseMouseCapture(); - e.Handled = true; - } - - #region Position - - public void Move(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - // Allow the user to move the shape only horizontally or vertically when holding down shift - if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) - { - // Keep the X position static if dragging vertically - if (_draggingVertically) - position.X = _dragStart.X; - // Keep the Y position static if dragging horizontally - else if (_draggingHorizontally) - position.Y = _dragStart.Y; - // Snap into place only if the mouse moved atleast a full pixel - else if (Math.Abs(position.X - _dragStart.X) > 1 || Math.Abs(position.Y - _dragStart.Y) > 1) - { - // Pick between X and Y by comparing which moved the furthers from the starting point - _draggingHorizontally = Math.Abs(position.X - _dragStart.X) > Math.Abs(position.Y - _dragStart.Y); - _draggingVertically = Math.Abs(position.X - _dragStart.X) < Math.Abs(position.Y - _dragStart.Y); - return; - } - } - // Reset both states when shift is not held down - else - { - _draggingVertically = false; - _draggingHorizontally = false; - } - - // Scale down the resulting position and make it relative - var scaled = _layerEditorService.GetScaledPoint(layer, position, true); - // Update the position property - layer.PositionProperty.SetCurrentValue(scaled, ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - #endregion - - #region Anchor - - public void AnchorEditMouseDown(object sender, MouseButtonEventArgs e) - { - if (_isDragging) - return; - - if (ProfileEditorService.SelectedProfileElement is Layer layer) - { - var dragStartPosition = GetRelativePosition(sender, e).ToSKPoint(); - - // Mouse doesn't care about rotation so get the layer path without rotation - var path = _layerEditorService.GetLayerPath(layer, true, true, false); - var topLeft = path.Points[0]; - // Measure from the top-left of the shape (without rotation) - _dragOffset = topLeft + (dragStartPosition - topLeft); - // Get the absolute layer anchor and make it relative to the unrotated shape - _dragStartAnchor = _layerEditorService.GetLayerAnchor(layer, true) - topLeft; - // Ensure the anchor starts in the center of the shape it is now relative to - _dragStartAnchor.X -= path.Bounds.Width / 2f; - _dragStartAnchor.Y -= path.Bounds.Height / 2f; - } - - _isDragging = true; - ((IInputElement) sender).CaptureMouse(); - e.Handled = true; - } - - public void AnchorMove(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - // The start anchor is relative to an unrotated version of the shape - var start = _dragStartAnchor; - // Add the current position to the start anchor to determine the new position - var current = start + (GetRelativePosition(sender, e).ToSKPoint() - _dragOffset); - // In order to keep the mouse movement unrotated, counter-act the active rotation - var countered = UnTransformPoints(new[] {start, current}, layer, start, true); - var scaled = _layerEditorService.GetScaledPoint(layer, countered[1], false); - - // Update the anchor point, this causes the shape to move - layer.AnchorPointProperty.SetCurrentValue(RoundPoint(scaled, 5), ProfileEditorService.CurrentTime); - // TopLeft is not updated yet and acts as a snapshot of the top-left before changing the anchor - var path = _layerEditorService.GetLayerPath(layer, true, true, true); - // Calculate the (scaled) difference between the old and now position - var difference = _layerEditorService.GetScaledPoint(layer, TopLeft - path.Points[0], false); - // Apply the difference so that the shape effectively stays in place - layer.PositionProperty.SetCurrentValue(RoundPoint(layer.PositionProperty.CurrentValue + difference, 5), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - private SKPoint RoundPoint(SKPoint point, int decimals) - { - return new SKPoint((float) Math.Round(point.X, decimals, MidpointRounding.AwayFromZero), (float) Math.Round(point.Y, decimals, MidpointRounding.AwayFromZero)); - } - - #endregion - - #region Size - - public void TopLeftResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = HorizontalResize(layer, position, ResizeOrigin.Left); - var height = VerticalResize(layer, position, ResizeOrigin.Top); - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - public void TopCenterResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = layer.SizeProperty.CurrentValue.Width; - var height = VerticalResize(layer, position, ResizeOrigin.Top); - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - public void TopRightResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = HorizontalResize(layer, position, ResizeOrigin.Right); - var height = VerticalResize(layer, position, ResizeOrigin.Top); - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - public void RightCenterResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = HorizontalResize(layer, position, ResizeOrigin.Right); - var height = layer.SizeProperty.CurrentValue.Height; - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - public void BottomRightResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = HorizontalResize(layer, position, ResizeOrigin.Right); - var height = VerticalResize(layer, position, ResizeOrigin.Bottom); - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - public void BottomCenterResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = layer.SizeProperty.CurrentValue.Width; - var height = VerticalResize(layer, position, ResizeOrigin.Bottom); - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - public void BottomLeftResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = HorizontalResize(layer, position, ResizeOrigin.Left); - var height = VerticalResize(layer, position, ResizeOrigin.Bottom); - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - public void LeftCenterResize(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - - var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; - var width = HorizontalResize(layer, position, ResizeOrigin.Left); - var height = layer.SizeProperty.CurrentValue.Height; - layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); - - ProfileEditorService.UpdateProfilePreview(); - } - - #endregion #region Rotation - public void Rotate(object sender, MouseEventArgs e) + private bool _rotating; + private float _previousDragAngle; + + public void RotateMouseDown(object sender, ShapeControlEventArgs e) { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) + _rotating = true; + if (ProfileEditorService.SelectedProfileElement is Layer layer) + _previousDragAngle = CalculateAngle(_layerEditorService.GetLayerAnchor(layer, true), GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint()); + else + _previousDragAngle = 0; + } + + public void RotateMouseUp(object sender, ShapeControlEventArgs e) + { + ProfileEditorService.UpdateSelectedProfileElement(); + _rotating = false; + } + + public void RotateMouseMove(object sender, ShapeControlEventArgs e) + { + if (!_rotating || !(ProfileEditorService.SelectedProfileElement is Layer layer)) return; var previousDragAngle = _previousDragAngle; - var newRotation = CalculateAngle(_layerEditorService.GetLayerAnchor(layer, true), GetRelativePosition(sender, e).ToSKPoint()); + var newRotation = CalculateAngle(_layerEditorService.GetLayerAnchor(layer, true), GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint()); _previousDragAngle = newRotation; // Allow the user to rotate the shape in increments of 5 @@ -396,58 +104,92 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools else newRotation = (float) Math.Round(newRotation, 2, MidpointRounding.AwayFromZero); - Debug.WriteLine(newRotation); layer.RotationProperty.SetCurrentValue(newRotation, ProfileEditorService.CurrentTime); ProfileEditorService.UpdateProfilePreview(); } - public void TopRightRotate(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - } - - public void BottomRightRotate(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - } - - public void BottomLeftRotate(object sender, MouseEventArgs e) - { - if (!_isDragging || !(ProfileEditorService.SelectedProfileElement is Layer layer)) - return; - } - #endregion - #region Private methods + #region Size - private SKPoint[] TransformPoints(SKPoint[] skPoints, Layer layer, SKPoint pivot) + private bool _isResizing; + + public void ResizeMouseDown(object sender, ShapeControlEventArgs e) { - var counterRotatePath = new SKPath(); - counterRotatePath.AddPoly(skPoints, false); - counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.RotationProperty.CurrentValue, pivot.X, pivot.Y)); - // counterRotatePath.Transform(SKMatrix.MakeScale(layer.SizeProperty.CurrentValue.Width, layer.SizeProperty.CurrentValue.Height)); + if (_isResizing || !(ProfileEditorService.SelectedProfileElement is Layer layer)) + return; - return counterRotatePath.Points; + // The path starts at 0,0 so there's no simple way to get the position relative to the top-left of the path + _dragStart = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint(); + _dragStartScale = layer.SizeProperty.CurrentValue; + + // Store the original position and do a test to figure out the mouse offset + var originalPosition = layer.PositionProperty.CurrentValue; + var scaledDragStart = _layerEditorService.GetScaledPoint(layer, _dragStart, true); + layer.PositionProperty.SetCurrentValue(scaledDragStart, ProfileEditorService.CurrentTime); + + // TopLeft is not updated yet and acts as a snapshot of the top-left before changing the position + // GetLayerPath will return the updated position with all transformations applied, the difference is the offset + _dragOffset = _topLeft - _layerEditorService.GetLayerPath(layer, true, true, true).Points[0]; + _dragStart += _dragOffset; + + // Restore the position back to before the test was done + layer.PositionProperty.SetCurrentValue(originalPosition, ProfileEditorService.CurrentTime); + _isResizing = true; } - private SKPoint[] UnTransformPoints(SKPoint[] skPoints, Layer layer, SKPoint pivot, bool includeScale) + public void ResizeMouseUp(object sender, ShapeControlEventArgs e) { - var counterRotatePath = new SKPath(); - counterRotatePath.AddPoly(skPoints, false); - counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.RotationProperty.CurrentValue * -1, pivot.X, pivot.Y)); - if (includeScale) - counterRotatePath.Transform(SKMatrix.MakeScale(1f / layer.SizeProperty.CurrentValue.Width, 1f / layer.SizeProperty.CurrentValue.Height)); - - return counterRotatePath.Points; + _isResizing = false; } - private Point GetRelativePosition(object sender, MouseEventArgs mouseEventArgs) + public void ResizeMouseMove(object sender, ShapeControlEventArgs e) { - var parent = VisualTreeHelper.GetParent((DependencyObject) sender); - return mouseEventArgs.GetPosition((IInputElement) parent); + if (!_isResizing || !(ProfileEditorService.SelectedProfileElement is Layer layer)) + return; + + float width, height; + var position = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint() + _dragOffset; + switch (e.ShapeControlPoint) + { + case ShapeControlPoint.TopLeft: + height = VerticalResize(layer, position, ResizeOrigin.Top); + width = HorizontalResize(layer, position, ResizeOrigin.Left); + break; + case ShapeControlPoint.TopRight: + height = VerticalResize(layer, position, ResizeOrigin.Top); + width = HorizontalResize(layer, position, ResizeOrigin.Right); + break; + case ShapeControlPoint.BottomRight: + height = VerticalResize(layer, position, ResizeOrigin.Bottom); + width = HorizontalResize(layer, position, ResizeOrigin.Right); + break; + case ShapeControlPoint.BottomLeft: + height = VerticalResize(layer, position, ResizeOrigin.Bottom); + width = HorizontalResize(layer, position, ResizeOrigin.Left); + break; + case ShapeControlPoint.TopCenter: + height = VerticalResize(layer, position, ResizeOrigin.Top); + width = layer.SizeProperty.CurrentValue.Width; + break; + case ShapeControlPoint.RightCenter: + width = HorizontalResize(layer, position, ResizeOrigin.Right); + height = layer.SizeProperty.CurrentValue.Height; + break; + case ShapeControlPoint.BottomCenter: + width = layer.SizeProperty.CurrentValue.Width; + height = VerticalResize(layer, position, ResizeOrigin.Bottom); + break; + case ShapeControlPoint.LeftCenter: + width = HorizontalResize(layer, position, ResizeOrigin.Left); + height = layer.SizeProperty.CurrentValue.Height; + break; + default: + throw new ArgumentOutOfRangeException(); + } + + layer.SizeProperty.SetCurrentValue(new SKSize(width, height), ProfileEditorService.CurrentTime); + ProfileEditorService.UpdateProfilePreview(); } private float HorizontalResize(Layer layer, SKPoint position, ResizeOrigin origin) @@ -490,6 +232,164 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools return Math.Max(0.001f, _dragStartScale.Height + scaleToAdd); } + #endregion + + #region Position + + private bool _movingShape; + private bool _movingAnchor; + private bool _draggingHorizontally; + private bool _draggingVertically; + private SKPoint _dragStartAnchor; + + public void MoveMouseDown(object sender, ShapeControlEventArgs e) + { + if (!(ProfileEditorService.SelectedProfileElement is Layer layer)) + return; + + if (e.ShapeControlPoint == ShapeControlPoint.LayerShape) + { + // The path starts at 0,0 so there's no simple way to get the position relative to the top-left of the path + _dragStart = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint(); + _dragStartScale = layer.SizeProperty.CurrentValue; + + // Store the original position and do a test to figure out the mouse offset + var originalPosition = layer.PositionProperty.CurrentValue; + var scaledDragStart = _layerEditorService.GetScaledPoint(layer, _dragStart, true); + layer.PositionProperty.SetCurrentValue(scaledDragStart, ProfileEditorService.CurrentTime); + + // TopLeft is not updated yet and acts as a snapshot of the top-left before changing the position + // GetLayerPath will return the updated position with all transformations applied, the difference is the offset + _dragOffset = _topLeft - _layerEditorService.GetLayerPath(layer, true, true, true).Points[0]; + _dragStart += _dragOffset; + + // Restore the position back to before the test was done + layer.PositionProperty.SetCurrentValue(originalPosition, ProfileEditorService.CurrentTime); + + _movingShape = true; + } + else if (e.ShapeControlPoint == ShapeControlPoint.Anchor) + { + var dragStartPosition = GetRelativePosition(sender, e.MouseEventArgs).ToSKPoint(); + + // Mouse doesn't care about rotation so get the layer path without rotation + var path = _layerEditorService.GetLayerPath(layer, true, true, false); + var topLeft = path.Points[0]; + // Measure from the top-left of the shape (without rotation) + _dragOffset = topLeft + (dragStartPosition - topLeft); + // Get the absolute layer anchor and make it relative to the unrotated shape + _dragStartAnchor = _layerEditorService.GetLayerAnchor(layer, true) - topLeft; + // Ensure the anchor starts in the center of the shape it is now relative to + _dragStartAnchor.X -= path.Bounds.Width / 2f; + _dragStartAnchor.Y -= path.Bounds.Height / 2f; + _movingAnchor = true; + } + } + + public void MoveMouseUp(object sender, ShapeControlEventArgs e) + { + _movingShape = false; + _movingAnchor = false; + } + + public void MoveMouseMove(object sender, ShapeControlEventArgs e) + { + if (_movingShape) + MoveShape(sender, e.MouseEventArgs); + else if (_movingAnchor) + MoveAnchor(sender, e.MouseEventArgs); + } + + public void MoveShape(object sender, MouseEventArgs e) + { + if (!(ProfileEditorService.SelectedProfileElement is Layer layer)) + return; + + var position = GetRelativePosition(sender, e).ToSKPoint() + _dragOffset; + // Allow the user to move the shape only horizontally or vertically when holding down shift + if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift)) + { + // Keep the X position static if dragging vertically + if (_draggingVertically) + position.X = _dragStart.X; + // Keep the Y position static if dragging horizontally + else if (_draggingHorizontally) + position.Y = _dragStart.Y; + // Snap into place only if the mouse moved atleast a full pixel + else if (Math.Abs(position.X - _dragStart.X) > 1 || Math.Abs(position.Y - _dragStart.Y) > 1) + { + // Pick between X and Y by comparing which moved the furthers from the starting point + _draggingHorizontally = Math.Abs(position.X - _dragStart.X) > Math.Abs(position.Y - _dragStart.Y); + _draggingVertically = Math.Abs(position.X - _dragStart.X) < Math.Abs(position.Y - _dragStart.Y); + return; + } + } + // Reset both states when shift is not held down + else + { + _draggingVertically = false; + _draggingHorizontally = false; + } + + // Scale down the resulting position and make it relative + var scaled = _layerEditorService.GetScaledPoint(layer, position, true); + // Update the position property + layer.PositionProperty.SetCurrentValue(scaled, ProfileEditorService.CurrentTime); + + ProfileEditorService.UpdateProfilePreview(); + } + + public void MoveAnchor(object sender, MouseEventArgs e) + { + if (!_movingAnchor || !(ProfileEditorService.SelectedProfileElement is Layer layer)) + return; + + // The start anchor is relative to an unrotated version of the shape + var start = _dragStartAnchor; + // Add the current position to the start anchor to determine the new position + var current = start + (GetRelativePosition(sender, e).ToSKPoint() - _dragOffset); + // In order to keep the mouse movement unrotated, counter-act the active rotation + var countered = UnTransformPoints(new[] {start, current}, layer, start, true); + var scaled = _layerEditorService.GetScaledPoint(layer, countered[1], false); + + // Update the anchor point, this causes the shape to move + layer.AnchorPointProperty.SetCurrentValue(RoundPoint(scaled, 5), ProfileEditorService.CurrentTime); + // TopLeft is not updated yet and acts as a snapshot of the top-left before changing the anchor + var path = _layerEditorService.GetLayerPath(layer, true, true, true); + // Calculate the (scaled) difference between the old and now position + var difference = _layerEditorService.GetScaledPoint(layer, _topLeft - path.Points[0], false); + // Apply the difference so that the shape effectively stays in place + layer.PositionProperty.SetCurrentValue(RoundPoint(layer.PositionProperty.CurrentValue + difference, 5), ProfileEditorService.CurrentTime); + + ProfileEditorService.UpdateProfilePreview(); + } + + #endregion + + #region Private methods + + private SKPoint RoundPoint(SKPoint point, int decimals) + { + return new SKPoint((float) Math.Round(point.X, decimals, MidpointRounding.AwayFromZero), (float) Math.Round(point.Y, decimals, MidpointRounding.AwayFromZero)); + } + + private SKPoint[] UnTransformPoints(SKPoint[] skPoints, Layer layer, SKPoint pivot, bool includeScale) + { + var counterRotatePath = new SKPath(); + counterRotatePath.AddPoly(skPoints, false); + counterRotatePath.Transform(SKMatrix.MakeRotationDegrees(layer.RotationProperty.CurrentValue * -1, pivot.X, pivot.Y)); + if (includeScale) + counterRotatePath.Transform(SKMatrix.MakeScale(1f / layer.SizeProperty.CurrentValue.Width, 1f / layer.SizeProperty.CurrentValue.Height)); + + return counterRotatePath.Points; + } + + private Point GetRelativePosition(object sender, MouseEventArgs mouseEventArgs) + { + var parent = VisualTreeHelper.GetParent((DependencyObject) sender); + return mouseEventArgs.GetPosition((IInputElement) parent); + } + private float CalculateAngle(SKPoint start, SKPoint arrival) { var radian = (float) Math.Atan2(start.Y - arrival.Y, start.X - arrival.X); diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs index 93d1d703b..00f3b707d 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/EllipseToolViewModel.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Diagnostics; +using System.IO; using System.Windows; using System.Windows.Input; using Artemis.Core.Models.Profile; diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs index de499d4d5..f2e660324 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/FillToolViewModel.cs @@ -1,17 +1,23 @@ -using System.IO; +using System; +using System.Diagnostics; +using System.IO; using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core.Models.Profile.LayerShapes; using Artemis.UI.Properties; +using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { public class FillToolViewModel : VisualizationToolViewModel { - public FillToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) + private readonly ILayerEditorService _layerEditorService; + + public FillToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService) : base(profileViewModel, profileEditorService) { + _layerEditorService = layerEditorService; using (var stream = new MemoryStream(Resources.aero_fill)) { Cursor = new Cursor(stream); @@ -34,6 +40,9 @@ namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools if (layer == null) return; layer.LayerShape = new Fill(layer); + + // Apply the full layer rectangle + _layerEditorService.SetShapeRenderRect(layer.LayerShape, _layerEditorService.GetLayerRect(layer)); ProfileEditorService.UpdateSelectedProfileElement(); } } diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml deleted file mode 100644 index ea940689d..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml.cs deleted file mode 100644 index dfeb04a68..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolView.xaml.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Windows.Controls; - -namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools -{ - /// - /// Interaction logic for SelectionAddToolView.xaml - /// - public partial class SelectionAddToolView : UserControl - { - public SelectionAddToolView() - { - InitializeComponent(); - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolViewModel.cs deleted file mode 100644 index f808e7a6e..000000000 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionAddToolViewModel.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.IO; -using System.Windows; -using System.Windows.Input; -using Artemis.UI.Properties; -using Artemis.UI.Services.Interfaces; - -namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools -{ - public class SelectionAddToolViewModel : VisualizationToolViewModel - { - public SelectionAddToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) - { - using (var stream = new MemoryStream(Resources.aero_pen_plus)) - { - Cursor = new Cursor(stream); - } - } - - public override void MouseUp(object sender, MouseButtonEventArgs e) - { - base.MouseUp(sender, e); - - var position = e.GetPosition((IInputElement) sender); - var selectedRect = new Rect(MouseDownStartPosition, position); - - foreach (var device in ProfileViewModel.DeviceViewModels) - { - foreach (var ledViewModel in device.Leds) - { - if (ProfileViewModel.PanZoomViewModel.TransformContainingRect(ledViewModel.Led.RgbLed.AbsoluteLedRectangle).IntersectsWith(selectedRect)) - ledViewModel.IsSelected = true; - } - } - } - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolView.xaml index 04865a926..a702d0bd9 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolView.xaml @@ -3,8 +3,43 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs index 793ab0c5b..83a65852a 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionRemoveToolViewModel.cs @@ -1,36 +1,97 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Windows; using System.Windows.Input; +using Artemis.Core.Models.Profile; +using Artemis.Core.Models.Surface; +using Artemis.UI.Extensions; using Artemis.UI.Properties; +using Artemis.UI.Services; using Artemis.UI.Services.Interfaces; namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools { public class SelectionRemoveToolViewModel : VisualizationToolViewModel { - public SelectionRemoveToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService) : base(profileViewModel, profileEditorService) + private readonly ILayerEditorService _layerEditorService; + + public SelectionRemoveToolViewModel(ProfileViewModel profileViewModel, IProfileEditorService profileEditorService, ILayerEditorService layerEditorService) : base(profileViewModel, + profileEditorService) { + _layerEditorService = layerEditorService; using (var stream = new MemoryStream(Resources.aero_pen_min)) { Cursor = new Cursor(stream); } } + public Rect DragRectangle { get; set; } + public override void MouseUp(object sender, MouseButtonEventArgs e) { base.MouseUp(sender, e); - var position = e.GetPosition((IInputElement) sender); + var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); + var selectedRect = new Rect(MouseDownStartPosition, position); + + // Get selected LEDs + var selectedLeds = new List(); + foreach (var device in ProfileViewModel.DeviceViewModels) + { + foreach (var ledViewModel in device.Leds) + { + if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect)) + selectedLeds.Add(ledViewModel.Led); + // Unselect everything + ledViewModel.IsSelected = false; + } + } + + // Apply the selection to the selected layer layer + if (ProfileEditorService.SelectedProfileElement is Layer layer) + { + // If the layer has a shape, save it's size + var shapeSize = Rect.Empty; + if (layer.LayerShape != null) + shapeSize = _layerEditorService.GetShapeRenderRect(layer.LayerShape); + + var remainingLeds = layer.Leds.Except(selectedLeds).ToList(); + layer.ClearLeds(); + layer.AddLeds(remainingLeds); + + // Restore the saved size + if (layer.LayerShape != null) + _layerEditorService.SetShapeRenderRect(layer.LayerShape, shapeSize); + + ProfileEditorService.UpdateSelectedProfileElement(); + } + } + + public override void MouseMove(object sender, MouseEventArgs e) + { + base.MouseMove(sender, e); + if (!IsMouseDown) + { + DragRectangle = new Rect(-1, -1, 0, 0); + return; + } + + var position = ProfileViewModel.PanZoomViewModel.GetRelativeMousePosition(sender, e); var selectedRect = new Rect(MouseDownStartPosition, position); foreach (var device in ProfileViewModel.DeviceViewModels) { foreach (var ledViewModel in device.Leds) { - if (ProfileViewModel.PanZoomViewModel.TransformContainingRect(ledViewModel.Led.RgbLed.AbsoluteLedRectangle).IntersectsWith(selectedRect)) + if (ledViewModel.Led.RgbLed.AbsoluteLedRectangle.ToWindowsRect(1).IntersectsWith(selectedRect)) + ledViewModel.IsSelected = true; + else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) ledViewModel.IsSelected = false; } } + + DragRectangle = selectedRect; } } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml index 2a590c293..93985248e 100644 --- a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/Tools/SelectionToolView.xaml @@ -3,9 +3,6 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:Artemis.UI.Screens.Module.ProfileEditor.Visualization.Tools" - xmlns:s="https://github.com/canton7/Stylet" - xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> @@ -31,21 +28,6 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/UserControls/LayerShapeControl.xaml.cs b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/UserControls/LayerShapeControl.xaml.cs new file mode 100644 index 000000000..676eaa337 --- /dev/null +++ b/src/Artemis.UI/Screens/Module/ProfileEditor/Visualization/UserControls/LayerShapeControl.xaml.cs @@ -0,0 +1,392 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Artemis.UI.Events; +using Artemis.UI.Utilities; +using SkiaSharp; + +namespace Artemis.UI.Screens.Module.ProfileEditor.Visualization.UserControls +{ + /// + /// Interaction logic for LayerShapeControl.xaml + /// + public partial class LayerShapeControl : UserControl + { + public static readonly DependencyProperty ZoomProperty = DependencyProperty.Register(nameof(Zoom), typeof(double), typeof(LayerShapeControl), + new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.None, ZoomChanged)); + + public static readonly DependencyProperty ShapePathProperty = DependencyProperty.Register(nameof(ShapePath), typeof(SKPath), typeof(LayerShapeControl), + new FrameworkPropertyMetadata(default(SKPath), FrameworkPropertyMetadataOptions.None, ShapePathChanged)); + + public static readonly DependencyProperty ShapeAnchorProperty = DependencyProperty.Register(nameof(ShapeAnchor), typeof(SKPoint), typeof(LayerShapeControl), + new FrameworkPropertyMetadata(default(SKPoint), FrameworkPropertyMetadataOptions.None, ShapeAnchorChanged)); + + public static readonly DependencyProperty ShapeGeometryProperty = DependencyProperty.Register(nameof(ShapeGeometry), typeof(Geometry), typeof(LayerShapeControl), + new FrameworkPropertyMetadata(default(Geometry), FrameworkPropertyMetadataOptions.None, ShapeGeometryChanged)); + + public LayerShapeControl() + { + InitializeComponent(); + UpdatePositions(); + UpdateDimensions(); + } + + public double Zoom + { + get => (double) GetValue(ZoomProperty); + set => SetValue(ZoomProperty, value); + } + + public SKPath ShapePath + { + get => (SKPath) GetValue(ShapePathProperty); + set => SetValue(ShapePathProperty, value); + } + + public SKPoint ShapeAnchor + { + get => (SKPoint) GetValue(ShapeAnchorProperty); + set => SetValue(ShapeAnchorProperty, value); + } + + public Geometry ShapeGeometry + { + get => (Geometry) GetValue(ShapeGeometryProperty); + set => SetValue(ShapeGeometryProperty, value); + } + + private static void ZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var layerShapeControl = (LayerShapeControl) d; + + layerShapeControl.SetCurrentValue(ZoomProperty, e.NewValue); + layerShapeControl.UpdateDimensions(); + } + + private static void ShapePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var layerShapeControl = (LayerShapeControl) d; + + layerShapeControl.SetCurrentValue(ShapePathProperty, e.NewValue); + layerShapeControl.UpdatePositions(); + } + + private static void ShapeAnchorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var layerShapeControl = (LayerShapeControl) d; + + layerShapeControl.SetCurrentValue(ShapeAnchorProperty, e.NewValue); + layerShapeControl.UpdateShapeAnchor(); + } + + private static void ShapeGeometryChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var layerShapeControl = (LayerShapeControl) d; + + layerShapeControl.SetCurrentValue(ShapeGeometryProperty, e.NewValue); + layerShapeControl.UpdateShapeGeometry(); + } + + public void UpdateDimensions() + { + if (Zoom == 0) + return; + + // Rotation controls + UpdateRotateDimensions(RotateTopLeft); + UpdateRotateDimensions(RotateTopRight); + UpdateRotateDimensions(RotateBottomRight); + UpdateRotateDimensions(RotateBottomLeft); + + // Size controls + UpdateResizeDimensions(ResizeTopCenter); + UpdateResizeDimensions(ResizeRightCenter); + UpdateResizeDimensions(ResizeBottomCenter); + UpdateResizeDimensions(ResizeLeftCenter); + UpdateResizeDimensions(ResizeTopLeft); + UpdateResizeDimensions(ResizeTopRight); + UpdateResizeDimensions(ResizeBottomRight); + UpdateResizeDimensions(ResizeBottomLeft); + + // Anchor point + UpdateResizeDimensions(AnchorPoint); + + // Layer outline + LayerShapeOutline.StrokeThickness = Math.Max(2 / Zoom, 1); + } + + private void UpdateShapeGeometry() + { + LayerShapeOutline.Data = ShapeGeometry; + } + + private void UpdateShapeAnchor() + { + UpdateControlPosition(AnchorPoint, ShapeAnchor); + } + + private void UpdatePositions() + { + if (ShapePath == null) + return; + + // Rotation controls + UpdateControlPosition(RotateTopLeft, ShapePath.Points[0]); + UpdateControlPosition(RotateTopRight, ShapePath.Points[1]); + UpdateControlPosition(RotateBottomRight, ShapePath.Points[2]); + UpdateControlPosition(RotateBottomLeft, ShapePath.Points[3]); + + // Size controls + UpdateControlPosition(ResizeTopCenter, ShapePath.Points[0], ShapePath.Points[1]); + UpdateControlPosition(ResizeRightCenter, ShapePath.Points[1], ShapePath.Points[2]); + UpdateControlPosition(ResizeBottomCenter, ShapePath.Points[2], ShapePath.Points[3]); + UpdateControlPosition(ResizeLeftCenter, ShapePath.Points[3], ShapePath.Points[0]); + UpdateControlPosition(ResizeTopLeft, ShapePath.Points[0]); + UpdateControlPosition(ResizeTopRight, ShapePath.Points[1]); + UpdateControlPosition(ResizeBottomRight, ShapePath.Points[2]); + UpdateControlPosition(ResizeBottomLeft, ShapePath.Points[3]); + } + + #region Helpers + + private void UpdateControlPosition(UIElement control, SKPoint point) + { + if (float.IsInfinity(point.X) || float.IsInfinity(point.Y)) + return; + Canvas.SetLeft(control, point.X); + Canvas.SetTop(control, point.Y); + } + + private void UpdateControlPosition(UIElement control, SKPoint point1, SKPoint point2) + { + var point = new SKPoint((point1.X + point2.X) / 2, (point1.Y + point2.Y) / 2); + UpdateControlPosition(control, point); + } + + private void UpdateRotateDimensions(FrameworkElement rotateControl) + { + var controlSize = Math.Max(10 / Zoom, 4); + var rotateSize = controlSize * 8; + + rotateControl.Width = rotateSize; + rotateControl.Height = rotateSize; + rotateControl.Margin = new Thickness(rotateSize / 2 * -1, rotateSize / 2 * -1, 0, 0); + } + + private void UpdateResizeDimensions(FrameworkElement resizeControl) + { + var controlSize = Math.Max(10 / Zoom, 4); + + resizeControl.Width = controlSize; + resizeControl.Height = controlSize; + resizeControl.Margin = new Thickness(controlSize / 2 * -1, controlSize / 2 * -1, 0, 0); + } + + #endregion + + #region Event handlers + + private void RotationOnMouseDown(object sender, MouseEventArgs e) + { + ((IInputElement) sender).CaptureMouse(); + e.Handled = true; + + if (ReferenceEquals(sender, RotateTopLeft)) + OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft)); + else if (ReferenceEquals(sender, RotateTopRight)) + OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight)); + else if (ReferenceEquals(sender, RotateBottomRight)) + OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight)); + else if (ReferenceEquals(sender, RotateBottomLeft)) + OnRotateMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft)); + } + + private void RotationOnMouseUp(object sender, MouseEventArgs e) + { + if (ReferenceEquals(sender, RotateTopLeft)) + OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft)); + else if (ReferenceEquals(sender, RotateTopRight)) + OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight)); + else if (ReferenceEquals(sender, RotateBottomRight)) + OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight)); + else if (ReferenceEquals(sender, RotateBottomLeft)) + OnRotateMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft)); + + ((IInputElement) sender).ReleaseMouseCapture(); + e.Handled = true; + } + + private void RotationOnMouseMove(object sender, MouseEventArgs e) + { + if (ReferenceEquals(sender, RotateTopLeft)) + OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft)); + else if (ReferenceEquals(sender, RotateTopRight)) + OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight)); + else if (ReferenceEquals(sender, RotateBottomRight)) + OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight)); + else if (ReferenceEquals(sender, RotateBottomLeft)) + OnRotateMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft)); + } + + private void ResizeOnMouseDown(object sender, MouseEventArgs e) + { + ((IInputElement) sender).CaptureMouse(); + e.Handled = true; + + if (ReferenceEquals(sender, ResizeTopCenter)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopCenter)); + else if (ReferenceEquals(sender, ResizeRightCenter)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.RightCenter)); + else if (ReferenceEquals(sender, ResizeBottomCenter)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomCenter)); + else if (ReferenceEquals(sender, ResizeLeftCenter)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.LeftCenter)); + else if (ReferenceEquals(sender, ResizeTopLeft)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft)); + else if (ReferenceEquals(sender, ResizeTopRight)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight)); + else if (ReferenceEquals(sender, ResizeBottomRight)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight)); + else if (ReferenceEquals(sender, ResizeBottomLeft)) + OnResizeMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft)); + } + + private void ResizeOnMouseUp(object sender, MouseEventArgs e) + { + if (ReferenceEquals(sender, ResizeTopCenter)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopCenter)); + else if (ReferenceEquals(sender, ResizeRightCenter)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.RightCenter)); + else if (ReferenceEquals(sender, ResizeBottomCenter)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomCenter)); + else if (ReferenceEquals(sender, ResizeLeftCenter)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.LeftCenter)); + else if (ReferenceEquals(sender, ResizeTopLeft)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft)); + else if (ReferenceEquals(sender, ResizeTopRight)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight)); + else if (ReferenceEquals(sender, ResizeBottomRight)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight)); + else if (ReferenceEquals(sender, ResizeBottomLeft)) + OnResizeMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft)); + + ((IInputElement) sender).ReleaseMouseCapture(); + e.Handled = true; + } + + private void ResizeOnMouseMove(object sender, MouseEventArgs e) + { + if (ReferenceEquals(sender, ResizeTopCenter)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopCenter)); + else if (ReferenceEquals(sender, ResizeRightCenter)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.RightCenter)); + else if (ReferenceEquals(sender, ResizeBottomCenter)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomCenter)); + else if (ReferenceEquals(sender, ResizeLeftCenter)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.LeftCenter)); + else if (ReferenceEquals(sender, ResizeTopLeft)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopLeft)); + else if (ReferenceEquals(sender, ResizeTopRight)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.TopRight)); + else if (ReferenceEquals(sender, ResizeBottomRight)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomRight)); + else if (ReferenceEquals(sender, ResizeBottomLeft)) + OnResizeMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.BottomLeft)); + } + + private void MoveOnMouseDown(object sender, MouseEventArgs e) + { + ((IInputElement) sender).CaptureMouse(); + e.Handled = true; + + if (ReferenceEquals(sender, LayerShapeOutline)) + OnMoveMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.LayerShape)); + else if (ReferenceEquals(sender, AnchorPoint)) + OnMoveMouseDown(new ShapeControlEventArgs(e, ShapeControlPoint.Anchor)); + } + + private void MoveOnMouseUp(object sender, MouseEventArgs e) + { + if (ReferenceEquals(sender, LayerShapeOutline)) + OnMoveMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.LayerShape)); + else if (ReferenceEquals(sender, AnchorPoint)) + OnMoveMouseUp(new ShapeControlEventArgs(e, ShapeControlPoint.Anchor)); + + ((IInputElement) sender).ReleaseMouseCapture(); + e.Handled = true; + } + + private void MoveOnMouseMove(object sender, MouseEventArgs e) + { + if (ReferenceEquals(sender, LayerShapeOutline)) + OnMoveMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.LayerShape)); + else if (ReferenceEquals(sender, AnchorPoint)) + OnMoveMouseMove(new ShapeControlEventArgs(e, ShapeControlPoint.Anchor)); + } + + #endregion + + #region Events + + public event EventHandler RotateMouseDown; + public event EventHandler RotateMouseUp; + public event EventHandler RotateMouseMove; + + public event EventHandler ResizeMouseDown; + public event EventHandler ResizeMouseUp; + public event EventHandler ResizeMouseMove; + + public event EventHandler MoveMouseDown; + public event EventHandler MoveMouseUp; + public event EventHandler MoveMouseMove; + + protected virtual void OnMoveMouseMove(ShapeControlEventArgs e) + { + MoveMouseMove?.Invoke(this, e); + } + + protected virtual void OnMoveMouseUp(ShapeControlEventArgs e) + { + MoveMouseUp?.Invoke(this, e); + } + + protected virtual void OnMoveMouseDown(ShapeControlEventArgs e) + { + MoveMouseDown?.Invoke(this, e); + } + + protected virtual void OnResizeMouseMove(ShapeControlEventArgs e) + { + ResizeMouseMove?.Invoke(this, e); + } + + protected virtual void OnResizeMouseUp(ShapeControlEventArgs e) + { + ResizeMouseUp?.Invoke(this, e); + } + + protected virtual void OnResizeMouseDown(ShapeControlEventArgs e) + { + ResizeMouseDown?.Invoke(this, e); + } + + protected virtual void OnRotateMouseMove(ShapeControlEventArgs e) + { + RotateMouseMove?.Invoke(this, e); + } + + protected virtual void OnRotateMouseUp(ShapeControlEventArgs e) + { + RotateMouseUp?.Invoke(this, e); + } + + protected virtual void OnRotateMouseDown(ShapeControlEventArgs e) + { + RotateMouseDown?.Invoke(this, e); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Artemis.UI/Services/LayerShapeService.cs b/src/Artemis.UI/Services/LayerShapeService.cs index 6fd625b93..419ba60be 100644 --- a/src/Artemis.UI/Services/LayerShapeService.cs +++ b/src/Artemis.UI/Services/LayerShapeService.cs @@ -26,10 +26,10 @@ namespace Artemis.UI.Services // Adjust the render rectangle for the difference in render scale var renderScale = _settingsService.GetSetting("Core.RenderScale", 1.0).Value; return new Rect( - layer.AbsoluteRectangle.Left / renderScale * 1, - layer.AbsoluteRectangle.Top / renderScale * 1, - Math.Max(0, layer.AbsoluteRectangle.Width / renderScale * 1), - Math.Max(0, layer.AbsoluteRectangle.Height / renderScale * 1) + layer.Rectangle.Left / renderScale * 1, + layer.Rectangle.Top / renderScale * 1, + Math.Max(0, layer.Rectangle.Width / renderScale * 1), + Math.Max(0, layer.Rectangle.Height / renderScale * 1) ); } @@ -38,10 +38,10 @@ namespace Artemis.UI.Services // Adjust the render rectangle for the difference in render scale var renderScale = _settingsService.GetSetting("Core.RenderScale", 1.0).Value; return new Rect( - layer.Rectangle.Left / renderScale * 1, - layer.Rectangle.Top / renderScale * 1, - Math.Max(0, layer.Rectangle.Width / renderScale * 1), - Math.Max(0, layer.Rectangle.Height / renderScale * 1) + layer.AbsoluteRectangle.Left / renderScale * 1, + layer.AbsoluteRectangle.Top / renderScale * 1, + Math.Max(0, layer.AbsoluteRectangle.Width / renderScale * 1), + Math.Max(0, layer.AbsoluteRectangle.Height / renderScale * 1) ); } @@ -187,10 +187,10 @@ namespace Artemis.UI.Services // Adjust the provided rect for the difference in render scale var renderScale = _settingsService.GetSetting("Core.RenderScale", 1.0).Value; layerShape.ScaledRectangle = SKRect.Create( - 100f / layerShape.Layer.Rectangle.Width * ((float) (rect.Left * renderScale) - layerShape.Layer.Rectangle.Left) / 100f, - 100f / layerShape.Layer.Rectangle.Height * ((float) (rect.Top * renderScale) - layerShape.Layer.Rectangle.Top) / 100f, - 100f / layerShape.Layer.Rectangle.Width * (float) (rect.Width * renderScale) / 100f, - 100f / layerShape.Layer.Rectangle.Height * (float) (rect.Height * renderScale) / 100f + 100f / layerShape.Layer.AbsoluteRectangle.Width * ((float) (rect.Left * renderScale) - layerShape.Layer.AbsoluteRectangle.Left) / 100f, + 100f / layerShape.Layer.AbsoluteRectangle.Height * ((float) (rect.Top * renderScale) - layerShape.Layer.AbsoluteRectangle.Top) / 100f, + 100f / layerShape.Layer.AbsoluteRectangle.Width * (float) (rect.Width * renderScale) / 100f, + 100f / layerShape.Layer.AbsoluteRectangle.Height * (float) (rect.Height * renderScale) / 100f ); layerShape.CalculateRenderProperties(); } @@ -202,14 +202,14 @@ namespace Artemis.UI.Services if (absolute) { return new SKPoint( - 100f / layer.Rectangle.Width * ((float) (point.X * renderScale) - layer.Rectangle.Left) / 100f, - 100f / layer.Rectangle.Height * ((float) (point.Y * renderScale) - layer.Rectangle.Top) / 100f + 100f / layer.AbsoluteRectangle.Width * ((float) (point.X * renderScale) - layer.AbsoluteRectangle.Left) / 100f, + 100f / layer.AbsoluteRectangle.Height * ((float) (point.Y * renderScale) - layer.AbsoluteRectangle.Top) / 100f ); } return new SKPoint( - 100f / layer.Rectangle.Width * (float)(point.X * renderScale) / 100f, - 100f / layer.Rectangle.Height * (float)(point.Y * renderScale) / 100f + 100f / layer.AbsoluteRectangle.Width * (float)(point.X * renderScale) / 100f, + 100f / layer.AbsoluteRectangle.Height * (float)(point.Y * renderScale) / 100f ); } } @@ -217,12 +217,19 @@ namespace Artemis.UI.Services public interface ILayerEditorService : IArtemisUIService { /// - /// Returns an absolute and scaled rectangle for the given layer that is corrected for the current render scale. + /// Returns an relative and scaled rectangle for the given layer that is corrected for the current render scale. /// /// /// Rect GetLayerRenderRect(Layer layer); + /// + /// Returns an absolute and scaled rectangle for the given layer that is corrected for the current render scale. + /// + /// + /// + Rect GetLayerRect(Layer layer); + /// /// Returns an absolute and scaled rectangular path for the given layer that is corrected for the current render scale. /// diff --git a/src/Artemis.UI/Utilities/CursorRotator.cs b/src/Artemis.UI/Utilities/CursorRotator.cs deleted file mode 100644 index df9a77e1d..000000000 --- a/src/Artemis.UI/Utilities/CursorRotator.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Windows.Input; - -namespace Artemis.UI.Utilities -{ - public static class CursorUtilities - { - public static Cursor GetRotatedCursor(Icon icon, float rotationAngle) - { - // Load as Bitmap, convert to BitmapSource - using (var iconStream = new MemoryStream()) - using (var rotatedStream = new MemoryStream()) - { - icon.Save(iconStream); - - // Open the source image and create the bitmap for the rotated image - using (var sourceImage = icon.ToBitmap()) - using (var rotateImage = new Bitmap(sourceImage.Width, sourceImage.Height)) - { - // Set the resolution for the rotation image - rotateImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution); - // Create a graphics object - using (var gdi = Graphics.FromImage(rotateImage)) - { - //Rotate the image - gdi.TranslateTransform((float) sourceImage.Width / 2, (float) sourceImage.Height / 2); - gdi.RotateTransform(rotationAngle); - gdi.TranslateTransform(-(float) sourceImage.Width / 2, -(float) sourceImage.Height / 2); - gdi.DrawImage(sourceImage, new Point(0, 0)); - } - - // Save to a file - IconFromImage(rotateImage).Save(rotatedStream); - } - - // Convert saved file into .cur format - rotatedStream.Seek(2, SeekOrigin.Begin); - rotatedStream.Write(iconStream.ToArray(), 2, 1); - rotatedStream.Seek(10, SeekOrigin.Begin); - rotatedStream.Write(iconStream.ToArray(), 10, 2); - rotatedStream.Seek(0, SeekOrigin.Begin); - - // Construct Cursor - return new Cursor(rotatedStream); - } - } - - public static Icon IconFromImage(Image img) - { - using (var ms = new MemoryStream()) - using (var bw = new BinaryWriter(ms)) - { - // Header - bw.Write((short) 0); // 0 : reserved - bw.Write((short) 1); // 2 : 1=ico, 2=cur - bw.Write((short) 1); // 4 : number of images - // Image directory - var w = img.Width; - if (w >= 256) w = 0; - bw.Write((byte) w); // 0 : width of image - var h = img.Height; - if (h >= 256) h = 0; - bw.Write((byte) h); // 1 : height of image - bw.Write((byte) 0); // 2 : number of colors in palette - bw.Write((byte) 0); // 3 : reserved - bw.Write((short) 0); // 4 : number of color planes - bw.Write((short) 0); // 6 : bits per pixel - var sizeHere = ms.Position; - bw.Write(0); // 8 : image size - var start = (int) ms.Position + 4; - bw.Write(start); // 12: offset of image data - // Image data - img.Save(ms, ImageFormat.Png); - var imageSize = (int) ms.Position - start; - ms.Seek(sizeHere, SeekOrigin.Begin); - bw.Write(imageSize); - ms.Seek(0, SeekOrigin.Begin); - - // And load it - return new Icon(ms); - } - } - } -} \ No newline at end of file