NoteMAUI/GraphPage.xaml.cs
2024-11-11 17:59:20 +03:00

301 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Diagnostics;
using System.Linq;
namespace NotesDataAnalyst
{
public partial class GraphPage : ContentPage
{
private Point _initialPanPosition;
private PointF _initialNodePosition;
private PointF _initialTouchPosition;
private SizeF _initialNodeSize;
private readonly List<GraphNode> _nodes = new();
private readonly List<GraphConnection> _connections = new();
private GraphNode _selectedNode;
private bool _isScrollingBlocked = false;
private double _scale = 1.0;
private double _initialScale = 1.0;
private bool _isNodeBeingMoved = false;
private const double SmoothFactor = 0.85; // Коэффициент сглаживания
private Point _lastTranslation;
// Переменная для хранения центральной точки жеста (между двумя пальцами при старте масштабирования)
private Point _gestureCenter;
private Point _canvasTranslation = new Point(0, 0);
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
public GraphPage()
{
InitializeComponent();
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);
// Добавляем Pinch только один раз
if (!canvasView.GestureRecognizers.OfType<PinchGestureRecognizer>().Any())
{
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
canvasView.GestureRecognizers.Add(pinchGesture);
}
}
// Обработчик жеста перемещения (Pan) для перемещения канвы или ноды
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (_selectedNode != null)
{
// Начало перетаскивания
if (e.StatusType == GestureStatus.Started)
{
// Запоминаем начальную позицию касания и ноды
_initialTouchPosition = new Point(e.TotalX, e.TotalY);
_initialNodePosition = _selectedNode.Position;
}
// Перемещение
else if (e.StatusType == GestureStatus.Running)
{
var deltaX = e.TotalX - _initialTouchPosition.X; // Разница с начальной точкой
var deltaY = e.TotalY - _initialTouchPosition.Y;
// Перемещаем ноду относительно её начальной позиции
_selectedNode.Position = new Point(
_initialNodePosition.X + deltaX,
_initialNodePosition.Y + deltaY
);
canvasView.Invalidate(); // Перерисовываем канву
}
// Завершение перетаскивания
else if (e.StatusType == GestureStatus.Completed)
{
// Можно добавить логику на завершение перетаскивания, например, включить прокрутку
//scrollView.IsEnabled = true;
}
}
else // Перемещение канвы при снятии выделения с ноды
{
if (e.StatusType == GestureStatus.Started)
{
_initialPanPosition = new Point(canvasView.TranslationX - e.TotalX, canvasView.TranslationY - e.TotalY);
}
else if (e.StatusType == GestureStatus.Running)
{
var deltaX = e.TotalX;
var deltaY = e.TotalY;
// Применяем сглаживание к движению
double smoothedX = _lastTranslation.X + SmoothFactor * (deltaX - _lastTranslation.X);
double smoothedY = _lastTranslation.Y + SmoothFactor * (deltaY - _lastTranslation.Y);
// Обновляем положение канвы
canvasView.TranslationX = _initialPanPosition.X + smoothedX;
canvasView.TranslationY = _initialPanPosition.Y + smoothedY;
// Сохраняем последнюю смягченную позицию для следующего цикла
_lastTranslation = new Point(smoothedX, smoothedY);
}
else if (e.StatusType == GestureStatus.Completed)
{
_initialPanPosition = new Point(canvasView.TranslationX, canvasView.TranslationY);
canvasView.Invalidate();
}
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = Math.Clamp(targetX, -Content.Width * (currentScale - 1), 0);
Content.TranslationY = Math.Clamp(targetY, -Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
private async void OnEditTextClicked(object sender, EventArgs e)
{
// Проверяем, выбрана ли нода
if (_selectedNode == null)
{
await DisplayAlert("Ошибка", "Сначала выберите ноду", "OK");
return;
}
// Показываем поле для редактирования текста и заполняем его текущим текстом ноды
textEditor.IsVisible = true;
textEditor.Text = _selectedNode.Text;
// Ставим фокус на текстовое поле для ввода
textEditor.Focus();
}
// Обработчик завершения редактирования текста
private void OnTextEditorCompleted(object sender, EventArgs e)
{
if (_selectedNode != null)
{
// Обновляем текст ноды с введенным значением
_selectedNode.Text = textEditor.Text;
// Скрываем поле для редактирования текста
textEditor.IsVisible = false;
// Перерисовываем канву для обновления отображения текста
canvasView.Invalidate();
}
}
private async void OnResizeNodeClicked(object sender, EventArgs e)
{
// Проверяем, выбрана ли нода
if (_selectedNode == null)
{
await DisplayAlert("Ошибка", "Сначала выберите ноду", "OK");
return;
}
// Открываем диалог для ввода новых размеров
var result = await DisplayPromptAsync("Изменить размер", "Введите ширину и высоту",
initialValue: $"{_selectedNode.Size.Width} x {_selectedNode.Size.Height}",
keyboard: Keyboard.Numeric);
if (string.IsNullOrEmpty(result)) return;
// Разбиваем введенное значение на ширину и высоту
var dimensions = result.Split('x');
if (dimensions.Length == 2 &&
double.TryParse(dimensions[0].Trim(), out double width) &&
double.TryParse(dimensions[1].Trim(), out double height))
{
// Обновляем размеры ноды
_selectedNode.Size = new Size(width, height);
canvasView.Invalidate(); // Перерисовываем канву
}
else
{
await DisplayAlert("Ошибка", "Неверный формат. Введите значения в формате ширина x высота.", "OK");
}
}
private async void OnAddNodeClicked(object sender, EventArgs e)
{
var newNode = new GraphNode
{
Text = "Новая нода",
Position = new Point(100, 200),
Size = new Size(100, 50),
};
_nodes.Add(newNode);
canvasView.Invalidate();
}
private void OnChildClicked(object sender, EventArgs e)
{
if (_selectedNode != null)
{
var childNode = new GraphNode
{
Text = "Child Node",
Position = new Point(_selectedNode.Position.X + 100, _selectedNode.Position.Y + 70),
Size = _selectedNode.Size,
BackgroundColor = Colors.Gray,
};
_nodes.Add(childNode);
_connections.Add(new GraphConnection { FromNode = _selectedNode, ToNode = childNode });
canvasView.Invalidate();
_selectedNode = childNode;
}
else
{
Debug.WriteLine("Сначала выберите ноду");
}
}
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 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;
}
}
}