From c3f8c7117dae5e676883fc17cc4de1ffe7f36f25 Mon Sep 17 00:00:00 2001 From: backuppc Date: Mon, 3 Nov 2025 15:08:48 +0900 Subject: [PATCH] feat: Add real-time node drag preview and improve pan accuracy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../Controls/UnifiedAGVCanvas.Events.cs | 436 ++++++++++++++++-- .../Controls/UnifiedAGVCanvas.Mouse.cs | 29 +- .../Controls/UnifiedAGVCanvas.cs | 4 +- 3 files changed, 435 insertions(+), 34 deletions(-) diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs index 5a0e4fd..6aa1681 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Events.cs @@ -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 } } + /// + /// ๋“œ๋ž˜๊ทธ ๊ณ ์ŠคํŠธ ๊ทธ๋ฆฌ๊ธฐ - ์›๋ž˜ ์œ„์น˜์— ๋ฐ˜ํˆฌ๋ช… ๋…ธ๋“œ ํ‘œ์‹œ + /// + 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)) { diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs index efb0df7..ae488d0 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.Mouse.cs @@ -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(); } diff --git a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs index c340465..ee9a9ad 100644 --- a/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs +++ b/Cs_HMI/AGVLogic/AGVNavigationCore/Controls/UnifiedAGVCanvas.cs @@ -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; // ์ž๋™ ์ฆ๊ฐ€ ์นด์šดํ„ฐ