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.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; // 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); // 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; 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) { 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); // Elimina solo los ROIs, no la imagen de fondo for (int i = ImagenEnTrabajoCanvas.Children.Count - 1; i >= 0; i--) { if (ImagenEnTrabajoCanvas.Children[i] is not Image) { ImagenEnTrabajoCanvas.Children.RemoveAt(i); } } ImagenEnTrabajoCanvas.Width = bitmap.Width; ImagenEnTrabajoCanvas.Height = bitmap.Height; Canvas.SetLeft(imagenDeFondo, 0); Canvas.SetTop(imagenDeFondo, 0); } 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) { // Solo permitir el panning si el clic fue en el canvas o en la imagen de fondo // y no en otros controles if (e.Source == ImagenEnTrabajoCanvas || e.Source == imagenDeFondo) { ImagenEnTrabajoCanvas.Focus(); // Asegurar que el canvas tiene el foco _isDraggingCanvas = true; _lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer); ImagenEnTrabajoCanvas.CaptureMouse(); 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 (_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); _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 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(); var multiSelectMenuItem = new MenuItem { Header = "Modo Multi-Selección", IsCheckable = true, StaysOpenOnClick = false }; if (DataContext is MainViewModel viewModel) { multiSelectMenuItem.IsChecked = viewModel.IsMultiSelectionActive; multiSelectMenuItem.Click += (s, e) => { viewModel.IsMultiSelectionActive = multiSelectMenuItem.IsChecked; }; // Solo mostrar opciones de alineación si hay objetos seleccionados if (_objectManager.SelectedObjects.Count > 1) { var alignmentMenu = new MenuItem { Header = "Alinear" }; var sizeMenu = new MenuItem { Header = "Igualar Tamaño" }; var joinMenu = new MenuItem { Header = "Unir" }; // Opciones de alineación alignmentMenu.Items.Add(new MenuItem { Header = "Alinear a la Izquierda", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Left)) }); alignmentMenu.Items.Add(new MenuItem { Header = "Alinear a la Derecha", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Right)) }); alignmentMenu.Items.Add(new MenuItem { Header = "Alinear Arriba", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Top)) }); alignmentMenu.Items.Add(new MenuItem { Header = "Alinear Abajo", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.Bottom)) }); alignmentMenu.Items.Add(new Separator()); alignmentMenu.Items.Add(new MenuItem { Header = "Centrar Horizontalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.CenterHorizontally)) }); alignmentMenu.Items.Add(new MenuItem { Header = "Centrar Verticalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.CenterVertically)) }); alignmentMenu.Items.Add(new Separator()); alignmentMenu.Items.Add(new MenuItem { Header = "Distribuir Horizontalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.DistributeHorizontally)) }); alignmentMenu.Items.Add(new MenuItem { Header = "Distribuir Verticalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.DistributeVertically)) }); // Opciones de igualar tamaño sizeMenu.Items.Add(new MenuItem { Header = "Igualar Ancho", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.EqualWidth)) }); sizeMenu.Items.Add(new MenuItem { Header = "Igualar Alto", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.EqualHeight)) }); sizeMenu.Items.Add(new MenuItem { Header = "Igualar Ángulo", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.EqualAngle)) }); // Opciones de unir joinMenu.Items.Add(new MenuItem { Header = "Unir Horizontalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.JoinHorizontally)) }); joinMenu.Items.Add(new MenuItem { Header = "Unir Verticalmente", Command = new RelayCommand(() => _objectManager.AlignObjects(AlignmentType.JoinVertically)) }); contextMenu.Items.Add(alignmentMenu); contextMenu.Items.Add(sizeMenu); contextMenu.Items.Add(joinMenu); contextMenu.Items.Add(new Separator()); } contextMenu.Items.Add(multiSelectMenuItem); contextMenu.PlacementTarget = ImagenEnTrabajoCanvas; contextMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.MousePoint; contextMenu.IsOpen = true; } } 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 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."); } } }