diff --git a/AppShell.xaml b/AppShell.xaml index ec82c4e..96d0e53 100644 --- a/AppShell.xaml +++ b/AppShell.xaml @@ -10,7 +10,7 @@ - + diff --git a/GraphPage.xaml.cs b/GraphPage.xaml.cs index e6c5873..7345952 100644 --- a/GraphPage.xaml.cs +++ b/GraphPage.xaml.cs @@ -1,18 +1,13 @@ -using System.Diagnostics; +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 PointF _initialNodePosition; + private PointF _initialTouchPosition; + private SizeF _initialNodeSize; private readonly List _nodes = new(); private readonly List _connections = new(); private GraphNode _selectedNode; @@ -22,13 +17,9 @@ namespace NotesDataAnalyst { InitializeComponent(); - - - // Íàñòðàèâàåì Drawable äëÿ îòðèñîâêè óçëîâ è ñâÿçåé var graphDrawable = new GraphDrawable(_nodes, _connections); canvasView.Drawable = graphDrawable; - // Äîáàâëÿåì æåñòû var panGesture = new PanGestureRecognizer(); panGesture.PanUpdated += OnPanUpdated; canvasView.GestureRecognizers.Add(panGesture); @@ -36,73 +27,6 @@ namespace NotesDataAnalyst 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) @@ -110,18 +34,38 @@ namespace NotesDataAnalyst var newNode = new GraphNode { - Text = "Íîâàÿ íîäà", + Text = "Ã�îâàÿ íîäà", Position = new Point(300, 200), Size = new Size(200, 200), - + }; _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(); + } + else + { + Debug.WriteLine("Сначала выберите ноду"); + } + } + + private void OnCanvasTapped(object sender, TappedEventArgs e) { var tapPosition = e.GetPosition((View)sender); @@ -132,37 +76,27 @@ namespace NotesDataAnalyst 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); + _initialNodeSize = _selectedNode.Size; + _isResizing = IsOnResizeHandle(_selectedNode, _initialTouchPosition); } else if (e.StatusType == GestureStatus.Running) { - // Âû÷èñëÿåì ñìåùåíèå îò íà÷àëüíîé ïîçèöèè êàñàíèÿ var deltaX = (float)e.TotalX - _initialTouchPosition.X; var deltaY = (float)e.TotalY - _initialTouchPosition.Y; @@ -172,7 +106,6 @@ namespace NotesDataAnalyst } else { - // Ïåðåìåùåíèå _selectedNode.Position = new PointF( _initialNodePosition.X + deltaX, _initialNodePosition.Y + deltaY @@ -187,86 +120,32 @@ namespace NotesDataAnalyst } } - // Èçìåíåíèå ðàçìåðà óçëà private void ResizeNode(GraphNode node, double deltaX, double deltaY) { - const float minSize = 50f; // Óáèðàåì "f" äëÿ double + const double minSize = 50.0; - // Óâåëè÷èâàåì èëè óìåíüøàåì ðàçìåð â çàâèñèìîñòè îò íàïðàâëåíèÿ 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 - ); - } + _selectedNode.Size = new Size( + Math.Max(minSize, _initialNodeSize.Width + deltaX), + Math.Max(minSize, _initialNodeSize.Height + deltaY) + ); } - - - - } - - private bool IsOnRightResizeHandle(GraphNode node, PointF point) + private bool IsOnResizeHandle(GraphNode node, PointF point) { - const float handleSize = 10; - return Math.Abs(point.X - (node.Position.X + node.Size.Width - 1)) <= handleSize; + const float handleSize = 20f; + + var resizeHandleRect = new RectF( + (float)(node.Position.X + node.Size.Width - handleSize), + (float)(node.Position.Y + node.Size.Height - handleSize), + handleSize, handleSize + ); + + return resizeHandleRect.Contains(point); } - private bool IsOnLeftResizeHandle(GraphNode node, PointF point) - { - const float handleSize = 10; - return Math.Abs(point.X - node.Position.X) <= handleSize; - } - - private bool IsOnTopResizeHandle(GraphNode node, PointF point) - { - const float handleSize = 10; - return Math.Abs(point.Y - node.Position.Y) <= handleSize; - } - - private bool IsOnBottomResizeHandle(GraphNode node, PointF point) - { - const float handleSize = 10; - 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 && @@ -274,42 +153,5 @@ namespace NotesDataAnalyst point.Y >= node.Position.Y && point.Y <= node.Position.Y + node.Size.Height; } - - - // Ïðîâåðêà, íàõîäèòñÿ ëè òî÷êà íà "ðó÷êå" èçìåíåíèÿ ðàçìåðà - private bool IsOnResizeHandle(GraphNode node, PointF point) - { - const float handleSize = 20f; // Ðàçìåð ðó÷êè èçìåíåíèÿ ðàçìåðà - - // Îïðåäåëÿåì ïðÿìîóãîëüíèêè äëÿ êàæäîé ðó÷êè - 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); - } - - } } - diff --git a/ResizableCanvasPage.xaml.cs b/ResizableCanvasPage.xaml.cs index 427b8d2..f206d16 100644 --- a/ResizableCanvasPage.xaml.cs +++ b/ResizableCanvasPage.xaml.cs @@ -1,5 +1,6 @@ using Microsoft.Maui.Controls; using Microsoft.Maui.Graphics; +using Microsoft.Maui.Layouts; using System; namespace NotesDataAnalyst @@ -9,98 +10,120 @@ namespace NotesDataAnalyst private const double InitialWidth = 150; private const double InitialHeight = 100; private double startX, startY; + private bool isResizing; public ResizableCanvasPage() { InitializeComponent(); } - // Ìåòîä äëÿ äîáàâëåíèÿ îáúåêòà íà õîëñò private void AddResizableObject(double x, double y) { - // Ñîçäàíèå Frame äëÿ ãðàôè÷åñêîãî îáúåêòà + // Ñîçäàåì îñíîâíîé êîíòåéíåð (Frame) äëÿ îáúåêòà è ðó÷êè var resizableFrame = new Frame { BackgroundColor = Colors.LightBlue, BorderColor = Colors.DarkBlue, CornerRadius = 8, WidthRequest = InitialWidth, - HeightRequest = InitialHeight + HeightRequest = InitialHeight, + Padding = 0, + HasShadow = true }; - // Îáðàáîòêà ïåðåìåùåíèÿ îáúåêòà - var panGesture = new PanGestureRecognizer(); - panGesture.PanUpdated += (s, e) => OnDragPanUpdated(s, e, resizableFrame); - resizableFrame.GestureRecognizers.Add(panGesture); + var content = new Label + { + Text = "Resizable Object", + HorizontalOptions = LayoutOptions.Center, + VerticalOptions = LayoutOptions.Center + }; - // Ñîçäàíèå ðó÷êè äëÿ èçìåíåíèÿ ðàçìåðà + resizableFrame.Content = content; + + // Ñîçäàåì ðó÷êó äëÿ èçìåíåíèÿ ðàçìåðà var resizeHandle = new BoxView { BackgroundColor = Colors.Gray, WidthRequest = 20, - HeightRequest = 20 + HeightRequest = 20, + HorizontalOptions = LayoutOptions.End, + VerticalOptions = LayoutOptions.End }; - // Îáðàáîòêà èçìåíåíèÿ ðàçìåðà îáúåêòà + // Ñîçäàåì ñëîé AbsoluteLayout äëÿ ðó÷êè è ôðåéìà + var layout = new AbsoluteLayout(); + AbsoluteLayout.SetLayoutBounds(resizableFrame, new Rect(0, 0, InitialWidth, InitialHeight)); + layout.Children.Add(resizableFrame); + + // Ïîçèöèîíèðóåì ðó÷êó + AbsoluteLayout.SetLayoutFlags(resizeHandle, AbsoluteLayoutFlags.PositionProportional); + AbsoluteLayout.SetLayoutBounds(resizeHandle, new Rect(1, 1, 20, 20)); + layout.Children.Add(resizeHandle); + + // Äîáàâëÿåì îáðàáîò÷èê ïåðåìåùåíèÿ + var panGesture = new PanGestureRecognizer(); + panGesture.PanUpdated += (s, e) => OnDragPanUpdated(s, e, layout); + resizableFrame.GestureRecognizers.Add(panGesture); + + // Äîáàâëÿåì îáðàáîò÷èê äëÿ èçìåíåíèÿ ðàçìåðà var resizePanGesture = new PanGestureRecognizer(); - resizePanGesture.PanUpdated += (s, e) => OnResizePanUpdated(s, e, resizableFrame, resizeHandle); + resizePanGesture.PanUpdated += (s, e) => OnResizePanUpdated(s, e, layout); resizeHandle.GestureRecognizers.Add(resizePanGesture); - // Ïîçèöèîíèðîâàíèå îáúåêòà è ðó÷êè - AbsoluteLayout.SetLayoutBounds(resizableFrame, new Rect(x, y, InitialWidth, InitialHeight)); - CanvasLayout.Children.Add(resizableFrame); - CanvasLayout.Children.Add(resizeHandle); - - UpdateResizeHandlePosition(resizableFrame, resizeHandle); + // Óñòàíàâëèâàåì íà÷àëüíóþ ïîçèöèþ + AbsoluteLayout.SetLayoutBounds(layout, new Rect(x, y, InitialWidth, InitialHeight)); + CanvasLayout.Children.Add(layout); } - // Îáðàáîòêà ïåðåìåùåíèÿ îáúåêòà - private void OnDragPanUpdated(object sender, PanUpdatedEventArgs e, Frame frame) + // Ëîãèêà ïåðåìåùåíèÿ + private void OnDragPanUpdated(object sender, PanUpdatedEventArgs e, AbsoluteLayout layout) { + if (isResizing) return; + if (e.StatusType == GestureStatus.Started) { - startX = AbsoluteLayout.GetLayoutBounds(frame).X; - startY = AbsoluteLayout.GetLayoutBounds(frame).Y; + startX = AbsoluteLayout.GetLayoutBounds(layout).X; + startY = AbsoluteLayout.GetLayoutBounds(layout).Y; } else if (e.StatusType == GestureStatus.Running) { double x = startX + e.TotalX; double y = startY + e.TotalY; - AbsoluteLayout.SetLayoutBounds(frame, new Rect(x, y, frame.WidthRequest, frame.HeightRequest)); - UpdateResizeHandlePosition(frame, CanvasLayout.Children[CanvasLayout.Children.IndexOf(frame) + 1] as BoxView); + + // Îáíîâëåíèå UI ÷åðåç Dispatcher + Dispatcher.Dispatch(() => + { + AbsoluteLayout.SetLayoutBounds(layout, new Rect(x, y, layout.WidthRequest, layout.HeightRequest)); + }); } } - // Îáðàáîòêà èçìåíåíèÿ ðàçìåðà îáúåêòà - private void OnResizePanUpdated(object sender, PanUpdatedEventArgs e, Frame frame, BoxView resizeHandle) + // Ëîãèêà èçìåíåíèÿ ðàçìåðà + private void OnResizePanUpdated(object sender, PanUpdatedEventArgs e, AbsoluteLayout layout) { + isResizing = true; if (e.StatusType == GestureStatus.Started) { - startX = frame.WidthRequest; - startY = frame.HeightRequest; + startX = layout.WidthRequest; + startY = layout.HeightRequest; } else if (e.StatusType == GestureStatus.Running) { double newWidth = Math.Max(50, startX + e.TotalX); double newHeight = Math.Max(50, startY + e.TotalY); - frame.WidthRequest = newWidth; - frame.HeightRequest = newHeight; - UpdateResizeHandlePosition(frame, resizeHandle); + Dispatcher.Dispatch(() => + { + layout.WidthRequest = newWidth; + layout.HeightRequest = newHeight; + }); + } + else if (e.StatusType == GestureStatus.Completed || e.StatusType == GestureStatus.Canceled) + { + isResizing = false; } } - // Ìåòîä äëÿ ôèêñàöèè ïîçèöèè ðó÷êè â óãëó îáúåêòà - private void UpdateResizeHandlePosition(Frame frame, BoxView resizeHandle) - { - var frameBounds = AbsoluteLayout.GetLayoutBounds(frame); - double x = frameBounds.X + frame.WidthRequest - resizeHandle.WidthRequest / 2; - double y = frameBounds.Y + frame.HeightRequest - resizeHandle.HeightRequest / 2; - - AbsoluteLayout.SetLayoutBounds(resizeHandle, new Rect(x, y, resizeHandle.WidthRequest, resizeHandle.HeightRequest)); - } - - // Äîáàâëåíèå îáúåêòîâ íà õîëñò ïðè çàïóñêå protected override void OnAppearing() { base.OnAppearing();