添加Avalonia项目的demo

This commit is contained in:
fengjiayi
2025-01-01 17:49:48 +08:00
parent 6c6d493f93
commit 28df2d8fce
61 changed files with 4404 additions and 0 deletions

View File

@@ -0,0 +1,217 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Skia;
using Avalonia.Styling;
using Serein.Library.Utils;
using Serein.Workbench.Avalonia.Extension;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Avalonia.Custom.Views
{
public class ConnectionLineShape : Control
{
private readonly double strokeThickness;
/// <summary>
/// 确定起始坐标和目标坐标、外光样式的曲线
/// </summary>
/// <param name="start">起始坐标</param>
/// <param name="end">结束坐标</param>
/// <param name="brush">颜色</param>
/// <param name="isDotted">是否为虚线</param>
public ConnectionLineShape(Point start,
Point end,
Brush brush,
bool isDotted = false,
bool isTop = false)
{
this.brush = brush;
startPoint = start;
endPoint = end;
this.strokeThickness = 4;
InitElementPoint(isDotted, isTop);
InvalidateVisual(); // 触发重绘
}
public void InitElementPoint(bool isDotted, bool isTop = false)
{
//hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
//hitVisiblePen.Freeze(); // Freeze以提高性能
visualPen = new Pen(brush, 3.0); // 默认可视化Pen
opacity = 1.0d;
var dashStyle = new DashStyle();
if (isDotted)
{
opacity = 0.42d;
visualPen.DashStyle = new DashStyle(); // DashStyles.Dash; // 选择虚线样式
}
//visualPen.Freeze(); // Freeze以提高性能
linkSize = 4; // 整线条粗细
int zIndex = 999999;
this.ZIndex = zIndex;
//Panel.SetZIndex(this, zIndex); // 置底
}
/// <summary>
/// 更新线条落点位置
/// </summary>
/// <param name="start"></param>
/// <param name="end"></param>
public void UpdatePoints(Point start, Point end)
{
startPoint = start;
endPoint = end;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 更新线条落点位置
/// </summary>
/// <param name="point"></param>
public void UpdateEndPoints(Point point)
{
endPoint = point;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 更新线条起点位置
/// </summary>
/// <param name="point"></param>
public void UpdateStartPoints(Point point)
{
startPoint = point;
InvalidateVisual(); // 触发重绘
}
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
public override void Render(DrawingContext drawingContext)
{
// 刷新线条显示位置
DrawBezierCurve(drawingContext, startPoint, endPoint);
}
#region
private StreamGeometry streamGeometry = new StreamGeometry();
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
//private Pen hitVisiblePen; // 初始化碰撞检测线
private Pen visualPen; // 默认可视化Pen
private Point startPoint; // 连接线的起始节点
private Point endPoint; // 连接线的终点
private Brush brush; // 线条颜色
private double opacity; // 透明度
double linkSize; // 根据缩放比例调整线条粗细
//public void UpdateLineColor()
//{
// visualPen = new Pen(brush, 3.0); // 默认可视化Pen
// InvalidateVisual(); // 触发重绘
//}
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
private Vector axis = new Vector(1, 0);
private Vector startToEnd;
private int i = 0;
private void DrawBezierCurve(DrawingContext drawingContext,
Point start,
Point end)
{
// 控制点的计算逻辑
double power = 140; // 控制贝塞尔曲线的“拉伸”强度
drawingContext.PushOpacity(opacity);
// 计算轴向向量与起点到终点的向量
//var axis = new Vector(1, 0);
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
//var dp = axis.DotProduct(startToEnd);
//dp = dp < 50 ? 50 : dp;
//var pow = Math.Max(0, dp) ;
//var k = 1 - Math.Pow(pow, 10.0);
//
//Debug.WriteLine("pow : " + pow);
//Debug.WriteLine("k : " + k);
//Debug.WriteLine("");
// 计算拉伸程度k拉伸与水平夹角正相关
var dp = axis.DotProduct(startToEnd) ;
var pow = Math.Pow(dp, 10.0);
pow = pow > 0 ? 0 : pow;
var k = 1 - pow;
// 如果起点x大于终点x增加额外的偏移量避免重叠
var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0;
// 控制点的实际计算
c0 = new Point(+(power + bias) * k + start.X, start.Y);
c1 = new Point(-(power + bias) * k + end.X, end.Y);
// 准备StreamGeometry以用于绘制曲线
// why can't clearValue()?
//streamGeometry.ClearValue(ThemeProperty);
//var streamGeometry = new StreamGeometry();
//if( i++ > 100 && streamGeometry is AvaloniaObject avaloniaObject)
//{
// var platformImplInfo = streamGeometry.GetType().GetProperty("PlatformImpl", BindingFlags.NonPublic | BindingFlags.Instance);
// var platformImpl = platformImplInfo?.GetValue(streamGeometry);
// var pathCache = platformImpl?.GetType().GetField("_bounds", BindingFlags.NonPublic | BindingFlags.Instance);
// if(pathCache is IDisposable disposable)
// {
// disposable.Dispose();
// }
// //pathCache?.Invoke(platformImpl, []);
// Debug.WriteLine("invoke => InvalidateCaches");
// i = 0;
// //public class AvaloniaObject : IAvaloniaObjectDebug, INotifyPropertyChanged
// //private readonly ValueStore _values;
//}
// this is override "Render()" method
// display a bezier-line on canvas and follow the mouse in real time
// I don't want to re-instantiate StreamGeometry()
// because I want to reduce the number of GC
// but , it seems impossible to do so in avalonia
streamGeometry = new StreamGeometry();
// in wpf , this method allows display content to be cleared
// streamGeometry.Clear(); // this is wpf method
// in avalonia, why does this method need value of "AvaloniaProperty" data type ?
// but I don't know use what "AvaloniaProperty" to clear the displayed content
// if I don't clear the cache or re-instantiate it
// the canvas will display repeated lines , because exits cache inside streamGeometry
// streamGeometry.ClearValue("AvaloniaProperty");
using (var context = streamGeometry.Open())
{
context.BeginFigure(start, true); // start point of the bezier-line
context.CubicBezierTo(c0, c1, end, true); // drawing bezier-line
}
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
}
#endregion
}
}

View File

@@ -0,0 +1,56 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
xmlns:cv="using:Serein.Workbench.Avalonia.Custom.Views"
xmlns:converter="using:Serein.Workbench.Avalonia.Converters"
xmlns:dtp="using:Serein.Workbench.Avalonia.DataTemplates"
>
<!--预览-->
<Design.PreviewWith>
<StackPanel Width="400" Spacing="10">
<StackPanel Background="{DynamicResource SystemRegionBrush}">
<cv:FlowLibraryInfoView />
</StackPanel>
</StackPanel>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type cv:FlowLibraryInfoView}" TargetType="cv:FlowLibraryInfoView">
<Setter Property="Template">
<ControlTemplate>
<StackPanel>
<!--类库名称-->
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" Margin="2">
<TextBlock Text="library : " FontSize="18"></TextBlock>
<TextBlock Text="{TemplateBinding LibraryName}" FontSize="18"></TextBlock>
</StackPanel>
<!--Action Method Info-->
<!--动作节点方法信息-->
<ListBox x:Name="PART_ActionMethodInfos" ItemsSource="{TemplateBinding ActionMethods}" Background="#F1FBFB">
<ItemsControl.ItemTemplate>
<!--use custom DataTemplate create items -->
<!--使用自定义模板创建子项控件-->
<dtp:LibraryMethodInfoDataTemplate>
</dtp:LibraryMethodInfoDataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<!--Flipflop Method Info-->
<!--触发器节点方法信息-->
<ListBox x:Name="PART_FlipflopMethodInfos" ItemsSource="{TemplateBinding FlipflopMethods}" Background="#FBF8F1">
<ItemsControl.ItemTemplate>
<dtp:LibraryMethodInfoDataTemplate>
</dtp:LibraryMethodInfoDataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
</StackPanel>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -0,0 +1,236 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
using Serein.Library;
using Serein.Workbench.Avalonia.Services;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class FlowLibraryInfoView : TemplatedControl
{
private IWorkbenchEventService workbenchEventService;
public FlowLibraryInfoView()
{
workbenchEventService = App.GetService<IWorkbenchEventService>();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
// 如果改变的属性是 MdsProperty ,则加载方法信息
if (change.Property == MdsProperty)
{
if(change.NewValue is MethodDetailsInfo[] value)
{
onNext(value);
}
}
}
/// <summary>
/// 获取到控件信息
/// </summary>
/// <param name="e"></param>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
#region
if (e.NameScope.Find("PART_ActionMethodInfos") is ListBox p_am)
{
p_am.AddHandler(InputElement.PointerExitedEvent, ListBox_PointerExited);
p_am.AddHandler(SelectingItemsControl.SelectionChangedEvent, ListBox_SelectionChanged);
}
#endregion
#region
if (e.NameScope.Find("PART_FlipflopMethodInfos") is ListBox p_fm)
{
p_fm.SelectionChanged += ListBox_SelectionChanged;
p_fm.PointerExited += ListBox_PointerExited;
}
#endregion
}
private void ListBox_SelectionChanged(object? o, SelectionChangedEventArgs e)
{
if (o is ListBox listBox && listBox.SelectedIndex > 0 && listBox.SelectedItem is MethodDetailsInfo mdInfo)
{
workbenchEventService.PreviewLibraryMethodInfo(mdInfo); // 通知其它地方预览了某个方法信息
}
}
private void ListBox_PointerExited(object? o, PointerEventArgs e)
{
if (o is ListBox listBox && listBox.SelectedIndex > -1)
{
listBox.SelectedIndex = -1; // 如果鼠标离开了,取消已选状态
}
}
/// <summary>
/// 将信息加载出来
/// </summary>
/// <param name="value"></param>
private void onNext(MethodDetailsInfo[] value)
{
if(value is null)
{
return;
}
var fmd = value.Where(item => nameof(NodeType.Flipflop).Equals(item.NodeType));
FlipflopMethods = new ObservableCollection<MethodDetailsInfo>(fmd);
var amd = value.Where(item => nameof(NodeType.Action).Equals(item.NodeType));
ActionMethods = new ObservableCollection<MethodDetailsInfo>(amd);
}
#region Template Public Property /
public static readonly DirectProperty<FlowLibraryInfoView, string> LibraryNameProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, string>(nameof(LibraryName), o => o.LibraryName, (o, v) => o.LibraryName = v);
public static readonly DirectProperty<FlowLibraryInfoView, MethodDetailsInfo[]> MdsProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, MethodDetailsInfo[]>(nameof(Mds), o => o.Mds, (o, v) => o.Mds = v);
public static readonly DirectProperty<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>> ActionMethodsProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>>(nameof(ActionMethods), o => o.ActionMethods, (o, v) => o.ActionMethods = v);
public static readonly DirectProperty<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>> FlipflopMethodsProperty =
AvaloniaProperty.RegisterDirect<FlowLibraryInfoView, ObservableCollection<MethodDetailsInfo>>(nameof(FlipflopMethods), o => o.FlipflopMethods, (o, v) => o.FlipflopMethods = v);
private string libraryName = string.Empty;
private ObservableCollection<MethodDetailsInfo> actionMethods;
private ObservableCollection<MethodDetailsInfo> flipflopMethods;
private MethodDetailsInfo[] mds = [];
public string LibraryName
{
get { return libraryName; }
set { SetAndRaise(LibraryNameProperty, ref libraryName, value); }
}
/*
public static readonly AttachedProperty<string> LibraryName2Property = AvaloniaProperty.RegisterAttached<FlowLibraryInfoView, Control, string>("LibraryName2");
public static string GetLibraryName2(Control element)
{
return element.GetValue(LibraryName2Property);
}
public static void SetLibraryName2(Control element, string value)
{
element.SetValue(LibraryName2Property, value);
}
*/
/// <summary>
/// Method Info
/// 方法信息
/// </summary>
public MethodDetailsInfo[] Mds
{
get { return mds; }
set
{
SetAndRaise(MdsProperty, ref mds, value);
}
}
/// <summary>
/// 动作节点的方法
/// </summary>
public ObservableCollection<MethodDetailsInfo> ActionMethods
{
get { return actionMethods; }
set
{
SetAndRaise(ActionMethodsProperty, ref actionMethods, value);
}
}
/// <summary>
/// 触发器节点的方法
/// </summary>
public ObservableCollection<MethodDetailsInfo> FlipflopMethods
{
get { return flipflopMethods; }
set
{
SetAndRaise(FlipflopMethodsProperty, ref flipflopMethods, value);
}
}
#endregion
}
public class ItemsChangeObservableCollection<T> : ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//launch an event Reset with name of property changed
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

View File

@@ -0,0 +1,40 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.FlowLibraryMethodInfoView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
x:DataType="vm:FlowLibraryMethodInfoViewModel">
<Design.DataContext>
<vm:FlowLibraryMethodInfoViewModel />
</Design.DataContext>
<UserControl.Styles>
<Style Selector="StackPanel">
<Setter Property="Margin" Value="2,1,2,1" />
</Style>
</UserControl.Styles>
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="节点类型 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.NodeType}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="方法描述 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.MethodAnotherName}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="方法名称 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.MethodName}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text=" 返 回 值 - "/>
<TextBlock Text="{Binding MethodDetailsInfo.ReturnTypeFullName}"/>
</StackPanel>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,50 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using Serein.Workbench.Avalonia.Custom.Views;
using Serein.Workbench.Avalonia.Services;
using System.Diagnostics;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class FlowLibraryMethodInfoView : UserControl
{
private FlowLibraryMethodInfoViewModel _vm;
public FlowLibraryMethodInfoView()
{
InitializeComponent();
_vm = App.GetService<FlowLibraryMethodInfoViewModel>();
DataContext = _vm;
//this.PointerPressed += FlowLibraryMethodInfoView_PointerPressed;
}
//private async void FlowLibraryMethodInfoView_PointerPressed(object? sender, PointerPressedEventArgs e)
//{
// if (_vm.MethodDetailsInfo is null)
// {
// return;
// }
// DataObject dragData = new DataObject();
// dragData.Set(DataFormats.Text, $"{_vm.MethodDetailsInfo.MethodAnotherName}");
// var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
// Debug.WriteLine("DoDrag :" + result);
// switch (result)
// {
// case DragDropEffects.Copy:
// Debug.WriteLine("文本来自 Copy");
// break;
// case DragDropEffects.Link:
// Debug.WriteLine("文本来自 Link");
// break;
// case DragDropEffects.None:
// Debug.WriteLine("拖拽操作被取消");
// break;
// }
//}
}

View File

@@ -0,0 +1,39 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.FlowLibrarysView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
x:DataType="vm:FlowLibrarysViewModel"
>
<Design.DataContext>
<vm:FlowLibrarysViewModel />
</Design.DataContext>
<UserControl.Resources>
<cv:FlowLibraryInfoView x:Key="FlowLibraryInfoView">
</cv:FlowLibraryInfoView>
</UserControl.Resources>
<!-- , DataType={x:Type vm:FlowLibrarysViewModel} -->
<!-- x:DataType="baselibrary:LibraryMds" -->
<!-- LibraryInfo="{Binding}"-->
<ScrollViewer HorizontalAlignment="Left" VerticalAlignment="Top">
<!--Displays dependecy information loaded from runtime environment-->
<!--显示从运行环境加载的所有依赖信息-->
<ItemsControl ItemsSource="{Binding LibraryList}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="baselibrary:LibraryMds" >
<cv:FlowLibraryInfoView LibraryName="{Binding AssemblyName}" Mds="{Binding Mds}"/>
<!--<StackPanel Background="{DynamicResource SystemRegionBrush}" Margin="2,2,2,8">
</StackPanel>-->
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</UserControl>

View File

@@ -0,0 +1,17 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using System;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class FlowLibrarysView : UserControl
{
public FlowLibrarysView()
{
InitializeComponent();
DataContext = App.GetService<FlowLibrarysViewModel>();
}
}

View File

@@ -0,0 +1,43 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.MainMenuBarView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
x:DataType="vm:MainMenuBarViewModel">
<UserControl.Styles>
<Style Selector="MenuItem">
<Setter Property="FontSize" Value="20" />
</Style>
</UserControl.Styles>
<Design.DataContext>
<vm:MainMenuBarViewModel />
</Design.DataContext>
<StackPanel HorizontalAlignment="Center" >
<StackPanel.Resources>
<SolidColorBrush x:Key="MenuFlyoutBackground">#FFFFFF</SolidColorBrush>
</StackPanel.Resources>
<Menu Background="Transparent">
<MenuItem Header="项目">
<MenuItem Header="保存项目" Command="{Binding SaveProjectCommand}"/>
<MenuItem Header="打开本地项目"/>
</MenuItem>
<MenuItem Header="调试">
<MenuItem Header="运行(从起始节点)"/>
<MenuItem Header="运行(从当前节点)"/>
<MenuItem Header="结束流程"/>
</MenuItem>
<MenuItem Header="视图">
<MenuItem Header="输出窗口"/>
<MenuItem Header="重置画布"/>
<MenuItem Header="定位节点"/>
</MenuItem>
<MenuItem Header="远程">
<MenuItem Header="启动远程服务" />
<MenuItem Header="连接远程环境" />
</MenuItem>
</Menu>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,16 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using System;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class MainMenuBarView : UserControl
{
public MainMenuBarView()
{
InitializeComponent();
DataContext = App.GetService<MainMenuBarViewModel>();
}
}

View File

@@ -0,0 +1,142 @@
using static System.Math;
namespace Avalonia.Controls.PanAndZoom;
/// <summary>
/// Avalonia Matrix helper methods.
/// </summary>
public static class MatrixHelper
{
/// <summary>
/// Creates a translation matrix using the specified offsets.
/// </summary>
/// <param name="offsetX">X-coordinate offset.</param>
/// <param name="offsetY">Y-coordinate offset.</param>
/// <returns>The created translation matrix.</returns>
public static Matrix Translate(double offsetX, double offsetY)
{
return new Matrix(1.0, 0.0, 0.0, 1.0, offsetX, offsetY);
}
/// <summary>
/// Prepends a translation around the center of provided matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend translation.</param>
/// <param name="offsetX">X-coordinate offset.</param>
/// <param name="offsetY">Y-coordinate offset.</param>
/// <returns>The created translation matrix.</returns>
public static Matrix TranslatePrepend(Matrix matrix, double offsetX, double offsetY)
{
return Translate(offsetX, offsetY) * matrix;
}
/// <summary>
/// Creates a matrix that scales along the x-axis and y-axis.
/// </summary>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <returns>The created scaling matrix.</returns>
public static Matrix Scale(double scaleX, double scaleY)
{
return new Matrix(scaleX, 0, 0, scaleY, 0.0, 0.0);
}
/// <summary>
/// Creates a matrix that is scaling from a specified center.
/// </summary>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <param name="centerX">The center X-coordinate of the scaling.</param>
/// <param name="centerY">The center Y-coordinate of the scaling.</param>
/// <returns>The created scaling matrix.</returns>
public static Matrix ScaleAt(double scaleX, double scaleY, double centerX, double centerY)
{
return new Matrix(scaleX, 0, 0, scaleY, centerX - (scaleX * centerX), centerY - (scaleY * centerY));
}
/// <summary>
/// Prepends a scale around the center of provided matrix.
/// </summary>
/// <param name="matrix">The matrix to prepend scale.</param>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <param name="centerX">The center X-coordinate of the scaling.</param>
/// <param name="centerY">The center Y-coordinate of the scaling.</param>
/// <returns>The created scaling matrix.</returns>
public static Matrix ScaleAtPrepend(Matrix matrix, double scaleX, double scaleY, double centerX, double centerY)
{
return ScaleAt(scaleX, scaleY, centerX, centerY) * matrix;
}
/// <summary>
/// Creates a translation and scale matrix using the specified offsets and scales along the x-axis and y-axis.
/// </summary>
/// <param name="scaleX">Scaling factor that is applied along the x-axis.</param>
/// <param name="scaleY">Scaling factor that is applied along the y-axis.</param>
/// <param name="offsetX">X-coordinate offset.</param>
/// <param name="offsetY">Y-coordinate offset.</param>
/// <returns>The created translation and scale matrix.</returns>
public static Matrix ScaleAndTranslate(double scaleX, double scaleY, double offsetX, double offsetY)
{
return new Matrix(scaleX, 0.0, 0.0, scaleY, offsetX, offsetY);
}
/// <summary>
/// Creates a skew matrix.
/// </summary>
/// <param name="angleX">Angle of skew along the X-axis in radians.</param>
/// <param name="angleY">Angle of skew along the Y-axis in radians.</param>
/// <returns>When the method completes, contains the created skew matrix.</returns>
public static Matrix Skew(float angleX, float angleY)
{
return new Matrix(1.0, Tan(angleX), Tan(angleY), 1.0, 0.0, 0.0);
}
/// <summary>
/// Creates a matrix that rotates.
/// </summary>
/// <param name="radians">Angle of rotation in radians. Angles are measured clockwise when looking along the rotation axis.</param>
/// <returns>The created rotation matrix.</returns>
public static Matrix Rotation(double radians)
{
double cos = Cos(radians);
double sin = Sin(radians);
return new Matrix(cos, sin, -sin, cos, 0, 0);
}
/// <summary>
/// Creates a matrix that rotates about a specified center.
/// </summary>
/// <param name="angle">Angle of rotation in radians.</param>
/// <param name="centerX">The center X-coordinate of the rotation.</param>
/// <param name="centerY">The center Y-coordinate of the rotation.</param>
/// <returns>The created rotation matrix.</returns>
public static Matrix Rotation(double angle, double centerX, double centerY)
{
return Translate(-centerX, -centerY) * Rotation(angle) * Translate(centerX, centerY);
}
/// <summary>
/// Creates a matrix that rotates about a specified center.
/// </summary>
/// <param name="angle">Angle of rotation in radians.</param>
/// <param name="center">The center of the rotation.</param>
/// <returns>The created rotation matrix.</returns>
public static Matrix Rotation(double angle, Vector center)
{
return Translate(-center.X, -center.Y) * Rotation(angle) * Translate(center.X, center.Y);
}
/// <summary>
/// Transforms a point by this matrix.
/// </summary>
/// <param name="matrix">The matrix to use as a transformation matrix.</param>
/// <param name="point">>The original point to apply the transformation.</param>
/// <returns>The result of the transformation for the input point.</returns>
public static Point TransformPoint(Matrix matrix, Point point)
{
return new Point(
(point.X * matrix.M11) + (point.Y * matrix.M21) + matrix.M31,
(point.X * matrix.M12) + (point.Y * matrix.M22) + matrix.M32);
}
}

View File

@@ -0,0 +1,22 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Serein.Workbench.Avalonia.Custom.Views.NodeContainerView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
x:DataType="vm:NodeContainerViewModel"
Background="#F8FBF1"
DragDrop.AllowDrop="True">
<Design.DataContext>
<vm:NodeContainerViewModel />
</Design.DataContext>
<DockPanel>
<!--放置节点-->
<Canvas x:Name="PART_NodeContainer">
</Canvas>
</DockPanel>
</UserControl>

View File

@@ -0,0 +1,427 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.PanAndZoom;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.VisualTree;
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Utils;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Avalonia.Custom.ViewModels;
using Serein.Workbench.Avalonia.Extension;
using Serein.Workbench.Avalonia.Services;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using Point = Avalonia.Point;
namespace Serein.Workbench.Avalonia.Custom.Views;
public partial class NodeContainerView : UserControl
{
private readonly NodeContainerViewModel _vm;
private readonly INodeOperationService nodeOperationService;
private readonly IKeyEventService keyEventService;
#region
/// <summary>
/// 是否正在预览节点控件
/// </summary>
private bool IsPreviewNodeControl;
/// <summary>
/// 标记是否正在尝试选取控件
/// </summary>
private bool IsSelectControl;
/// <summary>
/// 标记是否正在拖动控件
/// </summary>
private bool IsControlDragging;
/// <summary>
/// 标记是否正在拖动画布
/// </summary>
private bool IsCanvasDragging;
/// <summary>
/// 标记是否正在选取节点
/// </summary>
private bool IsSelectDragging;
/// <summary>
/// 当前选取的控件
/// </summary>
private readonly List<Control> selectNodeControls = [];
/// <summary>
/// 记录开始拖动节点控件时的鼠标位置
/// </summary>
private Point startControlDragPoint;
/// <summary>
/// 记录移动画布开始时的鼠标位置
/// </summary>
private Point startCanvasDragPoint;
/// <summary>
/// 记录开始选取节点控件时的鼠标位置
/// </summary>
private Point startSelectControolPoint;
/// <summary>
/// 组合变换容器
/// </summary>
private readonly TransformGroup canvasTransformGroup;
/// <summary>
/// 缩放画布
/// </summary>
private readonly ScaleTransform scaleTransform;
/// <summary>
/// 平移画布
/// </summary>
private readonly TranslateTransform translateTransform;
#endregion
public NodeContainerView()
{
InitializeComponent();
_vm= App.GetService<NodeContainerViewModel>();
DataContext = _vm;
#region UI相关的服务
keyEventService = App.GetService<IKeyEventService>();
nodeOperationService = App.GetService<INodeOperationService>();
nodeOperationService.MainCanvas = PART_NodeContainer;
nodeOperationService.OnNodeViewCreate += NodeOperationService_OnNodeViewCreate; // 处理事件
keyEventService.KeyUp += (k) =>
{
if (k == Key.Escape)
{
IsCanvasDragging = false;
IsControlDragging = false;
}
};
#endregion
#region UI事件
AddHandler(DragDrop.DropEvent, Drop); // 创建节点相关
PointerPressed += NodeContainerView_PointerPressed;
PointerReleased += NodeContainerView_PointerReleased;
PointerMoved += NodeContainerView_PointerMoved;
PointerWheelChanged += NodeContainerView_PointerWheelChanged;
#endregion
#region
canvasTransformGroup = new TransformGroup();
scaleTransform = new ScaleTransform();
translateTransform = new TranslateTransform();
canvasTransformGroup.Children.Add(scaleTransform);
canvasTransformGroup.Children.Add(translateTransform);
PART_NodeContainer.RenderTransform = canvasTransformGroup;
#endregion
}
#region
public Point GetPositionOfCanvas(PointerEventArgs e)
{
return e.GetPosition(PART_NodeContainer);
}
public Point GetPositionOfCanvas(DragEventArgs e)
{
return e.GetPosition(PART_NodeContainer);
}
#endregion
#region
#region
private void Drop(object? sender, DragEventArgs e)
{
if (e.Data.Contains(DataFormats.Text))
{
var json = e.Data.GetText();
if (string.IsNullOrEmpty(json))
{
return;
}
var mdInfo = json.ToJsonObject<MethodDetailsInfo>();
if (mdInfo is not null)
{
var canvasDropPosition = GetPositionOfCanvas(e); // 更新画布落点
PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
nodeOperationService.CreateNodeView(mdInfo, position); // 提交创建节点的请求
}
}
else // if (e.Data.Contains(DataFormats.FileNames))
{
var files = e.Data.GetFiles();
var str = files?.Select(f => f.Path);
if (str is not null)
{
}
}
}
#endregion
#region
private void NodeContainerView_PointerPressed(object? sender, PointerPressedEventArgs e)
{
if (IsPreviewNodeControl)
{
IsCanvasDragging = false;
e.Handled = true;
return;
}
if (!IsCanvasDragging)
{
IsCanvasDragging = true;
startCanvasDragPoint = e.GetPosition(this);
e.Handled = true;
}
}
private void NodeContainerView_PointerReleased(object? sender, PointerReleasedEventArgs e)
{
IsCanvasDragging = false; // 不再拖动
}
private void NodeContainerView_PointerMoved(object? sender, PointerEventArgs e)
{
var myData = nodeOperationService.ConnectingData;
if (myData.IsCreateing)
{
var isPass = e.JudgePointer(sender, PointerType.Mouse, p => p.IsLeftButtonPressed);
//Debug.WriteLine("canvas ispass = " + isPass);
if (isPass)
{
if (myData.Type == JunctionOfConnectionType.Invoke)
{
_vm.IsConnectionInvokeNode = true; // 正在连接节点的调用关系
}
else
{
_vm.IsConnectionArgSourceNode = true; // 正在连接节点的调用关系
}
var currentPoint = e.GetPosition(PART_NodeContainer);
myData.UpdatePoint(new Point(currentPoint.X - 5, currentPoint.Y - 5));
return;
}
}
if (IsCanvasDragging)
{
// 拖动画布
Point currentMousePosition = e.GetPosition(this);
double deltaX = currentMousePosition.X - startCanvasDragPoint.X;
double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y;
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
startCanvasDragPoint = currentMousePosition;
}
}
// 缩放
private void NodeContainerView_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
{
var delta = e.Delta.Y;
if (delta < 0 && scaleTransform.ScaleX < 0.02) return;
if (delta > 0 && scaleTransform.ScaleY > 4.0) return;
// 缩放因子,根据滚轮方向调整
double zoomFactor = delta > 0 ? 1.23 : 0.78;
// 当前缩放比例
double oldScale = scaleTransform.ScaleX;
double newScale = oldScale * zoomFactor;
// 记录缩放前的鼠标位置
var mousePosition = GetPositionOfCanvas(e);
// 更新缩放比例
scaleTransform.ScaleX = newScale;
scaleTransform.ScaleY = newScale;
// 记录缩放后的鼠标位置
var newMousePosition = GetPositionOfCanvas(e);
// 更新 TranslateTransform确保以鼠标位置为中心进行缩放
var s_position = newMousePosition - mousePosition; // 计算偏移量
translateTransform.X += s_position.X * newScale; // 根据缩放比例进行偏移
translateTransform.Y += s_position.Y * newScale; // 根据缩放比例进行偏移
}
#endregion
#endregion
#region
/// <summary>
/// 拖拽创建控件
/// </summary>
/// <param name="eventArgs"></param>
/// <returns></returns>
private bool NodeOperationService_OnNodeViewCreate(NodeViewCreateEventArgs eventArgs)
{
if (eventArgs.NodeControl is not Control control)
{
return false;
}
var position = eventArgs.Position;// 坐标
SetNodeEvent(control); // 设置该控件与画布交互的相关事件
DragControl(control, position.X, position.Y);
PART_NodeContainer.Children.Add(control);
return true;
}
/// <summary>
/// 设置节点与画布容器相关的操作事件
/// </summary>
/// <param name="nodeControl"></param>
private void SetNodeEvent(Control nodeControl)
{
nodeControl.PointerMoved += NodeControl_PointerMoved; ;
nodeControl.PointerExited += NodeControl_PointerExited;
nodeControl.PointerPressed += Block_MouseLeftButtonDown;
nodeControl.PointerMoved += Block_MouseMove;
nodeControl.PointerReleased += (s, e) => IsControlDragging = false;
}
#endregion
#region
/// <summary>
/// 移动控件
/// </summary>
/// <param name="nodeControl"></param>
/// <param name="x"></param>
/// <param name="y"></param>
private void DragControl(Control nodeControl, double x, double y)
{
Canvas.SetLeft(nodeControl, x);
Canvas.SetTop(nodeControl, y);
}
/// <summary>
/// 控件的鼠标左键按下事件,启动拖动操作。
/// </summary>
private void Block_MouseLeftButtonDown(object? sender, PointerPressedEventArgs e)
{
var isPass = e.JudgePointer(sender, PointerType.Mouse, p => p.IsRightButtonPressed);
if (!isPass)
{
return;
}
if (sender is Control nodeControl)
{
IsControlDragging = true;
startControlDragPoint = GetPositionOfCanvas(e); // 记录鼠标按下时的位置
e.Handled = true; // 防止事件传播影响其他控件
}
}
/// <summary>
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
/// </summary>
private void Block_MouseMove(object? sender, PointerEventArgs e)
{
if (sender is not Control nodeControl)
{
return;
}
if (IsCanvasDragging)
return;
if (IsSelectControl)
return;
if (IsControlDragging) // 如果正在拖动控件
{
Point currentPosition = GetPositionOfCanvas(e); // 获取当前鼠标位置
// 单个移动
if (selectNodeControls.Count == 0 || !selectNodeControls.Contains(nodeControl))
{
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
double newLeft = Canvas.GetLeft(nodeControl) + deltaX; // 新的左边距
double newTop = Canvas.GetTop(nodeControl) + deltaY; // 新的上边距
DragControl(nodeControl, newLeft, newTop);
}
// 批量移动
else
{
// 进行批量移动
// 获取旧位置
var oldLeft = Canvas.GetLeft(nodeControl);
var oldTop = Canvas.GetTop(nodeControl);
// 计算被选择控件的偏移量
var deltaX = /*(int)*/(currentPosition.X - startControlDragPoint.X);
var deltaY = /*(int)*/(currentPosition.Y - startControlDragPoint.Y);
// 移动被选择的控件
var newLeft = oldLeft + deltaX;
var newTop = oldTop + deltaY;
//this.EnvDecorator.MoveNode(nodeControlMain.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点
DragControl(nodeControl, newLeft, newTop);
// 计算控件实际移动的距离
var actualDeltaX = newLeft - oldLeft;
var actualDeltaY = newTop - oldTop;
// 移动其它选中的控件
foreach (var selectItemNode in selectNodeControls)
{
if (selectItemNode != nodeControl) // 跳过已经移动的控件
{
var otherNewLeft = Canvas.GetLeft(selectItemNode) + actualDeltaX;
var otherNewTop = Canvas.GetTop(selectItemNode) + actualDeltaY;
DragControl(selectItemNode, otherNewLeft, otherNewTop);
//this.EnvDecorator.MoveNode(nodeControl.ViewModel.NodeModel.Guid, otherNewLeft, otherNewTop); // 移动节点
}
}
// 更新节点之间线的连接位置
//foreach (var nodeControl in selectNodeControls)
//{
// //nodeControl.UpdateLocationConnections();
//}
}
startControlDragPoint = currentPosition; // 更新起始点位置
}
}
private void NodeControl_PointerExited(object? sender, PointerEventArgs e)
{
IsPreviewNodeControl = false;
}
private void NodeControl_PointerMoved(object? sender, PointerEventArgs e)
{
IsPreviewNodeControl = true;
}
#endregion
}

View File

@@ -0,0 +1,27 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Serein.Workbench.Avalonia.Custom.Views">
<!--
Additional resources
Using Control Themes:
https://docs.avaloniaui.net/docs/basics/user-interface/styling/control-themes
Using Theme Variants:
https://docs.avaloniaui.net/docs/guides/styles-and-resources/how-to-use-theme-variants
-->
<Design.PreviewWith>
<StackPanel Width="400" Spacing="10">
<StackPanel Background="{DynamicResource SystemRegionBrush}">
<controls:NodeJunctionView />
</StackPanel>
</StackPanel>
</Design.PreviewWith>
<ControlTheme x:Key="{x:Type controls:NodeJunctionView}" TargetType="controls:NodeJunctionView">
<Setter Property="Template">
<ControlTemplate>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -0,0 +1,215 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
using Serein.Library;
using Serein.Workbench.Avalonia.Views;
using System.Drawing;
using System;
using Color = Avalonia.Media.Color;
using Point = Avalonia.Point;
using System.Diagnostics;
using Avalonia.Threading;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Avalonia.Extension;
namespace Serein.Workbench.Avalonia.Custom.Views;
/// <summary>
/// 连接控制点
/// </summary>
public class NodeJunctionView : TemplatedControl
{
private readonly INodeOperationService nodeOperationService;
/// <summary>
/// Render方法中控制自绘内容
/// </summary>
protected readonly StreamGeometry StreamGeometry = new StreamGeometry();
/// <summary>
/// 正在查看
/// </summary>
private bool IsPreviewing;
public NodeJunctionView()
{
nodeOperationService = App.GetService<INodeOperationService>();
this.PointerMoved += NodeJunctionView_PointerMoved;
this.PointerExited += NodeJunctionView_PointerExited;
this.PointerPressed += NodeJunctionView_PointerPressed;
this.PointerReleased += NodeJunctionView_PointerReleased;
}
private void NodeJunctionView_PointerReleased(object? sender, PointerReleasedEventArgs e)
{
nodeOperationService.ConnectingData.IsCreateing = false;
}
private void NodeJunctionView_PointerPressed(object? sender, PointerPressedEventArgs e)
{
nodeOperationService.TryCreateConnectionOnJunction(this); // 尝试开始创建
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
}
/// <summary>
/// 获取到控件信息
/// </summary>
/// <param name="e"></param>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
//if (e.NameScope.Find("PART_FlipflopMethodInfos") is ListBox p_fm)
//{
// //p_fm.SelectionChanged += ListBox_SelectionChanged;
// //p_fm.PointerExited += ListBox_PointerExited;
//}
}
public static readonly DirectProperty<NodeJunctionView, JunctionType> JunctionTypeProperty =
AvaloniaProperty.RegisterDirect<NodeJunctionView, JunctionType>(nameof(JunctionType), o => o.JunctionType, (o, v) => o.JunctionType = v);
private JunctionType junctionType;
public JunctionType JunctionType
{
get { return junctionType; }
set { SetAndRaise(JunctionTypeProperty, ref junctionType, value); }
}
public static readonly DirectProperty<NodeJunctionView, NodeModelBase?> MyNodeProperty =
AvaloniaProperty.RegisterDirect<NodeJunctionView, NodeModelBase?>(nameof(MyNode), o => o.MyNode, (o, v) => o.MyNode = v);
private NodeModelBase? myNode;
public NodeModelBase? MyNode
{
get { return myNode; }
set { SetAndRaise(MyNodeProperty, ref myNode, value); }
}
#region UI视觉
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
public override void Render(DrawingContext drawingContext)
{
double width = 44;
double height = 26;
var background = GetBackgrounp();
var pen = new Pen(Brushes.Black, 1);
// 输入连接器的背景
var connectorRect = new Rect(0, 0, width, height);
drawingContext.DrawRectangle(Brushes.Transparent, new Pen(), connectorRect);
double circleCenterX = width / 2 ; // 中心 X 坐标
double circleCenterY = height / 2 ; // 中心 Y 坐标
//_myCenterPoint = new Point(circleCenterX - Width / 2, circleCenterY); // 中心坐标
// 定义圆形的大小和位置
var diameterCircle = width - 20;
Rect rect = new(4, 2, diameterCircle / 2, diameterCircle / 2);
var ellipse = new EllipseGeometry(rect);
drawingContext.DrawGeometry(background, pen, ellipse);
// 定义三角形的间距
double triangleCenterX = width / 2 - 2; // 三角形中心 X 坐标
double triangleCenterY = height / 2 -5; // 三角形中心 Y 坐标
// 绘制三角形
var pathGeometry = new StreamGeometry();
using (var context = pathGeometry.Open())
{
int t = 6;
context.BeginFigure(new Point(triangleCenterX, triangleCenterY - t), true);
context.LineTo(new Point(triangleCenterX + 8, triangleCenterY), true);
context.LineTo(new Point(triangleCenterX, triangleCenterY + t), true);
context.LineTo(new Point(triangleCenterX, triangleCenterY - t), true);
}
drawingContext.DrawGeometry(background, new Pen(Brushes.Black, 1), pathGeometry);
}
#region
private void NodeJunctionView_PointerExited(object? sender, PointerEventArgs e)
{
if (IsPreviewing)
{
IsPreviewing = false;
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
}
}
private void NodeJunctionView_PointerMoved(object? sender, PointerEventArgs e)
{
if (!IsPreviewing)
{
IsPreviewing = true;
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
}
}
#endregion
/// <summary>
/// 获取背景颜色
/// </summary>
/// <returns></returns>
protected IBrush GetBackgrounp()
{
var myData = nodeOperationService.ConnectingData;
if (!myData.IsCreateing)
{
//Debug.WriteLine($"return color is {Brushes.BurlyWood}");
return new SolidColorBrush(Color.Parse("#76ABEE"));
}
if (myData.IsCanConnected)
{
if (myData.Type == JunctionOfConnectionType.Invoke)
{
return myData.ConnectionInvokeType.ToLineColor();
}
else
{
return myData.ConnectionArgSourceType.ToLineColor();
}
}
else
{
return Brushes.Red;
}
if (IsPreviewing)
{
//return new SolidColorBrush(Color.Parse("#04FC10"));
}
else
{
//Debug.WriteLine($"return color is {Brushes.BurlyWood}");
return new SolidColorBrush(Color.Parse("#76ABEE"));
}
}
#endregion
}

View File

@@ -0,0 +1,63 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="390" d:DesignHeight="40"
x:Class="Serein.Workbench.Avalonia.Custom.Views.ParameterDetailsInfoView"
xmlns:vm="clr-namespace:Serein.Workbench.Avalonia.Custom.ViewModels"
xmlns:cv="clr-namespace:Serein.Workbench.Avalonia.Custom.Views"
xmlns:baselibrary="clr-namespace:Serein.Library;assembly=Serein.Library"
xmlns:converter="using:Serein.Workbench.Avalonia.Converters"
x:DataType="vm:ParameterDetailsViewModel"
VerticalAlignment="Center">
<Design.DataContext>
<vm:ParameterDetailsViewModel/>
</Design.DataContext>
<StackPanel >
<StackPanel.Resources>
<converter:IsVisibleOfParameterConverter x:Key="visibleConverter"/>
</StackPanel.Resources>
<Grid ColumnDefinitions="20,40,90,auto" Margin="6,0,10,0">
<!--<ToolTip.Tip>
<StackPanel>
</StackPanel>
</ToolTip.Tip>-->
<!--<ToolTip Background="LightYellow" Foreground="#071042" Content="" />-->
<cv:NodeJunctionView Grid.Column="0" JunctionType="ArgData" MyNode="{Binding ParameterDetails.NodeModel}" Width="30" Height="15" Margin="2" HorizontalAlignment="Center" VerticalAlignment="Center" />
<CheckBox Grid.Column="1" IsChecked="{Binding ParameterDetails.IsExplicitData, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" >
</CheckBox>
<!--<TextBlock Grid.Column="2" Text="{Binding ParameterDetails.Index, StringFormat='arg{0} '}" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" />-->
<TextBlock Grid.Column="2" Text="{Binding ParameterDetails.Name}" FontSize="14"
HorizontalAlignment="Left" VerticalAlignment="Center"
ToolTip.Placement="Bottom" ToolTip.VerticalOffset="6">
<ToolTip.Tip>
<StackPanel>
<TextBlock Text="{Binding ParameterDetails}" FontSize="14" TextTrimming="None" TextWrapping="WrapWithOverflow"/>
</StackPanel>
</ToolTip.Tip>
</TextBlock>
<TextBlock Grid.Column="3" IsVisible="{Binding IsVisibleA}" FontSize="14" Text=" [ 自动取参 ]" MinWidth="100" MaxWidth="300" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBox Grid.Column="3" IsVisible="{Binding IsVisibleB}" FontSize="14" Text="{Binding ParameterDetails.DataValue, Mode=TwoWay}" MinWidth="100" MaxWidth="300" HorizontalAlignment="Left" VerticalAlignment="Center" />
<ComboBox Grid.Column="3" IsVisible="{Binding IsVisibleC}"
ItemsSource="{Binding ParameterDetails.Items}"
SelectedValue="{Binding ParameterDetails.DataValue,Mode=OneTime}"
MinWidth="100" MaxWidth="300">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" FontFamily="{Binding}" FontSize="14"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,31 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Serein.Library;
using Serein.Workbench.Avalonia.Custom.ViewModels;
namespace Serein.Workbench.Avalonia.Custom.Views;
internal partial class ParameterDetailsInfoView : UserControl
{
private readonly ParameterDetailsViewModel _vm;
public ParameterDetailsInfoView()
{
InitializeComponent();
var pd = new ParameterDetails();
pd.Name = "param name";
pd.IsParams = true;
pd.DataValue = "data value";
pd.Items = ["A","B","C"];
_vm = new (pd);
DataContext = _vm;
}
public ParameterDetailsInfoView(ParameterDetailsViewModel parameterDetailsViewModel)
{
InitializeComponent();
_vm = parameterDetailsViewModel;
DataContext = _vm;
}
}