using CtrEditor.ObjetosSim; using System.Diagnostics; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; using System.Windows.Threading; using MouseEventArgs = System.Windows.Input.MouseEventArgs; using UserControl = System.Windows.Controls.UserControl; namespace CtrEditor { /// /// Interaction logic for MainWindow.xaml /// public partial class MainWindow : Window { // Para el Canvas private Point _lastMousePosition; private bool _isDrawingCanvas = false; private bool _isDraggingCanvas = false; private Image imagenDeFondo; internal ObjectManipulationManager _objectManager; private Rectangle _panningArea; // Temporizadores y animación private DispatcherTimer _zoomTimer; private double _targetZoomFactor; private double _initialZoomFactor; private double _currentZoomStep; private Point _zoomCursorPosition; private const int ZoomDuration = 500; // Duración del zoom en milisegundos private int _ZoomDuration; private const double MinZoomScale = 0.1; // Límite mínimo de zoom private Stopwatch _stopwatch; private dataDebug dataDebug = new dataDebug(); public MainWindow() { InitializeComponent(); _objectManager = new ObjectManipulationManager(this, ImagenEnTrabajoCanvas); _panningArea = new Rectangle { Fill = Brushes.Transparent, IsHitTestVisible = true }; // 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; _panningArea.MouseDown += Canvas_MouseDown_Panning; _panningArea.MouseRightButtonDown += Canvas_MouseRightButtonDown; this.KeyDown += MainWindow_KeyDown; ImagenEnTrabajoCanvas.MouseEnter += Canvas_MouseEnter; this.Closed += MainWindow_Closed; // Importante: Agregar el evento para el menú contextual ImagenEnTrabajoCanvas.MouseRightButtonDown += Canvas_MouseRightButtonDown; // Asegurarse de que estos eventos estén conectados ImagenEnTrabajoCanvas.MouseUp += Canvas_MouseUp_Panning; } private void MainWindow_Loaded(object sender, RoutedEventArgs e) { if (DataContext is MainViewModel viewModel) { viewModel.MainWindow = this; viewModel.ImageSelected += ViewModel_ImageSelected; viewModel?.LoadInitialData(); viewModel.simulationManager.DebugCanvas = ImagenEnTrabajoCanvas; viewModel.MainCanvas = ImagenEnTrabajoCanvas; } } public void SuscribirEventos(UserControl userControl) { _objectManager.SuscribirEventos(userControl); } public void AgregarRegistrarUserControlCanvas(UserControl userControl) { if (userControl is IDataContainer dataContainer) { SuscribirEventos(userControl); Canvas.SetZIndex(userControl, ((int)dataContainer.ZIndex_Base() + dataContainer.zIndex_fromFrames)); ImagenEnTrabajoCanvas.Children.Add(userControl); } } public void EliminarUserControlDelCanvas(UserControl userControl) { if (ImagenEnTrabajoCanvas.Children.Contains(userControl)) { ImagenEnTrabajoCanvas.Children.Remove(userControl); } } private void LoadImageToCanvas(string imagePath) { BitmapImage bitmap = new BitmapImage(new Uri(imagePath, UriKind.Absolute)); if (imagenDeFondo == null) { imagenDeFondo = new Image(); ImagenEnTrabajoCanvas.Children.Add(imagenDeFondo); } imagenDeFondo.Source = bitmap; RenderOptions.SetBitmapScalingMode(imagenDeFondo, BitmapScalingMode.HighQuality); // Actualizar dimensiones del canvas ImagenEnTrabajoCanvas.Width = bitmap.Width; ImagenEnTrabajoCanvas.Height = bitmap.Height; // Configurar el área de panning _panningArea.Width = bitmap.Width * 4; _panningArea.Height = bitmap.Height * 4; // Posicionar el área de panning centrada respecto a la imagen Canvas.SetLeft(_panningArea, -bitmap.Width / 4); Canvas.SetTop(_panningArea, -bitmap.Height / 4); Canvas.SetZIndex(_panningArea, -1); // Asegurar que está detrás de todo // Asegurarse de que el área de panning está en el canvas if (!ImagenEnTrabajoCanvas.Children.Contains(_panningArea)) { ImagenEnTrabajoCanvas.Children.Add(_panningArea); } // Posicionar la imagen Canvas.SetLeft(imagenDeFondo, 0); Canvas.SetTop(imagenDeFondo, 0); // Eliminar solo los ROIs, no la imagen de fondo ni el área de panning for (int i = ImagenEnTrabajoCanvas.Children.Count - 1; i >= 0; i--) { var child = ImagenEnTrabajoCanvas.Children[i]; if (child != imagenDeFondo && child != _panningArea) { ImagenEnTrabajoCanvas.Children.RemoveAt(i); } } } private void Canvas_MouseUp_Panning(object sender, MouseButtonEventArgs e) { if (_isDraggingCanvas) { _isDraggingCanvas = false; ImagenEnTrabajoCanvas.ReleaseMouseCapture(); Mouse.OverrideCursor = null; if (_objectManager.SelectedObjects.Count > 0) { _objectManager.MakeResizeRectanglesNormal(); _objectManager.UpdateSelectionVisuals(); // Agregar esta línea } e.Handled = true; } } private void Canvas_MouseDown_Panning(object sender, MouseButtonEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed && !_isDrawingCanvas) { // Permitir el panning cuando se hace clic en el área de panning o en la imagen if (e.Source == _panningArea || e.Source == imagenDeFondo || e.Source == ImagenEnTrabajoCanvas || (e.Source is Rectangle rect && rect == _objectManager._selectionRectangle)) { ImagenEnTrabajoCanvas.Focus(); if (_objectManager.IsRectangleSelectionActive) { if (!_objectManager.HasActiveSelectionRectangle) { _objectManager.StartRectangleSelection(e.GetPosition(ImagenEnTrabajoCanvas)); } else { _objectManager.FinishRectangleSelection(e.GetPosition(ImagenEnTrabajoCanvas)); } } else { _isDraggingCanvas = true; _lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer); ImagenEnTrabajoCanvas.CaptureMouse(); // Importante: siempre capturar en el Canvas principal Mouse.OverrideCursor = Cursors.Hand; if (DataContext is MainViewModel viewModel) { viewModel.SelectedItemOsList = null; _objectManager.RemoveResizeRectangles(); } } e.Handled = true; } } } private void Canvas_MouseMove_Panning(object sender, MouseEventArgs e) { if (_objectManager.IsRectangleSelectionActive && _objectManager.HasActiveSelectionRectangle) { var currentPoint = e.GetPosition(ImagenEnTrabajoCanvas); _objectManager.UpdateRectangleSelection(currentPoint); } else if (_isDraggingCanvas && e.LeftButton == MouseButtonState.Pressed) { var currentPosition = e.GetPosition(ImagenEnTrabajoScrollViewer); var dx = currentPosition.X - _lastMousePosition.X; var dy = currentPosition.Y - _lastMousePosition.Y; _objectManager.MakeResizeRectanglesTransparent(); _objectManager.RemoveHighlightRectangles(); 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) { _objectManager.MakeResizeRectanglesTransparent(); _objectManager.RemoveHighlightRectangles(); _initialZoomFactor = ((ScaleTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform) .Children.First(t => t is ScaleTransform)).ScaleX; double minZoomFactor = Math.Min( ImagenEnTrabajoScrollViewer.ViewportWidth / ImagenEnTrabajoCanvas.ActualWidth, ImagenEnTrabajoScrollViewer.ViewportHeight / ImagenEnTrabajoCanvas.ActualHeight)/1.5; _targetZoomFactor = e.Delta > 0 ? _initialZoomFactor * 1.4 : Math.Max(_initialZoomFactor * 0.75, minZoomFactor); _zoomCursorPosition = e.GetPosition(ImagenEnTrabajoCanvas); RenderOptions.SetBitmapScalingMode(ImagenEnTrabajoCanvas, BitmapScalingMode.LowQuality); _ZoomDuration = !_zoomTimer.IsEnabled ? ZoomDuration : ZoomDuration / 3; _stopwatch.Restart(); _zoomTimer.Start(); e.Handled = true; } private void ZoomTimer_Tick(object sender, EventArgs e) { double elapsedMilliseconds = _stopwatch.Elapsed.TotalMilliseconds; if (elapsedMilliseconds >= _ZoomDuration) { _zoomTimer.Stop(); _stopwatch.Stop(); RenderOptions.SetBitmapScalingMode(ImagenEnTrabajoCanvas, BitmapScalingMode.HighQuality); if (_objectManager.SelectedObjects.Count > 0) { _objectManager.MakeResizeRectanglesNormal(); _objectManager.UpdateSelectionVisuals(); // Agregar esta línea } return; } 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 t = elapsedMilliseconds / _ZoomDuration; double easeOutT = t * (2 - t); double zoomFactor = _initialZoomFactor + (_targetZoomFactor - _initialZoomFactor) * easeOutT; zoomFactor = Math.Max(zoomFactor, MinZoomScale); Point cursorPosition = _zoomCursorPosition; var relativeX = cursorPosition.X * st.ScaleX + tt.X; var relativeY = cursorPosition.Y * st.ScaleY + tt.Y; st.ScaleX = zoomFactor; st.ScaleY = zoomFactor; tt.X = relativeX - cursorPosition.X * st.ScaleX; tt.Y = relativeY - cursorPosition.Y * st.ScaleY; } private void ListaOs_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e) { ImagenEnTrabajoCanvas.Focus(); // Asegurar que el canvas tiene el foco UserControlFactory.LimpiarPropiedadesosDatos(PanelEdicion); if (e.AddedItems.Count > 0 && e.AddedItems[0] is osBase selectedObject) { // 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 { _objectManager.RemoveResizeRectangles(); } } private void ImagePathButton_Click(object sender, RoutedEventArgs e) { var dlg = new Ookii.Dialogs.Wpf.VistaOpenFileDialog { Filter = "Image files (*.png;*.jpg;*.bmp)|*.png;*.jpg;*.bmp|All files (*.*)|*.*" }; if (dlg.ShowDialog() == true) { // El DataContext de este botón es el PropertyItem asociado if ((sender as Button)?.DataContext is Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem propertyItem) { propertyItem.Value = dlg.FileName; } } } private void MainWindow_KeyDown(object sender, KeyEventArgs e) { // Only force canvas focus if PanelEdicion doesn't have focus if (!PanelEdicion.IsKeyboardFocusWithin) { if (!ImagenEnTrabajoCanvas.IsFocused) { ImagenEnTrabajoCanvas.Focus(); } } if (DataContext is MainViewModel viewModel) { if (e.Key == Key.Delete) { _objectManager.EliminarObjetosSeleccionados(); e.Handled = true; } else if (e.Key == Key.Escape) { viewModel.SelectedItemOsList = null; _objectManager.ClearSelection(); _objectManager.RemoveResizeRectangles(); e.Handled = true; } else if (_objectManager.SelectedObjects.Any()) { const float moveDistance = 0.1f; switch (e.Key) { case Key.Left: MoveSelectedObjects(-moveDistance, 0); e.Handled = true; break; case Key.Right: MoveSelectedObjects(moveDistance, 0); e.Handled = true; break; case Key.Up: MoveSelectedObjects(0, -moveDistance); e.Handled = true; break; case Key.Down: MoveSelectedObjects(0, moveDistance); e.Handled = true; break; } } } } private void MoveSelectedObjects(float deltaX, float deltaY) { // Mover todos los objetos primero foreach (var obj in _objectManager.SelectedObjects) { obj.Left += deltaX; obj.Top += deltaY; } // Forzar una actualización del layout antes de actualizar los visuales ImagenEnTrabajoCanvas.UpdateLayout(); // Ahora actualizar los visuales de selección _objectManager.UpdateSelectionVisuals(); if (DataContext is MainViewModel viewModel) { viewModel.HasUnsavedChanges = true; } } private void Canvas_MouseEnter(object sender, MouseEventArgs e) { if (e.OriginalSource == ImagenEnTrabajoCanvas) { //_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 || e.Source is UserControl) && DataContext is MainViewModel viewModel) { ImagenEnTrabajoCanvas.Focus(); // Asegurar que el canvas tiene el foco e.Handled = true; ShowContextMenu(e.GetPosition(ImagenEnTrabajoCanvas)); } } private void ShowContextMenu(Point position) { var contextMenu = new ContextMenu(); if (DataContext is MainViewModel viewModel) { // Multi-selection checkbox var multiSelectMenuItem = new MenuItem { Header = "Modo Multi-Selección", IsCheckable = true, StaysOpenOnClick = false, IsChecked = viewModel.IsMultiSelectionActive }; multiSelectMenuItem.Click += (s, e) => { viewModel.IsMultiSelectionActive = multiSelectMenuItem.IsChecked; // Si se desactiva la multi-selección, desactivar también la selección por rectángulo if (!multiSelectMenuItem.IsChecked) _objectManager.IsRectangleSelectionActive = false; }; contextMenu.Items.Add(multiSelectMenuItem); // Mostrar opción de selección por rectángulo solo cuando multi-selección está activa if (viewModel.IsMultiSelectionActive) { var rectangleSelectMenuItem = new MenuItem { Header = "Selección por Rectángulo", IsCheckable = true, IsChecked = _objectManager.IsRectangleSelectionActive, StaysOpenOnClick = false }; rectangleSelectMenuItem.Click += (s, e) => { _objectManager.IsRectangleSelectionActive = rectangleSelectMenuItem.IsChecked; _objectManager.IsDraggingCanvas = false; // Desactivar panning cuando se activa la selección por rectángulo }; contextMenu.Items.Add(rectangleSelectMenuItem); } // Si hay objetos seleccionados, agregar opciones de alineación if (_objectManager.SelectedObjects.Count > 1) { contextMenu.Items.Add(new Separator()); var alignSubmenu = new MenuItem { Header = "Alinear" }; // Alineación horizontal var alignLeftItem = new MenuItem { Header = "Izquierda" }; alignLeftItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Left); alignSubmenu.Items.Add(alignLeftItem); var alignRightItem = new MenuItem { Header = "Derecha" }; alignRightItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Right); alignSubmenu.Items.Add(alignRightItem); var alignCenterHItem = new MenuItem { Header = "Centro Horizontal" }; alignCenterHItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.CenterHorizontally); alignSubmenu.Items.Add(alignCenterHItem); // Alineación vertical alignSubmenu.Items.Add(new Separator()); var alignTopItem = new MenuItem { Header = "Arriba" }; alignTopItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Top); alignSubmenu.Items.Add(alignTopItem); var alignBottomItem = new MenuItem { Header = "Abajo" }; alignBottomItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.Bottom); alignSubmenu.Items.Add(alignBottomItem); var alignCenterVItem = new MenuItem { Header = "Centro Vertical" }; alignCenterVItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.CenterVertically); alignSubmenu.Items.Add(alignCenterVItem); // Distribución alignSubmenu.Items.Add(new Separator()); var distributeHItem = new MenuItem { Header = "Distribuir Horizontalmente" }; distributeHItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.DistributeHorizontally); alignSubmenu.Items.Add(distributeHItem); var distributeVItem = new MenuItem { Header = "Distribuir Verticalmente" }; distributeVItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.DistributeVertically); alignSubmenu.Items.Add(distributeVItem); // Igualar tamaños alignSubmenu.Items.Add(new Separator()); var equalWidthItem = new MenuItem { Header = "Igualar Ancho" }; equalWidthItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualWidth); alignSubmenu.Items.Add(equalWidthItem); var equalHeightItem = new MenuItem { Header = "Igualar Alto" }; equalHeightItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualHeight); alignSubmenu.Items.Add(equalHeightItem); var equalAngleItem = new MenuItem { Header = "Igualar Ángulo" }; equalAngleItem.Click += (s, e) => _objectManager.AlignObjects(AlignmentType.EqualAngle); alignSubmenu.Items.Add(equalAngleItem); contextMenu.Items.Add(alignSubmenu); } contextMenu.IsOpen = true; contextMenu.PlacementTarget = ImagenEnTrabajoCanvas; contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint; } } private void Canvas_KeyDown(object sender, KeyEventArgs e) { // Only handle if PanelEdicion doesn't have focus if (!PanelEdicion.IsKeyboardFocusWithin) { HandleKeyDown(e); } } private void ScrollViewer_PreviewKeyDown(object sender, KeyEventArgs e) { // Only handle if PanelEdicion doesn't have focus if (!PanelEdicion.IsKeyboardFocusWithin && (e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up || e.Key == Key.Down)) { HandleKeyDown(e); e.Handled = true; } } private void HandleKeyDown(KeyEventArgs e) { if (DataContext is MainViewModel viewModel) { if (e.Key == Key.Delete) { _objectManager.EliminarObjetosSeleccionados(); // Cambiar aquí e.Handled = true; } else if (e.Key == Key.Escape) { viewModel.SelectedItemOsList = null; _objectManager.ClearSelection(); _objectManager.RemoveResizeRectangles(); e.Handled = true; } else if (_objectManager.SelectedObjects.Any()) { const float moveDistance = 0.01f; switch (e.Key) { case Key.Left: MoveSelectedObjects(-moveDistance, 0); e.Handled = true; break; case Key.Right: MoveSelectedObjects(moveDistance, 0); e.Handled = true; break; case Key.Up: MoveSelectedObjects(0, -moveDistance); e.Handled = true; break; case Key.Down: MoveSelectedObjects(0, moveDistance); e.Handled = true; break; } } } } public Image ImagenDeFondo => imagenDeFondo; } public class FloatValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (string.IsNullOrEmpty(value?.ToString())) return new ValidationResult(false, "El campo no puede estar vacío."); if (float.TryParse(value.ToString(), NumberStyles.Float, cultureInfo, out float result)) return ValidationResult.ValidResult; else return new ValidationResult(false, "Ingrese un número válido."); } } }