diff --git a/Svg.Editor.Avalonia.Forms/Dialog/UserInteractionServiceExt.cs b/Svg.Editor.Avalonia.Forms/Dialog/UserInteractionServiceExt.cs deleted file mode 100644 index 62cb6be7e..000000000 --- a/Svg.Editor.Avalonia.Forms/Dialog/UserInteractionServiceExt.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace Svg.Editor.Avalon.Forms.Dialog; - -public static class UserInteractionServiceExt -{ - public static IUserInteraction UserInteractionInst => new UserInteractionService(); -} \ No newline at end of file diff --git a/Svg.Editor.Avalonia.Forms/Services/ColorInputService.cs b/Svg.Editor.Avalonia.Forms/Services/ColorInputService.cs index 9ea7b9834..1d69e8001 100644 --- a/Svg.Editor.Avalonia.Forms/Services/ColorInputService.cs +++ b/Svg.Editor.Avalonia.Forms/Services/ColorInputService.cs @@ -11,9 +11,15 @@ namespace Svg.Editor.Avalon.Forms.Services { public class ColorInputService : Editor.Tools.IColorInputService { + private readonly IUserInteraction _userInteractionService; + + public ColorInputService() + { + _userInteractionService = SvgEngine.Resolve(); + } public async Task GetHexaColorFromUserInput(string title) { - var result = await UserInteractionServiceExt.UserInteractionInst.ColorPickerAsync(title); + var result = await _userInteractionService.ColorPickerAsync(title); if (result == null) return "#000000"; diff --git a/Svg.Editor.Avalonia.Forms/Services/MarkerOptionsInputService.cs b/Svg.Editor.Avalonia.Forms/Services/MarkerOptionsInputService.cs index 132e769d3..f5f3d1838 100644 --- a/Svg.Editor.Avalonia.Forms/Services/MarkerOptionsInputService.cs +++ b/Svg.Editor.Avalonia.Forms/Services/MarkerOptionsInputService.cs @@ -1,4 +1,5 @@ using Svg.Editor.Avalon.Forms.Dialog; +using Svg.Editor.Interfaces; using Svg.Editor.Tools; using System.Collections.Generic; using System.Linq; @@ -8,22 +9,30 @@ namespace Svg.Editor.Avalon.Forms.Services { public class MarkerOptionsInputService : IMarkerOptionsInputService { - public async Task GetUserInput(string title, IEnumerable markerStartOptions, int markerStartSelected, IEnumerable markerEndOptions, - int markerEndSelected) + private readonly ILocalizationService _localizationService; + private readonly IUserInteraction _userInteractionService; + + + public MarkerOptionsInputService() { + _localizationService = SvgEngine.Resolve(); + _userInteractionService = SvgEngine.Resolve(); + } + public async Task GetUserInput(IEnumerable markerStartOptions, int markerStartSelected, IEnumerable markerEndOptions, + int markerEndSelected) + { var mso = markerStartOptions.ToList(); - var start = await UserInteractionServiceExt.UserInteractionInst.ActionSheetAsync(title, mso.ToArray()); + var start = await _userInteractionService.ActionSheetAsync(_localizationService.GetString("Svg.Editor.Marker.Options.Start"), mso.ToArray(), cancelButton: _localizationService.GetString("Svg.Editor.Global.Cancel"), cancellable: true); - await UserInteractionServiceExt.UserInteractionInst.ActionSheetAsync(title, mso.ToArray()); var startIndex = mso.IndexOf(start); if (start == null || startIndex < 0) return new[] { markerStartSelected, markerEndSelected }; var meo = markerEndOptions.ToList(); - var end = await UserInteractionServiceExt.UserInteractionInst.ActionSheetAsync(title, mso.ToArray()); + var end = await _userInteractionService.ActionSheetAsync(_localizationService.GetString("Svg.Editor.Marker.Options.End"), meo.ToArray(), cancelButton: _localizationService.GetString("Svg.Editor.Global.Cancel"), cancellable: true); var endIndex = meo.IndexOf(end); if (end == null || endIndex < 0) diff --git a/Svg.Editor.Avalonia.Forms/Services/PinInputService.cs b/Svg.Editor.Avalonia.Forms/Services/PinInputService.cs index 942f7a87c..b9efd2dbf 100644 --- a/Svg.Editor.Avalonia.Forms/Services/PinInputService.cs +++ b/Svg.Editor.Avalonia.Forms/Services/PinInputService.cs @@ -1,21 +1,29 @@ using Avalonia.Controls; +using Svg.Editor.Avalon.Forms.Dialog; +using Svg.Editor.Interfaces; using Svg.Editor.Tools; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -using Svg.Editor.Avalon.Forms.Dialog; namespace Svg.Editor.Avalon.Forms.Services { public class PinInputService : IPinInputService { + private readonly IUserInteraction _userInteractionService; + + + public PinInputService() + { + _userInteractionService = SvgEngine.Resolve(); + } public async Task GetUserInput(IEnumerable pinSizeOptions, int oldSizeIndex = 1) { var defaultResult = (PinTool.PinSize)oldSizeIndex; - var sizeResult = await UserInteractionServiceExt.UserInteractionInst.ActionSheetAsync("Select pin size", pinSizeOptions.ToArray()); + var sizeResult = await _userInteractionService.ActionSheetAsync("Select pin size", pinSizeOptions.ToArray()); PinTool.PinSize.Medium.ToString(); if (sizeResult == "Cancel") diff --git a/Svg.Editor.Avalonia.Forms/Services/StrokeStyleOptionsInputService.cs b/Svg.Editor.Avalonia.Forms/Services/StrokeStyleOptionsInputService.cs index e6cc7d59a..fbcfe3a58 100644 --- a/Svg.Editor.Avalonia.Forms/Services/StrokeStyleOptionsInputService.cs +++ b/Svg.Editor.Avalonia.Forms/Services/StrokeStyleOptionsInputService.cs @@ -4,23 +4,34 @@ using System.Threading.Tasks; using Avalonia.Controls; using Svg.Editor.Avalon.Forms.Dialog; +using Svg.Editor.Interfaces; using Svg.Editor.Tools; namespace Svg.Editor.Avalon.Forms.Services { public class StrokeStyleOptionsInputService : IStrokeStyleOptionsInputService { + private readonly ILocalizationService _localizationService; + private readonly IUserInteraction _userInteractionService; + + public StrokeStyleOptionsInputService() + { + _localizationService = SvgEngine.Resolve(); + _userInteractionService = SvgEngine.Resolve(); + + } + public async Task GetUserInput(string title, IEnumerable strokeDashOptions, int strokeDashSelected, IEnumerable strokeWidthOptions, int strokeWidthSelected) { var dashes = strokeDashOptions.ToArray(); - var dash = await UserInteractionServiceExt.UserInteractionInst.ActionSheetAsync(title, dashes); + var dash = await _userInteractionService.ActionSheetAsync(title, dashes, cancelButton: _localizationService.GetString("Svg.Editor.Global.Cancel"), cancellable: true); - if (dash == null || dash == "cancel") + if (dash == null) return null; var widths = strokeWidthOptions.ToArray(); - var width = await UserInteractionServiceExt.UserInteractionInst.ActionSheetAsync(title, widths); - if (width == null || width == "cancel") + var width = await _userInteractionService.ActionSheetAsync(title, widths, cancelButton: _localizationService.GetString("Svg.Editor.Global.Cancel"), cancellable: true); + if (width == null) return null; return new StrokeStyleTool.StrokeStyleOptions() { StrokeDashIndex = Array.IndexOf(dashes, dash), StrokeWidthIndex = Array.IndexOf(widths, width) }; } diff --git a/Svg.Editor.Avalonia.Forms/Services/TextInputService.cs b/Svg.Editor.Avalonia.Forms/Services/TextInputService.cs index f308b13c0..62770a055 100644 --- a/Svg.Editor.Avalonia.Forms/Services/TextInputService.cs +++ b/Svg.Editor.Avalonia.Forms/Services/TextInputService.cs @@ -9,15 +9,21 @@ namespace Svg.Editor.Avalon.Forms.Services { public class TextInputService : ITextInputService { + private readonly IUserInteraction _userInteractionService; + + public TextInputService() + { + _userInteractionService = SvgEngine.Resolve(); + } public async Task GetUserInput(string title, string textValue = "", IEnumerable textSizeOptions = null, int textSizeSelected = 0, int maxTextLength = -1) { - var result = await UserInteractionServiceExt.UserInteractionInst.InputAsync("Text edit", title, "Ok", "Cancel", textValue, placeholder: "Enter text"); + var result = await _userInteractionService.InputAsync("Text edit", title, "Ok", "Cancel", textValue, placeholder: "Enter text"); if (maxTextLength != -1) { while (result.Text.Length > 2) { - result = await UserInteractionServiceExt.UserInteractionInst.InputAsync("Text edit", title, "Ok", "Cancel", textValue, placeholder: "Enter text"); + result = await _userInteractionService.InputAsync("Text edit", title, "Ok", "Cancel", textValue, placeholder: "Enter text"); } } var defaultResult = new TextTool.TextProperties @@ -36,7 +42,7 @@ public class TextInputService : ITextInputService int sizeIndex = textSizeSelected; if (textSizeOptions != null) { - var sizeResult = await UserInteractionServiceExt.UserInteractionInst.ActionSheetAsync("Font size", textSizeOptions.ToArray()); + var sizeResult = await _userInteractionService.ActionSheetAsync("Font size", textSizeOptions.ToArray()); sizeIndex = textSizeOptions.ToList().IndexOf(sizeResult); sizeIndex = sizeIndex >= 0 ? sizeIndex : textSizeSelected; diff --git a/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj b/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj index db051317c..52e36113d 100644 --- a/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj +++ b/Svg.Editor.Avalonia.Forms/Svg.Editor.Avalon.Forms.csproj @@ -3,8 +3,10 @@ net10.0 enable latest - 3.2.0-optiq01 + 3.2.0-optiq04 + #3.2.0-optiq04 + Improved marker options input service and StrokeStyleOptionsInputService #3.2.0-optiq01 Moved to avalonia 12 #3.1.4-optiq01 diff --git a/Svg.Editor.Avalonia.Forms/SvgEditorForms.cs b/Svg.Editor.Avalonia.Forms/SvgEditorForms.cs index 02e8a83e8..a727235cf 100644 --- a/Svg.Editor.Avalonia.Forms/SvgEditorForms.cs +++ b/Svg.Editor.Avalonia.Forms/SvgEditorForms.cs @@ -1,4 +1,5 @@ using Avalonia.Controls.Templates; +using Svg.Editor.Avalon.Forms.Dialog; using Svg.Editor.Avalon.Forms.Services; using Svg.Editor.Avalon.Forms.ToolBar; using Svg.Editor.Interfaces; @@ -30,6 +31,7 @@ public static void Init(Avalonia.Application app) SvgEngine.Register(() => new FormsPickImageService()); SvgEngine.Register(() => new PinInputService()); SvgEngine.Register(() => new ToolTipInfoService()); + SvgEngine.Register(() => new UserInteractionService()); app.DataTemplates.Add(new MenuItemHeaderTemplate()); diff --git a/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj b/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj index 9e7e01b18..4c71c40bf 100644 --- a/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj +++ b/Svg.Editor.Avalonia.Views/Svg.Editor.Avalon.Views.csproj @@ -3,8 +3,10 @@ net10.0 enable latest - 3.2.0-optiq01 + 3.2.0-optiq04 + #3.2.0-optiq04 + Improved marker options input service and StrokeStyleOptionsInputService #3.2.0-optiq01 Moved to avalonia 12 #3.1.4-optiq01 diff --git a/Svg.Editor.Core.Tests/MarkerOptionsInputServiceTests.cs b/Svg.Editor.Core.Tests/MarkerOptionsInputServiceTests.cs new file mode 100644 index 000000000..5356857eb --- /dev/null +++ b/Svg.Editor.Core.Tests/MarkerOptionsInputServiceTests.cs @@ -0,0 +1,313 @@ +using Moq; +using NUnit.Framework; +using Shouldly; +using Svg.Editor.Avalon.Forms.Dialog; +using Svg.Editor.Avalon.Forms.Services; +using Svg.Editor.Interfaces; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Svg.Editor.Core.Tests +{ + [TestFixture] + public class MarkerOptionsInputServiceTests + { + private Mock _userInteractionMock; + private Mock _localizationServiceMock; + private MarkerOptionsInputService _service; + + [SetUp] + public void Setup() + { + SvgEditor.Init(); + _userInteractionMock = new Mock(); + _localizationServiceMock = new Mock(); + SvgEngine.Register(() => _userInteractionMock.Object); + SvgEngine.Register(() => _localizationServiceMock.Object); + + _service = new MarkerOptionsInputService(); + + // Setup localization strings + _localizationServiceMock + .Setup(x => x.GetString(It.IsAny(), It.IsAny())) + .Returns((string key, CultureInfo culture) => key); + } + + [Test] + public async Task GetUserInput_WithValidSelections_ReturnsSelectedIndices() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow", "Circle" }; + var markerEndOptions = new[] { "None", "Diamond", "Square" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("Arrow") // Index 1 + .ReturnsAsync("Square"); // Index 2 + + // Act + var result = await _service.GetUserInput(markerStartOptions, 0, markerEndOptions, 0); + + // Assert + result.ShouldNotBeNull(); + result.Length.ShouldBe(2); + result[0].ShouldBe(1); + result[1].ShouldBe(2); + } + + [Test] + public async Task GetUserInput_WithStartMarkerCancellation_ReturnsOriginalIndices() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow", "Circle" }; + var markerEndOptions = new[] { "None", "Diamond", "Square" }; + const int startSelected = 2; + const int endSelected = 1; + + _userInteractionMock + .Setup(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync((string)null); // User cancels + + // Act + var result = await _service.GetUserInput(markerStartOptions, startSelected, markerEndOptions, endSelected); + + // Assert + result.ShouldNotBeNull(); + result.Length.ShouldBe(2); + result[0].ShouldBe(startSelected); + result[1].ShouldBe(endSelected); + _userInteractionMock.Verify( + x => x.ActionSheetAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once, "Should only call ActionSheetAsync once"); + } + + [Test] + public async Task GetUserInput_WithEndMarkerCancellation_ReturnsOriginalIndices() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow", "Circle" }; + var markerEndOptions = new[] { "None", "Diamond", "Square" }; + const int startSelected = 1; + const int endSelected = 2; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("Arrow") // User selects start + .ReturnsAsync((string)null); // User cancels end + + // Act + var result = await _service.GetUserInput(markerStartOptions, startSelected, markerEndOptions, endSelected); + + // Assert + result.ShouldNotBeNull(); + result.Length.ShouldBe(2); + result[0].ShouldBe(startSelected); + result[1].ShouldBe(endSelected); + _userInteractionMock.Verify( + x => x.ActionSheetAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Exactly(2), "Should call ActionSheetAsync twice"); + } + + [Test] + public async Task GetUserInput_WithFirstOptions_ReturnsZeroIndices() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow" }; + var markerEndOptions = new[] { "None", "Diamond" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("None") // First option + .ReturnsAsync("None"); // First option + + // Act + var result = await _service.GetUserInput(markerStartOptions, 1, markerEndOptions, 1); + + // Assert + result.ShouldNotBeNull(); + result[0].ShouldBe(0); + result[1].ShouldBe(0); + } + + [Test] + public async Task GetUserInput_WithInvalidSelection_ReturnsOriginalIndices() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow", "Circle" }; + var markerEndOptions = new[] { "None", "Diamond", "Square" }; + const int startSelected = 1; + const int endSelected = 2; + + _userInteractionMock + .Setup(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("InvalidOption"); // Option not in list + + // Act + var result = await _service.GetUserInput(markerStartOptions, startSelected, markerEndOptions, endSelected); + + // Assert + result.ShouldNotBeNull(); + result[0].ShouldBe(startSelected); + result[1].ShouldBe(endSelected); + } + + [Test] + public async Task GetUserInput_CallsActionSheetWithCorrectStartParameters() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow" }; + var markerEndOptions = new[] { "None", "Diamond" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("Arrow") + .ReturnsAsync("Diamond"); + + // Act + await _service.GetUserInput(markerStartOptions, 0, markerEndOptions, 0); + + // Assert - Verify first call (start markers) + _userInteractionMock.Verify( + x => x.ActionSheetAsync( + "Svg.Editor.Marker.Options.Start", + It.Is>(options => options.SequenceEqual(markerStartOptions)), + It.IsAny(), + "Svg.Editor.Global.Cancel", + It.IsAny(), + true), + Times.Once); + } + + [Test] + public async Task GetUserInput_CallsActionSheetWithCorrectEndParameters() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow" }; + var markerEndOptions = new[] { "None", "Diamond" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("Arrow") + .ReturnsAsync("Diamond"); + + // Act + await _service.GetUserInput(markerStartOptions, 0, markerEndOptions, 0); + + // Assert - Verify second call (end markers) + _userInteractionMock.Verify( + x => x.ActionSheetAsync( + "Svg.Editor.Marker.Options.End", + It.Is>(options => options.SequenceEqual(markerEndOptions)), + It.IsAny(), + "Svg.Editor.Global.Cancel", + It.IsAny(), + true), + Times.Once); + } + + [Test] + public async Task GetUserInput_WithLastOptions_ReturnsCorrectIndices() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow", "Circle", "Triangle" }; + var markerEndOptions = new[] { "None", "Diamond", "Square", "Pentagon", "Hexagon" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("Triangle") // Index 3 + .ReturnsAsync("Hexagon"); // Index 4 + + // Act + var result = await _service.GetUserInput(markerStartOptions, 0, markerEndOptions, 0); + + // Assert + result.ShouldNotBeNull(); + result[0].ShouldBe(3); + result[1].ShouldBe(4); + } + + [Test] + public async Task GetUserInput_PreservesSelectedIndicesAcrossMultipleCalls() + { + // Arrange + var markerStartOptions = new[] { "None", "Arrow", "Circle" }; + var markerEndOptions = new[] { "None", "Diamond", "Square" }; + const int startSelected = 2; + const int endSelected = 1; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync((string)null) // Cancel first time + .ReturnsAsync("Arrow") // Select second time + .ReturnsAsync("Square"); // Select third time + + // Act + var result1 = await _service.GetUserInput(markerStartOptions, startSelected, markerEndOptions, endSelected); + var result2 = await _service.GetUserInput(markerStartOptions, startSelected, markerEndOptions, endSelected); + + // Assert + result1[0].ShouldBe(startSelected); // Should be preserved + result1[1].ShouldBe(endSelected); + result2[0].ShouldBe(1); + result2[1].ShouldBe(2); + } + } +} \ No newline at end of file diff --git a/Svg.Editor.Core.Tests/StrokeStyleOptionsInputServiceTests.cs b/Svg.Editor.Core.Tests/StrokeStyleOptionsInputServiceTests.cs new file mode 100644 index 000000000..d7faca32a --- /dev/null +++ b/Svg.Editor.Core.Tests/StrokeStyleOptionsInputServiceTests.cs @@ -0,0 +1,221 @@ +using Moq; +using NUnit.Framework; +using Shouldly; +using Svg.Editor.Avalon.Forms.Dialog; +using Svg.Editor.Avalon.Forms.Services; +using Svg.Editor.Interfaces; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Svg.Editor.Core.Tests +{ + [TestFixture] + public class StrokeStyleOptionsInputServiceTests + { + private Mock _userInteractionMock; + private Mock _localizationServiceMock; + private StrokeStyleOptionsInputService _service; + + [SetUp] + public void Setup() + { + SvgEditor.Init(); + _userInteractionMock = new Mock(); + _localizationServiceMock = new Mock(); + SvgEngine.Register(() => _userInteractionMock.Object); + SvgEngine.Register(() => _localizationServiceMock.Object); + + _service = new StrokeStyleOptionsInputService(); + + // Setup localization strings + _localizationServiceMock + .Setup(x => x.GetString(It.IsAny(), It.IsAny())) + .Returns((string key, CultureInfo culture) => key); + } + + [Test] + public async Task GetUserInput_WithValidSelections_ReturnsStrokeStyleOptions() + { + // Arrange + var strokeDashOptions = new[] { "solid", "dashed", "dotted" }; + var strokeWidthOptions = new[] { "thin", "medium", "thick" }; + const string title = "Choose Stroke Style"; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("dashed") // User selects "dashed" (index 1) + .ReturnsAsync("thick"); // User selects "thick" (index 2) + + // Act + var result = await _service.GetUserInput(title, strokeDashOptions, 0, strokeWidthOptions, 0); + + // Assert + result.ShouldNotBeNull(); + result.StrokeDashIndex.ShouldBe(1); + result.StrokeWidthIndex.ShouldBe(2); + } + + [Test] + public async Task GetUserInput_WithDashCancellation_ReturnsNull() + { + // Arrange + var strokeDashOptions = new[] { "solid", "dashed", "dotted" }; + var strokeWidthOptions = new[] { "thin", "medium", "thick" }; + + _userInteractionMock + .Setup(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync((string)null); // User cancels + + // Act + var result = await _service.GetUserInput("Test", strokeDashOptions, 0, strokeWidthOptions, 0); + + // Assert + result.ShouldBeNull(); + _userInteractionMock.Verify( + x => x.ActionSheetAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Once, "Should only call ActionSheetAsync once for dash options"); + } + + [Test] + public async Task GetUserInput_WithWidthCancellation_ReturnsNull() + { + // Arrange + var strokeDashOptions = new[] { "solid", "dashed", "dotted" }; + var strokeWidthOptions = new[] { "thin", "medium", "thick" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("dashed") // User selects dash + .ReturnsAsync((string)null); // User cancels width + + // Act + var result = await _service.GetUserInput("Test", strokeDashOptions, 0, strokeWidthOptions, 0); + + // Assert + result.ShouldBeNull(); + _userInteractionMock.Verify( + x => x.ActionSheetAsync(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), + Times.Exactly(2), "Should call ActionSheetAsync twice"); + } + + [Test] + public async Task GetUserInput_WithFirstOption_ReturnsZeroIndices() + { + // Arrange + var strokeDashOptions = new[] { "solid", "dashed", "dotted" }; + var strokeWidthOptions = new[] { "thin", "medium", "thick" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("solid") // First option + .ReturnsAsync("thin"); // First option + + // Act + var result = await _service.GetUserInput("Test", strokeDashOptions, 0, strokeWidthOptions, 0); + + // Assert + result.ShouldNotBeNull(); + result.StrokeDashIndex.ShouldBe(0); + result.StrokeWidthIndex.ShouldBe(0); + } + + [Test] + public async Task GetUserInput_CallsActionSheetWithCorrectParameters() + { + // Arrange + var strokeDashOptions = new[] { "solid", "dashed" }; + var strokeWidthOptions = new[] { "thin", "thick" }; + const string title = "Stroke Options"; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("dashed") + .ReturnsAsync("thick"); + + // Act + await _service.GetUserInput(title, strokeDashOptions, 0, strokeWidthOptions, 0); + + // Assert + _userInteractionMock.Verify( + x => x.ActionSheetAsync( + title, + It.Is>(options => options.SequenceEqual(strokeDashOptions)), + It.IsAny(), + "Svg.Editor.Global.Cancel", + It.IsAny(), + true), + Times.Once); + + _userInteractionMock.Verify( + x => x.ActionSheetAsync( + title, + It.Is>(options => options.SequenceEqual(strokeWidthOptions)), + It.IsAny(), + "Svg.Editor.Global.Cancel", + It.IsAny(), + true), + Times.Once); + } + + [Test] + public async Task GetUserInput_WithLastOption_ReturnsCorrectIndices() + { + // Arrange + var strokeDashOptions = new[] { "solid", "dashed", "dotted", "dashdot" }; + var strokeWidthOptions = new[] { "1px", "2px", "3px", "4px", "5px" }; + + _userInteractionMock + .SetupSequence(x => x.ActionSheetAsync( + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .ReturnsAsync("dashdot") // Index 3 + .ReturnsAsync("5px"); // Index 4 + + // Act + var result = await _service.GetUserInput("Test", strokeDashOptions, 0, strokeWidthOptions, 0); + + // Assert + result.ShouldNotBeNull(); + result.StrokeDashIndex.ShouldBe(3); + result.StrokeWidthIndex.ShouldBe(4); + } + } +} \ No newline at end of file diff --git a/Svg.Editor.Core.Tests/Svg.Editor.Core.Tests.csproj b/Svg.Editor.Core.Tests/Svg.Editor.Core.Tests.csproj index 009db1800..eb430b12a 100644 --- a/Svg.Editor.Core.Tests/Svg.Editor.Core.Tests.csproj +++ b/Svg.Editor.Core.Tests/Svg.Editor.Core.Tests.csproj @@ -11,13 +11,16 @@ + + + diff --git a/Svg.Editor.Core/Services/Localization/Resources/String.de.resx b/Svg.Editor.Core/Services/Localization/Resources/String.de.resx index ec0cab479..1325a749c 100644 --- a/Svg.Editor.Core/Services/Localization/Resources/String.de.resx +++ b/Svg.Editor.Core/Services/Localization/Resources/String.de.resx @@ -1,59 +1,158 @@  - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Öffnen Sie den Farbpicker, um eine neue Farbe aus einer Palette zum Zeichnen auszuwählen. - - - Wählen Sie Optionen wie dicke, dünne, gepunktete oder gestrichelte Muster, um Ihre Striche zu personalisieren. - - - Wählen Sie eine Bilddatei aus, um sie als Hintergrund für die Zeichenfläche festzulegen. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Öffnen Sie den Farbpicker, um eine neue Farbe aus einer Palette zum Zeichnen auszuwählen. + + + Wählen Sie Optionen wie dicke, dünne, gepunktete oder gestrichelte Muster, um Ihre Striche zu personalisieren. + + + Wählen Sie eine Bilddatei aus, um sie als Hintergrund für die Zeichenfläche festzulegen. + + + Machen Sie die letzte Aktion rückgängig, um Ihre Zeichnung wieder in den vorherigen Zustand zu versetzen. + + + Wiederholen Sie die letzte rückgängig gemachte Aktion, um Änderungen oder Anpassungen problemlos wiederherzustellen. - - - Bewegen Sie das ausgewählte Element durch verschiedene Ebenen im Zeichenfläche. - - - Vergrößert die Leinwand auf die gesamte Skizze. - - - Blenden Sie das Raster-Overlay ein oder aus, um die Elemente auf der Leinwand auszurichten - - - Klicken Sie auf , um Ihre SVG-Datei zu speichern und alle an Ihrem Entwurf vorgenommenen Änderungen beizubehalten. - - - Ändern Sie die Größe des Stiftes. - - - Ändern Sie das Ende und die Spitze der Linienform. - - - Wählen Sie ein Werkzeug, mit dem Sie auf der Leinwand zeichnen können. + + + Bewegen Sie das ausgewählte Element durch verschiedene Ebenen im Zeichenfläche. + + + Vergrößert die Leinwand auf die gesamte Skizze. + + + Blenden Sie das Raster-Overlay ein oder aus, um die Elemente auf der Leinwand auszurichten + + + Klicken Sie auf , um Ihre SVG-Datei zu speichern und alle an Ihrem Entwurf vorgenommenen Änderungen beizubehalten. + + + Ändern Sie die Größe des Stiftes. + + + Ändern Sie das Ende und die Spitze der Linienform. + + + Wählen Sie ein Werkzeug, mit dem Sie auf der Leinwand zeichnen können. Select: Ziehen und wählen Sie Zeichnungen im Bereich aus. Text: Klicken Sie auf die Stelle, an der Sie einen Text schreiben möchten. Line: Ziehen Sie, um eine Linie zu erstellen. @@ -62,29 +161,38 @@ Free drawing: Zeichnen Sie, wie Sie wollen. Polygon: Setzen Sie Punkte, bis sich der letzte Punkt mit dem ersten Punkt schließt. Pin: Platzieren Sie eine Pin. - - - Ziehen und wählen Sie Zeichnungen im Bereich aus - - - Klicken Sie auf die Stelle, an der Sie einen Text schreiben möchten - - - Ziehen Sie, um eine Linie zu erstellen - - - Ziehen Sie, um einen Kreis zu erstellen - - - Ziehen Sie, um ein Rechteck zu erstellen - - - Zeichnen Sie, wie Sie wollen. - - - Setzen Sie Punkte, bis sich der letzte Punkt mit dem ersten Punkt schließt - - - Platzieren Sie eine Pin - + + + Ziehen und wählen Sie Zeichnungen im Bereich aus + + + Klicken Sie auf die Stelle, an der Sie einen Text schreiben möchten + + + Ziehen Sie, um eine Linie zu erstellen + + + Ziehen Sie, um einen Kreis zu erstellen + + + Ziehen Sie, um ein Rechteck zu erstellen + + + Zeichnen Sie, wie Sie wollen. + + + Setzen Sie Punkte, bis sich der letzte Punkt mit dem ersten Punkt schließt + + + Platzieren Sie eine Pin + + + Start + + + Ende + + + Abbrechen + \ No newline at end of file diff --git a/Svg.Editor.Core/Services/Localization/Resources/String.fr.resx b/Svg.Editor.Core/Services/Localization/Resources/String.fr.resx index a7b653d86..12c4dde85 100644 --- a/Svg.Editor.Core/Services/Localization/Resources/String.fr.resx +++ b/Svg.Editor.Core/Services/Localization/Resources/String.fr.resx @@ -1,59 +1,158 @@  - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Ouvrir le sélecteur de couleur pour choisir une nouvelle couleur dans une palette pour dessiner. - - - Choisissez des options telles que des motifs épais, fins, en pointillés ou en tirets pour personnaliser vos traits. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ouvrir le sélecteur de couleur pour choisir une nouvelle couleur dans une palette pour dessiner. + + + Choisissez des options telles que des motifs épais, fins, en pointillés ou en tirets pour personnaliser vos traits. + + + Sélectionnez un fichier image pour le définir comme arrière-plan de la zone de dessin. + + + Annulez la dernière action pour rétablir votre dessin dans son état précédent. + + + Répétez la dernière action annulée pour rétablir facilement les modifications ou les ajustements. - - - Annulez la dernière action pour rétablir votre dessin dans son état précédent. - - - Déplacez l'élément sélectionné à travers différents plans dans la zone de dessin. - - - Agrandit le canevas jusqu'à la totalité de l'esquisse. - - - Affichez ou masquez la superposition de la grille pour aligner les éléments sur le canevas. - - - Cliquez sur pour enregistrer votre fichier SVG et conserver toutes les modifications apportées à votre dessin. - - - Modifier la taille du stylo. - - - Modifier l'extrémité et la pointe de la forme de la ligne. - - - Sélectionnez un outil pour dessiner sur le canevas. + + + Déplacez l'élément sélectionné à travers différents plans dans la zone de dessin. + + + Agrandit le canevas jusqu'à la totalité de l'esquisse. + + + Affichez ou masquez la superposition de la grille pour aligner les éléments sur le canevas. + + + Cliquez sur pour enregistrer votre fichier SVG et conserver toutes les modifications apportées à votre dessin. + + + Modifier la taille du stylo. + + + Modifier l'extrémité et la pointe de la forme de la ligne. + + + Sélectionnez un outil pour dessiner sur le canevas. Select: faire glisser et sélectionner des dessins dans la zone. Text: Cliquez à l'endroit où vous souhaitez écrire un texte. Line: faites glisser pour créer une ligne. @@ -62,29 +161,38 @@ Free drawing (dessin libre): Dessinez comme vous le souhaitez. Polygone: Placez des points jusqu'à ce que le dernier point rejoigne le premier. Pin: Placez un pin. - - - faire glisser et sélectionner des dessins dans la zone - - - Cliquez à l'endroit où vous souhaitez écrire un texte - - - faites glisser pour créer une ligne - - - Faites glisser pour créer un cercle - - - Faites glisser pour créer un rectangle - - - Dessinez comme vous le souhaitez - - - Placez des points jusqu'à ce que le dernier point rejoigne le premier - - - Placez un pin - + + + faire glisser et sélectionner des dessins dans la zone + + + Cliquez à l'endroit où vous souhaitez écrire un texte + + + faites glisser pour créer une ligne + + + Faites glisser pour créer un cercle + + + Faites glisser pour créer un rectangle + + + Dessinez comme vous le souhaitez + + + Placez des points jusqu'à ce que le dernier point rejoigne le premier + + + Placez un pin + + + Début + + + Fin + + + Annuler + \ No newline at end of file diff --git a/Svg.Editor.Core/Services/Localization/Resources/String.resx b/Svg.Editor.Core/Services/Localization/Resources/String.resx index f95f1d1f2..f1e79d75d 100644 --- a/Svg.Editor.Core/Services/Localization/Resources/String.resx +++ b/Svg.Editor.Core/Services/Localization/Resources/String.resx @@ -1,58 +1,157 @@  - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Open the color picker to select a new color from a palette for drawing. - - - Select options like thick, thin, dotted, dashed patterns to personalize your strokes. - - - Pick an image file to set it as background in the canvas. - - - Undo the last action to revert your drawing to its previous state. - - - Redo the last undone action to restore changes or adjustments with ease. - - - Move selected item through different layers in the canvas. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Open the color picker to select a new color from a palette for drawing. + + + Select options like thick, thin, dotted, dashed patterns to personalize your strokes. + + + Pick an image file to set it as background in the canvas. + + + Undo the last action to revert your drawing to its previous state. + + + Redo the last undone action to restore changes or adjustments with ease. + + + Move selected item through different layers in the canvas. + + + Enlarges the canvas to the entire sketch. + + + Show or hide the grid overlay to help align elements on the canvas. + + + Click to save your SVG file and preserve all changes made to your design. + + + Change the size of the pin. + + + Change end and the tip of the line shape. + + + Choose a tool to use on the canvas to draw. Select: drag and select drawings in the area. Text: click on the place where to write a text. Line: drag to create a line. @@ -61,29 +160,38 @@ Free draw: draw however the movement is. Polygon: put points until the last closes with the first point. Pin: place a pin. - - - drag and select drawings in the area - - - click on the place where to write a text. - - - drag to create a line - - - drag to create a circle - - - drag to create a rectangle - - - draw however the movement is - - - put points until the last closes with the first point - - - place a pin - + + + drag and select drawings in the area + + + click on the place where to write a text. + + + drag to create a line + + + drag to create a circle + + + drag to create a rectangle + + + draw however the movement is + + + put points until the last closes with the first point + + + place a pin + + + Start + + + End + + + Cancel + \ No newline at end of file diff --git a/Svg.Editor.Core/Svg.Editor.Core.csproj b/Svg.Editor.Core/Svg.Editor.Core.csproj index c24a193f1..d3ca5491e 100644 --- a/Svg.Editor.Core/Svg.Editor.Core.csproj +++ b/Svg.Editor.Core/Svg.Editor.Core.csproj @@ -4,9 +4,11 @@ Svg.Editor en-US net10.0 - 3.2.0-optiq01 + 3.2.0-optiq04 latest + #3.2.0-optiq04 + Improved marker options input service and StrokeStyleOptionsInputService #3.2.0-optiq01 Moved to avalonia 12 #3.1.4-optiq01 diff --git a/Svg.Editor.Core/Tools/LineTool.cs b/Svg.Editor.Core/Tools/LineTool.cs index 8ae7774a3..494464806 100644 --- a/Svg.Editor.Core/Tools/LineTool.cs +++ b/Svg.Editor.Core/Tools/LineTool.cs @@ -14,7 +14,6 @@ namespace Svg.Editor.Tools public interface IMarkerOptionsInputService { Task GetUserInput( - string title, IEnumerable markerStartOptions, int markerStartSelected, IEnumerable markerEndOptions, @@ -579,7 +578,7 @@ public override async void Execute(object parameter) try { - selectedOptions = await MarkerOptionsInputServiceProxy.GetUserInput("Choose line endings", + selectedOptions = await MarkerOptionsInputServiceProxy.GetUserInput( t.MarkerStartNames, markerStartIndex, t.MarkerEndNames, markerEndIndex); } diff --git a/Svg/Svg.csproj b/Svg/Svg.csproj index edaf6b0a9..9a95edde0 100644 --- a/Svg/Svg.csproj +++ b/Svg/Svg.csproj @@ -3,10 +3,12 @@ netstandard2.0;net48;net10.0 PackageReference - 3.2.0-optiq03 + 3.2.0-optiq04 gentledpp,zepr Opti-Q GmbH + #3.2.0-optiq04 + Improved marker options input service and StrokeStyleOptionsInputService #3.2.0-optiq03 Fixed: tile cache key now includes the tile source (zip Href / tile folder) so the shared singleton TileRenderer no longer returns one source's tiles for another — reports rendering