using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Linq; using System.Text; using System.Windows.Controls; using System.Windows.Media; using AIStudio.Wpf.DiagramDesigner; using AIStudio.Wpf.DiagramDesigner.Algorithms; using AIStudio.Wpf.DiagramDesigner.Geometrys; using AIStudio.Wpf.DiagramDesigner.Models; namespace AIStudio.Wpf.Mind.ViewModels { public class MindNode : DesignerItemViewModelBase { public MindNode(NodeLevel nodeLevel) : this(null, nodeLevel) { } public MindNode(IDiagramViewModel root, NodeLevel nodeLevel) : base(root) { NodeLevel = nodeLevel; LevelInit(true); } public MindNode(IDiagramViewModel root, SelectableItemBase designer) : base(root, designer) { } public MindNode(IDiagramViewModel root, SerializableItem serializableItem, string serializableType) : base(root, serializableItem, serializableType) { } protected override void Init(IDiagramViewModel root) { base.Init(root); EnabledForConnection = false; AddChildCommand = new SimpleCommand(Command_Enable, ExecuteAddChildCommand); AddParentCommand = new SimpleCommand(Level_Enable, ExecuteAddParentCommand); AddPeerCommand = new SimpleCommand(Level_Enable, ExecuteAddPeerCommand); DeleteCommand = new SimpleCommand(Level_Enable, ExecuteDeleteCommand); MoveForwardCommand = new SimpleCommand(Command_Enable, ExecuteMoveForwardCommand); MoveBackCommand = new SimpleCommand(Command_Enable, ExecuteMoveBackCommand); BuildMenuOptions(); this.PropertyChanged += this.Item_PropertyChanged; } private void LevelInit(bool init = false) { switch (NodeLevel) { case NodeLevel.Level1: { ItemWidth = 110; ItemHeight = 40; this.ClearConnectors(); var port = new FullyCreatedConnectorInfo(Root, this, ConnectorOrientation.None, true) { XRatio = 0.5, YRatio = 0.5 }; this.AddConnector(port); IsInnerConnector = true; ColorViewModel.FillColor.Color = Color.FromRgb(0x73, 0xa1, 0xbf); ColorViewModel.LineColor.Color = Color.FromRgb(0x73, 0xa1, 0xbf); FontViewModel.FontColor = Colors.White; FontViewModel.FontSize = 15; Spacing = new SizeBase(50, 15); ShapeViewModel.SinkMarker.PathStyle = ArrowPathStyle.Circle; ShapeViewModel.SinkMarker.SizeStyle = ArrowSizeStyle.VerySmall; break; } case NodeLevel.Level2: { ItemWidth = 80; ItemHeight = 25; this.ClearConnectors(); var port1 = new FullyCreatedConnectorInfo(Root, this, ConnectorOrientation.Left, true) { XRatio = 0, YRatio = 0.5 }; this.AddConnector(port1); var port2 = new FullyCreatedConnectorInfo(Root, this, ConnectorOrientation.Right, true) { XRatio = 1, YRatio = 0.5 }; this.AddConnector(port2); IsInnerConnector = true; ColorViewModel.LineColor.Color = Color.FromRgb(0x73, 0xa1, 0xbf); ShapeViewModel.SinkMarker.PathStyle = ArrowPathStyle.None; ShapeViewModel.SinkMarker.SizeStyle = ArrowSizeStyle.VerySmall; break; } case NodeLevel.Level3: { ItemWidth = 80; ItemHeight = 25; this.ClearConnectors(); var port1 = new FullyCreatedConnectorInfo(Root, this, ConnectorOrientation.Left, true) { XRatio = 0, YRatio = 1 }; this.AddConnector(port1); var port2 = new FullyCreatedConnectorInfo(Root, this, ConnectorOrientation.Right, true) { XRatio = 1, YRatio = 1 }; this.AddConnector(port2); IsInnerConnector = true; ColorViewModel.LineColor.Color = Color.FromRgb(0x73, 0xa1, 0xbf); ShapeViewModel.SinkMarker.PathStyle = ArrowPathStyle.None; ShapeViewModel.SinkMarker.SizeStyle = ArrowSizeStyle.VerySmall; break; } } } private bool Level_Enable(object obj) { if (Command_Enable(obj) == false) return false; return NodeLevel != NodeLevel.Level1; } #region 属性 [Browsable(false)] private NodeLevel _nodeLevel; public NodeLevel NodeLevel { get { return _nodeLevel; } set { SetProperty(ref _nodeLevel, value); } } private double _cornerRadius = 3; public double CornerRadius { get { return _cornerRadius; } set { SetProperty(ref _cornerRadius, value); } } private bool _isExpanded = true; public bool IsExpanded { get { return _isExpanded; } set { SetProperty(ref _isExpanded, value); } } public SizeBase Spacing { get; set; } = new SizeBase(15, 15); public List Children { get; set; } = new List(); public SizeBase SizeWithSpacing { get { return this.Size.Add(Spacing.Width * 2, Spacing.Height * 2); } } public SizeBase DesiredSize { get; set; } public PointBase DesiredPosition { get; set; } public PointBase Offset { get; set; } private MindType _mindType; public MindType MindType { get { return _mindType; } set { SetProperty(ref _mindType, value); } } private bool _layoutUpdating; private bool _isRightLayout = true; #endregion #region 命令 public SimpleCommand AddParentCommand { get; private set; } public SimpleCommand AddChildCommand { get; private set; } public SimpleCommand AddPeerCommand { get; private set; } public SimpleCommand DeleteCommand { get; private set; } public SimpleCommand MoveForwardCommand { get; private set; } public SimpleCommand MoveBackCommand { get; private set; } #endregion #region 菜单 private void BuildMenuOptions() { menuOptions = new ObservableCollection(); CinchMenuItem menuItem = new CinchMenuItem(); menuItem.Text = "下级"; menuItem.Command = AddChildCommand; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "同级"; menuItem.Command = AddPeerCommand; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "上级"; menuItem.Command = AddParentCommand; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "前移"; menuItem.Command = MoveForwardCommand; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "后移"; menuItem.Command = MoveBackCommand; menuOptions.Add(menuItem); menuItem = new CinchMenuItem(); menuItem.Text = "删除"; menuItem.Command = DeleteCommand; menuOptions.Add(menuItem); } #endregion #region 操作 public void ExecuteAddChildCommand(object obj) { if (obj is MindNode node) { } else { if (NodeLevel == NodeLevel.Level1) node = new MindNode(Root, NodeLevel.Level2) { Text = "分支主题" }; else node = new MindNode(Root, NodeLevel.Level3) { Text = "分支主题" }; } AddChild(node); LayoutUpdated(); } public void ExecuteAddParentCommand(object obj) { if (Parent is MindNode parent) { if (obj is MindNode node) { } else { if (NodeLevel == NodeLevel.Level1) { return; } else if (NodeLevel == NodeLevel.Level2) node = new MindNode(Root, NodeLevel.Level2) { Text = "分支主题" }; else node = new MindNode(Root, NodeLevel.Level3) { Text = "分支主题" }; } parent.RemoveChild(this); int index = parent.Children.IndexOf(this); parent.AddChild(node, index + 1); node.AddChild(this); LayoutUpdated(); } } public void ExecuteAddPeerCommand(object obj) { if (Parent is MindNode parent) { if (obj is MindNode node) { } else { if (NodeLevel == NodeLevel.Level1) { return; } else if (NodeLevel == NodeLevel.Level2) node = new MindNode(Root, NodeLevel.Level2) { Text = "分支主题" }; else node = new MindNode(Root, NodeLevel.Level3) { Text = "分支主题" }; } int index = parent.Children.IndexOf(this); parent.AddChild(node, index + 1); LayoutUpdated(); } } private void ExecuteMoveBackCommand(object obj) { if (Parent is MindNode parent) { int index = parent.Children.IndexOf(this); if (index < parent.Children.Count - 1) { parent.RemoveChild(this); parent.AddChild(this, index + 1); LayoutUpdated(); } } } private void ExecuteMoveForwardCommand(object obj) { if (Parent is MindNode parent) { int index = parent.Children.IndexOf(this); if (index > 0) { parent.RemoveChild(this); parent.AddChild(this, index - 1); LayoutUpdated(); } } } private void ExecuteDeleteCommand(object obj) { if (Parent is MindNode parent) { parent.RemoveChild(this, true); LayoutUpdated(); } } public void AddChild(MindNode item, int index = -1) { if (this.NodeLevel == NodeLevel.Level1) { item.NodeLevel = NodeLevel.Level2; } else { item.NodeLevel = NodeLevel.Level3; } if (index >= 0) { this.Children.Insert(index, item); } else { this.Children.Add(item); } item.Parent = this; Root?.DirectAddItemCommand.Execute(item); ConnectionViewModel connector = new ConnectionViewModel(Root, this.Connectors.FirstOrDefault(), item.Connectors.FirstOrDefault()); connector.ColorViewModel.LineColor = this.ColorViewModel.LineColor; connector.SmoothMargin = 20; connector.SmoothAutoSlope = 0.2; connector.ShapeViewModel.SinkMarker.PathStyle = this.ShapeViewModel.SinkMarker.PathStyle; connector.ShapeViewModel.SinkMarker.SizeStyle = this.ShapeViewModel.SinkMarker.SizeStyle; Root?.DirectAddItemCommand.Execute(connector); Root?.ClearSelectedItemsCommand.Execute(new SelectableDesignerItemViewModelBase[] { connector }); Root?.BringForwardCommand.Execute(new DesignerItemViewModelBase[] { item }); } public void RemoveChild(MindNode item, bool removeall = false) { item.PropertyChanged -= Item_PropertyChanged; this.Children.Remove(item); var connectors = Root?.Items.OfType().Where(p => p.SinkConnectorInfoFully?.DataItem == item).ToList(); Root?.DirectRemoveItemCommand.Execute(item); Root?.DirectRemoveItemCommand.Execute(connectors); if (removeall) { if (item.Children?.Count > 0) { foreach (var child in item.Children.ToList()) { item.RemoveChild(child); } } } } #endregion private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) { switch (e.PropertyName) { case nameof(IsExpanded): case nameof(ItemWidth): case nameof(ItemHeight): case nameof(MindType): GetLevel1Node()?.LayoutUpdated(); break; case nameof(NodeLevel): LevelInit(); break; case nameof(Left): { if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs) { if (NodeLevel == NodeLevel.Level1) { LayoutUpdated(); } else { UpdateOffsetX((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue); LayoutUpdated(); } } break; } case nameof(Top): { if (e is ValuePropertyChangedEventArgs valuePropertyChangedEventArgs) { if (NodeLevel == NodeLevel.Level1) { LayoutUpdated(); } else { UpdateOffsetY((double)valuePropertyChangedEventArgs.OldValue, (double)valuePropertyChangedEventArgs.NewValue); LayoutUpdated(); } } break; } } } #region 布局相关 protected MindNode GetLevel1Node() { var node = this; while (node.Parent is MindNode mindNode) { node = mindNode; } return node; } protected MindNode GetLevel2Node() { var node = this; while (node.Parent is MindNode mindNode && mindNode.NodeLevel == NodeLevel.Level2) { node = mindNode; } return node; } protected void UpdateOffsetX(double oldvalue, double newvalue) { if (GetLevel1Node()?._layoutUpdating == true) return; Offset += new VectorBase(newvalue - oldvalue, 0); } protected void UpdateOffsetY(double oldvalue, double newvalue) { if (GetLevel1Node()?._layoutUpdating == true) return; Offset += new VectorBase(0, newvalue - oldvalue); } public void LayoutUpdated() { GetLevel1Node()?.Level1LayoutUpdated(); } protected void Level1LayoutUpdated() { _layoutUpdating = true; var size = MeasureOverride(); ArrangeOverride(); Root.BringToFrontCommand.Execute(new SelectableDesignerItemViewModelBase[] { this }); Root?.ReconnectLinksToClosestPorts(); _layoutUpdating = false; } protected SizeBase MeasureOverride(bool isExpanded = true) { switch (MindType) { case MindType.Mind: { var sizewithSpacing = SizeWithSpacing; if (Children?.Count > 0) { if (NodeLevel == NodeLevel.Level1) { var rights = Children.Where((p, index) => index % 2 == 0).ToList(); rights.ForEach(p => p._isRightLayout = true); var rightsizes = rights.Select(p => p.MeasureOverride(IsExpanded && isExpanded)).ToArray(); var lefts = Children.Where((p, index) => index % 2 == 1).ToList(); lefts.ForEach(p => p._isRightLayout = false); var leftsizes = lefts.Select(p => p.MeasureOverride(IsExpanded && isExpanded)).ToArray(); sizewithSpacing = new SizeBase(sizewithSpacing.Width + rightsizes.Max(p => p.Width) + +leftsizes.Max(p => p.Width), Math.Max(sizewithSpacing.Height, Math.Max(rightsizes.Sum(p => p.Height), leftsizes.Sum(p => p.Height)))); } else { var childrensizes = Children.Select(p => p.MeasureOverride(IsExpanded && isExpanded)).ToArray(); sizewithSpacing = new SizeBase(sizewithSpacing.Width + childrensizes.Max(p => p.Width), Math.Max(sizewithSpacing.Height, childrensizes.Sum(p => p.Height))); } } DesiredSize = isExpanded ? sizewithSpacing : new SizeBase(0, 0); Visible = isExpanded; var connectors = Root?.Items.OfType().Where(p => p.SinkConnectorInfoFully?.DataItem == this).ToList(); connectors?.ForEach(p => p.Visible = Visible); break; } case MindType.Logical: { var sizewithSpacing = SizeWithSpacing; if (Children?.Count > 0) { var childrensizes = Children.Select(p => p.MeasureOverride(IsExpanded && isExpanded)).ToArray(); sizewithSpacing = new SizeBase(sizewithSpacing.Width + childrensizes.Max(p => p.Width), Math.Max(sizewithSpacing.Height, childrensizes.Sum(p => p.Height))); } DesiredSize = isExpanded ? sizewithSpacing : new SizeBase(0, 0); Visible = isExpanded; var connectors = Root?.Items.OfType().Where(p => p.SinkConnectorInfoFully?.DataItem == this).ToList(); connectors?.ForEach(p => p.Visible = Visible); break; } default: { var sizewithSpacing = SizeWithSpacing; if (Children?.Count > 0) { var childrensizes = Children.Select(p => p.MeasureOverride(IsExpanded && isExpanded)).ToArray(); sizewithSpacing = new SizeBase(sizewithSpacing.Width + childrensizes.Max(p => p.Width), Math.Max(sizewithSpacing.Height, childrensizes.Sum(p => p.Height))); } DesiredSize = isExpanded ? sizewithSpacing : new SizeBase(0, 0); Visible = isExpanded; var connectors = Root?.Items.OfType().Where(p => p.SinkConnectorInfoFully?.DataItem == this).ToList(); connectors?.ForEach(p => p.Visible = Visible); break; } } return DesiredSize; } protected void ArrangeOverride() { switch (MindType) { case MindType.Mind: { if (NodeLevel == NodeLevel.Level1) { if (Children?.Count > 0) { var rights = Children.Where(p => p._isRightLayout == true).ToList(); double left = MiddlePosition.X + ItemWidth / 2 + Spacing.Width; double lefttop = MiddlePosition.Y - Math.Min(DesiredSize.Height, rights.Sum(p => p.DesiredSize.Height)) / 2; foreach (var child in rights) { child.Left = left + child.Spacing.Width + child.Offset.X; child.Top = lefttop + child.DesiredSize.Height / 2 - child.ItemHeight / 2 + child.Offset.Y; child.DesiredPosition = child.Position; lefttop += child.DesiredSize.Height; child.ArrangeOverride(); } var lefts = Children.Where(p => p._isRightLayout == false).ToList(); double right = MiddlePosition.X - ItemWidth / 2 - Spacing.Width; double righttop = MiddlePosition.Y - Math.Min(DesiredSize.Height, lefts.Sum(p => p.DesiredSize.Height)) / 2; foreach (var child in lefts) { child.Left = right - child.Spacing.Width - child.ItemWidth + child.Offset.X ; child.Top = righttop + child.DesiredSize.Height / 2 - child.ItemHeight / 2 + child.Offset.Y; child.DesiredPosition = child.Position; righttop += child.DesiredSize.Height; child.ArrangeOverride(); } } } else { if (GetLevel2Node()._isRightLayout) { double left = MiddlePosition.X + ItemWidth / 2 + Spacing.Width; double top = MiddlePosition.Y - Math.Min(DesiredSize.Height, Children.Sum(p => p.DesiredSize.Height)) / 2; if (Children?.Count > 0) { foreach (var child in Children) { child.Left = left + child.Spacing.Width + child.Offset.X; child.Top = top + child.DesiredSize.Height / 2 - child.ItemHeight / 2 + child.Offset.Y; child.DesiredPosition = child.Position; top += child.DesiredSize.Height; child.ArrangeOverride(); } } } else { double right = MiddlePosition.X - ItemWidth / 2 - Spacing.Width; double top = MiddlePosition.Y - Math.Min(DesiredSize.Height, Children.Sum(p => p.DesiredSize.Height)) / 2; if (Children?.Count > 0) { foreach (var child in Children) { child.Left = right - child.Spacing.Width - child.ItemWidth + child.Offset.X; child.Top = top + child.DesiredSize.Height / 2 - child.ItemHeight / 2 + child.Offset.Y; child.DesiredPosition = child.Position; top += child.DesiredSize.Height; child.ArrangeOverride(); } } } } break; } case MindType.Logical: { double left = MiddlePosition.X + ItemWidth / 2 + Spacing.Width; double top = MiddlePosition.Y - Math.Min(DesiredSize.Height, Children.Sum(p => p.DesiredSize.Height)) / 2; if (Children?.Count > 0) { foreach (var child in Children) { child.Left = left + child.Spacing.Width + child.Offset.X; child.Top = top + child.DesiredSize.Height / 2 - child.ItemHeight / 2 + child.Offset.Y; child.DesiredPosition = child.Position; top += child.DesiredSize.Height; child.ArrangeOverride(); } } break; } default: { break; } } } #endregion } }