using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; using System.Windows.Threading; using CtrEditor.FuncionesBase; using CtrEditor.ObjetosSim; using Color = System.Windows.Media.Color; using System.Collections.ObjectModel; namespace CtrEditor { public class ObjectManipulationManager { private enum ResizeMode { None, Move, Rotate, ResizeWidth, ResizeHeight, ResizeBoth } private enum HandleMode { None, Move, ResizeWidth, ResizeHeight, ResizeBoth, Rotate } private ResizeMode _currentResizeMode; private readonly MainWindow _mainWindow; private readonly Canvas _canvas; private Point _lastMousePosition; private bool _isDrawingCanvas = false; private bool _isDraggingUserControl = false; private bool _isMovingUserControl = false; private UserControl _currentDraggingControl; private Point _startPointUserControl; private Point _transformedBoundingBoxCenter; private float _lastAngle; private List _resizeRectangles = new List(); private List _highlightRectangles = new List(); private System.Threading.Timer _timerRemoveHighlight = null; private Rectangle _currentDraggingRectangle; private dataDebug _dataDebug = new dataDebug(); private ObservableCollection _selectedObjects = new ObservableCollection(); public ObjectManipulationManager(MainWindow mainWindow, Canvas canvas) { _mainWindow = mainWindow; _canvas = canvas; } public ObservableCollection SelectedObjects { get => _selectedObjects; private set { _selectedObjects = value; UpdateSelectionVisuals(); } } private void UpdateSelectionVisuals() { RemoveResizeRectangles(); if (_selectedObjects.Any()) { AddResizeRectangles(_selectedObjects); } } public void SuscribirEventos(UserControl userControl) { userControl.MouseEnter += UserControl_MouseEnter; userControl.MouseLeave += UserControl_MouseLeave; userControl.Cursor = Cursors.Hand; userControl.MouseLeftButtonDown += UserControl_MouseLeftButtonDown; userControl.MouseLeftButtonUp += UserControl_MouseLeftButtonUp; userControl.MouseMove += UserControl_MouseMove; } private void UserControl_MouseEnter(object sender, MouseEventArgs e) { if (sender is UserControl userControl && userControl is IDataContainer dataContainer) { dataContainer.Highlight(true); if (_mainWindow.DataContext is MainViewModel viewModel) { var selectedObject = viewModel.SelectedItemOsList; if (selectedObject?.VisualRepresentation != userControl) { AddHighlightRectangles(userControl); } } } } private void UserControl_MouseLeave(object sender, MouseEventArgs e) { if (sender is UserControl userControl && userControl is IDataContainer dataContainer) dataContainer.Highlight(false); } public void AddResizeRectangles(IEnumerable selectedObjects) { double rectHighlightSize = 1; RemoveResizeRectangles(); // Calcular el bounding box que contenga todos los objetos seleccionados Rect boundingBox = CalculateTotalBoundingBox(selectedObjects); FuncionesBase.MutableRect rectBox = new FuncionesBase.MutableRect(boundingBox); rectBox.Left -= (float)rectHighlightSize; rectBox.Right += (float)rectHighlightSize; rectBox.Top -= (float)rectHighlightSize; rectBox.Bottom += (float)rectHighlightSize; _transformedBoundingBoxCenter = new Point( boundingBox.Left + boundingBox.Width / 2, boundingBox.Top + boundingBox.Height / 2 ); // Selection rectangle Rectangle selectionRect = CreateSelectionRectangle(rectBox, rectHighlightSize); _resizeRectangles.Add(selectionRect); _canvas.Children.Add(selectionRect); // Load rotation cursors Cursor rotationCursorRx = new Cursor(Application.GetResourceStream( new Uri("pack://application:,,,/CtrEditor;component/Icons/rotationRx.cur")).Stream); Cursor rotationCursorSx = new Cursor(Application.GetResourceStream( new Uri("pack://application:,,,/CtrEditor;component/Icons/rotationSx.cur")).Stream); // Add resize/rotation handles AddResizeHandles(rectBox, 10, rotationCursorRx, rotationCursorSx); } private Rect CalculateTotalBoundingBox(IEnumerable selectedObjects) { double left = double.MaxValue; double top = double.MaxValue; double right = double.MinValue; double bottom = double.MinValue; foreach (var obj in selectedObjects) { if (obj.VisualRepresentation != null && obj.Show_On_This_Page) { // Obtener el bounding box del objeto actual Rect objectBounds = VisualTreeHelper.GetDescendantBounds(obj.VisualRepresentation); GeneralTransform transform = obj.VisualRepresentation.TransformToAncestor(_canvas); Rect transformedBounds = transform.TransformBounds(objectBounds); // Actualizar los límites del bounding box total left = Math.Min(left, transformedBounds.Left); top = Math.Min(top, transformedBounds.Top); right = Math.Max(right, transformedBounds.Right); bottom = Math.Max(bottom, transformedBounds.Bottom); } } return new Rect( new Point(left, top), new Point(right, bottom) ); } private Rectangle CreateSelectionRectangle(FuncionesBase.MutableRect rectBox, double rectHighlightSize) { var rect = new Rectangle { Width = rectBox.Width + (rectHighlightSize * 2), Height = rectBox.Height + (rectHighlightSize * 2), Fill = Brushes.Transparent, Stroke = new SolidColorBrush(Color.FromArgb(180, 0, 120, 215)), StrokeThickness = 1.5, Tag = "Selection", IsHitTestVisible = false, StrokeDashArray = new DoubleCollection(new double[] { 3, 3 }) }; Canvas.SetLeft(rect, rectBox.Left - rectHighlightSize); Canvas.SetTop(rect, rectBox.Top - rectHighlightSize); Canvas.SetZIndex(rect, ((int)ZIndexEnum.RectangulosPropiead - 1)); return rect; } private void AddResizeHandles(FuncionesBase.MutableRect rectBox, double rectSize, Cursor rotationCursorRx, Cursor rotationCursorSx) { var positions = new List<(Point Position, string Tag, Cursor Cursor, Brush Stroke)> { (new Point(rectBox.Left, rectBox.Top), "TopLeft", Cursors.Arrow, Brushes.Gray), (new Point(rectBox.Right, rectBox.Top), "TopRight", rotationCursorRx, Brushes.Red), (new Point(rectBox.Left, rectBox.Bottom), "BottomLeft", rotationCursorSx, Brushes.DarkRed), (new Point(rectBox.Right, rectBox.Bottom), "BottomRight", Cursors.SizeNWSE, Brushes.Blue), (new Point(rectBox.Left + rectBox.Width / 2, rectBox.Top), "TopCenter", Cursors.Arrow, Brushes.Gray), (new Point(rectBox.Left + rectBox.Width / 2, rectBox.Bottom), "BottomCenter", Cursors.SizeNS, Brushes.Blue), (new Point(rectBox.Left, rectBox.Top + rectBox.Height / 2), "CenterLeft", rotationCursorRx, Brushes.Red), (new Point(rectBox.Right, rectBox.Top + rectBox.Height / 2), "CenterRight", Cursors.SizeWE, Brushes.Blue) }; foreach (var (Position, Tag, Cursor, Stroke) in positions) { var handle = CreateResizeHandle(rectSize, Tag, Cursor, Stroke); SetHandlePosition(handle, Position, rectSize); _resizeRectangles.Add(handle); _canvas.Children.Add(handle); } } private Rectangle CreateResizeHandle(double rectSize, string tag, Cursor cursor, Brush stroke) { var handle = new Rectangle { Width = rectSize, Height = rectSize, Fill = Brushes.Transparent, Stroke = stroke, StrokeThickness = 1, Tag = tag, Cursor = cursor }; handle.MouseLeftButtonDown += UserControl_MouseLeftButtonDown; handle.MouseMove += UserControl_MouseMove; handle.MouseLeftButtonUp += UserControl_MouseLeftButtonUp; handle.MouseLeave += ResizeRectangle_MouseLeave; Canvas.SetZIndex(handle, (int)ZIndexEnum.RectangulosPropiead); return handle; } private void SetHandlePosition(Rectangle handle, Point position, double rectSize) { if (!double.IsInfinity(position.X) && !double.IsNaN(position.X)) Canvas.SetLeft(handle, position.X - rectSize / 2); if (!double.IsInfinity(position.Y) && !double.IsNaN(position.Y)) Canvas.SetTop(handle, position.Y - rectSize / 2); } public void RemoveResizeRectangles() { if (_resizeRectangles == null || _resizeRectangles.Count == 0) return; foreach (var rect in _resizeRectangles) { _canvas.Children.Remove(rect); } _resizeRectangles.Clear(); } public void MakeResizeRectanglesTransparent() { if (_resizeRectangles == null || _resizeRectangles.Count == 0) return; foreach (var rect in _resizeRectangles) { rect.Opacity = 0; } } public void MakeResizeRectanglesNormal() { if (_resizeRectangles == null || _resizeRectangles.Count == 0) return; foreach (var rect in _resizeRectangles) { rect.Opacity = 1; } } private void HandleObjectSelection(UserControl userControl, osBase datos) { var viewModel = _mainWindow.DataContext as MainViewModel; if (viewModel == null) return; bool isControlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); if (viewModel.IsMultiSelectionActive) { if (isControlPressed) { // Deseleccionar objeto si está presionada la tecla Ctrl if (_selectedObjects.Contains(datos)) { DeselectObject(datos); } } else if (!_selectedObjects.Contains(datos)) { // Seleccionar objeto si no está ya seleccionado SelectObject(datos); } } else { // Modo selección única ClearSelection(); SelectObject(datos); } viewModel.SelectedItemOsList = datos; UpdateSelectionVisuals(); } public void SelectObject(osBase obj) { if (!_selectedObjects.Contains(obj)) { _selectedObjects.Add(obj); obj.IsSelected = true; } } public void DeselectObject(osBase obj) { if (_selectedObjects.Contains(obj)) { _selectedObjects.Remove(obj); obj.IsSelected = false; } } public void ClearSelection() { foreach (var obj in _selectedObjects.ToList()) { DeselectObject(obj); } RemoveResizeRectangles(); if (_mainWindow.DataContext is MainViewModel viewModel) { viewModel.SelectedItemOsList = null; } } public void OnMultiSelectionModeChanged(bool isActive) { if (!isActive) { // Mantener solo el último objeto seleccionado if (_selectedObjects.Count > 1) { var lastSelected = _selectedObjects.Last(); ClearSelection(); SelectObject(lastSelected); } } } private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (!_isDrawingCanvas) { if (_resizeRectangles != null && _resizeRectangles.Contains(sender)) { _currentDraggingRectangle = sender as Rectangle; _lastMousePosition = e.GetPosition(_canvas); _currentDraggingRectangle.CaptureMouse(); _isMovingUserControl = true; _startPointUserControl = e.GetPosition(_canvas); e.Handled = true; return; } var userControl = sender as UserControl; if (userControl != null) { if (userControl.DataContext is osBase datos) { HandleObjectSelection(userControl, datos); } // Solo iniciar el arrastre si no se presionó Ctrl if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))) { userControl.CaptureMouse(); _currentDraggingControl = userControl; _isMovingUserControl = true; InitializeDrag(e); } } } } private void InitializeDrag(MouseButtonEventArgs e) { _isDraggingUserControl = true; _startPointUserControl = e.GetPosition(_canvas); } private void UserControl_MouseMove(object sender, MouseEventArgs e) { if (_isMovingUserControl) { var currentPosition = e.GetPosition(_canvas); MakeResizeRectanglesTransparent(); if (_isDraggingUserControl) { HandleDrag(currentPosition); } else if (_currentDraggingRectangle != null) { string resizeDirection = _currentDraggingRectangle.Tag as string; if (resizeDirection != null) { var mode = DetermineHandleMode(resizeDirection); switch (mode) { case HandleMode.Rotate: HandleRotation(currentPosition); break; case HandleMode.ResizeWidth: case HandleMode.ResizeHeight: case HandleMode.ResizeBoth: HandleResize(currentPosition, mode); break; } } } } } private void HandleDrag(Point currentPosition) { var dx = currentPosition.X - _startPointUserControl.X; var dy = currentPosition.Y - _startPointUserControl.Y; foreach (var selectedObject in _selectedObjects) { var newX = Canvas.GetLeft(selectedObject.VisualRepresentation) + dx; var newY = Canvas.GetTop(selectedObject.VisualRepresentation) + dy; selectedObject.Move((float)newX, (float)newY); } _startPointUserControl = currentPosition; } private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (_isMovingUserControl) { if (sender is UserControl userControl) { userControl.ReleaseMouseCapture(); } else if (sender is Rectangle rectangle) { rectangle.ReleaseMouseCapture(); } UpdateSelectionVisuals(); _isDraggingUserControl = false; _isMovingUserControl = false; _currentDraggingRectangle = null; _currentResizeMode = ResizeMode.None; _lastAngle = 0; } MarkUnsavedChanges(); } private void MarkUnsavedChanges() { if (_mainWindow.DataContext is MainViewModel viewModel) { if (_isMovingUserControl || _isDraggingUserControl) { viewModel.HasUnsavedChanges = true; } } } private void ResizeRectangle_MouseLeave(object sender, MouseEventArgs e) { var rect = sender as Rectangle; if (rect != null) { rect.Fill = Brushes.Transparent; } } public void AddHighlightRectangles(UserControl userControl) { double rectSize = 1; RemoveHighlightRectangles(); if (userControl is IDataContainer dataContainer && dataContainer.Datos is osBase mvBase && mvBase.Show_On_This_Page) { Rect boundingBox = VisualTreeHelper.GetDescendantBounds(userControl); GeneralTransform transform = userControl.TransformToAncestor(_canvas); Rect transformedBoundingBox = transform.TransformBounds(boundingBox); FuncionesBase.MutableRect rectBox = new FuncionesBase.MutableRect(transformedBoundingBox); rectBox.Left -= (float)rectSize; rectBox.Right += (float)rectSize; rectBox.Top -= (float)rectSize; rectBox.Bottom += (float)rectSize; Rectangle highlightRect = new Rectangle { Width = rectBox.Width, Height = rectBox.Height, Fill = Brushes.Transparent, Stroke = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)), StrokeThickness = 1, Tag = "Highlight", IsHitTestVisible = false, StrokeDashArray = new DoubleCollection(new double[] { 2, 2 }) }; Canvas.SetLeft(highlightRect, rectBox.Left); Canvas.SetTop(highlightRect, rectBox.Top); // Corregido: usando rectBox.Top en lugar de highlightRect.Top _highlightRectangles.Add(highlightRect); _canvas.Children.Add(highlightRect); Canvas.SetZIndex(highlightRect, ((int)ZIndexEnum.RectangulosPropiead)); if (_timerRemoveHighlight == null) _timerRemoveHighlight = new System.Threading.Timer(TimerCallbackRemoveHighlight, null, Timeout.Infinite, Timeout.Infinite); _timerRemoveHighlight.Change(2000, Timeout.Infinite); } } private void TimerCallbackRemoveHighlight(object state) { if (Application.Current != null) { Application.Current.Dispatcher.Invoke(RemoveHighlightRectangles); } } public void RemoveHighlightRectangles() { if (_highlightRectangles == null || _highlightRectangles.Count == 0) return; foreach (var rect in _highlightRectangles) { _canvas.Children.Remove(rect); } _highlightRectangles.Clear(); } private HandleMode DetermineHandleMode(string resizeDirection) { return resizeDirection switch { "TopLeft" => HandleMode.None, "TopRight" or "BottomLeft" or "CenterLeft" => HandleMode.Rotate, "BottomRight" => HandleMode.ResizeBoth, "TopCenter" => HandleMode.None, "BottomCenter" => HandleMode.ResizeHeight, "CenterRight" => HandleMode.ResizeWidth, _ => HandleMode.None }; } private void HandleResize(Point currentPosition, HandleMode mode) { foreach (var selectedObject in _selectedObjects) { bool resizeWidth = mode == HandleMode.ResizeWidth || mode == HandleMode.ResizeBoth; bool resizeHeight = mode == HandleMode.ResizeHeight || mode == HandleMode.ResizeBoth; HandleResizeForObject(selectedObject, currentPosition, resizeWidth, resizeHeight); } _startPointUserControl = currentPosition; } private void HandleResizeForObject(osBase obj, Point currentPosition, bool activateSizeWidth, bool activateSizeHeight) { double widthChange = activateSizeWidth ? currentPosition.X - _startPointUserControl.X : 0; double heightChange = activateSizeHeight ? currentPosition.Y - _startPointUserControl.Y : 0; obj.Resize(widthChange, heightChange); } private void HandleRotation(Point currentPosition) { // Calcular el ángulo respecto al centro del bounding box que contiene todos los objetos seleccionados double deltaX = currentPosition.X - _transformedBoundingBoxCenter.X; double deltaY = currentPosition.Y - _transformedBoundingBoxCenter.Y; double angle = Math.Atan2(deltaY, deltaX) * (180 / Math.PI); if ((_lastAngle == 0 && angle != 0) || Math.Abs(angle - _lastAngle) > 45) { _lastAngle = (float)angle; } else { double deltaAngle = angle - _lastAngle; foreach (var selectedObject in _selectedObjects) { selectedObject.Rotate(deltaAngle); } _lastAngle = (float)angle; } } } }