using System.Globalization; using System.Windows; 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; 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; 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; // 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 double _initialAngleUserControl; private TextBlock _angleDisplayTextBlock; public MainWindow() { InitializeComponent(); _zoomTimer = new DispatcherTimer(); _zoomTimer.Interval = TimeSpan.FromMilliseconds(1); _zoomTimer.Tick += ZoomTimer_Tick; _stopwatch = new Stopwatch(); this.Loaded += MainWindow_Loaded; ImagenEnTrabajoScrollViewer.PreviewMouseWheel += ImagenEnTrabajoCanvas_MouseWheel; ImagenEnTrabajoCanvas.MouseDown += Canvas_MouseDown_Panning; ImagenEnTrabajoCanvas.MouseMove += Canvas_MouseMove_Panning; 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(); // Carga la primera imagen por defecto una vez cargada la ventana principal viewModel.simulationManager.DebugCanvas = ImagenEnTrabajoCanvas; viewModel.MainCanvas = ImagenEnTrabajoCanvas; } } public (float X, float Y) ObtenerCentroCanvasPixels() { var scaleTransform = ImagenEnTrabajoCanvas.LayoutTransform as ScaleTransform; float scaleX = (float)(scaleTransform?.ScaleX ?? 1.0); float scaleY = (float)(scaleTransform?.ScaleY ?? 1.0); // Obtiene el área visible del ScrollViewer float visibleWidth = (float)ImagenEnTrabajoScrollViewer.ViewportWidth; float visibleHeight = (float)ImagenEnTrabajoScrollViewer.ViewportHeight; // Obtiene la posición actual del desplazamiento ajustada por el zoom float offsetX = (float)ImagenEnTrabajoScrollViewer.HorizontalOffset / scaleX; float offsetY = (float)ImagenEnTrabajoScrollViewer.VerticalOffset / scaleY; // Calcula el centro visible ajustado float centerX = offsetX + (visibleWidth / scaleX) / 2; float centerY = offsetY + (visibleHeight / scaleY) / 2; return (centerX, centerY); } 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; // Suscribir a eventos de mouse para panning userControl.MouseLeftButtonDown += UserControl_MouseLeftButtonDown; userControl.MouseLeftButtonUp += UserControl_MouseLeftButtonUp; userControl.MouseMove += UserControl_MouseMove; } public void AgregarRegistrarUserControlCanvas(UserControl userControl) { if (userControl is IDataContainer dataContainer) { SuscribirEventos(userControl); // Añade el UserControl al Canvas Canvas.SetZIndex(userControl, dataContainer.ZIndex()); ImagenEnTrabajoCanvas.Children.Add(userControl); } } public void EliminarUserControlDelCanvas(UserControl userControl) { if (ImagenEnTrabajoCanvas.Children.Contains(userControl)) { ImagenEnTrabajoCanvas.Children.Remove(userControl); } } private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (!_isDrawingCanvas) { var userControl = sender as UserControl; _currentDraggingControl = userControl; userControl.CaptureMouse(); // Importante para recibir eventos de movimiento incluso fuera del control _isMovingUserControl = true; if (sender is UserControl control && control.DataContext is osBase datos) { var viewModel = DataContext as MainViewModel; if (viewModel != null) { viewModel.SelectedItemOsList = datos; // Esto desencadenará ListaOs_SelectionChanged } } // ROTACION if (Keyboard.IsKeyDown(Key.LeftShift)) { // Inicializar la rotación _isRotatingUserControl = true; RotateTransform rotateTransform = userControl.RenderTransform as RotateTransform; if (rotateTransform == null) { rotateTransform = new RotateTransform(); userControl.RenderTransform = rotateTransform; } _initialAngleUserControl = rotateTransform.Angle; // 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); } // TAMANO else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { // Inicializar el cambio de tamaño _isResizingUserControl = true; } // MOVIMIENTO else { // Inicializar el movimiento/panning _isDraggingUserControl = true; _startPointUserControl = e.GetPosition(ImagenEnTrabajoCanvas); } } } private void UserControl_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (_isMovingUserControl) { var userControl = sender as UserControl; userControl.ReleaseMouseCapture(); _currentDraggingControl = null; _isResizingUserControl = _isRotatingUserControl = _isDraggingUserControl = false ; _isMovingUserControl = false; // Ocultar el TextBlock de ángulo if (_angleDisplayTextBlock != null) { _angleDisplayTextBlock.Visibility = Visibility.Collapsed; } } } private void UserControl_MouseMove(object sender, MouseEventArgs e) { if (_isMovingUserControl && _currentDraggingControl != null) { var currentPosition = e.GetPosition(ImagenEnTrabajoCanvas); 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.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.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) { // 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; // Actualizar el ancho del control double newWidth = Math.Max(control.ActualWidth + widthChange, control.MinWidth); // Actualizar la altura del control double newHeight = Math.Max(control.ActualHeight + heightChange, control.MinHeight); // Asegurar que el nuevo tamaño no sea menor que los mínimos control.Width = newWidth; control.Height = newHeight; if (control is IDataContainer dataContainer) { dataContainer.Resize((float)newWidth, (float)newHeight); } // Actualizar el punto de inicio para el próximo evento de movimiento _startPointUserControl = currentPosition; } private void UserControl_MouseEnter(object sender, MouseEventArgs e) { if (sender is UserControl userControl) if (userControl is IDataContainer dataContainer) dataContainer.Highlight(true); } 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)); 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); } } // 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) { if (_isDraggingCanvas) { _isDraggingCanvas = false; ImagenEnTrabajoScrollViewer.ReleaseMouseCapture(); // Finaliza la captura del ratón } } private void Canvas_MouseDown_Panning(object sender, MouseButtonEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed && !_isDrawingCanvas && !_isMovingUserControl) { // Indica que se inicia el panning _isDraggingCanvas = true; // Guarda la posición actual del ratón _lastMousePosition = e.GetPosition(ImagenEnTrabajoScrollViewer); //ImagenEnTrabajoScrollViewer.CaptureMouse(); // Importante para capturar el movimiento } } private void Canvas_MouseMove_Panning(object sender, MouseEventArgs e) { if (_isDraggingCanvas) { var currentPosition = e.GetPosition(ImagenEnTrabajoScrollViewer); var dx = currentPosition.X - _lastMousePosition.X; var dy = currentPosition.Y - _lastMousePosition.Y; // Obtener la transformación actual del Canvas var transform = (TranslateTransform)((TransformGroup)ImagenEnTrabajoCanvas.RenderTransform).Children.First(t => t is TranslateTransform); transform.X += dx; transform.Y += dy; _lastMousePosition = currentPosition; } } private void ImagenEnTrabajoCanvas_MouseWheel(object sender, MouseWheelEventArgs e) { _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); _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; _stopwatch.Restart(); _zoomTimer.Start(); e.Handled = true; } private void ZoomTimer_Tick(object sender, EventArgs e) { double elapsedMilliseconds = _stopwatch.ElapsedMilliseconds; if (elapsedMilliseconds >= _ZoomDuration) { _zoomTimer.Stop(); _stopwatch.Stop(); // Volver a activar el escalado de alta calidad después de completar el zoom RenderOptions.SetBitmapScalingMode(ImagenEnTrabajoCanvas, BitmapScalingMode.HighQuality); 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); // 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 zoomFactor = _initialZoomFactor + (_targetZoomFactor - _initialZoomFactor) * easeOutT; // Asegúrate de no ir por debajo del zoom mínimo 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 TreeViewOs_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) { //PanelEdicion.Children.Clear(); // Limpiar el panel existente UserControlFactory.LimpiarPropiedadesosDatos(PanelEdicion); if (e.NewValue != null && e.NewValue is osBase selectedObject) CargarPropiedadesosDatos(selectedObject); } 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; } } } 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 else return new ValidationResult(false, "Ingrese un número válido."); } } }