using System.Diagnostics; using System.Linq; namespace NotesDataAnalyst { public partial class GraphPage : ContentPage { private PointF _initialNodePosition; // Хранит начальную позицию ноды private PointF _initialTouchPosition; // Хранит начальную позицию касания private SizeF _initialNodeSize; // Хранит начальный размер ноды private DateTime tapStartTime; private const int longPressDuration = 3000; // 3 секунды в миллисекундах private readonly List _nodes = new(); private readonly List _connections = new(); private GraphNode _selectedNode; private bool _isResizing = false; public GraphPage() { InitializeComponent(); // Настраиваем Drawable для отрисовки узлов и связей var graphDrawable = new GraphDrawable(_nodes, _connections); canvasView.Drawable = graphDrawable; // Добавляем жесты var panGesture = new PanGestureRecognizer(); panGesture.PanUpdated += OnPanUpdated; canvasView.GestureRecognizers.Add(panGesture); var tapGesture = new TapGestureRecognizer(); tapGesture.Tapped += OnCanvasTapped; canvasView.GestureRecognizers.Add(tapGesture); var tapGestureRecognizer = new TapGestureRecognizer(); tapGestureRecognizer.Tapped += OnTapped; canvasView.GestureRecognizers.Add(tapGestureRecognizer); } private void OnTapped(object sender, EventArgs e) { tapStartTime = DateTime.Now; Device.StartTimer(TimeSpan.FromMilliseconds(longPressDuration), () => { if ((DateTime.Now - tapStartTime).TotalMilliseconds >= longPressDuration) { // Здесь выполняется действие при длительном нажатии OnNodeLongPressed(); } return false; // Останавливаем таймер }); } private void OnNodeLongPressed() { if (_selectedNode != null) { // Добавляем дочернюю ноду рядом с активной var childNode = new GraphNode { Text = "Дочерняя нода", Position = new Point(_selectedNode.Position.X + 50, _selectedNode.Position.Y + 50), Size = new Size(150, 100) }; _nodes.Add(childNode); _connections.Add(new GraphConnection { FromNode = _selectedNode, ToNode = childNode }); canvasView.Invalidate(); // Перерисовываем } // Здесь реализуйте действие при длительном нажатии // DisplayAlert("Длительное нажатие", "Вы нажали и удерживали элемент", "ОК"); } private void OnNodeTapped(object sender, TappedEventArgs e) { var node = _selectedNode; Debug.WriteLine("Нажата нода - {node}"); } private void OnChildClicked(object sender, EventArgs e) { if (_selectedNode != null) { var child = new GraphNode { Text = "Child Node", Position = new Point(_selectedNode.Position.X + 5, _selectedNode.Position.Y + 5), Size = _selectedNode.Size, BackgroundColor = Colors.Gray, }; _nodes.Add(child); _connections.Add(new GraphConnection { FromNode = _selectedNode, ToNode = child }); canvasView.Invalidate(); } else { Debug.WriteLine("Сначала выбирите ноду"); } } private async void OnAddNodeClicked(object sender, EventArgs e) { var newNode = new GraphNode { Text = "Новая нода", Position = new Point(300, 200), Size = new Size(200, 200), }; _nodes.Add(newNode); canvasView.Invalidate(); } // Обработчик для выбора узла // Обработчик для выбора узла private void OnCanvasTapped(object sender, TappedEventArgs e) { var tapPosition = e.GetPosition((View)sender); _selectedNode = null; if (tapPosition.HasValue) { var touchPoint = new PointF((float)tapPosition.Value.X, (float)tapPosition.Value.Y); _selectedNode = _nodes.FirstOrDefault(node => IsPointInsideNode(touchPoint, node)); // Сброс активности для всех узлов, кроме выбранного foreach (var node in _nodes) node.IsActive = node == _selectedNode; // Обновляем экран после изменения состояния узлов canvasView.Invalidate(); } } private void OnPanUpdated(object sender, PanUpdatedEventArgs e) { if (_selectedNode == null) return; if (e.StatusType == GestureStatus.Started) { // Сохраняем начальную позицию ноды и размер _initialNodePosition = _selectedNode.Position; _initialTouchPosition = new PointF((float)e.TotalX, (float)e.TotalY); _initialNodeSize = _selectedNode.Size; // Сохраняем начальный размер // Проверяем, находимся ли мы на ручке изменения размера _isResizing = IsOnRightResizeHandle(_selectedNode, _initialTouchPosition) || IsOnLeftResizeHandle(_selectedNode, _initialTouchPosition) || IsOnTopResizeHandle(_selectedNode, _initialTouchPosition) || IsOnBottomResizeHandle(_selectedNode, _initialTouchPosition); } else if (e.StatusType == GestureStatus.Running) { // Вычисляем смещение от начальной позиции касания var deltaX = (float)e.TotalX - _initialTouchPosition.X; var deltaY = (float)e.TotalY - _initialTouchPosition.Y; if (_isResizing) { ResizeNode(_selectedNode, deltaX, deltaY); } else { // Перемещение _selectedNode.Position = new PointF( _initialNodePosition.X + deltaX, _initialNodePosition.Y + deltaY ); } canvasView.Invalidate(); } else if (e.StatusType == GestureStatus.Completed) { _isResizing = false; } } // Изменение размера узла private void ResizeNode(GraphNode node, double deltaX, double deltaY) { const float minSize = 50f; // Убираем "f" для double // Увеличиваем или уменьшаем размер в зависимости от направления if (_isResizing) { // Если мы тянем правую "ручку", увеличиваем ширину if (IsOnRightResizeHandle(node, _initialTouchPosition)) { _selectedNode.Size = new Size( Math.Max(minSize, node.Size.Width + deltaX), // double _selectedNode.Size.Height // double ); } // Если мы тянем левую "ручку", уменьшаем ширину else if (IsOnLeftResizeHandle(node, _initialTouchPosition)) { _selectedNode.Size = new Size( Math.Max(minSize, _selectedNode.Size.Width - deltaX), // double _selectedNode.Size.Height // double ); // Перемещаем ноду влево, чтобы сохранить ее позицию _selectedNode.Position = new Point(_selectedNode.Position.X + deltaX, _selectedNode.Position.Y); // double } // Если мы тянем верхнюю "ручку", уменьшаем высоту else if (IsOnTopResizeHandle(node, _initialTouchPosition)) { _selectedNode.Size = new Size( _selectedNode.Size.Width, // double Math.Max(minSize, _selectedNode.Size.Height - deltaY) // double ); // Перемещаем ноду вверх, чтобы сохранить ее позицию _selectedNode.Position = new Point(_selectedNode.Position.X, _selectedNode.Position.Y + deltaY); // double } // Если мы тянем нижнюю "ручку", увеличиваем высоту else if (IsOnBottomResizeHandle(node, _initialTouchPosition)) { _selectedNode.Size = new Size( _selectedNode.Size.Width, // double Math.Max(minSize, _selectedNode.Size.Height + deltaY) // double ); } } } private bool IsOnRightResizeHandle(GraphNode node, PointF point) { const float handleSize = 400; return Math.Abs(point.X - (node.Position.X + node.Size.Width)) <= handleSize; } private bool IsOnLeftResizeHandle(GraphNode node, PointF point) { const float handleSize = 400; return Math.Abs(point.X - node.Position.X) <= handleSize; } private bool IsOnTopResizeHandle(GraphNode node, PointF point) { const float handleSize = 400; return Math.Abs(point.Y - node.Position.Y) <= handleSize; } private bool IsOnBottomResizeHandle(GraphNode node, PointF point) { const float handleSize = 400; return Math.Abs(point.Y - (node.Position.Y + node.Size.Height)) <= handleSize; } // Метод для проверки, находится ли точка внутри узла private bool IsPointInsideNode(PointF point, GraphNode node) { return point.X >= node.Position.X && point.X <= node.Position.X + node.Size.Width && point.Y >= node.Position.Y && point.Y <= node.Position.Y + node.Size.Height; } // Проверка, находится ли точка на "ручке" изменения размера private bool IsOnResizeHandle(GraphNode node, PointF point) { const float handleSize = 200f; // Размер ручки изменения размера // Определяем прямоугольники для каждой ручки var rightHandleRect = new RectF( (float)(node.Position.X + node.Size.Width - handleSize), (float)(node.Position.Y + node.Size.Height - handleSize), handleSize, handleSize); var leftHandleRect = new RectF( (float)(node.Position.X), (float)(node.Position.Y + node.Size.Height - handleSize), handleSize, handleSize); var topHandleRect = new RectF( (float)(node.Position.X + (node.Size.Width / 2) - (handleSize / 2)), (float)(node.Position.Y), handleSize, handleSize); var bottomHandleRect = new RectF( (float)(node.Position.X + (node.Size.Width / 2) - (handleSize / 2)), (float)(node.Position.Y + node.Size.Height - handleSize), handleSize, handleSize); // Проверяем, находится ли курсор на одной из ручек return rightHandleRect.Contains(point) || leftHandleRect.Contains(point) || topHandleRect.Contains(point) || bottomHandleRect.Contains(point); } } }