From 3dab570f5dbb5430d74026b9a987ded64dd697db Mon Sep 17 00:00:00 2001 From: Miguel <miguel.vera.csa@gmail.com> Date: Tue, 18 Feb 2025 18:08:55 +0100 Subject: [PATCH] Multiseleccion funcionando --- MainViewModel.cs | 19 +- MainWindow.xaml | 5 +- MainWindow.xaml.cs | 884 +++++++---------------------------- ObjectManipulationManager.cs | 616 ++++++++++++++++++++++++ ObjetosSim/osBase.cs | 2 + 5 files changed, 799 insertions(+), 727 deletions(-) create mode 100644 ObjectManipulationManager.cs diff --git a/MainViewModel.cs b/MainViewModel.cs index 285a2f6..84e0b19 100644 --- a/MainViewModel.cs +++ b/MainViewModel.cs @@ -112,8 +112,17 @@ namespace CtrEditor private bool habilitarEliminarUserControl; private MainWindow mainWindow; + private ObjectManipulationManager _objectManager; // Add this line - public MainWindow MainWindow { get => mainWindow; set => mainWindow = value; } + public MainWindow MainWindow + { + get => mainWindow; + set + { + mainWindow = value; + _objectManager = mainWindow._objectManager; // Initialize _objectManager + } + } [ObservableProperty] public ICollectionView vistaFiltrada; @@ -273,6 +282,14 @@ namespace CtrEditor [ObservableProperty] public ObservableCollection<osBase> objetosSimulables; + [ObservableProperty] + private bool isMultiSelectionActive; + + partial void OnIsMultiSelectionActiveChanged(bool value) + { + _objectManager?.OnMultiSelectionModeChanged(value); + } + partial void OnObjetosSimulablesChanged(ObservableCollection<osBase> value) { VistaFiltrada = CollectionViewSource.GetDefaultView(ObjetosSimulables); diff --git a/MainWindow.xaml b/MainWindow.xaml index 921e08e..f54816e 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -155,8 +155,9 @@ <ScrollViewer Grid.Row="1" x:Name="ImagenEnTrabajoScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" PanningMode="Both"> - <Canvas x:Name="ImagenEnTrabajoCanvas" Margin="0"> - <!-- El Margin puede ser ajustado según el espacio adicional que quieras proporcionar --> + <Canvas x:Name="ImagenEnTrabajoCanvas" Margin="0" Background="Transparent"> + <!-- Agregar Background="Transparent" para que capture los eventos del mouse y --> + <!-- asegurar que el Canvas reciba los eventos del botón derecho --> <Canvas.RenderTransform> <TransformGroup> <ScaleTransform x:Name="CanvasScaleTransform" ScaleX="1" ScaleY="1" /> diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index f4ef896..684027e 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -4,25 +4,19 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; -using MouseEventArgs = System.Windows.Input.MouseEventArgs; -using UserControl = System.Windows.Controls.UserControl; -using CtrEditor.ObjetosSim; using System.Windows.Threading; using System.Diagnostics; -using CtrEditor.ObjetosSim.UserControls; -using DocumentFormat.OpenXml.Spreadsheet; -using System.Windows.Shapes; -using System.Numerics; -using CommunityToolkit.Mvvm.Input; +using CtrEditor.ObjetosSim; using CtrEditor.FuncionesBase; -using Color = System.Windows.Media.Color; +using Xceed.Wpf.Toolkit.PropertyGrid; +using MouseEventArgs = System.Windows.Input.MouseEventArgs; +using UserControl = System.Windows.Controls.UserControl; namespace CtrEditor { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> - /// public partial class MainWindow : Window { // Para el Canvas @@ -30,11 +24,9 @@ namespace CtrEditor private bool _isDrawingCanvas = false; private bool _isDraggingCanvas = false; private Image imagenDeFondo; + internal ObjectManipulationManager _objectManager; - private List<Rectangle> resizeRectangles = new List<Rectangle>(); - private List<Rectangle> highlightRectangles = new List<Rectangle>(); // Nueva lista para los rectángulos de resaltado - private System.Threading.Timer timerRemoveHighlight = null; - + // Temporizadores y animación private DispatcherTimer _zoomTimer; private double _targetZoomFactor; private double _initialZoomFactor; @@ -45,33 +37,32 @@ namespace CtrEditor private const double MinZoomScale = 0.1; // Límite mínimo de zoom private Stopwatch _stopwatch; - // Para los UserControl - private Point _startPointUserControl; - private UserControl _currentDraggingControl; - private bool _isRotatingUserControl = false; - private bool _isResizingUserControl = false; - private bool _isDraggingUserControl = false; - private bool _isMovingUserControl = false; - private TextBlock _angleDisplayTextBlock; + private dataDebug dataDebug = new dataDebug(); public MainWindow() { InitializeComponent(); + _objectManager = new ObjectManipulationManager(this, ImagenEnTrabajoCanvas); + + // Inicializar temporizador de zoom _zoomTimer = new DispatcherTimer(); _zoomTimer.Interval = TimeSpan.FromMilliseconds(1); _zoomTimer.Tick += ZoomTimer_Tick; _stopwatch = new Stopwatch(); + // Suscribir eventos this.Loaded += MainWindow_Loaded; ImagenEnTrabajoScrollViewer.PreviewMouseWheel += ImagenEnTrabajoCanvas_MouseWheel; ImagenEnTrabajoCanvas.MouseDown += Canvas_MouseDown_Panning; ImagenEnTrabajoCanvas.MouseMove += Canvas_MouseMove_Panning; ImagenEnTrabajoCanvas.MouseUp += Canvas_MouseUp_Panning; - - // Agregar el evento KeyDown a la ventana this.KeyDown += MainWindow_KeyDown; ImagenEnTrabajoCanvas.MouseEnter += Canvas_MouseEnter; + this.Closed += MainWindow_Closed; + + // Importante: Agregar el evento para el menú contextual + ImagenEnTrabajoCanvas.MouseRightButtonDown += Canvas_MouseRightButtonDown; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) @@ -80,28 +71,15 @@ namespace CtrEditor { viewModel.MainWindow = this; viewModel.ImageSelected += ViewModel_ImageSelected; - viewModel?.LoadInitialData(); // Carga la primera imagen por defecto una vez cargada la ventana principal + viewModel?.LoadInitialData(); viewModel.simulationManager.DebugCanvas = ImagenEnTrabajoCanvas; viewModel.MainCanvas = ImagenEnTrabajoCanvas; } } - public (float X, float Y) ObtenerCentroCanvasMeters() - { - var c = ObtenerCentroCanvasPixels(); - return (PixelToMeter.Instance.calc.PixelsToMeters(c.X), PixelToMeter.Instance.calc.PixelsToMeters(c.Y)); - } - public void SuscribirEventos(UserControl userControl) { - userControl.MouseEnter += UserControl_MouseEnter; - userControl.MouseLeave += UserControl_MouseLeave; - userControl.Cursor = Cursors.Hand; // Agregar cursor de mano - - // Suscribir a eventos de mouse para panning - userControl.MouseLeftButtonDown += UserControl_MouseLeftButtonDown; - userControl.MouseLeftButtonUp += UserControl_MouseLeftButtonUp; - userControl.MouseMove += UserControl_MouseMove; + _objectManager.SuscribirEventos(userControl); } public void AgregarRegistrarUserControlCanvas(UserControl userControl) @@ -109,8 +87,6 @@ namespace CtrEditor if (userControl is IDataContainer dataContainer) { SuscribirEventos(userControl); - - // Añade el UserControl al Canvas Canvas.SetZIndex(userControl, ((int)dataContainer.ZIndex_Base() + dataContainer.zIndex_fromFrames)); ImagenEnTrabajoCanvas.Children.Add(userControl); } @@ -124,594 +100,6 @@ namespace CtrEditor } } - private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) - { - if (!_isDrawingCanvas) - { - // Si el sender es un rectángulo de redimensionamiento - if (resizeRectangles != null && resizeRectangles.Contains(sender)) - { - _isResizingUserControl = true; - lastMousePosition = e.GetPosition(ImagenEnTrabajoCanvas); - ((Rectangle)sender).CaptureMouse(); - _isMovingUserControl = true; - lastAngle = 0; - e.Handled = true; // Evitar que el evento se propague - return; - } - - var userControl = sender as UserControl; - if (userControl != null) - { - // Si hacemos clic en un control diferente al seleccionado, remover los rectángulos anteriores - if (_currentDraggingControl != userControl) - { - RemoveResizeRectangles(); - } - - userControl.CaptureMouse(); - _currentDraggingControl = userControl; - _isMovingUserControl = true; - - // Actualizar la selección en el ViewModel - if (userControl.DataContext is osBase datos) - { - var viewModel = DataContext as MainViewModel; - if (viewModel != null) - { - viewModel.SelectedItemOsList = datos; - AddResizeRectangles(userControl); - } - } - - // Resto del código para manejar rotación, tamaño y movimiento - if (Keyboard.IsKeyDown(Key.LeftShift)) - { - // Código de rotación existente - // ROTACION - // Inicializar la rotación - _isRotatingUserControl = true; - RotateTransform rotateTransform = userControl.RenderTransform as RotateTransform; - if (rotateTransform == null) - { - rotateTransform = new RotateTransform(); - userControl.RenderTransform = rotateTransform; - } - - // Establecer el punto inicial de referencia para el cálculo de rotación - _startPointUserControl = new Point(rotateTransform.CenterX, rotateTransform.CenterY); - - // Ajusta el punto inicial al espacio del Canvas - _startPointUserControl = userControl.TranslatePoint(_startPointUserControl, ImagenEnTrabajoCanvas); - - // Crear y configurar el TextBlock si no existe - if (_angleDisplayTextBlock == null) - { - _angleDisplayTextBlock = new TextBlock - { - Foreground = Brushes.Black, - Background = Brushes.White, - Opacity = 0.8, - Padding = new Thickness(5) - }; - ImagenEnTrabajoCanvas.Children.Add(_angleDisplayTextBlock); - } - - PositionAngleDisplay(userControl); - _angleDisplayTextBlock.Visibility = Visibility.Visible; - Canvas.SetZIndex(_angleDisplayTextBlock, 15); - } - else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) - { - // Código de cambio de tamaño existente - // Inicializar el cambio de tamaño - _isResizingUserControl = true; - _startPointUserControl = e.GetPosition(ImagenEnTrabajoCanvas); - } - else - { - // Código de movimiento existente - // Inicializar el movimiento/panning - _isDraggingUserControl = true; - _startPointUserControl = e.GetPosition(ImagenEnTrabajoCanvas); - } - } - } - } - - dataDebug dataDebug = new dataDebug(); - - public void DebugWindow() - { - // Crear una instancia de wDebug - var debugWindow = new wDebug - { - Data = dataDebug // Asignar la instancia de Test a la propiedad Data - }; - - // Mostrar la ventana de depuración - debugWindow.Show(); - } - - private Point transformedBoundingBoxCenter = new Point(); - private float lastAngle; - - private void AddResizeRectangles(UserControl userControl) - { - double rectSize = 10; - double rectHighlightSize = 1; - RemoveResizeRectangles(); - - if (userControl is IDataContainer dataContainer && dataContainer.Datos is osBase mvBase && mvBase.Show_On_This_Page) - { - - // Obtener el BoundingBox aproximado del UserControl - Rect boundingBox = VisualTreeHelper.GetDescendantBounds(userControl); - - // Transformar el BoundingBox a las coordenadas del Canvas - GeneralTransform transform = userControl.TransformToAncestor(ImagenEnTrabajoCanvas); - Rect transformedBoundingBox = transform.TransformBounds(boundingBox); - - FuncionesBase.MutableRect rectBox = new FuncionesBase.MutableRect(transformedBoundingBox); - rectBox.Left -= (float)rectHighlightSize; - rectBox.Right += (float)rectHighlightSize; - rectBox.Top -= (float)rectHighlightSize; - rectBox.Bottom += (float)rectHighlightSize; - - transformedBoundingBoxCenter.X = transformedBoundingBox.Left + transformedBoundingBox.Width / 2; - transformedBoundingBoxCenter.Y = transformedBoundingBox.Top + transformedBoundingBox.Height / 2; - - // Agregar primero el rectángulo de selección - Rectangle selectionRect = new Rectangle - { - Width = rectBox.Width + (rectHighlightSize * 2), // Hacer el rectángulo un poco más grande - Height = rectBox.Height + (rectHighlightSize * 2), - Fill = Brushes.Transparent, - Stroke = new SolidColorBrush(Color.FromArgb(180, 0, 120, 215)), // Azul semi-transparente - StrokeThickness = 1.5, - Tag = "Selection", - IsHitTestVisible = false, - StrokeDashArray = new DoubleCollection(new double[] { 3, 3 }) // Borde punteado más visible - }; - - Canvas.SetLeft(selectionRect, rectBox.Left - rectHighlightSize); - Canvas.SetTop(selectionRect, rectBox.Top - rectHighlightSize); - Canvas.SetZIndex(selectionRect, ((int)ZIndexEnum.RectangulosPropiead - 1)); // Un nivel por debajo de los controles de redimensión - - resizeRectangles.Add(selectionRect); - ImagenEnTrabajoCanvas.Children.Add(selectionRect); - - // Cargar el cursor personalizado para rotación - 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); - - // Calcular las posiciones de los rectángulos de redimensionamiento - var positions = new List<Tuple<Point, string>>() - { - new Tuple<Point, string>(new Point(rectBox.Left, rectBox.Top), "TopLeft"), - new Tuple<Point, string>(new Point(rectBox.Right, rectBox.Top), "TopRight"), - new Tuple<Point, string>(new Point(rectBox.Left, rectBox.Bottom), "BottomLeft"), - new Tuple<Point, string>(new Point(rectBox.Right, rectBox.Bottom), "BottomRight"), - new Tuple<Point, string>(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Top), "TopCenter"), - new Tuple<Point, string>(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Bottom), "BottomCenter"), - new Tuple<Point, string>(new Point(rectBox.Left, rectBox.Top + rectBox.Height / 2), "CenterLeft"), - new Tuple<Point, string>(new Point(rectBox.Right, rectBox.Top + rectBox.Height / 2), "CenterRight") - }; - - // Add validation before setting Canvas position - void SetCanvasPosition(UIElement element, double left, double top) - { - if (!double.IsInfinity(left) && !double.IsNaN(left)) - Canvas.SetLeft(element, left); - if (!double.IsInfinity(top) && !double.IsNaN(top)) - Canvas.SetTop(element, top); - } - - foreach (var position in positions) - { - Rectangle rect = new Rectangle - { - Width = rectSize, - Height = rectSize, - Fill = Brushes.Transparent, - Stroke = Brushes.Black, - StrokeThickness = 1, - Tag = position.Item2 // Asignar la etiqueta - }; - - // Establecer el cursor adecuado - switch (position.Item2) - { - case "TopLeft": - rect.Cursor = Cursors.Arrow; - rect.Stroke = Brushes.Gray; - break; - case "TopRight": - rect.Cursor = rotationCursorRx; // Cursor de rotación - rect.Stroke = Brushes.Red; - break; - case "BottomLeft": - rect.Cursor = rotationCursorSx; // Cursor de rotación - rect.Stroke = Brushes.DarkRed; - break; - case "BottomRight": - rect.Cursor = Cursors.SizeNWSE; // Cursor de dimensionar altura y anchura - rect.Stroke = Brushes.Blue; - break; - case "TopCenter": - rect.Cursor = Cursors.Arrow; - rect.Stroke = Brushes.Gray; - break; - case "BottomCenter": - rect.Cursor = Cursors.SizeNS; // Cursor de dimensionar altura - rect.Stroke = Brushes.Blue; - break; - case "CenterLeft": - rect.Cursor = rotationCursorRx; // Cursor de rotación - rect.Stroke = Brushes.Red; - break; - case "CenterRight": - rect.Cursor = Cursors.SizeWE; // Cursor de dimensionar anchura - rect.Stroke = Brushes.Blue; - break; - } - - // Replace direct Canvas.Set calls with the validation method - SetCanvasPosition(rect, position.Item1.X - rectSize / 2, position.Item1.Y - rectSize / 2); - - rect.MouseLeftButtonDown += UserControl_MouseLeftButtonDown; - rect.MouseMove += UserControl_MouseMove; - rect.MouseLeftButtonUp += UserControl_MouseLeftButtonUp; - rect.MouseLeave += ResizeRectangle_MouseLeave; - - resizeRectangles.Add(rect); - ImagenEnTrabajoCanvas.Children.Add(rect); - Canvas.SetZIndex(rect, ((int)ZIndexEnum.RectangulosPropiead)); - } - } - } - - private void AddHighlightRectangles(UserControl userControl) - { - double rectSize = 1; - RemoveHighlightRectangles(); // Eliminar highlight anterior - - if (userControl is IDataContainer dataContainer && dataContainer.Datos is osBase mvBase && mvBase.Show_On_This_Page) - { - // Obtener el BoundingBox aproximado del UserControl - Rect boundingBox = VisualTreeHelper.GetDescendantBounds(userControl); - - // Transformar el BoundingBox a las coordenadas del Canvas - GeneralTransform transform = userControl.TransformToAncestor(ImagenEnTrabajoCanvas); - 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; - - // Crear un rectángulo de resaltado alrededor del objeto con estilo más sutil - Rectangle highlightRect = new Rectangle - { - Width = rectBox.Width, - Height = rectBox.Height, - Fill = Brushes.Transparent, - Stroke = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0)), // Borde semitransparente - StrokeThickness = 1, - Tag = "Highlight", - IsHitTestVisible = false, - StrokeDashArray = new DoubleCollection(new double[] { 2, 2 }) // Borde punteado - }; - - Canvas.SetLeft(highlightRect, rectBox.Left); - Canvas.SetTop(highlightRect, rectBox.Top); - - highlightRectangles.Add(highlightRect); // Usar la nueva lista - ImagenEnTrabajoCanvas.Children.Add(highlightRect); - Canvas.SetZIndex(highlightRect, ((int)ZIndexEnum.RectangulosPropiead)); - - // Reiniciar el timer - if (timerRemoveHighlight == null) - timerRemoveHighlight = new System.Threading.Timer(TimerCallbackRemoveHighlight, null, Timeout.Infinite, Timeout.Infinite); - - timerRemoveHighlight.Change(2000, Timeout.Infinite); - } - } - - private void ResizeRectangle_MouseLeave(object sender, MouseEventArgs e) - { - var rect = sender as Rectangle; - rect.Fill = Brushes.Transparent; // Volver al color original - } - - private void MakeResizeRectanglesTransparent() - { - if (resizeRectangles == null || resizeRectangles.Count == 0) - return; - - foreach (var rect in resizeRectangles) - { - rect.Opacity = 0; // Hacer transparente - } - } - - private void MakeResizeRectanglesNormal() - { - if (resizeRectangles == null || resizeRectangles.Count == 0) - return; - - foreach (var rect in resizeRectangles) - { - rect.Opacity = 1; - } - } - - - private void RemoveResizeRectangles() - { - if (resizeRectangles == null || resizeRectangles.Count == 0) - return; - - foreach (var rect in resizeRectangles) - { - ImagenEnTrabajoCanvas.Children.Remove(rect); - } - resizeRectangles.Clear(); - } - - private void RemoveHighlightRectangles() - { - if (highlightRectangles == null || highlightRectangles.Count == 0) - return; - - foreach (var rect in highlightRectangles) - { - ImagenEnTrabajoCanvas.Children.Remove(rect); - } - highlightRectangles.Clear(); - } - - private Point lastMousePosition; - - private void MarkUnsavedChanges() - { - if (DataContext is MainViewModel viewModel) - { - if (_isMovingUserControl || _isRotatingUserControl || _isResizingUserControl || _isDraggingUserControl) - { - viewModel.HasUnsavedChanges = true; - } - } - } - - private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) - { - MarkUnsavedChanges(); - if (_isResizingUserControl && resizeRectangles != null && resizeRectangles.Contains(sender)) - { - _isResizingUserControl = false; - _isMovingUserControl = false; - ((Rectangle)sender).ReleaseMouseCapture(); - AddResizeRectangles(_currentDraggingControl); - } - else if (_isMovingUserControl) - { - var userControl = sender as UserControl; - - userControl.ReleaseMouseCapture(); - // _currentDraggingControl = null; - AddResizeRectangles(_currentDraggingControl); - _isResizingUserControl = _isRotatingUserControl = _isDraggingUserControl = false; - _isMovingUserControl = false; - - // Ocultar el TextBlock de ángulo - if (_angleDisplayTextBlock != null) - { - _angleDisplayTextBlock.Visibility = Visibility.Collapsed; - } - } - } - - Rectangle _currentDraggingRectangle; - - private void UserControl_MouseMove(object sender, MouseEventArgs e) - { - if (!_isMovingUserControl && resizeRectangles != null && resizeRectangles.Contains(sender)) - { - var rect = sender as Rectangle; - rect.Fill = Brushes.Black; // Pintar de negro el rectángulo bajo el ratón - _currentDraggingRectangle = rect; // Asignar el rectángulo actual que se está arrastrando - _startPointUserControl = new Point(Canvas.GetLeft(rect), Canvas.GetTop(rect)); - MakeResizeRectanglesNormal(); - } - if (_isMovingUserControl && _currentDraggingControl != null) - { - var currentPosition = e.GetPosition(ImagenEnTrabajoCanvas); - MakeResizeRectanglesTransparent(); // Hacer transparente durante cualquier manipulación - - if (_isDraggingUserControl) - { - // Código para mover el control - var dx = currentPosition.X - _startPointUserControl.X; - var dy = currentPosition.Y - _startPointUserControl.Y; - var newX = Canvas.GetLeft(_currentDraggingControl) + dx; - var newY = Canvas.GetTop(_currentDraggingControl) + dy; - - if (_currentDraggingControl is IDataContainer dataContainer && dataContainer.Datos is osBase mvBase) - mvBase.Move((float)newX, (float)newY); - - _startPointUserControl = currentPosition; // Actualiza el punto inicial para el siguiente movimiento - } - else if (_isRotatingUserControl) - { - // Código para rotar el control - RotateControl(_currentDraggingControl, currentPosition); - } - else if (_isResizingUserControl) - { - // Código para cambiar el tamaño del control - ResizeControl(_currentDraggingControl, currentPosition); - } - } - } - - private void RotateControl(UserControl control, Point currentPosition) - { - double deltaX = currentPosition.X - _startPointUserControl.X; - double deltaY = currentPosition.Y - _startPointUserControl.Y; - double angle = Math.Atan2(deltaY, deltaX) * (180 / Math.PI); - - //RotateTransform rotateTransform = control.RenderTransform as RotateTransform; - //rotateTransform.Angle = angle; // - _initialAngleUserControl; // Asegúrate de ajustar esta parte según cómo calcules el ángulo inicial - - if (control is IDataContainer dataContainer && dataContainer.Datos is osBase mvBase) - mvBase.Rotate((float)angle); - - // Actualizar el ángulo mostrado - _angleDisplayTextBlock.Text = $"Ángulo: {angle:F2}°"; - PositionAngleDisplay(control); - } - - private void PositionAngleDisplay(UserControl control) - { - // Posicionar el TextBlock sobre el control - Canvas.SetLeft(_angleDisplayTextBlock, Canvas.GetLeft(control)); - Canvas.SetTop(_angleDisplayTextBlock, Canvas.GetTop(control)); - } - - private void ResizeControl(UserControl control, Point currentPosition) - { - bool ActivateRotation = false, ActivateSizeWidth = false, ActivateSizeHeight = false, ActivateMove = false; - - if (control is IDataContainer dataContainer && dataContainer.Datos is osBase mvBase) - { - MakeResizeRectanglesTransparent(); - - // Obtener el rectángulo desde el que se inició la redimensión - var resizeRect = _currentDraggingRectangle as Rectangle; - if (resizeRect == null) return; - - string resizeDirection = resizeRect.Tag as string; - if (resizeDirection == null) return; - - switch (resizeDirection) - { - case "TopLeft": - break; - case "TopRight": - ActivateRotation = true; - break; - case "BottomLeft": - ActivateRotation = true; - break; - case "BottomRight": - ActivateSizeHeight = true; - ActivateSizeWidth = true; - break; - case "TopCenter": - break; - case "BottomCenter": - ActivateSizeHeight = true; - break; - case "CenterLeft": - ActivateRotation = true; - break; - case "CenterRight": - ActivateSizeWidth = true; - break; - } - if (ActivateMove) - { - // Código para mover el control - var dx = currentPosition.X - _startPointUserControl.X; - var dy = currentPosition.Y - _startPointUserControl.Y; - - var newX = Canvas.GetLeft(_currentDraggingControl) + dx; - var newY = Canvas.GetTop(_currentDraggingControl) + dy; - - mvBase.Move(newX, newY); - // dataContainer.Move((float)newX, (float)newY); - } - if (ActivateRotation) - { - 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 - { - mvBase.Rotate(angle - lastAngle); - lastAngle = (float)angle; - - dataDebug.TransformedBoundingBoxCenter = transformedBoundingBoxCenter; - dataDebug.LastAngle = lastAngle; - dataDebug.CurrentPosition = currentPosition; - dataDebug.Angle = (float)angle; - } - } - if (ActivateSizeWidth || ActivateSizeHeight) - { - // Calcular la diferencia en la posición X desde el punto de inicio - double widthChange = currentPosition.X - _startPointUserControl.X; - - // Calcular la diferencia en la posición Y desde el punto de inicio - double heightChange = currentPosition.Y - _startPointUserControl.Y; - - if (!ActivateSizeHeight) heightChange = 0; - if (!ActivateSizeWidth) widthChange = 0; - - mvBase.Resize(widthChange, heightChange); - // dataContainer.Resize((float)widthChange, (float)heightChange); - } - _startPointUserControl = currentPosition; // Actualiza el punto inicial para el siguiente movimiento - } - } - - - private void UserControl_MouseEnter(object sender, MouseEventArgs e) - { - if (sender is UserControl userControl && userControl is IDataContainer dataContainer) - { - dataContainer.Highlight(true); - - if (DataContext is MainViewModel viewModel) - { - var selectedObject = viewModel.SelectedItemOsList; - - // Si el control actual no es el seleccionado - if (selectedObject?.VisualRepresentation != userControl) - { - // Siempre mostrar el highlight temporal - AddHighlightRectangles(userControl); - } - } - } - } - - private async void TimerCallbackRemoveHighlight(object state) - { - if (Application.Current != null) - { - Application.Current.Dispatcher.Invoke(() => - { - RemoveHighlightRectangles(); - }); - } - } - - private void UserControl_MouseLeave(object sender, MouseEventArgs e) - { - if (sender is UserControl userControl) - if (userControl is IDataContainer dataContainer) - dataContainer.Highlight(false); - } - - private void ViewModel_ImageSelected(object sender, string imagePath) - { - LoadImageToCanvas(imagePath); - } - private void LoadImageToCanvas(string imagePath) { BitmapImage bitmap = new BitmapImage(new Uri(imagePath, UriKind.Absolute)); @@ -724,7 +112,6 @@ namespace CtrEditor imagenDeFondo.Source = bitmap; RenderOptions.SetBitmapScalingMode(imagenDeFondo, BitmapScalingMode.HighQuality); - // Elimina solo los ROIs, no la imagen de fondo for (int i = ImagenEnTrabajoCanvas.Children.Count - 1; i >= 0; i--) { @@ -734,14 +121,11 @@ namespace CtrEditor } } - // Ajusta el tamaño del Canvas a la imagen, si es necesario ImagenEnTrabajoCanvas.Width = bitmap.Width; ImagenEnTrabajoCanvas.Height = bitmap.Height; - // Posiciona la imagen correctamente dentro del Canvas Canvas.SetLeft(imagenDeFondo, 0); Canvas.SetTop(imagenDeFondo, 0); - } private void Canvas_MouseUp_Panning(object sender, MouseButtonEventArgs e) @@ -749,81 +133,79 @@ namespace CtrEditor if (_isDraggingCanvas) { _isDraggingCanvas = false; - ImagenEnTrabajoScrollViewer.ReleaseMouseCapture(); // Finaliza la captura del ratón + ImagenEnTrabajoCanvas.ReleaseMouseCapture(); - // Restaurar la visibilidad de los rectángulos de selección si hay un objeto seleccionado if (DataContext is MainViewModel viewModel && viewModel.SelectedItemOsList != null) { - MakeResizeRectanglesNormal(); + _objectManager.MakeResizeRectanglesNormal(); } + e.Handled = true; } } private void Canvas_MouseDown_Panning(object sender, MouseButtonEventArgs e) { - if (e.LeftButton == MouseButtonState.Pressed && !_isDrawingCanvas && !_isMovingUserControl) + if (e.LeftButton == MouseButtonState.Pressed && !_isDrawingCanvas) { - _isDraggingCanvas = true; - _lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer); - - // Asegurarnos de que el clic fue directamente en el canvas y no en un elemento hijo + // Solo activar el panning si el clic fue directamente en el canvas if (e.Source == ImagenEnTrabajoCanvas) { + _isDraggingCanvas = true; + _lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer); + ImagenEnTrabajoCanvas.CaptureMouse(); + if (DataContext is MainViewModel viewModel) { - viewModel.SelectedItemOsList = null; // Deseleccionar el objeto actual - RemoveResizeRectangles(); // Eliminar los rectángulos después de deseleccionar + viewModel.SelectedItemOsList = null; + _objectManager.RemoveResizeRectangles(); } + e.Handled = true; } } } private void Canvas_MouseMove_Panning(object sender, MouseEventArgs e) { - if (_isDraggingCanvas) + if (_isDraggingCanvas && e.LeftButton == MouseButtonState.Pressed) { var currentPosition = e.GetPosition(ImagenEnTrabajoScrollViewer); var dx = currentPosition.X - _lastMousePosition.X; var dy = currentPosition.Y - _lastMousePosition.Y; - // Ocultar rectángulos y eliminar highlights durante el panning - MakeResizeRectanglesTransparent(); - RemoveHighlightRectangles(); + _objectManager.MakeResizeRectanglesTransparent(); + _objectManager.RemoveHighlightRectangles(); - // Obtener la transformación actual del Canvas - var transform = (TranslateTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform).Children.First(t => t is TranslateTransform); + var transform = (TranslateTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform) + .Children.First(t => t is TranslateTransform); transform.X += dx; transform.Y += dy; _lastMousePosition = currentPosition; + e.Handled = true; } } private void ImagenEnTrabajoCanvas_MouseWheel(object sender, MouseWheelEventArgs e) { - // Ocultar rectángulos y eliminar highlights - MakeResizeRectanglesTransparent(); - RemoveHighlightRectangles(); + _objectManager.MakeResizeRectanglesTransparent(); + _objectManager.RemoveHighlightRectangles(); - _initialZoomFactor = ((ScaleTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform).Children.First(t => t is ScaleTransform)).ScaleX; + _initialZoomFactor = ((ScaleTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform) + .Children.First(t => t is ScaleTransform)).ScaleX; - // Calcula el factor de escala mínimo para que toda la imagen sea visible double minZoomFactor = Math.Min( ImagenEnTrabajoScrollViewer.ViewportWidth / ImagenEnTrabajoCanvas.ActualWidth, ImagenEnTrabajoScrollViewer.ViewportHeight / ImagenEnTrabajoCanvas.ActualHeight); - // Ajusta el targetZoomFactor pero no permite que baje del mínimo - _targetZoomFactor = e.Delta > 0 ? _initialZoomFactor * 1.4 : Math.Max(_initialZoomFactor * 0.75, minZoomFactor); + _targetZoomFactor = e.Delta > 0 ? + _initialZoomFactor * 1.4 : + Math.Max(_initialZoomFactor * 0.75, minZoomFactor); _zoomCursorPosition = e.GetPosition(ImagenEnTrabajoCanvas); - // Desactivar el escalado de alta calidad para mejorar el rendimiento RenderOptions.SetBitmapScalingMode(ImagenEnTrabajoCanvas, BitmapScalingMode.LowQuality); - if (!_zoomTimer.IsEnabled) - _ZoomDuration = ZoomDuration; - else - _ZoomDuration = ZoomDuration / 3; + _ZoomDuration = !_zoomTimer.IsEnabled ? ZoomDuration : ZoomDuration / 3; _stopwatch.Restart(); _zoomTimer.Start(); @@ -831,7 +213,6 @@ namespace CtrEditor e.Handled = true; } - private void ZoomTimer_Tick(object sender, EventArgs e) { double elapsedMilliseconds = _stopwatch.Elapsed.TotalMilliseconds; @@ -841,13 +222,11 @@ namespace CtrEditor _zoomTimer.Stop(); _stopwatch.Stop(); - // Volver a activar el escalado de alta calidad después de completar el zoom RenderOptions.SetBitmapScalingMode(ImagenEnTrabajoCanvas, BitmapScalingMode.HighQuality); - // Restaurar la visibilidad de los rectángulos de selección si hay un objeto seleccionado if (DataContext is MainViewModel viewModel && viewModel.SelectedItemOsList != null) { - MakeResizeRectanglesNormal(); + _objectManager.MakeResizeRectanglesNormal(); } return; @@ -857,12 +236,10 @@ namespace CtrEditor var st = (ScaleTransform)tg.Children.First(t => t is ScaleTransform); var tt = (TranslateTransform)tg.Children.First(t => t is TranslateTransform); - // Interpolación cuadrática para la desaceleración double t = elapsedMilliseconds / _ZoomDuration; - double easeOutT = t * (2 - t); // Función de interpolación cuadrática (ease-out) + double easeOutT = t * (2 - t); double zoomFactor = _initialZoomFactor + (_targetZoomFactor - _initialZoomFactor) * easeOutT; - // Asegúrate de no ir por debajo del zoom mínimo zoomFactor = Math.Max(zoomFactor, MinZoomScale); Point cursorPosition = _zoomCursorPosition; @@ -877,64 +254,29 @@ namespace CtrEditor tt.Y = relativeY - cursorPosition.Y * st.ScaleY; } - public (float X, float Y) ObtenerCentroCanvasPixels() - { - // Obtener las transformaciones actuales del Canvas - var tg = (TransformGroup)ImagenEnTrabajoCanvas.RenderTransform; - var st = (ScaleTransform)tg.Children.First(t => t is ScaleTransform); - var tt = (TranslateTransform)tg.Children.First(t => t is TranslateTransform); - - // Obtener el tamaño del ScrollViewer visible - double visibleWidth = ImagenEnTrabajoScrollViewer.ViewportWidth; - double visibleHeight = ImagenEnTrabajoScrollViewer.ViewportHeight; - - // Obtener el desplazamiento del ScrollViewer - double offsetX = ImagenEnTrabajoScrollViewer.HorizontalOffset; - double offsetY = ImagenEnTrabajoScrollViewer.VerticalOffset; - - // Calcular las coordenadas del centro visible del ScrollViewer - double centerX = offsetX + (visibleWidth / 2); - double centerY = offsetY + (visibleHeight / 2); - - // Ajustar las coordenadas del centro para tener en cuenta las transformaciones del Canvas - double canvasCenterX = (centerX - tt.X) / st.ScaleX; - double canvasCenterY = (centerY - tt.Y) / st.ScaleY; - - return ((float)canvasCenterX, (float)canvasCenterY); - } - - - private void CargarPropiedadesosDatos(osBase selectedObject) - { - if (DataContext is MainViewModel viewModel) - viewModel.CargarPropiedadesosDatos(selectedObject, PanelEdicion, Resources); - } - - private void MainWindow_Closed(object sender, EventArgs e) - { - if (DataContext is MainViewModel viewModel) - { - viewModel.ImageSelected -= ViewModel_ImageSelected; - } - } - private void ListaOs_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { UserControlFactory.LimpiarPropiedadesosDatos(PanelEdicion); if (e.AddedItems.Count > 0 && e.AddedItems[0] is osBase selectedObject) { - if (selectedObject.VisualRepresentation != null) - { - _currentDraggingControl = selectedObject.VisualRepresentation; - RemoveResizeRectangles(); - AddResizeRectangles(_currentDraggingControl); - } + // Siempre trabajar con selección única para las propiedades CargarPropiedadesosDatos(selectedObject); + + // No modificar la selección múltiple aquí, solo actualizar los rectángulos de manipulación + // si el objeto seleccionado no está en la selección actual + if (!_objectManager.SelectedObjects.Contains(selectedObject)) + { + if (!((MainViewModel)DataContext).IsMultiSelectionActive) + { + _objectManager.ClearSelection(); + _objectManager.SelectObject(selectedObject); + } + } } else { - RemoveResizeRectangles(); + _objectManager.RemoveResizeRectangles(); } } @@ -949,9 +291,16 @@ namespace CtrEditor } else if (e.Key == Key.Escape) { - // Deseleccionar el objeto actual y eliminar los rectángulos de selección + // Limpiar la selección en el ListBox viewModel.SelectedItemOsList = null; - RemoveResizeRectangles(); + + // Limpiar la selección múltiple + _objectManager.ClearSelection(); + _objectManager.RemoveResizeRectangles(); + + // Desactivar el modo de selección múltiple + viewModel.IsMultiSelectionActive = false; + e.Handled = true; } } @@ -959,28 +308,115 @@ namespace CtrEditor private void Canvas_MouseEnter(object sender, MouseEventArgs e) { - // Solo remover los rectángulos si el mouse entra directamente al Canvas if (e.OriginalSource == ImagenEnTrabajoCanvas) { - // RemoveResizeRectangles(); + //_objectManager.RemoveResizeRectangles(); } } + + private void MainWindow_Closed(object sender, EventArgs e) + { + if (DataContext is MainViewModel viewModel) + { + viewModel.ImageSelected -= ViewModel_ImageSelected; + } + } + + private void CargarPropiedadesosDatos(osBase selectedObject) + { + if (DataContext is MainViewModel viewModel) + viewModel.CargarPropiedadesosDatos(selectedObject, PanelEdicion, Resources); + } + + public (float X, float Y) ObtenerCentroCanvasMeters() + { + var c = ObtenerCentroCanvasPixels(); + return (PixelToMeter.Instance.calc.PixelsToMeters(c.X), + PixelToMeter.Instance.calc.PixelsToMeters(c.Y)); + } + + public (float X, float Y) ObtenerCentroCanvasPixels() + { + var tg = (TransformGroup)ImagenEnTrabajoCanvas.RenderTransform; + var st = (ScaleTransform)tg.Children.First(t => t is ScaleTransform); + var tt = (TranslateTransform)tg.Children.First(t => t is TranslateTransform); + + double visibleWidth = ImagenEnTrabajoScrollViewer.ViewportWidth; + double visibleHeight = ImagenEnTrabajoScrollViewer.ViewportHeight; + + double offsetX = ImagenEnTrabajoScrollViewer.HorizontalOffset; + double offsetY = ImagenEnTrabajoScrollViewer.VerticalOffset; + + double centerX = offsetX + (visibleWidth / 2); + double centerY = offsetY + (visibleHeight / 2); + + double canvasCenterX = (centerX - tt.X) / st.ScaleX; + double canvasCenterY = (centerY - tt.Y) / st.ScaleY; + + return ((float)canvasCenterX, (float)canvasCenterY); + } + + private void ViewModel_ImageSelected(object sender, string imagePath) + { + LoadImageToCanvas(imagePath); + } + + public void DebugWindow() + { + var debugWindow = new wDebug + { + Data = dataDebug + }; + debugWindow.Show(); + } + + private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + // Aceptar el evento si viene del canvas o de la imagen de fondo + if ((e.Source == ImagenEnTrabajoCanvas || e.Source == imagenDeFondo) && DataContext is MainViewModel viewModel) + { + e.Handled = true; // Importante: marcar el evento como manejado + ShowContextMenu(e.GetPosition(ImagenEnTrabajoCanvas)); + } + } + + private void ShowContextMenu(Point position) + { + var contextMenu = new ContextMenu(); + var multiSelectMenuItem = new MenuItem + { + Header = "Modo Multi-Selección", + IsCheckable = true, + StaysOpenOnClick = false // Cerrar el menú al hacer clic + }; + + if (DataContext is MainViewModel viewModel) + { + multiSelectMenuItem.IsChecked = viewModel.IsMultiSelectionActive; + multiSelectMenuItem.Click += (s, e) => + { + viewModel.IsMultiSelectionActive = multiSelectMenuItem.IsChecked; + }; + } + + contextMenu.Items.Add(multiSelectMenuItem); + contextMenu.PlacementTarget = ImagenEnTrabajoCanvas; + contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint; + contextMenu.IsOpen = true; + } } public class FloatValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { - // Comprobamos si el valor es nulo o está vacío if (string.IsNullOrEmpty(value?.ToString())) return new ValidationResult(false, "El campo no puede estar vacío."); - // Intentamos convertir el valor a un tipo float if (float.TryParse(value.ToString(), NumberStyles.Float, cultureInfo, out float result)) - return ValidationResult.ValidResult; // La validación es exitosa + return ValidationResult.ValidResult; else return new ValidationResult(false, "Ingrese un número válido."); } } - } \ No newline at end of file diff --git a/ObjectManipulationManager.cs b/ObjectManipulationManager.cs new file mode 100644 index 0000000..c1ec8a4 --- /dev/null +++ b/ObjectManipulationManager.cs @@ -0,0 +1,616 @@ +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<Rectangle> _resizeRectangles = new List<Rectangle>(); + private List<Rectangle> _highlightRectangles = new List<Rectangle>(); + private System.Threading.Timer _timerRemoveHighlight = null; + private Rectangle _currentDraggingRectangle; + private dataDebug _dataDebug = new dataDebug(); + private ObservableCollection<osBase> _selectedObjects = new ObservableCollection<osBase>(); + + public ObjectManipulationManager(MainWindow mainWindow, Canvas canvas) + { + _mainWindow = mainWindow; + _canvas = canvas; + } + + public ObservableCollection<osBase> 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<osBase> 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<osBase> 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; + } + } + } +} \ No newline at end of file diff --git a/ObjetosSim/osBase.cs b/ObjetosSim/osBase.cs index df4cdce..984749b 100644 --- a/ObjetosSim/osBase.cs +++ b/ObjetosSim/osBase.cs @@ -509,6 +509,8 @@ namespace CtrEditor.ObjetosSim [property: Hidden] UniqueId cloned_from; + [ObservableProperty] + private bool isSelected; private async void TimerCallback(object state) {