Skip to content

Commit

Permalink
DYN-7601: resizable string nodes (#15719)
Browse files Browse the repository at this point in the history
  • Loading branch information
dnenov authored Dec 19, 2024
1 parent 7bcdb04 commit 23bf463
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 5 deletions.
56 changes: 55 additions & 1 deletion src/Libraries/CoreNodeModels/Input/BaseTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
using Dynamo.Utilities;
using Newtonsoft.Json;
using ProtoCore.AST.AssociativeAST;
using ProtoCore.DSASM;

namespace CoreNodeModels.Input
{
Expand All @@ -37,6 +36,41 @@ public override string NodeType
}
}


private double _serializedWidth;
/// <summary>
/// The serialized Width property of the text input; Width is taken and JsonIgnore applied to
/// </summary>
public double SerializedWidth
{
get => _serializedWidth;
set
{
if (_serializedWidth != value)
{
_serializedWidth = value;
RaisePropertyChanged(nameof(SerializedWidth)); // Notify change
}
}
}

private double _serializedHeight;
/// <summary>
/// The serialized Height property of the text input; Height is taken and JsonIgnore applied to
/// </summary>
public double SerializedHeight
{
get => _serializedHeight;
set
{
if (_serializedHeight != value)
{
_serializedHeight = value;
RaisePropertyChanged(nameof(SerializedHeight)); // Notify change
}
}
}

[JsonConstructor]
private StringInput(IEnumerable<PortModel> inPorts, IEnumerable<PortModel> outPorts) : base(inPorts, outPorts)
{
Expand All @@ -61,6 +95,16 @@ protected override bool UpdateValueCore(UpdateValueParams updateValueParams)
Value = value;
return true; // UpdateValueCore handled.
}
if (name == "SerializedWidth" && double.TryParse(value, out double width))
{
SerializedWidth = width;
return true; // UpdateValueCore handled
}
if (name == "SerializedHeight" && double.TryParse(value, out double height))
{
SerializedHeight = height;
return true; // UpdateValueCore handled
}

// There's another 'UpdateValueCore' method in 'String' base class,
// since they are both bound to the same property, 'StringInput'
Expand All @@ -76,6 +120,8 @@ protected override void SerializeCore(XmlElement nodeElement, SaveContext contex

var helper = new XmlElementHelper(outEl);
helper.SetAttribute("value", SerializeValue());
helper.SetAttribute("serializedWidth", SerializedWidth.ToString(CultureInfo.InvariantCulture));
helper.SetAttribute("serializedHeight", SerializedHeight.ToString(CultureInfo.InvariantCulture));
nodeElement.AppendChild(outEl);
}

Expand All @@ -92,6 +138,14 @@ protected override void DeserializeCore(XmlElement nodeElement, SaveContext cont
{
Value = DeserializeValue(attr.Value);
}
if (attr.Name.Equals("serializedWidth") && double.TryParse(attr.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out double width))
{
SerializedWidth = width;
}
if (attr.Name.Equals("serializedHeight") && double.TryParse(attr.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out double height))
{
SerializedHeight = height;
}
}
}
}
Expand Down
151 changes: 147 additions & 4 deletions src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/StringInput.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
using System.Windows;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using CoreNodeModels.Input;
using Dynamo.Controls;
using Dynamo.Graph.Workspaces;
using Dynamo.Models;
using Dynamo.Nodes;
using Dynamo.UI.Prompts;
using Dynamo.ViewModels;
Expand All @@ -15,13 +21,20 @@ namespace CoreNodeModelsWpf.Nodes
public class StringInputNodeViewCustomization : INodeViewCustomization<StringInput>
{
private DynamoViewModel dynamoViewModel;
private WorkspaceModel workspace;
private StringInput nodeModel;
private MenuItem editWindowItem;
private NodeView nodeView;
private readonly int minWidthSize = 100;
private readonly int minHeightSize = 34;
private readonly int defMaxWidthSize = 200;

public void CustomizeView(StringInput stringInput, NodeView nodeView)
{
this.nodeModel = stringInput;
this.nodeView = nodeView;
this.dynamoViewModel = nodeView.ViewModel.DynamoViewModel;
this.workspace = this.dynamoViewModel.CurrentSpace;

this.editWindowItem = new MenuItem
{
Expand All @@ -32,16 +45,32 @@ public void CustomizeView(StringInput stringInput, NodeView nodeView)

editWindowItem.Click += editWindowItem_Click;

//add a text box to the input grid of the control
// Add a text box to the input grid of the control
var tb = new StringTextBox
{
AcceptsReturn = true,
AcceptsTab = true,
TextWrapping = TextWrapping.Wrap,
MaxWidth = 200,
VerticalAlignment = VerticalAlignment.Top
MinHeight = minHeightSize,
VerticalAlignment = VerticalAlignment.Top,
};

// Set the recorded Width, if any
if (stringInput.SerializedWidth != 0)
{
tb.Width = stringInput.SerializedWidth;
}
else
{
tb.MaxWidth = defMaxWidthSize;
}

// Set the recorded Height, if any
if (stringInput.SerializedHeight != 0)
{
tb.Height = stringInput.SerializedHeight;
}

nodeView.inputGrid.Children.Add(tb);
Grid.SetColumn(tb, 0);
Grid.SetRow(tb, 0);
Expand All @@ -54,6 +83,86 @@ public void CustomizeView(StringInput stringInput, NodeView nodeView)
Source = stringInput,
UpdateSourceTrigger = UpdateSourceTrigger.Explicit
});

AddResizeThumb(tb, nodeView.inputGrid, stringInput);
}

/// <summary>
/// Adds a resize thumb to enable dynamic resizing of a StringTextBox.
/// </summary>
private void AddResizeThumb(StringTextBox tb, Grid inputGrid, StringInput stringInput)
{
var resizeThumb = CreateResizeThumb();
inputGrid.Children.Add(resizeThumb);

resizeThumb.DragStarted += (s, e) =>
{
// Set the binding to the Serialized Width/Height values here
SetNodeWidthHeightBinding(tb, stringInput);

// Record the undo/redo action prior to setting the new width/height values
RecordToUndoRedoStack(stringInput.SerializedWidth.ToString(), stringInput.SerializedHeight.ToString());
};

// Handle resizing logic in the resize thumb
resizeThumb.DragDelta += (s, e) =>
{
if (tb.MaxWidth == defMaxWidthSize)
{
tb.MaxWidth = double.PositiveInfinity;
}

var newWidth = tb.ActualWidth + e.HorizontalChange;
var newHeight = tb.ActualHeight + e.VerticalChange;

tb.Width = Math.Max(minWidthSize, newWidth);
tb.Height = Math.Max(minHeightSize, newHeight);

stringInput.Width = tb.ActualWidth;
stringInput.Height = tb.ActualHeight;

// Mark the graph as modified
if(!this.workspace.HasUnsavedChanges)
this.workspace.HasUnsavedChanges = true;
};
}

private void SetNodeWidthHeightBinding(StringTextBox tb, StringInput stringInput)
{
// Only set the binding if it hasn't been set already
var bindingExpression = tb.GetBindingExpression(FrameworkElement.WidthProperty);
if (bindingExpression == null)
{
// Bind Width property to SerializedWidth
BindingOperations.SetBinding(tb, FrameworkElement.WidthProperty, new Binding("SerializedWidth")
{
Mode = BindingMode.TwoWay,
Source = stringInput,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});

// Bind Height property to SerializedHeight
BindingOperations.SetBinding(tb, FrameworkElement.HeightProperty, new Binding("SerializedHeight")
{
Mode = BindingMode.TwoWay,
Source = stringInput,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
}
}

private void RecordToUndoRedoStack(string v1, string v2)
{
this.dynamoViewModel.ExecuteCommand(
new DynamoModel.UpdateModelValueCommand(
this.nodeView.ViewModel.WorkspaceViewModel.Model.Guid,
this.nodeModel.GUID, "SerializedWidth", v1));


this.dynamoViewModel.ExecuteCommand(
new DynamoModel.UpdateModelValueCommand(
this.nodeView.ViewModel.WorkspaceViewModel.Model.Guid,
this.nodeModel.GUID, "SerializedHeight", v2));
}

public void editWindowItem_Click(object sender, RoutedEventArgs e)
Expand All @@ -73,6 +182,40 @@ public void editWindowItem_Click(object sender, RoutedEventArgs e)
editWindow.ShowDialog();
}

#region Helpers

/// <summary>
/// Helper to create a resize thumb.
/// </summary>
private static Thumb CreateResizeThumb()
{
return new Thumb
{
Width = 10,
Height = 10,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Bottom,
Cursor = Cursors.SizeNWSE,
Margin = new Thickness(0, 0, 3, 3),
Template = CreateThumbTemplate()
};
}

/// <summary>
/// Helper to create the thumb template.
/// </summary>
private static ControlTemplate CreateThumbTemplate()
{
var template = new ControlTemplate(typeof(Thumb));
var polygon = new FrameworkElementFactory(typeof(System.Windows.Shapes.Polygon));
polygon.SetValue(System.Windows.Shapes.Polygon.FillProperty, new SolidColorBrush(Color.FromRgb(175, 175, 175)));
polygon.SetValue(System.Windows.Shapes.Polygon.PointsProperty, new PointCollection(new[] { new Point(0, 8), new Point(8, 8), new Point(8, 0) }));
template.VisualTree = polygon;
return template;
}

#endregion

public void Dispose()
{
editWindowItem.Click -= editWindowItem_Click;
Expand Down

0 comments on commit 23bf463

Please sign in to comment.