diff --git a/src/Libraries/CoreNodeModels/Input/BaseTypes.cs b/src/Libraries/CoreNodeModels/Input/BaseTypes.cs index 7c9430c7ebe..c729ce70073 100644 --- a/src/Libraries/CoreNodeModels/Input/BaseTypes.cs +++ b/src/Libraries/CoreNodeModels/Input/BaseTypes.cs @@ -13,7 +13,6 @@ using Dynamo.Utilities; using Newtonsoft.Json; using ProtoCore.AST.AssociativeAST; -using ProtoCore.DSASM; namespace CoreNodeModels.Input { @@ -37,6 +36,41 @@ public override string NodeType } } + + private double _serializedWidth; + /// + /// The serialized Width property of the text input; Width is taken and JsonIgnore applied to + /// + public double SerializedWidth + { + get => _serializedWidth; + set + { + if (_serializedWidth != value) + { + _serializedWidth = value; + RaisePropertyChanged(nameof(SerializedWidth)); // Notify change + } + } + } + + private double _serializedHeight; + /// + /// The serialized Height property of the text input; Height is taken and JsonIgnore applied to + /// + public double SerializedHeight + { + get => _serializedHeight; + set + { + if (_serializedHeight != value) + { + _serializedHeight = value; + RaisePropertyChanged(nameof(SerializedHeight)); // Notify change + } + } + } + [JsonConstructor] private StringInput(IEnumerable inPorts, IEnumerable outPorts) : base(inPorts, outPorts) { @@ -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' @@ -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); } @@ -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; + } } } } diff --git a/src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/StringInput.cs b/src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/StringInput.cs index e55793cb724..127bdc2c622 100644 --- a/src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/StringInput.cs +++ b/src/Libraries/CoreNodeModelsWpf/NodeViewCustomizations/StringInput.cs @@ -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; @@ -15,13 +21,20 @@ namespace CoreNodeModelsWpf.Nodes public class StringInputNodeViewCustomization : INodeViewCustomization { 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 { @@ -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); @@ -54,6 +83,86 @@ public void CustomizeView(StringInput stringInput, NodeView nodeView) Source = stringInput, UpdateSourceTrigger = UpdateSourceTrigger.Explicit }); + + AddResizeThumb(tb, nodeView.inputGrid, stringInput); + } + + /// + /// Adds a resize thumb to enable dynamic resizing of a StringTextBox. + /// + 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) @@ -73,6 +182,40 @@ public void editWindowItem_Click(object sender, RoutedEventArgs e) editWindow.ShowDialog(); } + #region Helpers + + /// + /// Helper to create a resize thumb. + /// + 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() + }; + } + + /// + /// Helper to create the thumb template. + /// + 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;