using System; using System.Reactive.Disposables; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; using System.Windows.Media; using Intromat.ViewModels; using NodeNetwork.Utilities; using NodeNetwork.ViewModels; using NodeNetwork.Views; using ReactiveUI; using Splat; namespace Intromat.Views { [TemplatePart(Name = nameof(NameLabel), Type = typeof(TextBlock))] [TemplatePart(Name = nameof(HeaderIcon), Type = typeof(Image))] [TemplatePart(Name = nameof(LeftList), Type = typeof(ItemsControl))] [TemplatePart(Name = nameof(RightList), Type = typeof(ItemsControl))] [TemplatePart(Name = nameof(ResizeVerticalThumb), Type = typeof(Thumb))] [TemplatePart(Name = nameof(ResizeHorizontalThumb), Type = typeof(Thumb))] [TemplatePart(Name = nameof(ResizeDiagonalThumb), Type = typeof(Thumb))] [TemplatePart(Name = nameof(Preview), Type = typeof(ViewModelViewHost))] [TemplateVisualState(Name = SelectedState, GroupName = SelectedVisualStatesGroup)] [TemplateVisualState(Name = UnselectedState, GroupName = SelectedVisualStatesGroup)] [TemplateVisualState(Name = CollapsedState, GroupName = CollapsedVisualStatesGroup)] [TemplateVisualState(Name = ExpandedState, GroupName = CollapsedVisualStatesGroup)] public class CodeGenNodeView : NodeViewBase, IViewFor { private Size _previousSize; public CodeGenNodeView() { DefaultStyleKey = typeof(CodeGenNodeView); SetupBindings(); SetupEvents(); SetupVisualStateBindings(); } private TextBlock NameLabel { get; set; } = null!; private Image HeaderIcon { get; set; } = null!; private ItemsControl LeftList { get; set; } = null!; private ItemsControl RightList { get; set; } = null!; private Thumb ResizeVerticalThumb { get; set; } = null!; private Thumb ResizeHorizontalThumb { get; set; } = null!; private Thumb ResizeDiagonalThumb { get; set; } = null!; private ViewModelViewHost Preview { get; set; } = null!; public override void OnApplyTemplate() { ResizeHorizontalThumb = (Thumb)GetTemplateChild(nameof(ResizeHorizontalThumb))!; ResizeVerticalThumb = (Thumb)GetTemplateChild(nameof(ResizeVerticalThumb))!; LeftList = (ItemsControl)GetTemplateChild(nameof(LeftList))!; RightList = (ItemsControl)GetTemplateChild(nameof(RightList))!; HeaderIcon = (Image)GetTemplateChild(nameof(HeaderIcon))!; NameLabel = (TextBlock)GetTemplateChild(nameof(NameLabel))!; ResizeDiagonalThumb = (Thumb)GetTemplateChild(nameof(ResizeDiagonalThumb))!; Preview = (ViewModelViewHost)GetTemplateChild(nameof(Preview))!; ResizeVerticalThumb.DragStarted += BeginResize; ResizeHorizontalThumb.DragStarted += BeginResize; ResizeDiagonalThumb.DragStarted += BeginResize; ResizeVerticalThumb.DragCompleted += EndResize; ResizeHorizontalThumb.DragCompleted += EndResize; ResizeDiagonalThumb.DragCompleted += EndResize; ResizeVerticalThumb.DragDelta += (_, e) => ApplyResize(e, false, true); ResizeHorizontalThumb.DragDelta += (_, e) => ApplyResize(e, true, false); ResizeDiagonalThumb.DragDelta += (_, e) => ApplyResize(e, true, true); VisualStateManager.GoToState(this, ExpandedState, false); VisualStateManager.GoToState(this, UnselectedState, false); } private void BeginResize(object sender, DragStartedEventArgs e) { _previousSize = ViewModel!.Size; } private void EndResize(object sender, DragCompletedEventArgs e) { var document = ((CodeGenNetworkViewModel)ViewModel!.Parent).Document; var undoRedo = document.MainViewModel.UndoRedo; undoRedo.Record(new UndoItem(document, ViewModel, vm => vm.Size, _previousSize, ViewModel.Size)); } private void ApplyResize(DragDeltaEventArgs e, bool horizontal, bool vertical) { if (horizontal) MinWidth = Math.Max(20, MinWidth + e.HorizontalChange); if (vertical) MinHeight = Math.Max(20, MinHeight + e.VerticalChange); } private void SetupBindings() { this.WhenActivated(d => { var viewModel = ViewModel!; this.Events() .MouseEnter.Subscribe(_ => { viewModel.IsMouseOver = true; }) .DisposeWith(d); this.Events() .MouseLeave.Subscribe(_ => { viewModel.IsMouseOver = false; }) .DisposeWith(d); this.OneWayBind(viewModel, vm => vm.Name, v => v.NameLabel.Text) .DisposeWith(d); this.BindList(viewModel, vm => vm.LeftEndpoints, v => v.LeftList.ItemsSource) .DisposeWith(d); this.BindList(viewModel, vm => vm.RightEndpoints, v => v.RightList.ItemsSource) .DisposeWith(d); this.WhenAnyValue(v => v.ActualWidth, v => v.ActualHeight, (width, height) => new Size(width, height)) .BindTo(this, v => v.ViewModel!.Size) .DisposeWith(d); this.WhenAnyValue(v => v.ViewModel!.Size) .Subscribe(size => { MinWidth = size.Width; MinHeight = size.Height; }) .DisposeWith(d); this.OneWayBind(viewModel, vm => vm.HeaderIcon, v => v.HeaderIcon.Source, img => img?.ToNative()) .DisposeWith(d); this.OneWayBind(viewModel, vm => vm.NodeType, v => v.Background, ConvertNodeTypeToBrush) .DisposeWith(d); this.OneWayBind(viewModel, vm => vm.Preview, v => v.Preview.ViewModel) .DisposeWith(d); }); } private void SetupEvents() { MouseLeftButtonDown += (_, _) => { Focus(); if (ViewModel == null) return; if (ViewModel.IsSelected) return; if (ViewModel.Parent != null && !Keyboard.IsKeyDown(Key.LeftCtrl) && !Keyboard.IsKeyDown(Key.RightCtrl)) ViewModel.Parent.ClearSelection(); ViewModel.IsSelected = true; }; } private void SetupVisualStateBindings() { this.WhenActivated(d => { this.WhenAnyValue(v => v.ViewModel!.IsCollapsed) .Subscribe(isCollapsed => { VisualStateManager.GoToState(this, isCollapsed ? CollapsedState : ExpandedState, true); }) .DisposeWith(d); this.WhenAnyValue(v => v.ViewModel!.IsSelected) .Subscribe(isSelected => { VisualStateManager.GoToState(this, isSelected ? SelectedState : UnselectedState, true); }) .DisposeWith(d); }); } public static Brush ConvertNodeTypeToBrush(NodeType type) { return type switch { NodeType.Special => new SolidColorBrush(Color.FromRgb(0x9b, 0x00, 0x00)), NodeType.Literal => new SolidColorBrush(Color.FromRgb(0x49, 0x49, 0x49)), NodeType.Code => new SolidColorBrush(Color.FromRgb(0x00, 0x60, 0x0f)), NodeType.Group => new SolidColorBrush(Color.FromRgb(0x7B, 0x1F, 0xA2)), _ => throw new Exception("Unsupported node type") }; } #region SelectedStates public const string SelectedVisualStatesGroup = "SelectedStates"; public const string SelectedState = "Selected"; public const string UnselectedState = "Unselected"; #endregion #region CollapsedStates public const string CollapsedVisualStatesGroup = "CollapsedStates"; public const string CollapsedState = "Collapsed"; public const string ExpandedState = "Expanded"; #endregion #region ViewModel public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register(nameof(ViewModel), typeof(CodeGenNodeViewModel), typeof(CodeGenNodeView), new PropertyMetadata(null)); public CodeGenNodeViewModel? ViewModel { get => (CodeGenNodeViewModel)GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } object? IViewFor.ViewModel { get => ViewModel; set => ViewModel = (CodeGenNodeViewModel?)value; } #endregion #region Properties public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(nameof(CornerRadius), typeof(CornerRadius), typeof(CodeGenNodeView)); public CornerRadius CornerRadius { get => (CornerRadius)GetValue(CornerRadiusProperty); set => SetValue(CornerRadiusProperty, value); } public static readonly DependencyProperty ArrowSizeProperty = DependencyProperty.Register(nameof(ArrowSize), typeof(double), typeof(CodeGenNodeView)); public double ArrowSize { get => (double)GetValue(ArrowSizeProperty); set => SetValue(ArrowSizeProperty, value); } public static readonly DependencyProperty TitleFontFamilyProperty = DependencyProperty.Register(nameof(TitleFontFamily), typeof(FontFamily), typeof(CodeGenNodeView)); public FontFamily TitleFontFamily { get => (FontFamily)GetValue(TitleFontFamilyProperty); set => SetValue(TitleFontFamilyProperty, value); } public static readonly DependencyProperty TitleFontSizeProperty = DependencyProperty.Register(nameof(TitleFontSize), typeof(double), typeof(CodeGenNodeView)); public double TitleFontSize { get => (double)GetValue(TitleFontSizeProperty); set => SetValue(TitleFontSizeProperty, value); } public static readonly DependencyProperty EndpointsStackingOrientationProperty = DependencyProperty.Register(nameof(EndpointsStackingOrientation), typeof(Orientation), typeof(CodeGenNodeView)); public Orientation EndpointsStackingOrientation { get => (Orientation)GetValue(EndpointsStackingOrientationProperty); set => SetValue(EndpointsStackingOrientationProperty, value); } public static readonly DependencyProperty TrailingControlPresenterStyleProperty = DependencyProperty.Register(nameof(TrailingControlPresenterStyle), typeof(Style), typeof(CodeGenNodeView)); public Style TrailingControlPresenterStyle { get => (Style)GetValue(TrailingControlPresenterStyleProperty); set => SetValue(TrailingControlPresenterStyleProperty, value); } #endregion } }