Skip to content

Commit d67d1d1

Browse files
ivaylo-matovzeusongitCopilot
authored
DYN-9313-Resizing group should feel natural and not jumpy (DynamoDS#16453)
Co-authored-by: Ashish Aggarwal <ashish.aggarwal@autodesk.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 7db7237 commit d67d1d1

File tree

6 files changed

+230
-6
lines changed

6 files changed

+230
-6
lines changed

src/DynamoCore/Graph/Annotations/AnnotationModel.cs

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class AnnotationModel : ModelBase
2323
private const double ExtendSize = 10.0;
2424
private const double ExtendYHeight = 5.0;
2525
private const double NoteYAdjustment = 8.0;
26+
private bool isTextChanging;
27+
internal bool IsThumbResizing;
2628

2729
#region Properties
2830

@@ -259,7 +261,10 @@ public double TextBlockHeight
259261
// a model and textbox. Otherwise there will be some overlap
260262
Y = InitialTop - ExtendSize - textBlockHeight;
261263
Height = InitialHeight + textBlockHeight - MinTextHeight;
264+
265+
isTextChanging = true;
262266
UpdateBoundaryFromSelection();
267+
isTextChanging = false;
263268
}
264269
}
265270

@@ -342,7 +347,10 @@ public double WidthAdjustment
342347
{
343348
if (value == widthAdjustment) return;
344349
widthAdjustment = value;
345-
UpdateBoundaryFromSelection();
350+
351+
// update boundary only while manually resizing the group
352+
if (IsThumbResizing)
353+
UpdateBoundaryFromSelection();
346354
}
347355
}
348356

@@ -359,7 +367,40 @@ public double HeightAdjustment
359367
{
360368
if (value == heightAdjustment) return;
361369
heightAdjustment = value;
362-
UpdateBoundaryFromSelection();
370+
371+
// update boundary only while manually resizing the group
372+
if (IsThumbResizing)
373+
UpdateBoundaryFromSelection();
374+
}
375+
}
376+
377+
private double userSetHeight;
378+
/// <summary>
379+
/// Indicates the height the user manually set using the resize thumb.
380+
/// </summary>
381+
public double UserSetHeight
382+
{
383+
get => userSetHeight;
384+
set
385+
{
386+
if (value == userSetHeight) return;
387+
userSetHeight = value;
388+
}
389+
}
390+
391+
private double userSetWidth;
392+
/// <summary>
393+
/// Indicates the height the user manually set using the resize thumb.
394+
/// Indicates the width the user manually set using the resize thumb.
395+
/// Not necessarily equal to the actual rendered width.
396+
/// </summary>
397+
public double UserSetWidth
398+
{
399+
get => userSetWidth;
400+
set
401+
{
402+
if (value == userSetWidth) return;
403+
userSetWidth = value;
363404
}
364405
}
365406

@@ -751,10 +792,60 @@ private void UpdateExpandedLayout(List<ModelBase> groupModels, double regionX, d
751792
Height = yDistance + ExtendSize + ExtendYHeight + HeightAdjustment - TextBlockHeight
752793
};
753794

754-
// Store layout size and apply dimensions
755-
ModelAreaHeight = region.Height;
756-
Height = ModelAreaHeight + TextBlockHeight;
757-
Width = Math.Max(region.Width, TextMaxWidth + ExtendSize);
795+
// The actual space used by nodes (without adjustments)
796+
double groupCalculatedWidth = xDistance + ExtendSize;
797+
double groupCalculatedHeight = yDistance + ExtendSize + ExtendYHeight - TextBlockHeight;
798+
799+
if (IsThumbResizing || isTextChanging)
800+
{
801+
// While dragging the resize thumb or editing text
802+
ModelAreaHeight = region.Height;
803+
Height = ModelAreaHeight + TextBlockHeight;
804+
Width = Math.Max(region.Width, TextMaxWidth + ExtendSize);
805+
}
806+
else
807+
{
808+
// HEIGHT logic
809+
// user has not resized the group
810+
if (UserSetHeight <= 0)
811+
{
812+
ModelAreaHeight = groupCalculatedHeight;
813+
Height = ModelAreaHeight + TextBlockHeight;
814+
}
815+
// some nodes are outside the user set size
816+
else if (groupCalculatedHeight >= UserSetHeight)
817+
{
818+
ModelAreaHeight = groupCalculatedHeight;
819+
Height = ModelAreaHeight + TextBlockHeight;
820+
HeightAdjustment = 0;
821+
}
822+
// all nodes are within the user set size
823+
else
824+
{
825+
HeightAdjustment = Math.Max(0, UserSetHeight - groupCalculatedHeight);
826+
ModelAreaHeight = UserSetHeight;
827+
Height = UserSetHeight + TextBlockHeight;
828+
}
829+
830+
// WIDTH logic
831+
// user has not resized the group
832+
if (UserSetWidth <= 0)
833+
{
834+
Width = Math.Max(region.Width, TextMaxWidth + ExtendSize);
835+
}
836+
// some nodes are outside the user set size
837+
else if (groupCalculatedWidth >= UserSetWidth)
838+
{
839+
Width = Math.Max(groupCalculatedWidth, TextMaxWidth + ExtendSize);
840+
WidthAdjustment = 0;
841+
}
842+
// all nodes are within the user set size
843+
else
844+
{
845+
WidthAdjustment = Math.Max(0, UserSetWidth - groupCalculatedWidth);
846+
Width = UserSetWidth;
847+
}
848+
}
758849

759850
// Only store the first calculated initial height
760851
if (InitialHeight <= 0.0)

src/DynamoCore/Graph/Workspaces/WorkspaceModel.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ public class ExtraAnnotationViewInfo
143143
public string PinnedNode;
144144
public double WidthAdjustment;
145145
public double HeightAdjustment;
146+
public double UserSetWidth;
147+
public double UserSetHeight;
146148
public bool IsOptionalInPortsCollapsed;
147149
public bool IsUnconnectedOutPortsCollapsed;
148150
public bool hasToggledOptionalInPorts;
@@ -176,6 +178,8 @@ public override bool Equals(object obj)
176178
this.Background == other.Background &&
177179
this.WidthAdjustment == other.WidthAdjustment &&
178180
this.HeightAdjustment == other.HeightAdjustment &&
181+
this.UserSetWidth == other.UserSetWidth &&
182+
this.UserSetHeight == other.UserSetHeight &&
179183
this.IsOptionalInPortsCollapsed == other.IsOptionalInPortsCollapsed &&
180184
this.IsUnconnectedOutPortsCollapsed == other.IsUnconnectedOutPortsCollapsed &&
181185
this.hasToggledOptionalInPorts == other.hasToggledOptionalInPorts &&
@@ -2723,6 +2727,8 @@ private void LoadAnnotation(ExtraAnnotationViewInfo annotationViewInfo)
27232727
annotationModel.GUID = annotationGuidValue;
27242728
annotationModel.HeightAdjustment = annotationViewInfo.HeightAdjustment;
27252729
annotationModel.WidthAdjustment = annotationViewInfo.WidthAdjustment;
2730+
annotationModel.UserSetWidth = annotationViewInfo.UserSetWidth;
2731+
annotationModel.UserSetHeight = annotationViewInfo.UserSetHeight;
27262732
annotationModel.IsOptionalInPortsCollapsed = annotationViewInfo.IsOptionalInPortsCollapsed;
27272733
annotationModel.IsUnconnectedOutPortsCollapsed = annotationViewInfo.IsUnconnectedOutPortsCollapsed;
27282734
annotationModel.HasToggledOptionalInPorts = annotationViewInfo.hasToggledOptionalInPorts;

src/DynamoCore/Models/DynamoModel.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3354,6 +3354,8 @@ private AnnotationModel CreateAnnotationModel(
33543354
AnnotationDescriptionText = model.AnnotationDescriptionText,
33553355
HeightAdjustment = model.HeightAdjustment,
33563356
WidthAdjustment = model.WidthAdjustment,
3357+
UserSetWidth = model.UserSetWidth,
3358+
UserSetHeight = model.UserSetHeight,
33573359
Background = model.Background,
33583360
FontSize = model.FontSize,
33593361
GroupStyleId = model.GroupStyleId,

src/DynamoCoreWpf/ViewModels/Core/Converters/SerializationConverters.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s
131131
writer.WriteValue(anno.WidthAdjustment);
132132
writer.WritePropertyName(nameof(ExtraAnnotationViewInfo.HeightAdjustment));
133133
writer.WriteValue(anno.HeightAdjustment);
134+
writer.WritePropertyName(nameof(ExtraAnnotationViewInfo.UserSetWidth));
135+
writer.WriteValue(anno.UserSetWidth);
136+
writer.WritePropertyName(nameof(ExtraAnnotationViewInfo.UserSetHeight));
137+
writer.WriteValue(anno.UserSetHeight);
134138
writer.WritePropertyName("Nodes");
135139
writer.WriteStartArray();
136140
foreach (var m in anno.Nodes)

src/DynamoCoreWpf/Views/Core/AnnotationView.xaml.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,8 @@ private void AnnotationView_Unloaded(object sender, RoutedEventArgs e)
251251
mainGroupThumb.DragDelta -= AnnotationRectangleThumb_DragDelta;
252252
mainGroupThumb.MouseEnter -= Thumb_MouseEnter;
253253
mainGroupThumb.MouseLeave -= Thumb_MouseLeave;
254+
mainGroupThumb.DragStarted -= Thumb_DragStarted;
255+
mainGroupThumb.DragCompleted -= Thumb_DragCompleted;
254256
}
255257
UnregisterNamesFromScope();
256258
}
@@ -638,6 +640,28 @@ private void Thumb_MouseLeave(object sender, MouseEventArgs e)
638640
ViewModel.WorkspaceViewModel.CurrentCursor = CursorLibrary.GetCursor(CursorSet.Pointer);
639641
}
640642

643+
/// <summary>
644+
/// Triggered when the user starts resizing the group using the resize thumb.
645+
/// Flag that manual resizing is in progress so user-set-size is not overridden by the model logic.
646+
/// </summary>
647+
private void Thumb_DragStarted(object sender, DragStartedEventArgs e)
648+
{
649+
ViewModel.AnnotationModel.IsThumbResizing = true;
650+
}
651+
652+
/// <summary>
653+
/// Triggered when the user completes resizing the group using the resize thumb.
654+
/// Finalizes the manually set size so it's preserved, and disables resizing flag.
655+
/// </summary>
656+
private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e)
657+
{
658+
var model = ViewModel.AnnotationModel;
659+
model.UserSetWidth = model.Width;
660+
model.UserSetHeight = model.ModelAreaHeight;
661+
662+
ViewModel.AnnotationModel.IsThumbResizing = false;
663+
}
664+
641665
private void GroupDescriptionTextBlock_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
642666
{
643667
SetTextHeight();
@@ -1527,6 +1551,8 @@ private Thumb CreateResizeThumb()
15271551
thumb.Style = _groupResizeThumbStyle;
15281552
thumb.MouseEnter += Thumb_MouseEnter;
15291553
thumb.MouseLeave += Thumb_MouseLeave;
1554+
thumb.DragStarted += Thumb_DragStarted;
1555+
thumb.DragCompleted += Thumb_DragCompleted;
15301556

15311557
return thumb;
15321558
}

test/DynamoCoreTests/Graph/Annotations/AnnotationModelTests.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,12 +207,107 @@ public void GroupAdaptsToSizeAdjustments()
207207
var heightAdjustment = 100;
208208

209209
// Act
210+
annotationModel.IsThumbResizing = true;
210211
annotationModel.WidthAdjustment = widthAdjustment;
211212
annotationModel.HeightAdjustment = heightAdjustment;
213+
annotationModel.IsThumbResizing = false;
212214

213215
// Assert
214216
Assert.That(annotationModel.Width == initialWidth + widthAdjustment);
215217
Assert.That(annotationModel.Height == initialHeight + heightAdjustment);
216218
}
219+
220+
[Test]
221+
[Category("UnitTests")]
222+
public void GroupDoesNotExpandBeyondUserSetSizeWhenNodeIsInside()
223+
{
224+
// Arrange
225+
// Add the first node to initialize the group
226+
var firstNode = new DummyNode();
227+
annotationModel.AddToTargetAnnotationModel(firstNode);
228+
229+
var originalWidth = annotationModel.Width;
230+
var originalHeight = annotationModel.Height;
231+
var originalRect = annotationModel.Rect;
232+
233+
// Simulate user resizing with thumb
234+
annotationModel.UserSetWidth = originalWidth + 200;
235+
annotationModel.UserSetHeight = originalHeight + 200;
236+
annotationModel.UpdateBoundaryFromSelection();
237+
238+
var userResizedWidth = annotationModel.Width;
239+
var userResizedHeight = annotationModel.Height;
240+
241+
Assert.AreEqual(1, annotationModel.Nodes.Count());
242+
Assert.IsTrue(userResizedWidth > originalWidth);
243+
Assert.IsTrue(userResizedHeight > originalHeight);
244+
245+
// Create a second node that sits within the resized area but outside the original bounds
246+
var secondNode = new DummyNode { X = 100, Y = 50 };
247+
248+
// Confirm the node exceeds the original boundary
249+
Assert.IsTrue(secondNode.Rect.Right > originalRect.Right);
250+
Assert.IsTrue(secondNode.Rect.Bottom > originalRect.Bottom);
251+
252+
// Act
253+
annotationModel.AddToTargetAnnotationModel(secondNode);
254+
annotationModel.UpdateBoundaryFromSelection();
255+
256+
// Assert: group size should remain at user-set dimensions
257+
Assert.AreEqual(2, annotationModel.Nodes.Count());
258+
Assert.AreEqual(userResizedWidth, annotationModel.Width);
259+
Assert.AreEqual(userResizedHeight, annotationModel.Height);
260+
}
261+
262+
[Test]
263+
[Category("UnitTests")]
264+
public void GroupExpandsWhenNodeExceedsUserSetSize_AndShrinksBackOnRemoval()
265+
{
266+
// Arrange: Add an initial node to initialize the group
267+
var firstNode = new DummyNode();
268+
annotationModel.AddToTargetAnnotationModel(firstNode);
269+
270+
var initialWidth = annotationModel.Width;
271+
var initialHeight = annotationModel.Height;
272+
273+
// Simulate manual resize by the user
274+
annotationModel.UserSetWidth = initialWidth + 200;
275+
annotationModel.UserSetHeight = initialHeight + 200;
276+
annotationModel.UpdateBoundaryFromSelection();
277+
278+
var resizedWidth = annotationModel.Width;
279+
var resizedHeight = annotationModel.Height;
280+
var resizedRect = annotationModel.Rect;
281+
282+
// Assert: Manual resize increased size
283+
Assert.Greater(resizedWidth, initialWidth);
284+
Assert.Greater(resizedHeight, initialHeight);
285+
286+
// Arrange: Create a second node that is fully outside the resized boundary
287+
var secondNode = new DummyNode { X = 1000, Y = 500 };
288+
289+
// Verify that the new node falls outside the resized group bounds
290+
Assert.Greater(secondNode.Rect.Left, resizedRect.Right);
291+
Assert.Greater(secondNode.Rect.Top, resizedRect.Bottom);
292+
293+
// Act: Add the second node to the group
294+
annotationModel.AddToTargetAnnotationModel(secondNode);
295+
annotationModel.UpdateBoundaryFromSelection();
296+
297+
// Assert: Group expanded to accommodate second node
298+
Assert.AreEqual(2, annotationModel.Nodes.Count());
299+
Assert.Greater(annotationModel.Width, resizedWidth);
300+
Assert.Greater(annotationModel.Height, resizedHeight);
301+
302+
// Act: Remove the second node
303+
var updatedNodes = annotationModel.Nodes.Where(n => n != secondNode).ToList();
304+
annotationModel.Nodes = updatedNodes;
305+
annotationModel.UpdateBoundaryFromSelection();
306+
307+
// Assert: Group size reverts to user-defined dimensions
308+
Assert.AreEqual(1, annotationModel.Nodes.Count());
309+
Assert.AreEqual(resizedWidth, annotationModel.Width);
310+
Assert.AreEqual(resizedHeight, annotationModel.Height);
311+
}
217312
}
218313
}

0 commit comments

Comments
 (0)