2020 lines
61 KiB
C#
2020 lines
61 KiB
C#
using CanFly.Canvas.Helper;
|
||
using CanFly.Canvas.Model;
|
||
using CanFly.Canvas.Shape;
|
||
using LabelSharp.Config;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel;
|
||
using System.Diagnostics;
|
||
using System.Drawing.Drawing2D;
|
||
using System.Linq;
|
||
using System.Reflection;
|
||
using System.Text;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using MethodInvoker = System.Windows.Forms.MethodInvoker;
|
||
|
||
|
||
|
||
namespace CanFly.Canvas.UI
|
||
{
|
||
public partial class FlyCanvas : UserControl
|
||
{
|
||
public event Action OnMenuItemCopyToHere;
|
||
public event Action OnMenuItemMoveToHere;
|
||
|
||
|
||
public event Action<int, Point> zoomRequest;
|
||
public event Action<float> ZoomRequestF;
|
||
public event Action<int, int> scrollRequest;
|
||
public event Action newShape;
|
||
public event Action<List<FlyShape>> selectionChanged;
|
||
public event Action ShapeMoved;
|
||
public event Action<List<FlyShape>> OnShapeMoving;
|
||
public event Action<bool> DrawingPolygon;
|
||
public event Action<bool> vertexSelected;
|
||
public event Action<PointF> mouseMoved;
|
||
|
||
public event Action<FlyShape> OnShapeUpdateEvent;
|
||
|
||
|
||
public ShapeTypeEnum _createMode = ShapeTypeEnum.Polygon;
|
||
public bool _fill_drawing = false;
|
||
|
||
|
||
private float epsilon = 8.0f;
|
||
private DoubleClickActionEnum double_click = DoubleClickActionEnum.Close;
|
||
private int num_backups = 10;
|
||
private bool _isEditMode = true;
|
||
|
||
public List<FlyShape> Shapes { get; set; } = new List<FlyShape>();
|
||
|
||
/// <summary>
|
||
/// 外部绘制的
|
||
/// </summary>
|
||
public List<FlyShape> OutsideShapes { get; set; } = new List<FlyShape>();
|
||
|
||
public List<List<FlyShape>> shapesBackups = new();
|
||
private FlyShape? current = null;
|
||
public List<FlyShape> selectedShapes = new List<FlyShape>();
|
||
private List<FlyShape> selectedShapesCopy = new List<FlyShape>();
|
||
|
||
private FlyShape line = new FlyShape();
|
||
private PointF prevPoint;
|
||
private PointF prevMovePoint;
|
||
private PointF[] offsets = new PointF[2] { new PointF(), new PointF() };
|
||
|
||
private float MOVE_SPEED = 5.0f;
|
||
|
||
|
||
/// <summary>
|
||
/// 是否允许多选
|
||
/// </summary>
|
||
public bool AllowMultiSelect { get; set; } = false;
|
||
|
||
public int _width { get; private set; }
|
||
public int _height { get; private set; }
|
||
|
||
|
||
//public List<ContextMenuStrip> MenuStripsWithoutSelection = new List<ContextMenuStrip>();
|
||
//public List<ContextMenuStrip> MenuStripsWithSelection = new List<ContextMenuStrip>();
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 缩放比例,该参数必须大于0
|
||
/// </summary>
|
||
private float _scale = 1.0f;
|
||
|
||
public new float Scale
|
||
{
|
||
get { return _scale; }
|
||
set
|
||
{
|
||
|
||
if (value <= 0.0001)
|
||
{
|
||
return;
|
||
}
|
||
_scale = value;
|
||
ZoomRequestF?.Invoke(_scale);
|
||
}
|
||
}
|
||
|
||
|
||
public Bitmap? pixmap;// = new Bitmap();
|
||
|
||
private Dictionary<Shape.FlyShape, bool> visible = new Dictionary<Shape.FlyShape, bool>();
|
||
private bool _hideBackround = false;
|
||
private bool hideBackround = false;
|
||
|
||
#region 高亮显示的图形
|
||
|
||
private Shape.FlyShape? hShape;
|
||
private Shape.FlyShape? prevhShape;
|
||
private int hVertex = -1;
|
||
private int prevhVertex = -1;
|
||
private int hEdge;
|
||
private int prevhEdge;
|
||
|
||
#endregion
|
||
|
||
private bool movingShape = false;
|
||
private bool snapping = true;
|
||
private bool hShapeIsSelected = false;
|
||
private Graphics _painter;
|
||
|
||
private Cursor _cursor = CustomCursors.CURSOR_DEFAULT;
|
||
private Cursor _preCursor = CustomCursors.CURSOR_DEFAULT;
|
||
|
||
|
||
/// <summary>
|
||
/// 点击的区域
|
||
/// </summary>
|
||
internal enum ClickArea
|
||
{
|
||
|
||
/// <summary>
|
||
/// 未知区域
|
||
/// </summary>
|
||
AREA_UNKNOW,
|
||
|
||
|
||
/// <summary>
|
||
/// 图片区域
|
||
/// </summary>
|
||
AREA_IMG,
|
||
|
||
|
||
/// <summary>
|
||
/// 缺陷元素区域
|
||
/// </summary>
|
||
AREA_DEFECT,
|
||
}
|
||
private ClickArea _clickArea = ClickArea.AREA_UNKNOW;
|
||
|
||
|
||
/// <summary>
|
||
/// 矩形框
|
||
/// </summary>
|
||
private Rectangle _rcImg = new Rectangle(0, 0, 0, 0);
|
||
|
||
|
||
/// <summary>
|
||
/// 变换矩阵
|
||
/// </summary>
|
||
private Matrix _matrix = new Matrix();
|
||
|
||
|
||
public Matrix Matrix => _matrix.Clone();
|
||
|
||
|
||
|
||
//private Bitmap image;
|
||
//private List<Rectangle> rectangles = new List<Rectangle>();
|
||
//private Rectangle currentRectangle;
|
||
//private bool drawing = false;
|
||
|
||
|
||
public FlyCanvas()
|
||
{
|
||
|
||
InitializeComponent();
|
||
|
||
// this.KeyDown += FlyCanvas_KeyDown;
|
||
// this.KeyPress += FlyCanvas_KeyPress;
|
||
|
||
this.DoubleBuffered = true;
|
||
SetStyle(ControlStyles.AllPaintingInWmPaint//控件忽略窗口消息 WM_ERASEBKGND 以减少闪烁
|
||
| ControlStyles.UserPaint//由控件而不是由操作系统来绘制控件自身,即自定义绘制
|
||
| ControlStyles.OptimizedDoubleBuffer//控件将首先绘制到缓冲区而不是直接绘制到屏幕,这可以减少闪烁
|
||
| ControlStyles.ResizeRedraw, true);//控件会在调整大小时进行重绘
|
||
|
||
|
||
|
||
//InitTask();
|
||
|
||
}
|
||
|
||
|
||
|
||
private void Init()
|
||
{
|
||
_clickArea = ClickArea.AREA_UNKNOW;//枚举
|
||
_rcImg = new Rectangle(0, 0, 0, 0);
|
||
|
||
_matrix.Reset();//重置为单位矩阵
|
||
// strImgPath = string.Empty;
|
||
pixmap = null;
|
||
|
||
}
|
||
|
||
|
||
|
||
public bool FillDrawing
|
||
{
|
||
get { return _fill_drawing; }
|
||
set { _fill_drawing = value; }
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 图形类型
|
||
/// </summary>
|
||
public ShapeTypeEnum CreateMode
|
||
{
|
||
get { return _createMode; }
|
||
set { _createMode = value; }
|
||
}
|
||
|
||
|
||
public void StoreShapes()
|
||
{
|
||
var shapesBackup = new List<Shape.FlyShape>();
|
||
foreach (var shape in Shapes)
|
||
{
|
||
shapesBackup.Add(shape.Copy());
|
||
}
|
||
// 限制备份数量
|
||
if (shapesBackups.Count >= num_backups)
|
||
{
|
||
shapesBackups.RemoveRange(0, shapesBackups.Count - num_backups + 1); // 移除多余的备份
|
||
}
|
||
|
||
shapesBackups.Add(shapesBackup); // 添加当前备份
|
||
}
|
||
|
||
|
||
|
||
public bool IsShapeRestorable
|
||
{
|
||
get
|
||
{
|
||
if (shapesBackups.Count < 2)
|
||
{
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
public void RestoreShape()
|
||
{
|
||
if (!this.IsShapeRestorable)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 确保备份不为空
|
||
if (shapesBackups.Count == 0)
|
||
{
|
||
return; // 如果没有备份,直接返回
|
||
}
|
||
|
||
shapesBackups.RemoveAt(shapesBackups.Count - 1);
|
||
// 弹出最新的备份
|
||
List<Shape.FlyShape> shapesBackup = shapesBackups[^1]; // 获取最后的备份而不移除它
|
||
shapesBackups.RemoveAt(shapesBackups.Count - 1); // 移除最新的备份
|
||
|
||
// 恢复形状
|
||
this.Shapes = shapesBackup;
|
||
this.selectedShapes = new List<Shape.FlyShape>(); // 清空选中的形状
|
||
|
||
foreach (Shape.FlyShape shape in Shapes)
|
||
{
|
||
shape.Selected = false; // 取消所有形状的选中状态
|
||
}
|
||
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
|
||
public bool IsVisible(FlyShape shape)
|
||
{
|
||
if (visible.Keys.Contains(shape))
|
||
{
|
||
return visible[shape];
|
||
}
|
||
return true;
|
||
}
|
||
|
||
|
||
public bool Drawing() => !_isEditMode;
|
||
|
||
public bool Editing() => _isEditMode;
|
||
|
||
|
||
|
||
private void UnHighlight()
|
||
{
|
||
if (hShape != null)
|
||
{
|
||
hShape.HighlightClear();
|
||
Invalidate();
|
||
}
|
||
prevhShape = hShape;
|
||
prevhVertex = hVertex;
|
||
prevhEdge = hEdge;
|
||
|
||
hShape = null;
|
||
hVertex = -1;
|
||
hEdge = -1;
|
||
}
|
||
|
||
|
||
public bool SelectedVertex() => hVertex >= 0;
|
||
public bool SelectedEdge() => hEdge >= 0;
|
||
|
||
|
||
|
||
|
||
|
||
private void DeSelectShape()
|
||
{
|
||
if (this.selectedShapes != null)
|
||
{
|
||
this.SetHiding(false);
|
||
|
||
{
|
||
this.selectedShapes.ForEach(shape => shape.Selected = false);
|
||
this.selectedShapes.Clear();
|
||
}
|
||
|
||
this.selectionChanged?.Invoke(new List<FlyShape>());
|
||
this.hShapeIsSelected = false;
|
||
Invalidate();
|
||
}
|
||
}
|
||
|
||
|
||
public List<FlyShape> DeleteSelected()
|
||
{
|
||
List<Shape.FlyShape> deleted_shapes = new List<Shape.FlyShape>();
|
||
if (this.selectedShapes != null)
|
||
{
|
||
foreach (var shape in this.selectedShapes)
|
||
{
|
||
this.Shapes.Remove(shape);
|
||
deleted_shapes.Add(shape);
|
||
}
|
||
this.StoreShapes();
|
||
this.selectedShapes = new List<Shape.FlyShape>();
|
||
Invalidate();
|
||
}
|
||
return deleted_shapes;
|
||
}
|
||
|
||
|
||
private void DeleteShape(Shape.FlyShape shape)
|
||
{
|
||
if (this.selectedShapes.Contains(shape))
|
||
{
|
||
this.selectedShapes.Remove(shape);
|
||
}
|
||
if (this.Shapes.Contains(shape))
|
||
{
|
||
this.Shapes.Remove(shape);
|
||
}
|
||
this.StoreShapes();
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
public void LoadImage(string path)
|
||
{
|
||
//image = new Bitmap(path);
|
||
//this.Invalidate();
|
||
}
|
||
|
||
|
||
|
||
|
||
private PointF _panBasePoint = PointF.Empty;
|
||
|
||
|
||
|
||
|
||
private void Finalise()
|
||
{
|
||
this.current.Close();
|
||
|
||
this.Shapes.Add(this.current);
|
||
this.StoreShapes();
|
||
this.current = null;
|
||
this.SetHiding(false);
|
||
newShape?.Invoke();
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
|
||
private void SetHiding(bool enable = true)
|
||
{
|
||
this._hideBackround = enable ? this.hideBackround : false;
|
||
}
|
||
|
||
|
||
private void FlyCanvas_MouseWheel(object? sender, MouseEventArgs e)
|
||
{
|
||
// base.OnMouseWheel(e);
|
||
//绑定滚轮键
|
||
if (_clickArea == ClickArea.AREA_UNKNOW || _clickArea == ClickArea.AREA_IMG)
|
||
{
|
||
var delta = e.Delta;
|
||
float scaleFactor = delta > 0 ? 1.1f : 0.9f;
|
||
Scale *= scaleFactor;
|
||
if (Scale >= 10)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var p = e.Location.ToImageCoordinate(_matrix);
|
||
var mat = new Matrix();
|
||
mat.Translate(p.X, p.Y);
|
||
|
||
mat.Scale(scaleFactor, scaleFactor);
|
||
|
||
mat.Translate(-p.X, -p.Y);
|
||
_matrix.Multiply(mat);
|
||
|
||
Refresh();
|
||
}
|
||
}
|
||
|
||
|
||
#region
|
||
|
||
|
||
private bool OutputOfPixmap(PointF p)
|
||
{
|
||
var w = this.pixmap.Width;
|
||
var h = this.pixmap.Height;
|
||
return !(p.X >= 0 && p.X <= (w - 1) && p.Y >= 0 && p.Y <= (h - 1));
|
||
}
|
||
|
||
|
||
|
||
|
||
public FlyShape SetLastLabel(string text, object flags)
|
||
{
|
||
int index = Shapes.Count - 1;
|
||
Shapes[index].label = text;
|
||
Shapes[index].flags = flags;
|
||
shapesBackups.RemoveAt(shapesBackups.Count - 1);
|
||
StoreShapes();
|
||
return Shapes[index];
|
||
}
|
||
|
||
|
||
|
||
public void UndoLastLine()
|
||
{
|
||
current = Shapes[^1];
|
||
Shapes.RemoveAt(Shapes.Count - 1);
|
||
current.SetOpen();
|
||
|
||
|
||
// TODO:
|
||
switch (CreateMode)
|
||
{
|
||
|
||
case ShapeTypeEnum.Polygon:
|
||
case ShapeTypeEnum.LineStrip:
|
||
line.Points = new List<PointF> { current[^1], current[0] };
|
||
break;
|
||
|
||
case ShapeTypeEnum.Rectangle:
|
||
case ShapeTypeEnum.Line:
|
||
case ShapeTypeEnum.Circle:
|
||
current.Points = new List<PointF> { current.Points[0] };
|
||
break;
|
||
|
||
case ShapeTypeEnum.Point:
|
||
current = null;
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
|
||
DrawingPolygon?.Invoke(true);
|
||
|
||
|
||
}
|
||
|
||
|
||
|
||
public void UndoLastPoint()
|
||
{
|
||
if (current == null || current.IsClosed())
|
||
{
|
||
return;
|
||
}
|
||
current.PopPoint();
|
||
if (current.Length > 0)
|
||
{
|
||
line[0] = current[-1];
|
||
}
|
||
else
|
||
{
|
||
current = null;
|
||
DrawingPolygon?.Invoke(false);
|
||
}
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
|
||
public void LoadPixmap(Bitmap pixmap, bool clear_shapes = true)
|
||
{
|
||
if (this.pixmap != null)
|
||
{
|
||
this.pixmap.Dispose();
|
||
}
|
||
Init();
|
||
this.pixmap = pixmap;
|
||
_rcImg.Width = this.pixmap.Width;
|
||
_rcImg.Height = this.pixmap.Width;
|
||
|
||
if (clear_shapes)
|
||
{
|
||
Shapes = new List<Shape.FlyShape>();
|
||
}
|
||
FitImage();
|
||
this.BackColor = Color.Gray;
|
||
Refresh();
|
||
|
||
this.Focus();
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 自适应图片,缩放到符合控件尺寸
|
||
/// </summary>
|
||
public void FitImage()
|
||
{
|
||
if (null == this.pixmap)
|
||
{
|
||
return;
|
||
}
|
||
|
||
_matrix = new Matrix();
|
||
|
||
try
|
||
{
|
||
// 先将图片缩放到适配控件尺寸
|
||
// 宽高比例中的较大值
|
||
float wRatio = 1f * pixmap.Width / Width;
|
||
float hRatio = 1f * pixmap.Height / Height;
|
||
float ratio = 1 / Math.Max(wRatio, hRatio);
|
||
|
||
|
||
_matrix.Scale(ratio, ratio);
|
||
_width = (int)(pixmap.Width * ratio);
|
||
_height = (int)(pixmap.Height * ratio);
|
||
|
||
// 再将图片平移到控件中心
|
||
// 将plMain的中心转换为图片坐标
|
||
PointF pControlCenter = new(Width / 2.0f, Height / 2.0f);
|
||
PointF pImgCoordinate = pControlCenter.ToImageCoordinate(_matrix);
|
||
|
||
//目标坐标减去当前坐标
|
||
_matrix.Translate(pImgCoordinate.X - pixmap.Width / 2.0f,
|
||
pImgCoordinate.Y - pixmap.Height / 2.0f);
|
||
|
||
}
|
||
catch (Exception)
|
||
{
|
||
throw;
|
||
}
|
||
|
||
//强制控件使其工作区无效并立即重绘自己和任何子控件
|
||
Invalidate();
|
||
}
|
||
|
||
public void LoadShapes(List<Shape.FlyShape> shapes, bool replace = true)
|
||
{
|
||
if (replace)
|
||
{
|
||
this.Shapes = new List<Shape.FlyShape>(shapes);
|
||
}
|
||
else
|
||
{
|
||
shapes.AddRange(shapes);
|
||
}
|
||
StoreShapes();
|
||
current = null;
|
||
hShape = null;
|
||
hVertex = -1;
|
||
hEdge = -1;
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
|
||
public void SetShapeVisible(Shape.FlyShape shape, bool value)
|
||
{
|
||
visible[shape] = value;
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
|
||
private void Canvas_SizeChanged(object sender, EventArgs e)
|
||
{
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
private void OverrideCursor(Cursor cursor)
|
||
{
|
||
RestoreCursor();
|
||
_preCursor = _cursor;
|
||
_cursor = cursor;
|
||
this.Cursor = cursor;
|
||
}
|
||
|
||
|
||
private void RestoreCursor()
|
||
{
|
||
this._cursor = _preCursor;
|
||
this.Cursor = this._cursor;
|
||
}
|
||
|
||
|
||
public void ResetState()
|
||
{
|
||
this.RestoreCursor();
|
||
this.pixmap = null;
|
||
this.shapesBackups = new();
|
||
this.Invalidate();
|
||
}
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 鼠标按下事件
|
||
/// </summary>
|
||
private void FlyCanvas_MouseDown(object? sender, MouseEventArgs e)
|
||
{
|
||
PointF pos = e.Location.ToImageCoordinate(_matrix);
|
||
|
||
bool is_shift_pressed = (ModifierKeys & Keys.Shift) == Keys.Shift;
|
||
|
||
if (MouseButtons.Left == e.Button)
|
||
{
|
||
if (Drawing())
|
||
{
|
||
if (current != null) // 画后续的点
|
||
{
|
||
switch (CreateMode)
|
||
{
|
||
case ShapeTypeEnum.Polygon:
|
||
{
|
||
if (!this.line[1].Equals(current[-1]))
|
||
{
|
||
this.current.AddPoint(this.line[1]);
|
||
this.line[0] = this.current[-1];
|
||
if (this.current.IsClosed())
|
||
{
|
||
this.Finalise();
|
||
}
|
||
}
|
||
}
|
||
break;
|
||
|
||
case ShapeTypeEnum.Rectangle: // 矩形
|
||
{
|
||
this.current.Points = this.line.Points;
|
||
OnShapeUpdateEvent?.Invoke(this.current);
|
||
this.Finalise();
|
||
|
||
break;
|
||
}
|
||
case ShapeTypeEnum.Circle:
|
||
case ShapeTypeEnum.Line:
|
||
{
|
||
this.current.Points = this.line.Points;
|
||
OnShapeUpdateEvent?.Invoke(this.current);
|
||
this.Finalise();
|
||
}
|
||
break;
|
||
|
||
case ShapeTypeEnum.LineStrip:
|
||
{
|
||
this.current.AddPoint(this.line[1]);
|
||
this.line[0] = this.current[-1];
|
||
if ((ModifierKeys & Keys.Control) == Keys.Control)
|
||
{
|
||
this.Finalise();
|
||
}
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
else if (!OutputOfPixmap(pos)) // 画第一个点
|
||
{
|
||
this.current = new FlyShape()
|
||
{
|
||
ShapeType = this.CreateMode,
|
||
};
|
||
|
||
this.current.AddPoint(pos, is_shift_pressed ? 0 : 1);
|
||
|
||
if (CreateMode == ShapeTypeEnum.Point) // 画点
|
||
{
|
||
this.Finalise();
|
||
}
|
||
else // 画其他图形
|
||
{
|
||
if (this.CreateMode == ShapeTypeEnum.Circle)
|
||
{
|
||
this.current.ShapeType = ShapeTypeEnum.Circle;
|
||
}
|
||
this.line.Points = new List<PointF>() { pos, pos };
|
||
this.line.point_labels = new List<int> { 1, 1 };
|
||
this.SetHiding();
|
||
this.DrawingPolygon?.Invoke(true);
|
||
|
||
}
|
||
}
|
||
}
|
||
else if (Editing())
|
||
{
|
||
if (this.SelectedEdge() && ((ModifierKeys & Keys.Alt) == Keys.Alt))
|
||
{
|
||
this.AddPointToEdge();
|
||
}
|
||
else if (this.SelectedVertex()
|
||
&& ((ModifierKeys & Keys.Alt) == Keys.Alt)
|
||
&& ((ModifierKeys & Keys.Shift) == Keys.Shift))
|
||
{
|
||
this.RemoveSelectedPoint();
|
||
}
|
||
|
||
|
||
bool group_mode = false;
|
||
if (AllowMultiSelect)
|
||
{
|
||
group_mode = (ModifierKeys & Keys.Control) == Keys.Control;
|
||
}
|
||
this.SelectShapePoint(pos, group_mode);
|
||
this.prevPoint = pos;
|
||
Invalidate();
|
||
} // else if (Editing())
|
||
} // if (MouseButtons.Left == e.Button)
|
||
else if (MouseButtons.Right == e.Button && this.Editing())
|
||
{
|
||
bool group_mode = false;
|
||
if (AllowMultiSelect)
|
||
{
|
||
group_mode = (ModifierKeys & Keys.Control) == Keys.Control;
|
||
}
|
||
|
||
if (this.selectedShapes == null
|
||
|| (this.hShape != null && !this.selectedShapes.Contains(this.hShape)))
|
||
{
|
||
this.SelectShapePoint(pos, group_mode);
|
||
Invalidate();
|
||
}
|
||
this.prevPoint = pos;
|
||
|
||
|
||
} // else if (MouseButtons.Right == e.Button && this.Editing())
|
||
else if (MouseButtons.Middle == e.Button)
|
||
{
|
||
|
||
if (_rcImg.Contains(pos.ToPoint()))
|
||
{
|
||
_clickArea = ClickArea.AREA_IMG;
|
||
_panBasePoint = pos;
|
||
}
|
||
|
||
} // else if (MouseButtons.Middle == e.Button)
|
||
|
||
|
||
Refresh();
|
||
}
|
||
|
||
|
||
private void FlyCanvas_MouseUp(object? sender, MouseEventArgs e)
|
||
{
|
||
if (MouseButtons.Left == e.Button || MouseButtons.Right == e.Button)
|
||
{
|
||
_panBasePoint = Point.Empty;
|
||
_clickArea = ClickArea.AREA_UNKNOW;
|
||
}
|
||
|
||
if (e.Button == MouseButtons.Right)
|
||
{
|
||
// menuWithSelection.Show(this, e.Location);
|
||
}
|
||
else if (e.Button == MouseButtons.Left)
|
||
{
|
||
if (this.Editing())
|
||
{
|
||
if (this.hShape != null && this.hShapeIsSelected && !this.movingShape)
|
||
{
|
||
var shps = this.selectedShapes.Where(shp => !shp.Equals(this.hShape)).ToList();
|
||
this.selectedShapes.ForEach(shp => shp.Selected = false);
|
||
this.selectedShapes = shps;
|
||
this.selectedShapes.ForEach(shp => shp.Selected = true);
|
||
this.selectionChanged?.Invoke(this.selectedShapes);
|
||
}
|
||
}
|
||
AfterMouseRelease();
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 鼠标移动事件
|
||
/// </summary>
|
||
private void FlyCanvas_OnMouseMove(object? sender, MouseEventArgs e)
|
||
{
|
||
PointF pos = e.Location.ToImageCoordinate(_matrix);
|
||
|
||
mouseMoved?.Invoke(pos);
|
||
|
||
this.prevMovePoint = pos;
|
||
RestoreCursor();
|
||
|
||
bool is_shift_pressed = (ModifierKeys & Keys.Shift) == Keys.Shift;
|
||
|
||
|
||
if (Drawing()) // 绘图状态
|
||
{
|
||
this.line.ShapeType = this.CreateMode;
|
||
this.OverrideCursor(CustomCursors.CURSOR_DRAW);
|
||
|
||
if (this.current == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (this.OutputOfPixmap(pos))
|
||
{
|
||
// TODO: 处理超出边界的情况
|
||
}
|
||
else if (this.snapping
|
||
&& this.current.Length > 1
|
||
&& this.CreateMode == ShapeTypeEnum.Polygon
|
||
&& this.CloseEnough(pos, this.current[0]))
|
||
{
|
||
pos = this.current[0];
|
||
this.OverrideCursor(CustomCursors.CURSOR_POINT);
|
||
this.current.HighlightVertex(0, HighlightModeEnum.NEAR_VERTEX);
|
||
}
|
||
|
||
|
||
switch (CreateMode)
|
||
{
|
||
case ShapeTypeEnum.Polygon:
|
||
case ShapeTypeEnum.LineStrip:
|
||
{
|
||
this.line.Points = new() { this.current[-1], pos };
|
||
this.line.point_labels = new() { 1, 1 };
|
||
}
|
||
break;
|
||
// case ShapeTypeEnum.Rectangle: // 矩形
|
||
// {
|
||
//#if false // 改动5
|
||
// float minX = Math.Min(this.current[0].X, pos.X);
|
||
// float maxX = Math.Max(this.current[0].X, pos.X);
|
||
// float minY = Math.Min(this.current[0].Y, pos.Y);
|
||
// float maxY = Math.Max(this.current[0].Y, pos.Y);
|
||
|
||
// List<PointF> tmpPoints = new List<PointF>() {
|
||
// new PointF(minX, minY),
|
||
// new PointF(maxX, minY),
|
||
// new PointF(maxX, maxY),
|
||
// new PointF(minX, maxY),
|
||
// };
|
||
|
||
// this.line.Points = tmpPoints;
|
||
//#else
|
||
// this.line.Points = new() { this.current[0], pos };
|
||
//#endif
|
||
// this.line.point_labels = new() { 1, 1 };
|
||
// this.line.Close();
|
||
// }
|
||
// break;
|
||
|
||
case ShapeTypeEnum.Rectangle: // 矩形
|
||
{
|
||
#if false
|
||
float minX = Math.Min(this.current[0].X, pos.X);
|
||
float maxX = Math.Max(this.current[0].X, pos.X);
|
||
float minY = Math.Min(this.current[0].Y, pos.Y);
|
||
float maxY = Math.Max(this.current[0].Y, pos.Y);
|
||
|
||
List<PointF> tmpPoints = new List<PointF>() {
|
||
new PointF(minX, minY),
|
||
new PointF(maxX, minY),
|
||
new PointF(maxX, maxY),
|
||
new PointF(minX, maxY),
|
||
};
|
||
|
||
this.line.Points = tmpPoints;
|
||
#else
|
||
this.line.Points = new() { this.current[0], pos };
|
||
#endif
|
||
this.line.point_labels = new() { 1, 1 };
|
||
this.line.Close();
|
||
}
|
||
break;
|
||
case ShapeTypeEnum.Circle:
|
||
{
|
||
this.line.Points = new() { this.current[0], pos };
|
||
this.line.point_labels = new() { 1, 1 };
|
||
this.line.ShapeType = ShapeTypeEnum.Circle;
|
||
OnShapeUpdateEvent?.Invoke(this.line);
|
||
}
|
||
break;
|
||
case ShapeTypeEnum.Line:
|
||
{
|
||
this.line.Points = new() { this.current[0], pos };
|
||
this.line.point_labels = new() { 1, 1 };
|
||
this.line.Close();
|
||
OnShapeUpdateEvent?.Invoke(this.line);
|
||
}
|
||
break;
|
||
|
||
case ShapeTypeEnum.Point:
|
||
{
|
||
this.line.Points = new() { this.current[0] };
|
||
this.line.point_labels = new() { 1 };
|
||
this.line.Close();
|
||
}
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
}
|
||
Refresh();
|
||
this.current.HighlightClear();
|
||
return;
|
||
}
|
||
|
||
|
||
// 多边形复制移动
|
||
if (e.Button == MouseButtons.Right)
|
||
{
|
||
if (this.selectedShapesCopy != null
|
||
&& this.selectedShapesCopy.Count > 0
|
||
&& this.prevPoint != null)
|
||
{
|
||
this.OverrideCursor(CustomCursors.CURSOR_MOVE);
|
||
this.BoundedMoveShapes(this.selectedShapesCopy, pos);
|
||
}
|
||
else if (this.selectedShapes != null && this.selectedShapes.Count > 0)
|
||
{
|
||
this.selectedShapesCopy = this.selectedShapes.Select(shp => shp.Copy()).ToList();
|
||
}
|
||
Invalidate();
|
||
return;
|
||
}
|
||
|
||
|
||
// 多边形/节点 移动
|
||
if (e.Button == MouseButtons.Left)
|
||
{
|
||
if (this.SelectedVertex())
|
||
{
|
||
this.BoundedMoveVertex(pos);
|
||
Invalidate();
|
||
this.movingShape = true;
|
||
}
|
||
else if (this.selectedShapes != null
|
||
&& this.selectedShapes.Count > 0
|
||
&& this.prevPoint != null)
|
||
{
|
||
this.OverrideCursor(CustomCursors.CURSOR_MOVE);
|
||
this.BoundedMoveShapes(this.selectedShapes, pos);
|
||
OnShapeMoving?.Invoke(this.selectedShapes);
|
||
Invalidate();
|
||
this.movingShape = true;
|
||
}
|
||
return;
|
||
}
|
||
|
||
|
||
|
||
//# Just hovering over the canvas, 2 possibilities:
|
||
//# - Highlight shapes
|
||
//# - Highlight vertex
|
||
//# Update shape/vertex fill and tooltip value accordingly.
|
||
var tmpShapes = this.Shapes.Where(shp => this.IsVisible(shp)).Reverse();
|
||
if (tmpShapes.Any())
|
||
{
|
||
bool found = false;
|
||
|
||
foreach (var shape in tmpShapes)
|
||
{
|
||
// 寻找距离鼠标最近的节点
|
||
int index = shape.NearestVertex(pos, this.epsilon);
|
||
int index_edge = shape.NearestEdge(pos, this.epsilon);
|
||
if (index >= 0)
|
||
{
|
||
if (this.SelectedVertex())
|
||
{
|
||
this.hShape?.HighlightClear();
|
||
}
|
||
this.prevhVertex = this.hVertex = index;
|
||
this.prevhShape = this.hShape = shape;
|
||
this.prevhEdge = this.hEdge;
|
||
this.hEdge = -1;
|
||
shape.HighlightVertex(index, HighlightModeEnum.MOVE_VERTEX);
|
||
this.OverrideCursor(CustomCursors.CURSOR_POINT);
|
||
// TODO: Tooltip
|
||
|
||
Invalidate();
|
||
found = true;
|
||
break;
|
||
}
|
||
else if (index_edge >= 0 && shape.CanAddPoint())
|
||
{
|
||
if (this.SelectedVertex())
|
||
{
|
||
this.hShape?.HighlightClear();
|
||
}
|
||
this.prevhVertex = this.hVertex;
|
||
this.hVertex = -1;
|
||
this.prevhShape = this.hShape = shape;
|
||
this.prevhEdge = this.hEdge = index_edge;
|
||
this.OverrideCursor(CustomCursors.CURSOR_POINT);
|
||
// TODO: Tooltip
|
||
Invalidate();
|
||
found = true;
|
||
break;
|
||
}
|
||
else if (shape.ContainsPoint(pos))
|
||
{
|
||
if (this.SelectedVertex())
|
||
{
|
||
this.hShape?.HighlightClear();
|
||
}
|
||
this.prevhVertex = this.hVertex;
|
||
this.hVertex = -1;
|
||
this.prevhShape = this.hShape = shape;
|
||
this.prevhEdge = this.hEdge;
|
||
this.hEdge = -1;
|
||
this.OverrideCursor(CustomCursors.CURSOR_GRAB);
|
||
Invalidate();
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!found)
|
||
{
|
||
UnHighlight();
|
||
}
|
||
|
||
this.vertexSelected?.Invoke(this.hVertex >= 0);
|
||
}
|
||
|
||
// 鼠标中键移动触发移动事件
|
||
if (MouseButtons.Middle == e.Button)
|
||
{
|
||
if (PointF.Empty == _panBasePoint)
|
||
{
|
||
return;
|
||
}
|
||
|
||
switch (_clickArea)
|
||
{
|
||
case ClickArea.AREA_IMG: // 点击了图像区域
|
||
{
|
||
PointF p = e.Location.ToImageCoordinate(_matrix);
|
||
|
||
float x = p.X - _panBasePoint.X;
|
||
float y = p.Y - _panBasePoint.Y;
|
||
_matrix.Translate(x, y);
|
||
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
Refresh();
|
||
|
||
}
|
||
|
||
|
||
|
||
protected override void OnPaint(PaintEventArgs e)
|
||
{
|
||
base.OnPaint(e);
|
||
try
|
||
{
|
||
//获取表示控件的工作区的矩形
|
||
Rectangle rect = ClientRectangle;
|
||
|
||
Graphics oriGraphics = e.Graphics;
|
||
|
||
//设置平滑程度为高质量,减少抗锯齿的出现
|
||
oriGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
||
|
||
// 双缓冲绘图
|
||
//获取当前应用程序域的 BufferedGraphicsContext,此实例管理该应用程序的所有默认双缓冲
|
||
BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
|
||
//BufferedGraphics 对象管理与呈现图面(例如窗体或控件)关联的内存缓冲
|
||
BufferedGraphics myBuffer = currentContext.Allocate(oriGraphics, rect);
|
||
//实例化一个直接表示内存缓冲的 Graphics 对象,可将图形呈现到内存缓冲,绘制到此对象中
|
||
Graphics bufferGraphics = myBuffer.Graphics;
|
||
bufferGraphics.SmoothingMode = SmoothingMode.HighQuality;
|
||
//高质量低速度呈现
|
||
bufferGraphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||
//使用的插值算法
|
||
bufferGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
|
||
//清除整个绘图面并以指定背景色填充,Console.BackColor指获取当前控件的背景色
|
||
bufferGraphics.Clear(Color.FromArgb(0xf0, 0xf0, 0xf0));
|
||
|
||
bufferGraphics.MultiplyTransform(_matrix);
|
||
|
||
|
||
#region 画背景图
|
||
|
||
if (pixmap != null)
|
||
{
|
||
try
|
||
{
|
||
bufferGraphics.DrawImage(pixmap, 0, 0, pixmap.Width, pixmap.Height);
|
||
//if (_path != null)
|
||
//{
|
||
// bufferGraphics.DrawPath(new Pen(Brushes.Yellow, 1f), _path);
|
||
//}
|
||
}
|
||
catch { }
|
||
}
|
||
else
|
||
{
|
||
pixmap = null;
|
||
return;
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
|
||
|
||
|
||
#region
|
||
|
||
foreach (var shape in Shapes)
|
||
{
|
||
if ((shape.Selected || !this._hideBackround)
|
||
&& this.IsVisible(shape))
|
||
{
|
||
shape.fill = shape.Selected || (shape == this.hShape);
|
||
shape.Paint(bufferGraphics);
|
||
}
|
||
}
|
||
|
||
if (this.current != null)
|
||
{
|
||
this.current.Paint(bufferGraphics);
|
||
this.line.Paint(bufferGraphics);
|
||
}
|
||
|
||
if (this.selectedShapesCopy != null)
|
||
{
|
||
foreach (var s in this.selectedShapesCopy)
|
||
{
|
||
s.Paint(bufferGraphics);
|
||
}
|
||
}
|
||
|
||
// TODO:
|
||
this.FillDrawing = true;
|
||
|
||
if (this.FillDrawing
|
||
&& this.CreateMode == ShapeTypeEnum.Polygon
|
||
&& this.current != null
|
||
&& this.current.Length >= 2)
|
||
{
|
||
var drawing_shape = this.current.Copy();
|
||
if (drawing_shape.fill_color.A == 0)
|
||
{
|
||
drawing_shape.fill_color = Color.FromArgb(64, drawing_shape.fill_color);
|
||
}
|
||
drawing_shape.AddPoint(this.line[1]);
|
||
drawing_shape.fill = true;
|
||
drawing_shape.Paint(bufferGraphics);
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
#region 绘制外部传递的图形
|
||
|
||
// this.OutsideShapes.ForEach(shp => shp.Paint(bufferGraphics));
|
||
for (int i = 0; i < OutsideShapes.Count; i++)
|
||
{
|
||
OutsideShapes[i].Paint(bufferGraphics);
|
||
}
|
||
|
||
|
||
#endregion
|
||
|
||
|
||
myBuffer.Render(oriGraphics);
|
||
//释放资源
|
||
bufferGraphics.Dispose();
|
||
myBuffer.Dispose();
|
||
currentContext.Dispose();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
throw;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
private void SelectShapePoint(PointF point, bool multiple_selection_mode)
|
||
{
|
||
if (this.SelectedVertex())
|
||
{
|
||
this.hShape?.HighlightVertex(this.hVertex, HighlightModeEnum.MOVE_VERTEX);
|
||
}
|
||
else
|
||
{
|
||
List<Shape.FlyShape> reversedShapes = Shapes.ToList();
|
||
reversedShapes.Reverse();
|
||
foreach (var shape in reversedShapes)
|
||
{
|
||
if (this.IsVisible(shape) && shape.ContainsPoint(point))
|
||
{
|
||
this.SetHiding();
|
||
if (this.selectedShapes.Contains(shape))
|
||
{
|
||
this.hShapeIsSelected = true;
|
||
}
|
||
else
|
||
{
|
||
if (multiple_selection_mode)
|
||
{
|
||
shape.Selected = true;
|
||
this.selectedShapes.Add(shape);
|
||
this.selectionChanged?.Invoke(this.selectedShapes);
|
||
}
|
||
else
|
||
{
|
||
shape.Selected = true;
|
||
this.selectedShapes = new List<FlyShape>() { shape };
|
||
this.selectionChanged?.Invoke(this.selectedShapes);
|
||
}
|
||
this.hShapeIsSelected = false;
|
||
}
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
this.DeSelectShape();
|
||
}
|
||
|
||
private void RemoveSelectedPoint()
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
private void AddPointToEdge()
|
||
{
|
||
throw new NotImplementedException();
|
||
}
|
||
|
||
private bool BoundedMoveShapes(List<FlyShape> shapes, PointF pos)
|
||
{
|
||
if (this.OutputOfPixmap(pos))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
PointF o1 = new PointF(
|
||
pos.X + this.offsets[0].X,
|
||
pos.Y + this.offsets[0].Y);
|
||
if (this.OutputOfPixmap(o1))
|
||
{
|
||
pos = new PointF(
|
||
pos.X - Math.Min(0, o1.X),
|
||
pos.Y - Math.Min(0, o1.Y));
|
||
}
|
||
|
||
PointF o2 = new PointF(
|
||
pos.X + this.offsets[1].X,
|
||
pos.Y + this.offsets[1].Y);
|
||
if (this.OutputOfPixmap(o1))
|
||
{
|
||
pos = new PointF(
|
||
pos.X + Math.Min(0, this.pixmap.Width - o2.X),
|
||
pos.Y + Math.Min(0, this.pixmap.Height - o2.Y));
|
||
}
|
||
|
||
try
|
||
{
|
||
PointF dp = new PointF(pos.X - this.prevPoint.X, pos.Y - this.prevPoint.Y);
|
||
foreach (FlyShape shape in shapes)
|
||
{
|
||
shape.MoveBy(dp);
|
||
}
|
||
this.prevPoint = pos;
|
||
return true;
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
return false;
|
||
}
|
||
|
||
|
||
private void BoundedMoveVertex(PointF pos)
|
||
{
|
||
PointF point = this.hShape[this.hVertex];
|
||
if (this.OutputOfPixmap(pos))
|
||
{
|
||
pos = this.IntersectionPoint(point, pos);
|
||
}
|
||
this.hShape.IsVertexMoving = true;
|
||
this.hShape.MoveVertexBy(this.hVertex, new PointF(pos.X - point.X, pos.Y - point.Y));
|
||
OnShapeUpdateEvent?.Invoke(hShape);
|
||
}
|
||
|
||
|
||
private PointF IntersectionPoint(PointF p1, PointF p2)
|
||
{
|
||
Size size = this.pixmap.Size;
|
||
List<PointF> points = new List<PointF>()
|
||
{
|
||
new PointF(0, 0),
|
||
new PointF(size.Width - 1, 0),
|
||
new PointF(size.Width - 1, size.Height - 1),
|
||
new PointF(0, size.Height - 1),
|
||
};
|
||
|
||
float x1 = Math.Min(Math.Max(p1.X, 0), size.Width - 1);
|
||
float y1 = Math.Min(Math.Max(p1.Y, 0), size.Height - 1);
|
||
|
||
float x2 = p2.X;
|
||
float y2 = p2.Y;
|
||
|
||
// Get the intersection details
|
||
var intersections = IntersectingEdges(
|
||
new PointF(x1, y1),
|
||
new PointF(x2, y2),
|
||
points);
|
||
var closestIntersection = intersections.OrderBy(result => result.distance).FirstOrDefault();
|
||
|
||
if (closestIntersection.Equals(default((double distance, int index, PointF intersectionPoint))))
|
||
{
|
||
return PointF.Empty; // No intersection found
|
||
}
|
||
|
||
var (d, i, intersection) = closestIntersection;
|
||
|
||
// Define the edge points
|
||
float x3 = points[i].X;
|
||
float y3 = points[i].Y;
|
||
float x4 = points[(i + 1) % 4].X;
|
||
float y4 = points[(i + 1) % 4].Y;
|
||
|
||
if (intersection == new PointF(x1, y1))
|
||
{
|
||
// Handle cases where the previous point is on one of the edges.
|
||
if (x3 == x4)
|
||
{
|
||
return new PointF(x3, Math.Min(Math.Max(0, y2), Math.Max(y3, y4)));
|
||
}
|
||
else // y3 == y4
|
||
{
|
||
return new PointF(Math.Min(Math.Max(0, x2), Math.Max(x3, x4)), y3);
|
||
}
|
||
}
|
||
|
||
return intersection;
|
||
}
|
||
|
||
|
||
private IEnumerable<(double distance, int index, PointF intersectionPoint)> IntersectingEdges(PointF point1, PointF point2, List<PointF> points)
|
||
{
|
||
float x1 = point1.X;
|
||
float y1 = point1.Y;
|
||
float x2 = point2.X;
|
||
float y2 = point2.Y;
|
||
|
||
for (int i = 0; i < 4; i++)
|
||
{
|
||
float x3 = points[i].X;
|
||
float y3 = points[i].Y;
|
||
float x4 = points[(i + 1) % 4].X;
|
||
float y4 = points[(i + 1) % 4].Y;
|
||
|
||
float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
|
||
float nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
|
||
float nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
|
||
|
||
if (denom == 0)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
float ua = nua / denom;
|
||
float ub = nub / denom;
|
||
|
||
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1)
|
||
{
|
||
float x = x1 + ua * (x2 - x1);
|
||
float y = y1 + ua * (y2 - y1);
|
||
PointF m = new PointF((x3 + x4) / 2, (y3 + y4) / 2);
|
||
float d = PointHelper.Distance(m, new PointF(x2, y2));
|
||
yield return (d, i, new PointF(x, y));
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
|
||
private bool CloseEnough(PointF p1, PointF p2)
|
||
{
|
||
var d1 = PointHelper.Distance(p1, p2);
|
||
//var d2 = (this.epsilon / this.Scale);
|
||
//return d1 < d2;
|
||
// TODO:
|
||
//return false;
|
||
return d1 < 16;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
private void InitTask()
|
||
{
|
||
TaskFactory taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.LongRunning);
|
||
taskFactory.StartNew(MenuWithSelectionEvent);
|
||
}
|
||
|
||
|
||
|
||
private void AfterMouseRelease()
|
||
{
|
||
if (this.InvokeRequired)
|
||
{
|
||
this.Invoke(new MethodInvoker(AfterMouseRelease));
|
||
return;
|
||
}
|
||
|
||
if (this.movingShape && this.hShape != null)
|
||
{
|
||
this.hShape.IsVertexMoving = false;
|
||
int index = this.Shapes.IndexOf(this.hShape);
|
||
|
||
if (shapesBackups.Count > 0)
|
||
{
|
||
if (this.shapesBackups[this.shapesBackups.Count - 1][index].Points
|
||
!= this.Shapes[index].Points)
|
||
{
|
||
this.StoreShapes();
|
||
this.ShapeMoved?.Invoke();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
this.movingShape = false;
|
||
}
|
||
}
|
||
|
||
|
||
private bool CanCloseShape()
|
||
{
|
||
var b1 = this.Drawing();
|
||
var b2 = this.current != null && this.current.Length > 2;
|
||
|
||
return b1 && b2;
|
||
}
|
||
|
||
|
||
|
||
private void FlyCanvas_MouseDoubleClick(object? sender, MouseEventArgs e)
|
||
{
|
||
if (this.double_click != DoubleClickActionEnum.Close)
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (this.CreateMode == ShapeTypeEnum.Polygon && this.CanCloseShape())
|
||
{
|
||
this.Finalise();
|
||
}
|
||
}
|
||
|
||
|
||
//private List<Shape.FlyShape> TestReflect()
|
||
//{
|
||
// List<Shape.FlyShape> shapes = new List<Shape.FlyShape>();
|
||
// List<string> names = new List<string>();
|
||
|
||
|
||
// // 获取对象的类型
|
||
// Type type = this.GetType();
|
||
|
||
// // 获取所有公共字段
|
||
// FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
||
// foreach (var field in fields)
|
||
// {
|
||
// if (field.FieldType.IsSubclassOf(typeof(Shape.FlyShape)) || field.FieldType == typeof(Shape.FlyShape))
|
||
// {
|
||
// shapes.Add((Shape.FlyShape)field.GetValue(this));
|
||
// names.Add(field.Name);
|
||
// }
|
||
// }
|
||
|
||
// // 获取所有公共属性
|
||
// PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic);
|
||
// foreach (var property in properties)
|
||
// {
|
||
// if (property.PropertyType.IsSubclassOf(typeof(Shape.FlyShape)) || property.PropertyType == typeof(Shape.FlyShape))
|
||
// {
|
||
// shapes.Add((Shape.FlyShape)property.GetValue(this));
|
||
// names.Add(property.Name);
|
||
// }
|
||
// }
|
||
|
||
// return shapes;
|
||
//}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 重写 ProcessDialogKey 使控件可以监听方向键与删除键
|
||
/// </summary>
|
||
protected override bool ProcessDialogKey(Keys keyData)
|
||
{
|
||
if (keyData == Keys.Up || keyData == Keys.Down ||
|
||
keyData == Keys.Left || keyData == Keys.Right || keyData == Keys.Delete)
|
||
{
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
return base.ProcessDialogKey(keyData);
|
||
}
|
||
}
|
||
|
||
//protected override void OnKeyDown(KeyEventArgs e)
|
||
|
||
|
||
private void FlyCanvas_KeyPress(object? sender, KeyPressEventArgs e)
|
||
{
|
||
//var modifiers = e.Modifiers;
|
||
//var key = e.KeyCode;
|
||
|
||
Debug.WriteLine("");
|
||
|
||
//if (this.Drawing())
|
||
//{
|
||
// if (key == Keys.Escape && this.current != null)
|
||
// {
|
||
// this.current = null;
|
||
// this.DrawingPolygon?.Invoke(false);
|
||
// this.Invalidate();
|
||
// }
|
||
// else if (key == Keys.Return && this.CanCloseShape())
|
||
// {
|
||
// this.Finalise();
|
||
// }
|
||
// else if ((ModifierKeys & Keys.Alt) == Keys.Alt)
|
||
// {
|
||
// this.snapping = false;
|
||
// }
|
||
//}
|
||
//else if (Editing())
|
||
//{
|
||
// switch (key)
|
||
// {
|
||
// case Keys.Up:
|
||
// this.MoveByKeyboard(new PointF(0f, -MOVE_SPEED));
|
||
// break;
|
||
// case Keys.Down:
|
||
// this.MoveByKeyboard(new PointF(0f, MOVE_SPEED));
|
||
// break;
|
||
// case Keys.Left:
|
||
// this.MoveByKeyboard(new PointF(-MOVE_SPEED, 0f));
|
||
// break;
|
||
// case Keys.Right:
|
||
// this.MoveByKeyboard(new PointF(MOVE_SPEED, 0f));
|
||
// break;
|
||
|
||
// case Keys.Delete:
|
||
// DeleteSelected();
|
||
// Invalidate();
|
||
// break;
|
||
|
||
|
||
// default:
|
||
// break;
|
||
// }
|
||
//}
|
||
}
|
||
|
||
|
||
|
||
private void FlyCanvas_KeyDown(object? sender, KeyEventArgs e)
|
||
{
|
||
var modifiers = e.Modifiers;
|
||
var key = e.KeyCode;
|
||
|
||
if (this.Drawing())
|
||
{
|
||
if (key == Keys.Escape && this.current != null)
|
||
{
|
||
this.current = null;
|
||
this.DrawingPolygon?.Invoke(false);
|
||
this.Invalidate();
|
||
}
|
||
else if (key == Keys.Return && this.CanCloseShape())
|
||
{
|
||
this.Finalise();
|
||
}
|
||
else if ((ModifierKeys & Keys.Alt) == Keys.Alt)
|
||
{
|
||
this.snapping = false;
|
||
}
|
||
}
|
||
else if (Editing())
|
||
{
|
||
switch (key)
|
||
{
|
||
case Keys.Up:
|
||
this.MoveByKeyboard(new PointF(0f, -MOVE_SPEED));
|
||
break;
|
||
case Keys.Down:
|
||
this.MoveByKeyboard(new PointF(0f, MOVE_SPEED));
|
||
break;
|
||
case Keys.Left:
|
||
this.MoveByKeyboard(new PointF(-MOVE_SPEED, 0f));
|
||
break;
|
||
case Keys.Right:
|
||
this.MoveByKeyboard(new PointF(MOVE_SPEED, 0f));
|
||
break;
|
||
|
||
case Keys.Delete:
|
||
DeleteSelected();
|
||
Invalidate();
|
||
break;
|
||
|
||
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void MoveByKeyboard(PointF offset)
|
||
{
|
||
if (this.selectedShapes == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
this.BoundedMoveShapes(this.selectedShapes,
|
||
new PointF(this.prevPoint.X + offset.X, this.prevPoint.Y + offset.Y));
|
||
this.Invalidate();
|
||
this.movingShape = true;
|
||
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 清理所有正在使用的资源。
|
||
/// </summary>
|
||
/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>
|
||
protected override void Dispose(bool disposing)
|
||
{
|
||
if (disposing && (components != null))
|
||
{
|
||
isControlAlive = false; // 设置标志,停止循环
|
||
menuCloseEvent.Set(); // 唤醒等待的线程,确保退出循环
|
||
components.Dispose();
|
||
}
|
||
base.Dispose(disposing);
|
||
}
|
||
|
||
|
||
#region 右键菜单点击事件
|
||
|
||
|
||
|
||
private bool isControlAlive = true;
|
||
private bool menuWithSelectionItemClicked = false;
|
||
private AutoResetEvent menuCloseEvent = new(false);
|
||
|
||
|
||
|
||
private void MenuWithSelectionEvent()
|
||
{
|
||
while (isControlAlive)
|
||
{
|
||
try
|
||
{
|
||
bool ret = menuCloseEvent.WaitOne();
|
||
|
||
if (!ret)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (menuWithSelectionItemClicked) // 点击了菜单项
|
||
{
|
||
menuWithSelectionItemClicked = false;
|
||
}
|
||
else if (this.selectedShapesCopy != null && this.selectedShapesCopy.Count > 0) // 未点击菜单项
|
||
{
|
||
this.selectedShapesCopy = new List<Shape.FlyShape>();
|
||
Invalidate();
|
||
AfterMouseRelease();
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
#region 选择了Shape时的右键菜单点击事件
|
||
|
||
|
||
|
||
|
||
private void menuItemCopyToHere_Click(object sender, EventArgs e)
|
||
{
|
||
OnMenuItemCopyToHere?.Invoke();
|
||
AfterMouseRelease();
|
||
|
||
}
|
||
|
||
private void menuItemMoveToHere_Click(object sender, EventArgs e)
|
||
{
|
||
OnMenuItemMoveToHere?.Invoke();
|
||
AfterMouseRelease();
|
||
}
|
||
|
||
|
||
private void menuWithSelection_Closed(object sender, ToolStripDropDownClosedEventArgs e)
|
||
{
|
||
menuCloseEvent.Set();
|
||
Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t menuWithSelection_Closed");
|
||
}
|
||
|
||
|
||
|
||
private void menuWithSelection_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
|
||
{
|
||
menuWithSelectionItemClicked = true;
|
||
Debug.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.fff")}\t menuWithSelection_ItemClicked");
|
||
}
|
||
|
||
#endregion
|
||
|
||
#endregion
|
||
|
||
|
||
public bool EndMove(bool copy)
|
||
{
|
||
if (!(this.selectedShapes != null && this.selectedShapes.Count > 0
|
||
&& this.selectedShapesCopy != null && this.selectedShapesCopy.Count > 0
|
||
&& this.selectedShapesCopy.Count == this.selectedShapes.Count))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
|
||
if (copy)
|
||
{
|
||
for (int i = 0; i < this.selectedShapesCopy.Count; i++)
|
||
{
|
||
Shape.FlyShape shape = this.selectedShapesCopy[i];
|
||
this.Shapes.Add(shape);
|
||
this.selectedShapes[i].Selected = false;
|
||
this.selectedShapes[i] = shape;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
for (int i = 0; i < this.selectedShapesCopy.Count; i++)
|
||
{
|
||
var shape = this.selectedShapesCopy[i];
|
||
this.selectedShapes[i].Points = shape.Points;
|
||
}
|
||
}
|
||
|
||
this.selectedShapesCopy = new List<Shape.FlyShape>();
|
||
this.Invalidate();
|
||
this.StoreShapes();
|
||
return true;
|
||
}
|
||
|
||
|
||
|
||
public void SelectedShapes(List<Shape.FlyShape> shapes)
|
||
{
|
||
this.SetHiding();
|
||
this.selectionChanged?.Invoke(shapes);
|
||
this.Invalidate();
|
||
}
|
||
|
||
|
||
public void DeSelectecShape()
|
||
{
|
||
if (this.selectedShapes != null && this.selectedShapes.Count > 0)
|
||
{
|
||
this.SetHiding(false);
|
||
this.selectionChanged?.Invoke(new List<Shape.FlyShape>());
|
||
this.hShapeIsSelected = false;
|
||
this.Invalidate();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
///// <summary>
|
||
///// 设置编辑状态
|
||
///// </summary>
|
||
///// <param name="value">false:创建图形,true:编辑图形</param>
|
||
//public void SetEditing(bool value = true)
|
||
//{
|
||
// this._isEditMode = !value;
|
||
// if (!this._isEditMode)
|
||
// {
|
||
// // CREATE -> EDIT
|
||
// //Repaint();
|
||
// }
|
||
// else
|
||
// {
|
||
// // EDIT -> CREATE
|
||
// UnHighlight();
|
||
// DeSelectShape();
|
||
// }
|
||
//}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 设置编辑状态
|
||
/// </summary>
|
||
/// <param name="value">false:创建图形,true:编辑图形</param>
|
||
public void StopDraw()
|
||
{
|
||
this._isEditMode = true;
|
||
|
||
// EDIT -> CREATE
|
||
UnHighlight();
|
||
DeSelectShape();
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 设置为绘图状态
|
||
/// </summary>
|
||
public void StartDraw(ShapeTypeEnum shapeType)
|
||
{
|
||
this._isEditMode = false;
|
||
this._createMode = shapeType;
|
||
|
||
// CREATE -> EDIT
|
||
//Repaint();
|
||
}
|
||
|
||
|
||
private void Canvas_shapeSelectionChanged(List<FlyShape> selected_shapes)
|
||
{
|
||
//this._noSelectionsSlot = true;
|
||
//this.canvas.selectedShapes.ForEach(shp => shp.Selected = false);
|
||
//this.dgvLabelList.ClearSelection();
|
||
//this.selectedShapes = selected_shapes;
|
||
//this.selectedShapes.ForEach(shape =>
|
||
//{
|
||
// shape.Selected = true;
|
||
// foreach (DataGridViewRow row in this.dgvLabelList.Rows)
|
||
// {
|
||
// if (row.IsNewRow)
|
||
// {
|
||
// continue;
|
||
// }
|
||
// if (row.Tag is not ShapeListItemTag listItemTag)
|
||
// {
|
||
// continue;
|
||
// }
|
||
// if (listItemTag.Shape == shape)
|
||
// {
|
||
// row.Selected = true;
|
||
// }
|
||
// }
|
||
//});
|
||
//this._noSelectionsSlot = false;
|
||
|
||
//bool n_selected = selected_shapes.Count > 0;
|
||
|
||
//this.btnDeleteSelectedShape.Enabled = n_selected;
|
||
//this.btnCopySelectedShape.Enabled = n_selected;
|
||
//this.btnEditMode.Enabled = n_selected;
|
||
|
||
}
|
||
|
||
|
||
|
||
public void ClearDraw()
|
||
{
|
||
this.OutsideShapes.Clear();
|
||
Invalidate();
|
||
}
|
||
|
||
public void DrawCircle(PointF center, float r, float lineWidth = 2)
|
||
{
|
||
FlyShape flyShape = new FlyShape();
|
||
flyShape.Points.Add(center);
|
||
flyShape.Points.Add(new PointF(center.X + r, center.Y));
|
||
flyShape.ShapeType = ShapeTypeEnum.Circle;
|
||
flyShape.line_color = Color.Red;
|
||
flyShape.LineWidth = lineWidth;
|
||
|
||
OutsideShapes.Add(flyShape);
|
||
|
||
Invalidate();
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
///
|
||
/// </summary>
|
||
/// <param name="p1"></param>
|
||
/// <param name="p2"></param>
|
||
/// <param name="rectWidth"></param>
|
||
public void DrawLine(PointF p1, PointF p2, float rectWidth=0)
|
||
{
|
||
FlyShape shp = new FlyShape();
|
||
shp.Points.Add(p1);
|
||
shp.Points.Add(p2);
|
||
shp.ShapeType = ShapeTypeEnum.Line;
|
||
shp.line_color = Color.Red;
|
||
shp.LineWidth = 2;
|
||
if (rectWidth > 0)
|
||
{
|
||
shp.IsDrawLineVirtualRect = true;
|
||
shp.LineVirtualRectWidth = rectWidth;
|
||
}
|
||
|
||
OutsideShapes.Add(shp);
|
||
|
||
Invalidate();
|
||
}
|
||
|
||
public void DrawRectangle(PointF p1, PointF p2, float rotate)
|
||
{
|
||
FlyShape flyShape = new FlyShape();
|
||
flyShape.Points.Add(p1);
|
||
//flyShape.Points.Add(new PointF(p2.X, p1.Y)); // 改动6
|
||
flyShape.Points.Add(p2);
|
||
//flyShape.Points.Add(new PointF(p1.X, p2.Y)); // 改动6
|
||
flyShape.ShapeType = ShapeTypeEnum.Rectangle;
|
||
flyShape.line_color = Color.Red;
|
||
flyShape.LineWidth = 2;
|
||
flyShape._currentRotateAngle = rotate;
|
||
|
||
OutsideShapes.Add(flyShape);
|
||
|
||
Invalidate();
|
||
}
|
||
|
||
}
|
||
}
|