feat: Add real-time node drag preview and improve pan accuracy

- Add ghost rendering at original position during node drag
- Implement real-time drag preview with Update() for immediate rendering
- Add visual feedback: shadow effects, cyan borders, pulse animations
- Improve pan movement accuracy with zoom level compensation (PointF precision)
- Add mouse capture for stable drag operations
- Fix coordinate precision loss by changing _panOffset from Point to PointF

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
backuppc
2025-11-03 15:08:48 +09:00
parent 5e14907f1c
commit c3f8c7117d
3 changed files with 435 additions and 34 deletions

View File

@@ -14,8 +14,17 @@ namespace AGVNavigationCore.Controls
{
#region Paint Events
private static int _paintCounter = 0;
private void UnifiedAGVCanvas_Paint(object sender, PaintEventArgs e)
{
// 디버그: Paint 이벤트 실행 확인
if (_isDragging)
{
_paintCounter++;
//System.Diagnostics.Debug.WriteLine($"Paint #{_paintCounter} 실행! 현재 노드 위치: ({_selectedNode?.Position.X}, {_selectedNode?.Position.Y})");
}
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.High;
@@ -50,6 +59,12 @@ namespace AGVNavigationCore.Controls
// 노드 그리기 (라벨 제외)
DrawNodesOnly(g);
// 드래그 고스트 그리기 (노드 위에 표시되도록 나중에 그리기)
if (_isDragging && _selectedNode != null)
{
DrawDragGhost(g);
}
// AGV 그리기
DrawAGVs(g);
@@ -382,7 +397,203 @@ namespace AGVNavigationCore.Controls
}
}
/// <summary>
/// 드래그 고스트 그리기 - 원래 위치에 반투명 노드 표시
/// </summary>
private void DrawDragGhost(Graphics g)
{
if (_selectedNode == null || !_isDragging) return;
// 반투명 효과를 위한 브러시 생성
Brush ghostBrush = null;
switch (_selectedNode.Type)
{
case NodeType.Normal:
ghostBrush = new SolidBrush(Color.FromArgb(120, 100, 149, 237)); // 반투명 파란색
break;
case NodeType.Rotation:
ghostBrush = new SolidBrush(Color.FromArgb(120, 255, 165, 0)); // 반투명 주황색
break;
case NodeType.Docking:
ghostBrush = new SolidBrush(Color.FromArgb(120, 50, 205, 50)); // 반투명 초록색
break;
case NodeType.Charging:
ghostBrush = new SolidBrush(Color.FromArgb(120, 255, 215, 0)); // 반투명 금색
break;
default:
ghostBrush = new SolidBrush(Color.FromArgb(120, 200, 200, 200)); // 반투명 회색
break;
}
// 고스트 노드 그리기
switch (_selectedNode.Type)
{
case NodeType.Label:
DrawLabelGhost(g);
break;
case NodeType.Image:
DrawImageGhost(g);
break;
case NodeType.Docking:
DrawPentagonGhost(g, ghostBrush);
break;
case NodeType.Charging:
DrawTriangleGhost(g, ghostBrush);
break;
default:
DrawCircleGhost(g, ghostBrush);
break;
}
ghostBrush?.Dispose();
}
private void DrawCircleGhost(Graphics g, Brush ghostBrush)
{
var rect = new Rectangle(
_dragStartPosition.X - NODE_RADIUS,
_dragStartPosition.Y - NODE_RADIUS,
NODE_SIZE,
NODE_SIZE
);
g.FillEllipse(ghostBrush, rect);
// 회색 점선 테두리
g.DrawEllipse(new Pen(Color.FromArgb(180, 128, 128, 128), 2) { DashStyle = DashStyle.Dash }, rect);
// 빨간색 외곽 테두리 (디버깅용 - 고스트가 확실히 보이도록)
var outerRect = new Rectangle(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4);
g.DrawEllipse(new Pen(Color.FromArgb(200, 255, 0, 0), 1), outerRect);
}
private void DrawPentagonGhost(Graphics g, Brush ghostBrush)
{
var points = new Point[5];
for (int i = 0; i < 5; i++)
{
var angle = (Math.PI * 2 * i / 5) - Math.PI / 2;
points[i] = new Point(
(int)(_dragStartPosition.X + NODE_RADIUS * Math.Cos(angle)),
(int)(_dragStartPosition.Y + NODE_RADIUS * Math.Sin(angle))
);
}
g.FillPolygon(ghostBrush, points);
// 회색 점선 테두리
g.DrawPolygon(new Pen(Color.FromArgb(180, 128, 128, 128), 2) { DashStyle = DashStyle.Dash }, points);
// 빨간색 외곽 테두리 (디버깅용)
var outerPoints = new Point[5];
for (int i = 0; i < 5; i++)
{
var angle = (Math.PI * 2 * i / 5) - Math.PI / 2;
outerPoints[i] = new Point(
(int)(_dragStartPosition.X + (NODE_RADIUS + 3) * Math.Cos(angle)),
(int)(_dragStartPosition.Y + (NODE_RADIUS + 3) * Math.Sin(angle))
);
}
g.DrawPolygon(new Pen(Color.FromArgb(200, 255, 0, 0), 1), outerPoints);
}
private void DrawTriangleGhost(Graphics g, Brush ghostBrush)
{
var points = new Point[3];
for (int i = 0; i < 3; i++)
{
var angle = (Math.PI * 2 * i / 3) - Math.PI / 2;
points[i] = new Point(
(int)(_dragStartPosition.X + NODE_RADIUS * Math.Cos(angle)),
(int)(_dragStartPosition.Y + NODE_RADIUS * Math.Sin(angle))
);
}
g.FillPolygon(ghostBrush, points);
// 회색 점선 테두리
g.DrawPolygon(new Pen(Color.FromArgb(180, 128, 128, 128), 2) { DashStyle = DashStyle.Dash }, points);
// 빨간색 외곽 테두리 (디버깅용)
var outerPoints = new Point[3];
for (int i = 0; i < 3; i++)
{
var angle = (Math.PI * 2 * i / 3) - Math.PI / 2;
outerPoints[i] = new Point(
(int)(_dragStartPosition.X + (NODE_RADIUS + 3) * Math.Cos(angle)),
(int)(_dragStartPosition.Y + (NODE_RADIUS + 3) * Math.Sin(angle))
);
}
g.DrawPolygon(new Pen(Color.FromArgb(200, 255, 0, 0), 1), outerPoints);
}
private void DrawLabelGhost(Graphics g)
{
var text = string.IsNullOrEmpty(_selectedNode.LabelText) ? _selectedNode.NodeId : _selectedNode.LabelText;
var font = new Font(_selectedNode.FontFamily, _selectedNode.FontSize, _selectedNode.FontStyle);
var textBrush = new SolidBrush(Color.FromArgb(120, _selectedNode.ForeColor));
var textSize = g.MeasureString(text, font);
var textPoint = new Point(
(int)(_dragStartPosition.X - textSize.Width / 2),
(int)(_dragStartPosition.Y - textSize.Height / 2)
);
if (_selectedNode.ShowBackground)
{
var backgroundBrush = new SolidBrush(Color.FromArgb(120, _selectedNode.BackColor));
var backgroundRect = new Rectangle(
textPoint.X - _selectedNode.Padding,
textPoint.Y - _selectedNode.Padding,
(int)textSize.Width + (_selectedNode.Padding * 2),
(int)textSize.Height + (_selectedNode.Padding * 2)
);
g.FillRectangle(backgroundBrush, backgroundRect);
g.DrawRectangle(new Pen(Color.FromArgb(180, 128, 128, 128), 2) { DashStyle = DashStyle.Dash }, backgroundRect);
backgroundBrush.Dispose();
}
g.DrawString(text, font, textBrush, textPoint);
// 배경이 없어도 테두리 표시
if (!_selectedNode.ShowBackground)
{
var borderRect = new Rectangle(
textPoint.X - 2,
textPoint.Y - 2,
(int)textSize.Width + 4,
(int)textSize.Height + 4
);
g.DrawRectangle(new Pen(Color.FromArgb(180, 128, 128, 128), 2) { DashStyle = DashStyle.Dash }, borderRect);
}
// 빨간색 외곽 테두리 (디버깅용)
var outerRect = new Rectangle(
textPoint.X - 4,
textPoint.Y - 4,
(int)textSize.Width + 8,
(int)textSize.Height + 8
);
g.DrawRectangle(new Pen(Color.FromArgb(200, 255, 0, 0), 1), outerRect);
font.Dispose();
textBrush.Dispose();
}
private void DrawImageGhost(Graphics g)
{
var displaySize = _selectedNode.GetDisplaySize();
if (displaySize.IsEmpty)
displaySize = new Size(50, 50);
var imageRect = new Rectangle(
_dragStartPosition.X - displaySize.Width / 2,
_dragStartPosition.Y - displaySize.Height / 2,
displaySize.Width,
displaySize.Height
);
// 반투명 회색 사각형
using (var ghostBrush = new SolidBrush(Color.FromArgb(120, 200, 200, 200)))
{
g.FillRectangle(ghostBrush, imageRect);
g.DrawRectangle(new Pen(Color.FromArgb(180, 128, 128, 128), 2) { DashStyle = DashStyle.Dash }, imageRect);
}
// 빨간색 외곽 테두리 (디버깅용)
var outerRect = new Rectangle(imageRect.X - 2, imageRect.Y - 2, imageRect.Width + 4, imageRect.Height + 4);
g.DrawRectangle(new Pen(Color.FromArgb(200, 255, 0, 0), 1), outerRect);
}
private void DrawNodesOnly(Graphics g)
{
@@ -444,19 +655,42 @@ namespace AGVNavigationCore.Controls
private void DrawCircleNodeShape(Graphics g, MapNode node, Brush brush)
{
// 드래그 중인 노드는 약간 크게 그리기
bool isDraggingThisNode = _isDragging && node == _selectedNode;
int sizeAdjustment = isDraggingThisNode ? 4 : 0;
var rect = new Rectangle(
node.Position.X - NODE_RADIUS,
node.Position.Y - NODE_RADIUS,
NODE_SIZE,
NODE_SIZE
node.Position.X - NODE_RADIUS - sizeAdjustment,
node.Position.Y - NODE_RADIUS - sizeAdjustment,
NODE_SIZE + sizeAdjustment * 2,
NODE_SIZE + sizeAdjustment * 2
);
// 드래그 중인 노드의 그림자 효과
if (isDraggingThisNode)
{
var shadowRect = new Rectangle(rect.X + 3, rect.Y + 3, rect.Width, rect.Height);
using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
{
g.FillEllipse(shadowBrush, shadowRect);
}
}
// 노드 그리기
g.FillEllipse(brush, rect);
g.DrawEllipse(Pens.Black, rect);
// 드래그 중인 노드 강조 (가장 강력한 효과)
if (isDraggingThisNode)
{
// 청록색 두꺼운 테두리
g.DrawEllipse(new Pen(Color.Cyan, 3), rect);
// 펄스 효과
var pulseRect = new Rectangle(rect.X - 4, rect.Y - 4, rect.Width + 8, rect.Height + 8);
g.DrawEllipse(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect);
}
// 선택된 노드 강조
if (node == _selectedNode)
else if (node == _selectedNode)
{
g.DrawEllipse(_selectedNodePen, rect);
}
@@ -472,8 +706,8 @@ namespace AGVNavigationCore.Controls
g.DrawEllipse(new Pen(Color.Gold, 2) { DashStyle = DashStyle.Dash }, pulseRect);
}
// 호버된 노드 강조
if (node == _hoveredNode)
// 호버된 노드 강조 (드래그 중이 아닐 때만)
if (node == _hoveredNode && !isDraggingThisNode)
{
var hoverRect = new Rectangle(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4);
g.DrawEllipse(new Pen(Color.Orange, 2), hoverRect);
@@ -488,7 +722,11 @@ namespace AGVNavigationCore.Controls
private void DrawPentagonNodeShape(Graphics g, MapNode node, Brush brush)
{
var radius = NODE_RADIUS;
// 드래그 중인 노드는 약간 크게 그리기
bool isDraggingThisNode = _isDragging && node == _selectedNode;
int radiusAdjustment = isDraggingThisNode ? 4 : 0;
var radius = NODE_RADIUS + radiusAdjustment;
var center = node.Position;
// 5각형 꼭짓점 계산 (위쪽부터 시계방향)
@@ -502,12 +740,43 @@ namespace AGVNavigationCore.Controls
);
}
// 드래그 중인 노드의 그림자 효과
if (isDraggingThisNode)
{
var shadowPoints = new Point[5];
for (int i = 0; i < 5; i++)
{
shadowPoints[i] = new Point(points[i].X + 3, points[i].Y + 3);
}
using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
{
g.FillPolygon(shadowBrush, shadowPoints);
}
}
// 5각형 그리기
g.FillPolygon(brush, points);
g.DrawPolygon(Pens.Black, points);
// 드래그 중인 노드 강조 (가장 강력한 효과)
if (isDraggingThisNode)
{
// 청록색 두꺼운 테두리
g.DrawPolygon(new Pen(Color.Cyan, 3), points);
// 펄스 효과
var pulsePoints = new Point[5];
for (int i = 0; i < 5; i++)
{
var angle = (Math.PI * 2 * i / 5) - Math.PI / 2;
pulsePoints[i] = new Point(
(int)(center.X + (radius + 5) * Math.Cos(angle)),
(int)(center.Y + (radius + 5) * Math.Sin(angle))
);
}
g.DrawPolygon(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulsePoints);
}
// 선택된 노드 강조
if (node == _selectedNode)
else if (node == _selectedNode)
{
g.DrawPolygon(_selectedNodePen, points);
}
@@ -531,8 +800,8 @@ namespace AGVNavigationCore.Controls
g.DrawPolygon(new Pen(Color.Gold, 2) { DashStyle = DashStyle.Dash }, pulsePoints);
}
// 호버된 노드 강조
if (node == _hoveredNode)
// 호버된 노드 강조 (드래그 중이 아닐 때만)
if (node == _hoveredNode && !isDraggingThisNode)
{
// 확장된 5각형 계산
var hoverPoints = new Point[5];
@@ -556,7 +825,11 @@ namespace AGVNavigationCore.Controls
private void DrawTriangleNodeShape(Graphics g, MapNode node, Brush brush)
{
var radius = NODE_RADIUS;
// 드래그 중인 노드는 약간 크게 그리기
bool isDraggingThisNode = _isDragging && node == _selectedNode;
int radiusAdjustment = isDraggingThisNode ? 4 : 0;
var radius = NODE_RADIUS + radiusAdjustment;
var center = node.Position;
// 삼각형 꼭짓점 계산 (위쪽 꼭짓점부터 시계방향)
@@ -570,12 +843,43 @@ namespace AGVNavigationCore.Controls
);
}
// 드래그 중인 노드의 그림자 효과
if (isDraggingThisNode)
{
var shadowPoints = new Point[3];
for (int i = 0; i < 3; i++)
{
shadowPoints[i] = new Point(points[i].X + 3, points[i].Y + 3);
}
using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
{
g.FillPolygon(shadowBrush, shadowPoints);
}
}
// 삼각형 그리기
g.FillPolygon(brush, points);
g.DrawPolygon(Pens.Black, points);
// 드래그 중인 노드 강조 (가장 강력한 효과)
if (isDraggingThisNode)
{
// 청록색 두꺼운 테두리
g.DrawPolygon(new Pen(Color.Cyan, 3), points);
// 펄스 효과
var pulsePoints = new Point[3];
for (int i = 0; i < 3; i++)
{
var angle = (Math.PI * 2 * i / 3) - Math.PI / 2;
pulsePoints[i] = new Point(
(int)(center.X + (radius + 5) * Math.Cos(angle)),
(int)(center.Y + (radius + 5) * Math.Sin(angle))
);
}
g.DrawPolygon(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulsePoints);
}
// 선택된 노드 강조
if (node == _selectedNode)
else if (node == _selectedNode)
{
g.DrawPolygon(_selectedNodePen, points);
}
@@ -599,8 +903,8 @@ namespace AGVNavigationCore.Controls
g.DrawPolygon(new Pen(Color.Gold, 2) { DashStyle = DashStyle.Dash }, pulsePoints);
}
// 호버된 노드 강조
if (node == _hoveredNode)
// 호버된 노드 강조 (드래그 중이 아닐 때만)
if (node == _hoveredNode && !isDraggingThisNode)
{
// 확장된 삼각형 계산
var hoverPoints = new Point[3];
@@ -662,7 +966,7 @@ namespace AGVNavigationCore.Controls
var descPoint = new Point(
(int)(node.Position.X - descSize.Width / 2),
(int)(node.Position.Y + NODE_RADIUS + 2)
);
);
// 설명 텍스트 그리기 (설명이 있는 경우에만)
if (!string.IsNullOrEmpty(descriptionText))
@@ -725,6 +1029,9 @@ namespace AGVNavigationCore.Controls
private void DrawLabelNode(Graphics g, MapNode node)
{
// 드래그 중인 노드 확인
bool isDraggingThisNode = _isDragging && node == _selectedNode;
var text = string.IsNullOrEmpty(node.LabelText) ? node.NodeId : node.LabelText;
// 폰트 설정
@@ -738,6 +1045,16 @@ namespace AGVNavigationCore.Controls
(int)(node.Position.Y - textSize.Height / 2)
);
// 드래그 중일 때 그림자 효과
if (isDraggingThisNode)
{
var shadowPoint = new Point(textPoint.X + 3, textPoint.Y + 3);
using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
{
g.DrawString(text, font, shadowBrush, shadowPoint);
}
}
// 배경 그리기 (설정된 경우)
if (node.ShowBackground)
{
@@ -756,8 +1073,24 @@ namespace AGVNavigationCore.Controls
// 텍스트 그리기
g.DrawString(text, font, textBrush, textPoint);
// 드래그 중인 노드 강조 (가장 강력한 효과)
if (isDraggingThisNode)
{
var dragPadding = node.Padding + 4;
var dragRect = new Rectangle(
textPoint.X - dragPadding,
textPoint.Y - dragPadding,
(int)textSize.Width + (dragPadding * 2),
(int)textSize.Height + (dragPadding * 2)
);
g.DrawRectangle(new Pen(Color.Cyan, 3), dragRect);
// 펄스 효과
var pulseRect = new Rectangle(dragRect.X - 2, dragRect.Y - 2, dragRect.Width + 4, dragRect.Height + 4);
g.DrawRectangle(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect);
}
// 선택된 노드 강조
if (node == _selectedNode)
else if (node == _selectedNode)
{
var selectionPadding = node.Padding + 2;
var selectionRect = new Rectangle(
@@ -769,8 +1102,8 @@ namespace AGVNavigationCore.Controls
g.DrawRectangle(_selectedNodePen, selectionRect);
}
// 호버된 노드 강조
if (node == _hoveredNode)
// 호버된 노드 강조 (드래그 중이 아닐 때만)
if (node == _hoveredNode && !isDraggingThisNode)
{
var hoverPadding = node.Padding + 4;
var hoverRect = new Rectangle(
@@ -788,6 +1121,9 @@ namespace AGVNavigationCore.Controls
private void DrawImageNode(Graphics g, MapNode node)
{
// 드래그 중인 노드 확인
bool isDraggingThisNode = _isDragging && node == _selectedNode;
// 이미지 로드 (필요시)
if (node.LoadedImage == null && !string.IsNullOrEmpty(node.ImagePath))
{
@@ -801,6 +1137,12 @@ namespace AGVNavigationCore.Controls
if (displaySize.IsEmpty)
displaySize = new Size(50, 50); // 기본 크기
// 드래그 중일 때 약간 크게 표시
if (isDraggingThisNode)
{
displaySize = new Size((int)(displaySize.Width * 1.1), (int)(displaySize.Height * 1.1));
}
var imageRect = new Rectangle(
node.Position.X - displaySize.Width / 2,
node.Position.Y - displaySize.Height / 2,
@@ -808,6 +1150,16 @@ namespace AGVNavigationCore.Controls
displaySize.Height
);
// 드래그 중일 때 그림자 효과
if (isDraggingThisNode)
{
var shadowRect = new Rectangle(imageRect.X + 3, imageRect.Y + 3, imageRect.Width, imageRect.Height);
using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
{
g.FillRectangle(shadowBrush, shadowRect);
}
}
// 회전이 있는 경우
if (node.Rotation != 0)
{
@@ -855,14 +1207,21 @@ namespace AGVNavigationCore.Controls
}
}
// 드래그 중인 노드 강조 (가장 강력한 효과)
if (isDraggingThisNode)
{
g.DrawRectangle(new Pen(Color.Cyan, 3), imageRect);
var pulseRect = new Rectangle(imageRect.X - 3, imageRect.Y - 3, imageRect.Width + 6, imageRect.Height + 6);
g.DrawRectangle(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect);
}
// 선택된 노드 강조
if (node == _selectedNode)
else if (node == _selectedNode)
{
g.DrawRectangle(_selectedNodePen, imageRect);
}
// 호버된 노드 강조
if (node == _hoveredNode)
// 호버된 노드 강조 (드래그 중이 아닐 때만)
if (node == _hoveredNode && !isDraggingThisNode)
{
var hoverRect = new Rectangle(imageRect.X - 2, imageRect.Y - 2, imageRect.Width + 4, imageRect.Height + 4);
g.DrawRectangle(new Pen(Color.Orange, 2), hoverRect);
@@ -872,13 +1231,24 @@ namespace AGVNavigationCore.Controls
else
{
// 이미지가 없는 경우 기본 사각형으로 표시
int sizeAdjustment = isDraggingThisNode ? 5 : 0;
var rect = new Rectangle(
node.Position.X - 25,
node.Position.Y - 25,
50,
50
node.Position.X - 25 - sizeAdjustment,
node.Position.Y - 25 - sizeAdjustment,
50 + sizeAdjustment * 2,
50 + sizeAdjustment * 2
);
// 드래그 중일 때 그림자 효과
if (isDraggingThisNode)
{
var shadowRect = new Rectangle(rect.X + 3, rect.Y + 3, rect.Width, rect.Height);
using (var shadowBrush = new SolidBrush(Color.FromArgb(100, 0, 0, 0)))
{
g.FillRectangle(shadowBrush, shadowRect);
}
}
g.FillRectangle(Brushes.LightGray, rect);
g.DrawRectangle(Pens.Black, rect);
@@ -893,14 +1263,21 @@ namespace AGVNavigationCore.Controls
g.DrawString(text, font, Brushes.Black, textPoint);
font.Dispose();
// 드래그 중인 노드 강조 (가장 강력한 효과)
if (isDraggingThisNode)
{
g.DrawRectangle(new Pen(Color.Cyan, 3), rect);
var pulseRect = new Rectangle(rect.X - 3, rect.Y - 3, rect.Width + 6, rect.Height + 6);
g.DrawRectangle(new Pen(Color.FromArgb(150, 0, 255, 255), 2) { DashStyle = DashStyle.Dash }, pulseRect);
}
// 선택된 노드 강조
if (node == _selectedNode)
else if (node == _selectedNode)
{
g.DrawRectangle(_selectedNodePen, rect);
}
// 호버된 노드 강조
if (node == _hoveredNode)
// 호버된 노드 강조 (드래그 중이 아닐 때만)
if (node == _hoveredNode && !isDraggingThisNode)
{
var hoverRect = new Rectangle(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4);
g.DrawRectangle(new Pen(Color.Orange, 2), hoverRect);
@@ -1600,6 +1977,7 @@ namespace AGVNavigationCore.Controls
g.DrawString(scaleText, font, Brushes.Black, scaleRect.X + 5, scaleRect.Y + 2);
}
// 측정 정보 (우하단 - 사용자 정의 정보가 있을 경우)
if (!string.IsNullOrEmpty(_measurementInfo))
{

View File

@@ -143,12 +143,17 @@ namespace AGVNavigationCore.Controls
if (hitNode != null)
{
_isDragging = true;
_isPanning = false; // 🔥 팬 모드 비활성화 - 중요!
_selectedNode = hitNode;
_dragStartPosition = hitNode.Position; // 원래 위치 저장 (고스트용)
_dragOffset = new Point(
worldPoint.X - hitNode.Position.X,
worldPoint.Y - hitNode.Position.Y
);
_mouseMoveCounter = 0; // 디버그: 카운터 리셋
Cursor = Cursors.SizeAll;
Capture = true; // 🔥 마우스 캡처 활성화 - 이게 핵심!
//System.Diagnostics.Debug.WriteLine($"MouseDown: 드래그 시작! Capture={Capture}, isDragging={_isDragging}, isPanning={_isPanning}, Node={hitNode.NodeId}");
Invalidate();
return;
}
@@ -158,6 +163,7 @@ namespace AGVNavigationCore.Controls
_isPanning = true;
_lastMousePosition = e.Location;
Cursor = Cursors.SizeAll;
Capture = true; // 🔥 마우스 캡처 활성화
}
else if (e.Button == MouseButtons.Middle)
{
@@ -165,6 +171,7 @@ namespace AGVNavigationCore.Controls
_isPanning = true;
_lastMousePosition = e.Location;
Cursor = Cursors.Hand;
Capture = true; // 🔥 마우스 캡처 활성화
}
else if (e.Button == MouseButtons.Right)
{
@@ -181,6 +188,12 @@ namespace AGVNavigationCore.Controls
{
var worldPoint = ScreenToWorld(e.Location);
// 디버그: MouseMove 카운터 증가
if (_isDragging)
{
_mouseMoveCounter++;
}
// 호버 노드 업데이트
var newHoveredNode = GetNodeAt(worldPoint);
if (newHoveredNode != _hoveredNode)
@@ -191,21 +204,25 @@ namespace AGVNavigationCore.Controls
if (_isPanning)
{
// 팬 처리
// 팬 처리 - 줌 레벨 보정 적용
var deltaX = e.X - _lastMousePosition.X;
var deltaY = e.Y - _lastMousePosition.Y;
_panOffset.X += deltaX;
_panOffset.Y += deltaY;
// 🔥 스크린 좌표 델타를 월드 좌표 델타로 변환 (줌 레벨 보정)
// PointF로 float 정밀도 유지 (소수점 손실 방지)
_panOffset.X += deltaX / _zoomFactor;
_panOffset.Y += deltaY / _zoomFactor;
_lastMousePosition = e.Location;
Invalidate();
Update(); // 팬도 실시간 업데이트
}
else if (_isDragging && _canvasMode == CanvasMode.Edit)
{
// 노드 드래그
if (_selectedNode != null)
{
var oldPosition = _selectedNode.Position;
var newPosition = new Point(
worldPoint.X - _dragOffset.X,
worldPoint.Y - _dragOffset.Y
@@ -222,6 +239,7 @@ namespace AGVNavigationCore.Controls
NodeMoved?.Invoke(this, _selectedNode);
MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate();
Update(); // 🔥 즉시 Paint 이벤트 처리하여 화면 업데이트
}
}
else if (_isConnectionMode && _canvasMode == CanvasMode.Edit)
@@ -242,12 +260,14 @@ namespace AGVNavigationCore.Controls
if (_isDragging && _canvasMode == CanvasMode.Edit)
{
_isDragging = false;
Capture = false; // 🔥 마우스 캡처 해제
Cursor = GetCursorForMode(_editMode);
}
if (_isPanning)
{
_isPanning = false;
Capture = false; // 🔥 마우스 캡처 해제
Cursor = Cursors.Default;
}
}
@@ -257,6 +277,7 @@ namespace AGVNavigationCore.Controls
if (_isPanning)
{
_isPanning = false;
Capture = false; // 🔥 마우스 캡처 해제
Cursor = Cursors.Default;
}
}
@@ -896,7 +917,7 @@ namespace AGVNavigationCore.Controls
public void ResetZoom()
{
_zoomFactor = 1.0f;
_panOffset = Point.Empty;
_panOffset = PointF.Empty;
Invalidate();
}

View File

@@ -88,15 +88,17 @@ namespace AGVNavigationCore.Controls
// 편집 관련 (EditMode에서만 사용)
private bool _isDragging;
private Point _dragOffset;
private Point _dragStartPosition; // 드래그 시작 위치 (고스트 표시용)
private Point _lastMousePosition;
private bool _isConnectionMode;
private MapNode _connectionStartNode;
private Point _connectionEndPoint;
private int _mouseMoveCounter = 0; // 디버그용: MouseMove 실행 횟수
// 그리드 및 줌 관련
private bool _showGrid = true;
private float _zoomFactor = 1.0f;
private Point _panOffset = Point.Empty;
private PointF _panOffset = PointF.Empty; // float 정밀도로 변경 (팬 이동 정확도 개선)
private bool _isPanning;
// 자동 증가 카운터