From f0f36030fda909fc2a595bb3a3349f735d36796c Mon Sep 17 00:00:00 2001 From: MarvinHo64 Date: Mon, 30 Oct 2023 17:57:40 +0100 Subject: [PATCH] Implemented cascading objectsInPathWay (#382) --- .../Forms/Space/CreateSpaceFormIt.cs | 10 +-- .../Forms/Space/CreateSpaceForm.razor | 12 +-- .../PathWayConditionViewModel.cs | 40 +++++----- .../LearningSpace/LearningSpaceViewModel.cs | 2 - .../LearningWorld/ILearningWorldPresenter.cs | 2 +- .../LearningWorld/LearningWorldPresenter.cs | 49 ++++++++++-- .../LearningWorld/LearningWorldPresenterUt.cs | 79 +++++++++++++++++++ 7 files changed, 154 insertions(+), 40 deletions(-) diff --git a/IntegrationTest/Forms/Space/CreateSpaceFormIt.cs b/IntegrationTest/Forms/Space/CreateSpaceFormIt.cs index 1ac387aa8..fc359672e 100644 --- a/IntegrationTest/Forms/Space/CreateSpaceFormIt.cs +++ b/IntegrationTest/Forms/Space/CreateSpaceFormIt.cs @@ -120,7 +120,7 @@ public async Task SubmitButtonClicked_SubmitsIfFormValid() var submitButton = systemUnderTest.FindComponent(); submitButton.Find("button").Click(); WorldPresenter.DidNotReceive().CreateLearningSpace(Expected, Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + Arg.Any(), Arg.Any()); var mudInput = systemUnderTest.FindComponent>(); var input = mudInput.Find("input"); @@ -132,7 +132,7 @@ public async Task SubmitButtonClicked_SubmitsIfFormValid() submitButton.Find("button").Click(); WorldPresenter.Received().CreateLearningSpace(Expected, Arg.Any(), Arg.Any(), - Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()); + Arg.Any(), Arg.Any()); } private void ConfigureValidatorNameIsTest() @@ -140,9 +140,9 @@ private void ConfigureValidatorNameIsTest() Validator.ValidateAsync(Entity, Arg.Any()).Returns(ci => { if (ci.Arg() != nameof(FormModel.Name)) return Enumerable.Empty(); - return (string) FormModel.GetType().GetProperty(ci.Arg()).GetValue(FormModel) == Expected + return (string)FormModel.GetType().GetProperty(ci.Arg()).GetValue(FormModel) == Expected ? Enumerable.Empty() - : new[] {"Must be test"}; + : new[] { "Must be test" }; } ); } @@ -160,7 +160,7 @@ private void ConfigureValidatorAllMembersTestOr123OrCampus() Theme t => t == Theme.Campus, _ => throw new ArgumentOutOfRangeException() }; - return valid ? Enumerable.Empty() : new[] {"Must be test or 123"}; + return valid ? Enumerable.Empty() : new[] { "Must be test or 123" }; } ); } diff --git a/Presentation/Components/Forms/Space/CreateSpaceForm.razor b/Presentation/Components/Forms/Space/CreateSpaceForm.razor index c28c5b0a5..453883e72 100644 --- a/Presentation/Components/Forms/Space/CreateSpaceForm.razor +++ b/Presentation/Components/Forms/Space/CreateSpaceForm.razor @@ -17,7 +17,7 @@ FormDataContainer="FormDataContainer"> + @bind-Collapsed="_collapsedGeneral">
+ @bind-Collapsed="_collapsedConditions">
@Localizer["CreateSpaceForm.Fields.Collapsable.CompletionConditions.Field.RequiredPoints.Text"] @@ -42,7 +42,7 @@ + @bind-Collapsed="_collapsedTheme">
+ @bind-Collapsed="_collapsedGoals">
@Localizer["CreateSpaceForm.Fields.Collapsable.Goals.Field.Description.Text"] @@ -111,13 +111,13 @@ private bool _collapsedGoals = true; private bool _collapsedConditions = true; private bool _collapsedTheme = true; - private Theme[] _themes = (Theme[]) Enum.GetValues(typeof(Theme)); + private Theme[] _themes = (Theme[])Enum.GetValues(typeof(Theme)); private void OnValidSubmit(LearningSpaceFormModel model) { LearningWorldPresenter.CreateLearningSpace(model.Name, model.Description, - model.Goals, (int) model.RequiredPoints, model.Theme, positionX: 0, positionY: 0); + model.Goals, (int)model.RequiredPoints, model.Theme); } } \ No newline at end of file diff --git a/Presentation/PresentationLogic/LearningPathWay/PathWayConditionViewModel.cs b/Presentation/PresentationLogic/LearningPathWay/PathWayConditionViewModel.cs index 9e3978903..51034e9f0 100644 --- a/Presentation/PresentationLogic/LearningPathWay/PathWayConditionViewModel.cs +++ b/Presentation/PresentationLogic/LearningPathWay/PathWayConditionViewModel.cs @@ -6,6 +6,14 @@ namespace Presentation.PresentationLogic.LearningPathway; public class PathWayConditionViewModel : IObjectInPathWayViewModel { + public const int InputConnectionXOffset = 38; + public const int InputConnectionYOffset = -10; + public const int OutputConnectionXOffset = 38; + public const int OutputConnectionYOffset = 45; + + private double _positionX; + private double _positionY; + /// /// Private Constructor for AutoMapper /// @@ -20,10 +28,10 @@ private PathWayConditionViewModel() Condition = ConditionEnum.Or; UnsavedChanges = false; } - + public PathWayConditionViewModel(ConditionEnum condition, bool unsavedChanges, double positionX = 0, double positionY = 0, - ICollection? inBoundObjects = null, + ICollection? inBoundObjects = null, ICollection? outBoundObjects = null) { Id = Guid.NewGuid(); @@ -34,37 +42,37 @@ public PathWayConditionViewModel(ConditionEnum condition, bool unsavedChanges, PositionX = positionX; PositionY = positionY; } - + + public ConditionEnum Condition { get; set; } + public Guid Id { get; private set; } - public double PositionX { + + public double PositionX + { get => _positionX; set { _positionX = value switch { < 0 => 0, - > 268 => 268, _ => value }; - } + } } - - public double PositionY { + + public double PositionY + { get => _positionY; set { _positionY = value switch { < 0 => 0, - > 711 => 711, _ => value }; - } + } } - public const int InputConnectionXOffset = 38; - public const int InputConnectionYOffset = -10; - public const int OutputConnectionXOffset = 38; - public const int OutputConnectionYOffset = 45; + public double InputConnectionX => PositionX + InputConnectionXOffset; public double InputConnectionY => PositionY + InputConnectionYOffset; public double OutputConnectionX => PositionX + OutputConnectionXOffset; @@ -72,8 +80,4 @@ public double PositionY { public bool UnsavedChanges { get; } public ICollection InBoundObjects { get; set; } public ICollection OutBoundObjects { get; set; } - public ConditionEnum Condition { get; set; } - - private double _positionX; - private double _positionY; } \ No newline at end of file diff --git a/Presentation/PresentationLogic/LearningSpace/LearningSpaceViewModel.cs b/Presentation/PresentationLogic/LearningSpace/LearningSpaceViewModel.cs index 2d9d34c13..09da6c376 100644 --- a/Presentation/PresentationLogic/LearningSpace/LearningSpaceViewModel.cs +++ b/Presentation/PresentationLogic/LearningSpace/LearningSpaceViewModel.cs @@ -133,7 +133,6 @@ public double PositionX _positionX = value switch { < 0 => 0, - > 437 => 437, _ => value }; } @@ -147,7 +146,6 @@ public double PositionY _positionY = value switch { < 0 => 0, - > 681 => 681, _ => value }; } diff --git a/Presentation/PresentationLogic/LearningWorld/ILearningWorldPresenter.cs b/Presentation/PresentationLogic/LearningWorld/ILearningWorldPresenter.cs index 350cb4ce8..44425de9b 100644 --- a/Presentation/PresentationLogic/LearningWorld/ILearningWorldPresenter.cs +++ b/Presentation/PresentationLogic/LearningWorld/ILearningWorldPresenter.cs @@ -42,7 +42,7 @@ public interface ILearningWorldPresenter : INotifyPropertyChanged, INotifyProper /// The Y position of the learning space (default is 0). /// The topic of the learning space (optional). void CreateLearningSpace(string name, string description, string goals, - int requiredPoints, Theme theme, double positionX = 0, double positionY = 0, TopicViewModel? topic = null); + int requiredPoints, Theme theme, TopicViewModel? topic = null); /// /// Asynchronously loads the learning space associated with the currently selected learning world. diff --git a/Presentation/PresentationLogic/LearningWorld/LearningWorldPresenter.cs b/Presentation/PresentationLogic/LearningWorld/LearningWorldPresenter.cs index 2f0fb4e6b..52d9ba454 100644 --- a/Presentation/PresentationLogic/LearningWorld/LearningWorldPresenter.cs +++ b/Presentation/PresentationLogic/LearningWorld/LearningWorldPresenter.cs @@ -261,17 +261,16 @@ public void SetSelectedLearningSpace(IObjectInPathWayViewModel objectInPathWayVi /// public void CreateLearningSpace(string name, string description, string goals, int requiredPoints, - Theme theme, - double positionX = 0D, - double positionY = 0D, - TopicViewModel? topic = null) + Theme theme, TopicViewModel? topic = null) { if (!CheckLearningWorldNotNull("CreateLearningSpace")) return; + var positionY = GetNextAvailableYPosition(25); + //Nullability of LearningWorldVm is checked in CheckLearningWorldNotNull _presentationLogic.CreateLearningSpace(LearningWorldVm!, name, description, goals, - requiredPoints, theme, positionX, positionY, topic); + requiredPoints, theme, 0, positionY, topic); } /// @@ -365,7 +364,8 @@ public void CreatePathWayCondition(ConditionEnum condition = ConditionEnum.Or) return; try { - _presentationLogic.CreatePathWayCondition(LearningWorldVm!, condition, 0, 0); + var positionY = GetNextAvailableYPosition(25); + _presentationLogic.CreatePathWayCondition(LearningWorldVm!, condition, 0, positionY); } catch (ApplicationException e) { @@ -420,14 +420,47 @@ public void SetOnHoveredObjectInPathWay(IObjectInPathWayViewModel sourceObject, { //LearningWorldVm can not be null because it is checked before call. -m.ho var objectAtPosition = LearningWorldVm?.LearningSpaces.FirstOrDefault(ls => - ls.PositionX <= x && ls.PositionX + 84 >= x && ls.PositionY <= y && - ls.PositionY + 84 >= y) ?? + ls.PositionX <= x && ls.PositionX + 66 >= x && ls.PositionY <= y && + ls.PositionY + 64 >= y) ?? (IObjectInPathWayViewModel?)LearningWorldVm?.PathWayConditions.FirstOrDefault(lc => lc.PositionX <= x && lc.PositionX + 76 >= x && lc.PositionY <= y && lc.PositionY + 43 >= y); return objectAtPosition; } + /// + /// Determines the next available Y position, taking into account objects already existing at the current position and the position shifted by an offset. + /// + /// The horizontal offset to consider when identifying colliding objects. + /// The Y starting point from which to begin the search. Defaults to 0. + /// Returns the next available Y position. If the determined Y position exceeds the maximum value, the maximum Y value is returned. + /// Thrown when an unknown object is found at the current position. + private int GetNextAvailableYPosition(int xOffset, int startY = 0) + { + var positionY = startY; + const int maxPositionY = 405; + + while (true) + { + var objAtPosition = GetObjectAtPosition(0, positionY); + var objAtPositionWithOffset = GetObjectAtPosition(0 + xOffset, positionY + xOffset); + + if (objAtPosition == null && objAtPositionWithOffset == null) + { + return positionY <= maxPositionY ? positionY : maxPositionY; + } + + var currentOffset = objAtPosition switch + { + ILearningSpaceViewModel => 70, + PathWayConditionViewModel => 55, + _ => throw new ArgumentOutOfRangeException() + }; + + positionY += currentOffset; + } + } + /// public void CreateLearningPathWay(IObjectInPathWayViewModel sourceObject, double x, double y) { diff --git a/PresentationTest/PresentationLogic/LearningWorld/LearningWorldPresenterUt.cs b/PresentationTest/PresentationLogic/LearningWorld/LearningWorldPresenterUt.cs index df29bc354..5ad25c3ab 100644 --- a/PresentationTest/PresentationLogic/LearningWorld/LearningWorldPresenterUt.cs +++ b/PresentationTest/PresentationLogic/LearningWorld/LearningWorldPresenterUt.cs @@ -10,6 +10,7 @@ using Presentation.PresentationLogic; using Presentation.PresentationLogic.API; using Presentation.PresentationLogic.AuthoringToolWorkspace; +using Presentation.PresentationLogic.LearningPathway; using Presentation.PresentationLogic.LearningSpace; using Presentation.PresentationLogic.LearningWorld; using Presentation.PresentationLogic.Mediator; @@ -355,6 +356,84 @@ public void CreateNewLearningSpace_SelectedLearningWorldIsNull_CallsErrorService errorService.Received().SetError("Operation failed", "No learning world selected"); } + [Test] + public void CreateMultipleLearningObjects_PositionIsCorrect() + { + var world = ViewModelProvider.GetLearningWorld(); + var presentationLogic = Substitute.For(); + + var systemUnderTest = CreatePresenterForTesting(presentationLogic: presentationLogic); + systemUnderTest.LearningWorldVm = world; + + systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus); + systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus)); + + systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus); + systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0, + null, 0, 70)); + + systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus); + systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0, + null, 0, 140)); + + systemUnderTest.CreatePathWayCondition(ConditionEnum.And); + systemUnderTest.LearningWorldVm.PathWayConditions.Add( + new PathWayConditionViewModel(ConditionEnum.And, false, 0, 210)); + + systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus); + systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0, + null, 0, 265)); + + systemUnderTest.CreatePathWayCondition(ConditionEnum.And); + systemUnderTest.LearningWorldVm.PathWayConditions.Add( + new PathWayConditionViewModel(ConditionEnum.And, false, 0, 335)); + + systemUnderTest.CreatePathWayCondition(ConditionEnum.And); + systemUnderTest.LearningWorldVm.PathWayConditions.Add( + new PathWayConditionViewModel(ConditionEnum.And, false, 0, 390)); + + systemUnderTest.CreateLearningSpace("foo", "bar", "foo", 5, Theme.Campus); + systemUnderTest.LearningWorldVm.LearningSpaces.Add(new LearningSpaceViewModel("aa", "bb", "cc", Theme.Campus, 0, + null, 0, 405)); + + systemUnderTest.CreatePathWayCondition(ConditionEnum.And); + systemUnderTest.LearningWorldVm.PathWayConditions.Add( + new PathWayConditionViewModel(ConditionEnum.And, false, 0, 405)); + + Received.InOrder(() => + { + presentationLogic.Received().CreateLearningSpace(world, Arg.Any(), Arg.Any(), + Arg.Any(), + Arg.Any(), Arg.Any(), 0, 0, Arg.Any()); + + presentationLogic.Received().CreateLearningSpace(world, Arg.Any(), Arg.Any(), + Arg.Any(), + Arg.Any(), Arg.Any(), 0, 70, Arg.Any()); + + presentationLogic.Received().CreateLearningSpace(world, Arg.Any(), Arg.Any(), + Arg.Any(), + Arg.Any(), Arg.Any(), 0, 140, Arg.Any()); + + presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 210); + + presentationLogic.Received().CreateLearningSpace(world, Arg.Any(), Arg.Any(), + Arg.Any(), + Arg.Any(), Arg.Any(), 0, 265, Arg.Any()); + + presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 335); + + presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 390); + + //max value for positionY is 405 + presentationLogic.Received().CreateLearningSpace(world, Arg.Any(), Arg.Any(), + Arg.Any(), + Arg.Any(), Arg.Any(), 0, 405, Arg.Any()); + + //max value for positionY is 405 + presentationLogic.Received().CreatePathWayCondition(world, ConditionEnum.And, 0, 405); + }); + } + [Test] public void DeleteSelectedLearningObject_SelectedLearningWorldIsNull_CallsErrorService() {