Files
test_demo/MX-PD-盘古 - new/PanGu.DieBonderApp/MainShell/Resources/CustomControl/DieMapControl.cs

595 lines
20 KiB
C#
Raw Normal View History

using MainShell.Common;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.ComponentModel;
namespace MainShell.Resources.CustomControl
{
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>оƬMapͼ<70>ؼ<EFBFBD><D8BC><EFBFBD>֧<EFBFBD><D6A7>5-10<31><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ķ<EFBFBD>̬״̬<D7B4><CCAC><EFBFBD>£<EFBFBD>֧<EFBFBD><D6A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ק<EFBFBD><D7A7><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
public class DieMapControl : FrameworkElement
{
private int _rows;
private int _columns;
private DieState[,] _dieStates;
private Point[,] _dieCoordinates;
private double _minX, _maxX, _minY, _maxY;
private Dictionary<DieState, Brush> _stateBrushes;
// <20><>Ⱦ<EFBFBD><C8BE><EFBFBD><EFBFBD>
private DrawingVisual _visual;
private VisualCollection _visuals;
private DispatcherTimer _renderTimer;
private bool _needsRedraw = false;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
private MatrixTransform _transform = new MatrixTransform();
private Point _lastMousePosition;
private Point _mouseDownPosition;
private bool _isDragging;
private bool _isViewChangedByUser;
private Popup _infoPopup;
private TextBlock _infoText;
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register("Background", typeof(Brush), typeof(DieMapControl), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.AffectsRender));
public Brush Background
{
get { return (Brush)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public static readonly DependencyProperty MapModelProperty =
DependencyProperty.Register("MapModel", typeof(DieMapModel), typeof(DieMapControl), new PropertyMetadata(null, OnMapModelChanged));
public DieMapModel MapModel
{
get { return (DieMapModel)GetValue(MapModelProperty); }
set { SetValue(MapModelProperty, value); }
}
public static readonly DependencyProperty DieClickedCommandProperty =
DependencyProperty.Register("DieClickedCommand", typeof(ICommand), typeof(DieMapControl), new PropertyMetadata(null));
public ICommand DieClickedCommand
{
get { return (ICommand)GetValue(DieClickedCommandProperty); }
set { SetValue(DieClickedCommandProperty, value); }
}
public event EventHandler<DieClickedEventArgs> DieClicked;
public class DieClickedEventArgs : EventArgs
{
public int Row { get; }
public int Col { get; }
public DieState State { get; }
public DieClickedEventArgs(int row, int col, DieState state)
{
Row = row;
Col = col;
State = state;
}
}
private static void OnMapModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (DieMapControl)d;
if (e.OldValue is DieMapModel oldModel)
{
oldModel.MapInitialized -= control.OnMapInitialized;
oldModel.DieStateChanged -= control.OnDieStateChanged;
oldModel.DieStatesChanged -= control.OnDieStatesChanged;
}
if (e.NewValue is DieMapModel newModel)
{
newModel.MapInitialized += control.OnMapInitialized;
newModel.DieStateChanged += control.OnDieStateChanged;
newModel.DieStatesChanged += control.OnDieStatesChanged;
control.InitializeFromModel(newModel);
}
}
private void OnMapInitialized(object sender, EventArgs e)
{
InitializeFromModel(MapModel);
}
private void OnDieStateChanged(object sender, (int Row, int Col, DieState State) e)
{
_needsRedraw = true;
}
private void OnDieStatesChanged(object sender, IEnumerable<(int Row, int Col, DieState State)> e)
{
_needsRedraw = true;
}
public static readonly DependencyProperty DieSizeProperty =
DependencyProperty.Register("DieSize", typeof(double), typeof(DieMapControl), new FrameworkPropertyMetadata(4.0, FrameworkPropertyMetadataOptions.AffectsRender, OnSizeChanged));
public double DieSize
{
get { return (double)GetValue(DieSizeProperty); }
set { SetValue(DieSizeProperty, value); }
}
public static readonly DependencyProperty SpacingProperty =
DependencyProperty.Register("Spacing", typeof(double), typeof(DieMapControl), new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender, OnSizeChanged));
public double Spacing
{
get { return (double)GetValue(SpacingProperty); }
set { SetValue(SpacingProperty, value); }
}
private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (DieMapControl)d;
control.RedrawAll();
}
public DieMapControl()
{
_visuals = new VisualCollection(this);
_visual = new DrawingVisual();
_visuals.Add(_visual);
// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>ˢ<EFBFBD>ֵ䣬<D6B5><E4A3AC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
_stateBrushes = new Dictionary<DieState, Brush>
{
{ DieState.Available, CreateFrozenBrush(Color.FromRgb(211, 211, 211)) }, // dz<><C7B3>ɫ
{ DieState.Used, CreateFrozenBrush(Color.FromRgb(50, 205, 50)) }, // <20><>ɫ
{ DieState.Error, CreateFrozenBrush(Color.FromRgb(255, 0, 0)) }, // <20><>ɫ
{ DieState.NotExist, Brushes.Transparent }, // ͸<><CDB8>
{ DieState.Current, CreateFrozenBrush(Color.FromRgb(0, 191, 255)) }, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> - <20><>ǰλ<C7B0><CEBB>
{ DieState.Target, CreateFrozenBrush(Color.FromRgb(255, 165, 0)) } // <20><>ɫ - Ŀ<><C4BF>λ<EFBFBD><CEBB>
};
// <20><>ʼ<EFBFBD><CABC>Popup
_infoPopup = new Popup
{
Placement = PlacementMode.Mouse,
StaysOpen = false,
AllowsTransparency = true
};
Border popupBorder = new Border
{
Background = new SolidColorBrush(Color.FromArgb(220, 30, 30, 30)),
BorderBrush = Brushes.Gray,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(4),
Padding = new Thickness(8)
};
_infoText = new TextBlock
{
Foreground = Brushes.White,
FontSize = 12
};
popupBorder.Child = _infoText;
_infoPopup.Child = popupBorder;
// <20><><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD>
this.MouseWheel += OnMouseWheel;
this.MouseDown += OnMouseDown;
this.MouseUp += OnMouseUp;
this.MouseMove += OnMouseMove;
this.MouseLeave += OnMouseLeave;
// <20>ü<EFBFBD><C3BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߽<EFBFBD><DFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
this.ClipToBounds = true;
// <20><>ʼ<EFBFBD><CABC><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD>1<EFBFBD><31>3<EFBFBD><33> (Լ333ms)
_renderTimer = new DispatcherTimer(DispatcherPriority.Render);
_renderTimer.Interval = TimeSpan.FromMilliseconds(333);
_renderTimer.Tick += RenderTimer_Tick;
_renderTimer.Start();
}
private void RenderTimer_Tick(object sender, EventArgs e)
{
if (_needsRedraw)
{
RedrawAll();
_needsRedraw = false;
}
}
private SolidColorBrush CreateFrozenBrush(Color color)
{
var brush = new SolidColorBrush(color);
brush.Freeze();
return brush;
}
protected override int VisualChildrenCount => _visuals.Count;
protected override Visual GetVisualChild(int index)
{
if (index < 0 || index >= _visuals.Count)
throw new ArgumentOutOfRangeException();
return _visuals[index];
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (Background != null)
{
drawingContext.DrawRectangle(Background, null, new Rect(0, 0, ActualWidth, ActualHeight));
}
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (!_isViewChangedByUser && _rows > 0)
{
FitToScreen();
}
}
private void InitializeFromModel(DieMapModel model)
{
if (model == null) return;
_rows = model.Rows;
_columns = model.Columns;
_dieStates = model.States;
_dieCoordinates = model.Coordinates;
if (_dieCoordinates != null)
{
CalculateBounds();
}
// <20><><EFBFBD>ñ任
_transform.Matrix = Matrix.Identity;
// <20>ӳ<EFBFBD>ִ<EFBFBD><D6B4>FitToScreen<65><6E>ȷ<EFBFBD><C8B7><EFBFBD>ؼ<EFBFBD><D8BC>Ѿ<EFBFBD><D1BE><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
Dispatcher.BeginInvoke(new Action(() =>
{
if (ActualWidth > 0 && ActualHeight > 0)
{
FitToScreen();
}
else
{
RedrawAll();
}
}), DispatcherPriority.Loaded);
}
private void CalculateBounds()
{
_minX = double.MaxValue;
_maxX = double.MinValue;
_minY = double.MaxValue;
_maxY = double.MinValue;
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
var pt = _dieCoordinates[r, c];
if (pt.X < _minX) _minX = pt.X;
if (pt.X > _maxX) _maxX = pt.X;
if (pt.Y < _minY) _minY = pt.Y;
if (pt.Y > _maxY) _maxY = pt.Y;
}
}
}
private void RedrawAll()
{
if (_rows == 0 || _columns == 0 || _dieStates == null) return;
using (DrawingContext dc = _visual.RenderOpen())
{
// Ӧ<>õ<EFBFBD>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD>ź<EFBFBD>ƽ<EFBFBD>Ʊ任
dc.PushTransform(_transform);
double size = DieSize;
double spacing = Spacing;
double physicalWidth = _maxX - _minX;
double physicalHeight = _maxY - _minY;
if (physicalWidth == 0) physicalWidth = 1;
if (physicalHeight == 0) physicalHeight = 1;
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>С<EFBFBD><D0A1>δ<EFBFBD><CEB4><EFBFBD><EFBFBD>ǰ<EFBFBD><C7B0>
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
DieState state = _dieStates[r, c];
if (state == DieState.NotExist) continue;
Brush brush = _stateBrushes.ContainsKey(state) ? _stateBrushes[state] : Brushes.Transparent;
if (brush == Brushes.Transparent) continue;
double x, y;
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[r, c];
x = ((pt.X - _minX) / physicalWidth) * baseWidth;
y = (1.0 - (pt.Y - _minY) / physicalHeight) * baseHeight;
}
else
{
x = c * (size + spacing);
y = r * (size + spacing);
}
dc.DrawRectangle(brush, null, new Rect(x, y, size, size));
}
}
dc.Pop();
}
_needsRedraw = false;
}
#region <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ƽ<EFBFBD><EFBFBD>)
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
{
Point mousePos = e.GetPosition(this);
double scaleFactor = e.Delta > 0 ? 1.1 : 1 / 1.1;
Matrix matrix = _transform.Matrix;
matrix.ScaleAt(scaleFactor, scaleFactor, mousePos.X, mousePos.Y);
_transform.Matrix = matrix;
_isViewChangedByUser = true;
RedrawAll();
e.Handled = true;
}
private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Middle)
{
FitToScreen();
}
else if (e.ChangedButton == MouseButton.Left)
{
_mouseDownPosition = e.GetPosition(this);
_lastMousePosition = _mouseDownPosition;
_isDragging = true;
this.CaptureMouse();
_infoPopup.IsOpen = false;
}
}
private void OnMouseUp(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
_isDragging = false;
this.ReleaseMouseCapture();
Point currentPosition = e.GetPosition(this);
if ((currentPosition - _mouseDownPosition).Length < 5)
{
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
ShowDieInfo(currentPosition);
}
else
{
HandleDieClick(currentPosition);
}
}
}
}
private void HandleDieClick(Point mousePos)
{
if (_rows == 0 || _columns == 0 || _dieStates == null) return;
if (!_transform.Matrix.HasInverse) return;
Matrix inverse = _transform.Matrix;
inverse.Invert();
Point canvasPos = inverse.Transform(mousePos);
double size = DieSize;
double spacing = Spacing;
double physicalWidth = _maxX - _minX;
double physicalHeight = _maxY - _minY;
if (physicalWidth == 0) physicalWidth = 1;
if (physicalHeight == 0) physicalHeight = 1;
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
int clickedRow = -1;
int clickedCol = -1;
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
DieState state = _dieStates[r, c];
if (state == DieState.NotExist) continue;
double x, y;
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[r, c];
x = ((pt.X - _minX) / physicalWidth) * baseWidth;
y = (1.0 - (pt.Y - _minY) / physicalHeight) * baseHeight;
}
else
{
x = c * (size + spacing);
y = r * (size + spacing);
}
if (canvasPos.X >= x && canvasPos.X <= x + size &&
canvasPos.Y >= y && canvasPos.Y <= y + size)
{
clickedRow = r;
clickedCol = c;
break;
}
}
if (clickedRow != -1) break;
}
if (clickedRow != -1)
{
var dieInfo = (Row: clickedRow, Col: clickedCol, State: _dieStates[clickedRow, clickedCol]);
// <20><><EFBFBD><EFBFBD><EFBFBD>¼<EFBFBD>
DieClicked?.Invoke(this, new DieClickedEventArgs(clickedRow, clickedCol, _dieStates[clickedRow, clickedCol]));
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (DieClickedCommand != null && DieClickedCommand.CanExecute(dieInfo))
{
DieClickedCommand.Execute(dieInfo);
}
}
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (_isDragging)
{
Point currentPosition = e.GetPosition(this);
Vector delta = currentPosition - _lastMousePosition;
Matrix matrix = _transform.Matrix;
matrix.Translate(delta.X, delta.Y);
_transform.Matrix = matrix;
_isViewChangedByUser = true;
_lastMousePosition = currentPosition;
RedrawAll();
}
}
private void OnMouseLeave(object sender, MouseEventArgs e)
{
if (_isDragging)
{
_isDragging = false;
this.ReleaseMouseCapture();
}
}
private void FitToScreen()
{
if (_rows == 0 || _columns == 0) return;
double size = DieSize;
double spacing = Spacing;
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
if (baseWidth == 0 || baseHeight == 0 || ActualWidth == 0 || ActualHeight == 0) return;
double scaleX = ActualWidth / baseWidth;
double scaleY = ActualHeight / baseHeight;
double scale = Math.Min(scaleX, scaleY) * 0.9; // 90% to leave some margin
if (scale <= 0) scale = 1;
_transform.Matrix = new Matrix(scale, 0, 0, scale,
(ActualWidth - baseWidth * scale) / 2,
(ActualHeight - baseHeight * scale) / 2);
_isViewChangedByUser = false;
RedrawAll();
}
private void ShowDieInfo(Point mousePos)
{
if (_rows == 0 || _columns == 0 || _dieStates == null) return;
if (!_transform.Matrix.HasInverse) return;
Matrix inverse = _transform.Matrix;
inverse.Invert();
Point canvasPos = inverse.Transform(mousePos);
double size = DieSize;
double spacing = Spacing;
double physicalWidth = _maxX - _minX;
double physicalHeight = _maxY - _minY;
if (physicalWidth == 0) physicalWidth = 1;
if (physicalHeight == 0) physicalHeight = 1;
double baseWidth = _columns * (size + spacing);
double baseHeight = _rows * (size + spacing);
int clickedRow = -1;
int clickedCol = -1;
for (int r = 0; r < _rows; r++)
{
for (int c = 0; c < _columns; c++)
{
DieState state = _dieStates[r, c];
if (state == DieState.NotExist) continue;
double x, y;
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[r, c];
x = ((pt.X - _minX) / physicalWidth) * baseWidth;
y = (1.0 - (pt.Y - _minY) / physicalHeight) * baseHeight;
}
else
{
x = c * (size + spacing);
y = r * (size + spacing);
}
if (canvasPos.X >= x && canvasPos.X <= x + size &&
canvasPos.Y >= y && canvasPos.Y <= y + size)
{
clickedRow = r;
clickedCol = c;
break;
}
}
if (clickedRow != -1) break;
}
if (clickedRow != -1)
{
DieState state = _dieStates[clickedRow, clickedCol];
string info = $"<22><> (Row): {clickedRow}\n<><6E> (Col): {clickedCol}\n״̬: {MainShell.Converter.EnumHelper.GetEnumDescription(state)}";
if (_dieCoordinates != null)
{
var pt = _dieCoordinates[clickedRow, clickedCol];
info += $"\nX: {pt.X:F3}\nY: {pt.Y:F3}";
}
_infoText.Text = info;
_infoPopup.IsOpen = true;
}
else
{
_infoPopup.IsOpen = false;
}
}
#endregion
}
}