1512 lines
58 KiB
C#
1512 lines
58 KiB
C#
using CtrEditor.FuncionesBase;
|
|
using CtrEditor.ObjetosSim;
|
|
using System.Collections.ObjectModel;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
using Color = System.Windows.Media.Color;
|
|
|
|
namespace CtrEditor
|
|
{
|
|
// Clase para almacenar el estado básico de un objeto para undo
|
|
public class ObjectState
|
|
{
|
|
public int ObjectId { get; set; }
|
|
public float Left { get; set; }
|
|
public float Top { get; set; }
|
|
public float Angle { get; set; }
|
|
public float Width { get; set; }
|
|
public float Height { get; set; }
|
|
|
|
public ObjectState(osBase obj)
|
|
{
|
|
ObjectId = obj.Id.Value;
|
|
Left = obj.Left;
|
|
Top = obj.Top;
|
|
Angle = obj.Angulo;
|
|
Width = obj.Ancho;
|
|
Height = obj.Alto;
|
|
}
|
|
|
|
public ObjectState(ObjectState other)
|
|
{
|
|
ObjectId = other.ObjectId;
|
|
Left = other.Left;
|
|
Top = other.Top;
|
|
Angle = other.Angle;
|
|
Width = other.Width;
|
|
Height = other.Height;
|
|
}
|
|
}
|
|
|
|
// Clase para almacenar un estado completo del workspace
|
|
public class UndoState
|
|
{
|
|
public List<ObjectState> ObjectStates { get; set; }
|
|
public DateTime Timestamp { get; set; }
|
|
|
|
public UndoState()
|
|
{
|
|
ObjectStates = new List<ObjectState>();
|
|
Timestamp = DateTime.Now;
|
|
}
|
|
|
|
public UndoState(IEnumerable<osBase> objects) : this()
|
|
{
|
|
foreach (var obj in objects)
|
|
{
|
|
ObjectStates.Add(new ObjectState(obj));
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum AlignmentType
|
|
{
|
|
Left,
|
|
Right,
|
|
Top,
|
|
Bottom,
|
|
CenterHorizontally,
|
|
CenterVertically,
|
|
DistributeHorizontally,
|
|
DistributeVertically,
|
|
EqualWidth,
|
|
EqualHeight,
|
|
EqualAngle,
|
|
JoinHorizontally,
|
|
JoinVertically
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gestor de manipulación de objetos que maneja la selección, movimiento, rotación y redimensionamiento de objetos.
|
|
///
|
|
/// MEJORAS PARA OBJETOS ROTADOS:
|
|
/// - HandleResizeForObject: Aplica transformaciones matemáticas para convertir los cambios del mouse
|
|
/// desde coordenadas del canvas a coordenadas locales del objeto rotado.
|
|
/// - DetermineHandleMode: Adapta el comportamiento de los handles según la rotación del objeto.
|
|
/// - GetAdaptiveCursors: Cambia los cursores de los handles para reflejar la orientación actual del objeto.
|
|
///
|
|
/// Estas mejoras hacen que el redimensionamiento sea más intuitivo cuando los objetos están rotados,
|
|
/// ya que los handles responden de acuerdo a la orientación del objeto en lugar de las coordenadas
|
|
/// absolutas del canvas.
|
|
/// </summary>
|
|
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>();
|
|
private List<Rectangle> _selectionHighlightRectangles = new List<Rectangle>(); // Add this line
|
|
private List<(UserControl Control, Rectangle Highlight)> _selectionHighlightPairs = new List<(UserControl, Rectangle)>();
|
|
private Point _rectangleStart;
|
|
public Rectangle _selectionRectangle; // Cambiado a private
|
|
private ScrollViewer _scrollViewer; // Add this line
|
|
private Image _backgroundImage; // Add this line
|
|
internal bool IsDraggingCanvas { get; set; }
|
|
private bool _isRectangleSelectionActive;
|
|
private bool _selectedObjectsAreVisible;
|
|
|
|
// Sistema de Undo
|
|
private Stack<UndoState> _undoHistory = new Stack<UndoState>();
|
|
private const int MaxUndoSteps = 3;
|
|
private bool _isApplyingUndo = false;
|
|
|
|
public bool IsRectangleSelectionActive
|
|
{
|
|
get => _isRectangleSelectionActive;
|
|
set
|
|
{
|
|
_isRectangleSelectionActive = value;
|
|
if (value)
|
|
{
|
|
// Al activar la selección por rectángulo, desactivar el panning
|
|
IsDraggingCanvas = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Propiedad pública para verificar si hay un rectángulo de selección activo
|
|
public bool HasActiveSelectionRectangle => _selectionRectangle != null;
|
|
|
|
public ObjectManipulationManager(MainWindow mainWindow, Canvas canvas)
|
|
{
|
|
_mainWindow = mainWindow;
|
|
_canvas = canvas;
|
|
_scrollViewer = mainWindow.ImagenEnTrabajoScrollViewer; // Add this line
|
|
_backgroundImage = mainWindow.ImagenDeFondo; // Add this line
|
|
}
|
|
|
|
private void PurgeDeletedObjects()
|
|
{
|
|
var deletedObjects = _selectedObjects.Where(obj =>
|
|
obj.VisualRepresentation == null ||
|
|
!_canvas.Children.Contains(obj.VisualRepresentation)).ToList();
|
|
|
|
foreach (var obj in deletedObjects)
|
|
{
|
|
DeselectObject(obj);
|
|
}
|
|
}
|
|
|
|
public ObservableCollection<osBase> SelectedObjects
|
|
{
|
|
get
|
|
{
|
|
PurgeDeletedObjects();
|
|
return _selectedObjects;
|
|
}
|
|
private set
|
|
{
|
|
_selectedObjects = value;
|
|
UpdateSelectionVisuals();
|
|
}
|
|
}
|
|
|
|
public void UpdateSelectionVisuals()
|
|
{
|
|
PurgeDeletedObjects();
|
|
// Asegurarse de que el canvas haya actualizado su layout
|
|
_canvas.UpdateLayout();
|
|
|
|
RemoveResizeRectangles();
|
|
if (_selectedObjects.Any())
|
|
{
|
|
// Verificar si la simulación está activa
|
|
if (_mainWindow.DataContext is MainViewModel viewModel && !viewModel.IsSimulationRunning)
|
|
{
|
|
AddResizeRectangles(_selectedObjects);
|
|
UpdateSelectionHighlights();
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
// Verificar si hay objetos bloqueados
|
|
bool hasLockedObjects = selectedObjects.Any(obj => obj.Lock_movement);
|
|
|
|
// Calcular el bounding box que contenga todos los objetos seleccionados
|
|
Rect boundingBox = CalculateTotalBoundingBox(selectedObjects);
|
|
if (_selectedObjectsAreVisible)
|
|
{
|
|
|
|
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
|
|
);
|
|
|
|
// Siempre mostrar el rectángulo de selección
|
|
Rectangle selectionRect = CreateSelectionRectangle(rectBox, rectHighlightSize, hasLockedObjects);
|
|
_resizeRectangles.Add(selectionRect);
|
|
_canvas.Children.Add(selectionRect);
|
|
|
|
// Solo agregar handles de manipulación si no hay objetos bloqueados
|
|
if (!hasLockedObjects)
|
|
{
|
|
// 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;
|
|
_selectedObjectsAreVisible = false;
|
|
|
|
foreach (var obj in selectedObjects)
|
|
{
|
|
if (obj.VisualRepresentation != null && obj.VisualRepresentation.Visibility != Visibility.Collapsed)
|
|
{
|
|
// 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);
|
|
_selectedObjectsAreVisible = true;
|
|
}
|
|
}
|
|
|
|
return new Rect(
|
|
new Point(left, top),
|
|
new Point(right, bottom)
|
|
);
|
|
}
|
|
|
|
private Rectangle CreateSelectionRectangle(FuncionesBase.MutableRect rectBox, double rectHighlightSize, bool hasLockedObjects = false)
|
|
{
|
|
if (double.IsInfinity(rectBox.Width) || double.IsInfinity(rectBox.Height) ||
|
|
double.IsNaN(rectBox.Width) || double.IsNaN(rectBox.Height))
|
|
{
|
|
throw new ArgumentException("Invalid dimensions for selection rectangle.");
|
|
}
|
|
|
|
// Cambiar el estilo visual dependiendo si hay objetos bloqueados
|
|
Brush strokeBrush;
|
|
DoubleCollection dashArray;
|
|
|
|
if (hasLockedObjects)
|
|
{
|
|
// Estilo para objetos bloqueados (rojo/naranja con líneas más separadas)
|
|
strokeBrush = new SolidColorBrush(Color.FromArgb(180, 255, 140, 0)); // Naranja
|
|
dashArray = new DoubleCollection(new double[] { 5, 3 }); // Líneas más largas
|
|
}
|
|
else
|
|
{
|
|
// Estilo normal para objetos no bloqueados (azul)
|
|
strokeBrush = new SolidColorBrush(Color.FromArgb(180, 0, 120, 215)); // Azul
|
|
dashArray = new DoubleCollection(new double[] { 3, 3 }); // Líneas normales
|
|
}
|
|
|
|
var rect = new Rectangle
|
|
{
|
|
Width = rectBox.Width + (rectHighlightSize * 2),
|
|
Height = rectBox.Height + (rectHighlightSize * 2),
|
|
Fill = Brushes.Transparent,
|
|
Stroke = strokeBrush,
|
|
StrokeThickness = 1.5,
|
|
Tag = "Selection",
|
|
IsHitTestVisible = false,
|
|
StrokeDashArray = dashArray
|
|
};
|
|
|
|
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 defaultRectSize,
|
|
Cursor rotationCursorRx, Cursor rotationCursorSx)
|
|
{
|
|
|
|
// Calcular el tamaño apropiado para los manejadores basado en el tamaño del objeto
|
|
double minObjectDimension = Math.Min(rectBox.Width, rectBox.Height);
|
|
double rectSize = Math.Min(defaultRectSize, minObjectDimension / 3);
|
|
|
|
// Asegurar un tamaño mínimo para los manejadores
|
|
rectSize = Math.Max(rectSize, 6);
|
|
|
|
// Calcular el offset para posicionar los manejadores fuera del rectángulo
|
|
double offset = rectSize / 2;
|
|
|
|
// Determinar cursores adaptativos según la rotación del objeto
|
|
var cursors = GetAdaptiveCursors();
|
|
|
|
var positions = new List<(Point Position, string Tag, Cursor Cursor, Brush Stroke)>
|
|
{
|
|
// Esquinas - mover hacia afuera del rectángulo
|
|
(new Point(rectBox.Left - offset, rectBox.Top - offset), "TopLeft", Cursors.Arrow, Brushes.Gray),
|
|
(new Point(rectBox.Right + offset, rectBox.Top - offset), "TopRight", rotationCursorRx, Brushes.Red),
|
|
(new Point(rectBox.Left - offset, rectBox.Bottom + offset), "BottomLeft", rotationCursorSx, Brushes.DarkRed),
|
|
(new Point(rectBox.Right + offset, rectBox.Bottom + offset), "BottomRight", cursors.ResizeBoth, Brushes.Blue),
|
|
|
|
// Centros de los bordes - mover hacia afuera del rectángulo
|
|
(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Top - offset), "TopCenter", Cursors.Arrow, Brushes.Gray),
|
|
(new Point(rectBox.Left + rectBox.Width / 2, rectBox.Bottom + offset), "BottomCenter", cursors.ResizeVertical, Brushes.Blue),
|
|
(new Point(rectBox.Left - offset, rectBox.Top + rectBox.Height / 2), "CenterLeft", rotationCursorRx, Brushes.Red),
|
|
(new Point(rectBox.Right + offset, rectBox.Top + rectBox.Height / 2), "CenterRight", cursors.ResizeHorizontal, 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 (Cursor ResizeHorizontal, Cursor ResizeVertical, Cursor ResizeBoth) GetAdaptiveCursors()
|
|
{
|
|
// Para un solo objeto o múltiples objetos con la misma rotación
|
|
var firstObject = _selectedObjects.FirstOrDefault();
|
|
if (firstObject == null)
|
|
return (Cursors.SizeWE, Cursors.SizeNS, Cursors.SizeNWSE);
|
|
|
|
double angle = Math.Abs(firstObject.Angulo % 360);
|
|
|
|
// Si el objeto no está significativamente rotado, usar cursores estándar
|
|
if (angle < 15 || (angle > 345 && angle < 360) ||
|
|
(angle > 165 && angle < 195))
|
|
{
|
|
return (Cursors.SizeWE, Cursors.SizeNS, Cursors.SizeNWSE);
|
|
}
|
|
else if ((angle > 75 && angle < 105) || (angle > 255 && angle < 285))
|
|
{
|
|
// Objeto rotado 90 grados - intercambiar cursores
|
|
return (Cursors.SizeNS, Cursors.SizeWE, Cursors.SizeNESW);
|
|
}
|
|
else if ((angle > 30 && angle < 60) || (angle > 210 && angle < 240))
|
|
{
|
|
// Objeto rotado aproximadamente 45 grados
|
|
return (Cursors.SizeNESW, Cursors.SizeNWSE, Cursors.SizeAll);
|
|
}
|
|
else if ((angle > 120 && angle < 150) || (angle > 300 && angle < 330))
|
|
{
|
|
// Objeto rotado aproximadamente 135 grados
|
|
return (Cursors.SizeNWSE, Cursors.SizeNESW, Cursors.SizeAll);
|
|
}
|
|
else
|
|
{
|
|
// Para otros ángulos, usar cursores generales
|
|
return (Cursors.SizeAll, Cursors.SizeAll, Cursors.SizeAll);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
|
|
PurgeDeletedObjects();
|
|
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 (_mainWindow.DataContext is MainViewModel vm)
|
|
{
|
|
// Add new object if not already selected
|
|
if (!_selectedObjects.Contains(obj))
|
|
{
|
|
// If not in multi-selection mode, clear existing selection first
|
|
if (!vm.IsMultiSelectionActive)
|
|
ClearSelection();
|
|
|
|
_selectedObjects.Add(obj);
|
|
obj.IsSelected = true;
|
|
|
|
// Add highlight visual only if in multi-selection mode
|
|
if (vm.IsMultiSelectionActive)
|
|
{
|
|
AddSelectionHighlight(obj.VisualRepresentation);
|
|
}
|
|
|
|
UpdateSelectionVisuals();
|
|
|
|
// Update the view model's selection state
|
|
vm.NotifySelectionChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DeselectObject(osBase obj)
|
|
{
|
|
if (_selectedObjects.Contains(obj))
|
|
{
|
|
_selectedObjects.Remove(obj);
|
|
obj.IsSelected = false;
|
|
RemoveSelectionHighlight(obj.VisualRepresentation);
|
|
|
|
// Update the view model's selection state
|
|
if (_mainWindow.DataContext is MainViewModel vm)
|
|
{
|
|
vm.NotifySelectionChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearSelection()
|
|
{
|
|
// Forzar la actualización de bindings pendientes antes de limpiar la selección
|
|
if (_mainWindow.DataContext is MainViewModel viewModel && viewModel.SelectedItemOsList != null)
|
|
{
|
|
// Obtener el PropertyGrid del PanelEdicion y forzar actualización de bindings
|
|
var panelEdicion = _mainWindow.PanelEdicion;
|
|
if (panelEdicion != null && panelEdicion.IsKeyboardFocusWithin)
|
|
{
|
|
UserControlFactory.ForzarActualizacionBindings(panelEdicion.PropertyGrid);
|
|
}
|
|
}
|
|
|
|
foreach (var obj in _selectedObjects.ToList())
|
|
{
|
|
DeselectObject(obj);
|
|
}
|
|
RemoveAllSelectionHighlights();
|
|
RemoveResizeRectangles();
|
|
|
|
if (_mainWindow.DataContext is MainViewModel viewModel2)
|
|
{
|
|
viewModel2.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);
|
|
}
|
|
RemoveAllSelectionHighlights();
|
|
}
|
|
else
|
|
{
|
|
UpdateAllSelectionHighlights();
|
|
}
|
|
}
|
|
|
|
private void AddSelectionHighlight(UserControl userControl, bool isReference = false)
|
|
{
|
|
if (userControl == null) return;
|
|
|
|
RemoveSelectionHighlight(userControl);
|
|
|
|
Rect boundingBox = VisualTreeHelper.GetDescendantBounds(userControl);
|
|
GeneralTransform transform = userControl.TransformToAncestor(_canvas);
|
|
Rect transformedBoundingBox = transform.TransformBounds(boundingBox);
|
|
|
|
Rectangle highlightRect = new Rectangle
|
|
{
|
|
Width = transformedBoundingBox.Width,
|
|
Height = transformedBoundingBox.Height,
|
|
Fill = Brushes.Transparent,
|
|
Stroke = new SolidColorBrush(isReference ?
|
|
Color.FromArgb(180, 128, 0, 128) : // Purple for reference
|
|
Color.FromArgb(180, 255, 0, 0)), // Red for others
|
|
StrokeThickness = isReference ? 3 : 2, // Más grueso para el de referencia
|
|
Tag = "SelectionHighlight",
|
|
IsHitTestVisible = false
|
|
};
|
|
|
|
Canvas.SetLeft(highlightRect, transformedBoundingBox.Left);
|
|
Canvas.SetTop(highlightRect, transformedBoundingBox.Top);
|
|
Canvas.SetZIndex(highlightRect, (int)ZIndexEnum.RectangulosPropiead - 1);
|
|
|
|
_selectionHighlightPairs.Add((userControl, highlightRect));
|
|
_canvas.Children.Add(highlightRect);
|
|
}
|
|
|
|
private void RemoveSelectionHighlight(UserControl userControl)
|
|
{
|
|
var pairsToRemove = _selectionHighlightPairs.Where(p => p.Control == userControl).ToList();
|
|
foreach (var pair in pairsToRemove)
|
|
{
|
|
_canvas.Children.Remove(pair.Highlight);
|
|
_selectionHighlightPairs.Remove(pair);
|
|
}
|
|
}
|
|
|
|
public void RemoveAllSelectionHighlights()
|
|
{
|
|
foreach (var pair in _selectionHighlightPairs)
|
|
{
|
|
_canvas.Children.Remove(pair.Highlight);
|
|
}
|
|
_selectionHighlightPairs.Clear();
|
|
}
|
|
|
|
private void UpdateAllSelectionHighlights()
|
|
{
|
|
if (_mainWindow.DataContext is MainViewModel viewModel && viewModel.IsMultiSelectionActive)
|
|
{
|
|
RemoveAllSelectionHighlights();
|
|
bool isFirst = true;
|
|
foreach (var selectedObject in _selectedObjects)
|
|
{
|
|
if (selectedObject.VisualRepresentation != null)
|
|
{
|
|
AddSelectionHighlight(selectedObject.VisualRepresentation, isFirst);
|
|
isFirst = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateSelectionHighlights()
|
|
{
|
|
UpdateAllSelectionHighlights();
|
|
}
|
|
|
|
private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (!_isDrawingCanvas)
|
|
{
|
|
// Verificar si hay objetos bloqueados en la selección actual
|
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
|
|
|
if (_resizeRectangles != null && _resizeRectangles.Contains(sender))
|
|
{
|
|
// Si hay objetos bloqueados, no permitir redimensionamiento
|
|
if (hasLockedObjects)
|
|
{
|
|
e.Handled = true;
|
|
return;
|
|
}
|
|
|
|
// Capturar estado antes de iniciar redimensionamiento
|
|
CaptureUndoState();
|
|
|
|
_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 && userControl.DataContext is osBase datos)
|
|
{
|
|
bool isControlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
|
|
|
|
// Determinar qué objetos serán afectados por el movimiento
|
|
List<osBase> objectsToMove = new List<osBase>();
|
|
|
|
if (!isControlPressed)
|
|
{
|
|
// Si no está Ctrl presionado y vamos a hacer dragging
|
|
if (_selectedObjects.Contains(datos))
|
|
{
|
|
// Si el objeto ya está seleccionado, vamos a mover todos los seleccionados
|
|
objectsToMove.AddRange(_selectedObjects);
|
|
}
|
|
else
|
|
{
|
|
// Si no está seleccionado, solo vamos a mover este objeto
|
|
objectsToMove.Add(datos);
|
|
}
|
|
|
|
// Verificar si alguno de los objetos a mover está bloqueado
|
|
bool willMoveLockedObjects = objectsToMove.Any(obj => obj.Lock_movement);
|
|
|
|
if (!willMoveLockedObjects)
|
|
{
|
|
// Capturar estado antes de iniciar movimiento
|
|
CaptureUndoStateForObjects(objectsToMove);
|
|
|
|
userControl.CaptureMouse();
|
|
_currentDraggingControl = userControl;
|
|
_isMovingUserControl = true;
|
|
InitializeDrag(e);
|
|
}
|
|
}
|
|
|
|
// Manejar la selección después de capturar el estado
|
|
HandleObjectSelection(userControl, datos);
|
|
|
|
// Actualizar el estado de objetos bloqueados después de la selección
|
|
hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
|
|
|
if (hasLockedObjects && !isControlPressed)
|
|
{
|
|
// Si hay objetos bloqueados, no permitir el inicio del arrastre
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
PurgeDeletedObjects();
|
|
|
|
// Verificar si hay objetos bloqueados antes de mover
|
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
|
if (hasLockedObjects)
|
|
{
|
|
return; // No mover si hay objetos bloqueados
|
|
}
|
|
|
|
var dx = currentPosition.X - _startPointUserControl.X;
|
|
var dy = currentPosition.Y - _startPointUserControl.Y;
|
|
|
|
RemoveAllSelectionHighlights(); // Remover antes de mover
|
|
|
|
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;
|
|
|
|
UpdateAllSelectionHighlights();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
RemoveAllSelectionHighlights(); // Remover antes de actualizar
|
|
UpdateSelectionVisuals();
|
|
UpdateAllSelectionHighlights();
|
|
|
|
// Restaurar los rectángulos de selección si es necesario
|
|
if (_mainWindow.DataContext is MainViewModel viewModel && viewModel.IsMultiSelectionActive)
|
|
{
|
|
foreach (var selectedObject in _selectedObjects)
|
|
{
|
|
AddSelectionHighlight(selectedObject.VisualRepresentation);
|
|
}
|
|
}
|
|
|
|
_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)
|
|
{
|
|
// Si tenemos múltiples objetos seleccionados con diferentes rotaciones,
|
|
// usar el comportamiento por defecto
|
|
if (_selectedObjects.Count > 1)
|
|
{
|
|
var angles = _selectedObjects.Select(obj => Math.Abs(obj.Angulo % 360)).Distinct().ToList();
|
|
if (angles.Count > 1) // Diferentes ángulos
|
|
{
|
|
return DetermineHandleModeDefault(resizeDirection);
|
|
}
|
|
}
|
|
|
|
// Para un solo objeto o múltiples objetos con la misma rotación
|
|
var firstObject = _selectedObjects.FirstOrDefault();
|
|
if (firstObject == null)
|
|
return HandleMode.None;
|
|
|
|
double angle = Math.Abs(firstObject.Angulo % 360);
|
|
|
|
// Si el objeto no está significativamente rotado, usar el comportamiento por defecto
|
|
if (angle < 15 || (angle > 345 && angle < 360) ||
|
|
(angle > 75 && angle < 105) ||
|
|
(angle > 165 && angle < 195) ||
|
|
(angle > 255 && angle < 285))
|
|
{
|
|
return DetermineHandleModeDefault(resizeDirection);
|
|
}
|
|
|
|
// Para objetos rotados significativamente, adaptar los handles
|
|
return DetermineHandleModeRotated(resizeDirection, angle);
|
|
}
|
|
|
|
private HandleMode DetermineHandleModeDefault(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 HandleMode DetermineHandleModeRotated(string resizeDirection, double angle)
|
|
{
|
|
// Para objetos rotados, hacer que todos los handles de redimensionamiento funcionen como los corners
|
|
// Esto es mucho más intuitivo ya que el usuario puede redimensionar en cualquier dirección
|
|
// sin preocuparse por la orientación específica del objeto
|
|
|
|
return resizeDirection switch
|
|
{
|
|
"TopLeft" => HandleMode.None,
|
|
"TopRight" or "BottomLeft" or "CenterLeft" => HandleMode.Rotate,
|
|
"BottomRight" or "BottomCenter" or "CenterRight" or "TopCenter" => HandleMode.ResizeBoth,
|
|
_ => HandleMode.None
|
|
};
|
|
}
|
|
|
|
private void HandleResize(Point currentPosition, HandleMode mode)
|
|
{
|
|
PurgeDeletedObjects();
|
|
|
|
// Verificar si hay objetos bloqueados antes de redimensionar
|
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
|
if (hasLockedObjects)
|
|
{
|
|
return; // No redimensionar si hay objetos bloqueados
|
|
}
|
|
|
|
RemoveAllSelectionHighlights(); // Remover antes de redimensionar
|
|
|
|
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;
|
|
|
|
UpdateAllSelectionHighlights();
|
|
}
|
|
|
|
private void HandleResizeForObject(osBase obj, Point currentPosition, bool activateSizeWidth, bool activateSizeHeight)
|
|
{
|
|
// Calcular el cambio del mouse en coordenadas del canvas
|
|
double deltaX = currentPosition.X - _startPointUserControl.X;
|
|
double deltaY = currentPosition.Y - _startPointUserControl.Y;
|
|
|
|
// Inicializar variables de cambio
|
|
double widthChange = 0;
|
|
double heightChange = 0;
|
|
|
|
// Si el objeto no está rotado, usar el método original
|
|
if (Math.Abs(obj.Angulo) < 0.1) // Umbral pequeño para considerar "sin rotación"
|
|
{
|
|
widthChange = activateSizeWidth ? deltaX : 0;
|
|
heightChange = activateSizeHeight ? deltaY : 0;
|
|
}
|
|
else
|
|
{
|
|
// Para objetos rotados, transformar los cambios del mouse según la orientación del objeto
|
|
// Convertir ángulo de grados a radianes
|
|
double angleRad = obj.Angulo * Math.PI / 180.0;
|
|
|
|
// Calcular los componentes de cambio transformados según la rotación
|
|
// Rotar el vector de cambio por el ángulo negativo del objeto para obtener
|
|
// los cambios en el sistema de coordenadas local del objeto
|
|
double cos = Math.Cos(-angleRad);
|
|
double sin = Math.Sin(-angleRad);
|
|
|
|
double localDeltaX = deltaX * cos - deltaY * sin;
|
|
double localDeltaY = deltaX * sin + deltaY * cos;
|
|
|
|
// Determinar qué componente aplicar según el tipo de redimensionamiento
|
|
if (activateSizeWidth && activateSizeHeight)
|
|
{
|
|
// Redimensionamiento proporcional - usar la componente más grande transformada
|
|
widthChange = localDeltaX;
|
|
heightChange = localDeltaY;
|
|
}
|
|
else if (activateSizeWidth)
|
|
{
|
|
// Solo redimensionar ancho - usar componente X transformada
|
|
widthChange = localDeltaX;
|
|
}
|
|
else if (activateSizeHeight)
|
|
{
|
|
// Solo redimensionar alto - usar componente Y transformada
|
|
heightChange = localDeltaY;
|
|
}
|
|
}
|
|
|
|
obj.Resize(widthChange, heightChange);
|
|
}
|
|
|
|
private void HandleRotation(Point currentPosition)
|
|
{
|
|
PurgeDeletedObjects();
|
|
|
|
// Verificar si hay objetos bloqueados antes de rotar
|
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
|
if (hasLockedObjects)
|
|
{
|
|
return; // No rotar si hay objetos bloqueados
|
|
}
|
|
|
|
RemoveAllSelectionHighlights(); // Remover antes de rotar
|
|
|
|
// Verificar si la tecla Shift está presionada para rotación en incrementos de 45 grados
|
|
bool isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
|
|
|
// 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;
|
|
|
|
if (isShiftPressed)
|
|
{
|
|
// Rotación en incrementos de 45 grados
|
|
// Calcular el ángulo total acumulado desde el inicio de la rotación
|
|
double totalAngleChange = angle - _lastAngle;
|
|
|
|
// Determinar a qué incremento de 45 grados corresponde
|
|
double snapAngle = Math.Round(totalAngleChange / 45.0) * 45.0;
|
|
|
|
// Solo aplicar la rotación si hay un cambio significativo (al menos 22.5 grados de movimiento)
|
|
if (Math.Abs(totalAngleChange) > 22.5)
|
|
{
|
|
// Calcular el cambio real que necesitamos aplicar
|
|
double actualRotationChange = snapAngle;
|
|
|
|
foreach (var selectedObject in _selectedObjects)
|
|
{
|
|
// Obtener el ángulo actual del objeto
|
|
double currentObjectAngle = selectedObject.Angulo;
|
|
|
|
// Calcular el nuevo ángulo alineado a incrementos de 45 grados
|
|
double targetAngle = Math.Round((currentObjectAngle + actualRotationChange) / 45.0) * 45.0;
|
|
|
|
// Aplicar la rotación necesaria para llegar al ángulo objetivo
|
|
double rotationNeeded = targetAngle - currentObjectAngle;
|
|
selectedObject.Rotate(rotationNeeded);
|
|
}
|
|
|
|
// Actualizar el ángulo de referencia para evitar aplicar la misma rotación múltiples veces
|
|
_lastAngle = (float)angle;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Rotación libre (comportamiento original)
|
|
foreach (var selectedObject in _selectedObjects)
|
|
{
|
|
selectedObject.Rotate(deltaAngle);
|
|
}
|
|
_lastAngle = (float)angle;
|
|
}
|
|
}
|
|
|
|
UpdateAllSelectionHighlights();
|
|
}
|
|
|
|
public void AlignObjects(AlignmentType alignmentType)
|
|
{
|
|
PurgeDeletedObjects();
|
|
if (_selectedObjects.Count <= 1) return;
|
|
|
|
// Verificar si hay objetos bloqueados antes de alinear
|
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
|
if (hasLockedObjects)
|
|
{
|
|
return; // No alinear si hay objetos bloqueados
|
|
}
|
|
|
|
// Capturar estado antes de alinear
|
|
CaptureUndoState();
|
|
|
|
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
|
|
|
|
switch (alignmentType)
|
|
{
|
|
case AlignmentType.Left:
|
|
alignment.AlignLeft();
|
|
break;
|
|
case AlignmentType.Right:
|
|
alignment.AlignRight();
|
|
break;
|
|
case AlignmentType.Top:
|
|
alignment.AlignTop();
|
|
break;
|
|
case AlignmentType.Bottom:
|
|
alignment.AlignBottom();
|
|
break;
|
|
case AlignmentType.CenterHorizontally:
|
|
alignment.AlignCenterHorizontally();
|
|
break;
|
|
case AlignmentType.CenterVertically:
|
|
alignment.AlignCenterVertically();
|
|
break;
|
|
case AlignmentType.DistributeHorizontally:
|
|
alignment.DistributeHorizontally();
|
|
break;
|
|
case AlignmentType.DistributeVertically:
|
|
alignment.DistributeVertically();
|
|
break;
|
|
case AlignmentType.EqualWidth:
|
|
alignment.EqualWidth();
|
|
break;
|
|
case AlignmentType.EqualHeight:
|
|
alignment.EqualHeight();
|
|
break;
|
|
case AlignmentType.EqualAngle:
|
|
alignment.EqualAngle();
|
|
break;
|
|
case AlignmentType.JoinHorizontally:
|
|
alignment.JoinHorizontally();
|
|
break;
|
|
case AlignmentType.JoinVertically:
|
|
alignment.JoinVertically();
|
|
break;
|
|
}
|
|
|
|
// Update the selection visuals after alignment
|
|
UpdateSelectionVisuals();
|
|
}
|
|
|
|
public void EliminarObjetosSeleccionados()
|
|
{
|
|
if (_selectedObjects.Count == 0) return;
|
|
|
|
var viewModel = _mainWindow.DataContext as MainViewModel;
|
|
if (viewModel == null) return;
|
|
|
|
// Verificar si hay objetos bloqueados antes de eliminar
|
|
bool hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
|
|
if (hasLockedObjects)
|
|
{
|
|
return; // No eliminar si hay objetos bloqueados
|
|
}
|
|
|
|
// Crear una copia de la lista para evitar modificaciones durante la iteración
|
|
var objectsToRemove = _selectedObjects.ToList();
|
|
|
|
foreach (var obj in objectsToRemove)
|
|
{
|
|
viewModel.RemoverObjetoSimulable(obj);
|
|
}
|
|
|
|
// Limpiar la selección y actualizar la interfaz
|
|
ClearSelection();
|
|
RemoveResizeRectangles();
|
|
RemoveAllSelectionHighlights();
|
|
|
|
// Ensure the property panel is cleared by explicitly setting SelectedItemOsList to null
|
|
viewModel.SelectedItemOsList = null;
|
|
|
|
// Actualizar el estado de cambios sin guardar
|
|
if (viewModel != null)
|
|
{
|
|
viewModel.HasUnsavedChanges = true;
|
|
}
|
|
}
|
|
|
|
public void StartRectangleSelection(Point startPoint)
|
|
{
|
|
_rectangleStart = startPoint;
|
|
_selectionRectangle = new Rectangle
|
|
{
|
|
Stroke = Brushes.Blue,
|
|
StrokeThickness = 1,
|
|
Fill = new SolidColorBrush(Color.FromArgb(50, 0, 0, 255))
|
|
};
|
|
|
|
Canvas.SetLeft(_selectionRectangle, startPoint.X);
|
|
Canvas.SetTop(_selectionRectangle, startPoint.Y);
|
|
Canvas.SetZIndex(_selectionRectangle, (int)ZIndexEnum.RectangulosPropiead);
|
|
|
|
_canvas.Children.Add(_selectionRectangle);
|
|
}
|
|
|
|
private void UpdateSelectionRectangle(Point currentPoint)
|
|
{
|
|
double left = Math.Min(_rectangleStart.X, currentPoint.X);
|
|
double top = Math.Min(_rectangleStart.Y, currentPoint.Y);
|
|
double width = Math.Abs(currentPoint.X - _rectangleStart.X);
|
|
double height = Math.Abs(currentPoint.Y - _rectangleStart.Y);
|
|
|
|
_selectionRectangle.Width = width;
|
|
_selectionRectangle.Height = height;
|
|
Canvas.SetLeft(_selectionRectangle, left);
|
|
Canvas.SetTop(_selectionRectangle, top);
|
|
}
|
|
|
|
public void FinishRectangleSelection(Point currentPoint)
|
|
{
|
|
if (_selectionRectangle != null)
|
|
{
|
|
var left = Canvas.GetLeft(_selectionRectangle);
|
|
var top = Canvas.GetTop(_selectionRectangle);
|
|
var right = left + _selectionRectangle.Width;
|
|
var bottom = top + _selectionRectangle.Height;
|
|
var selectionBounds = new Rect(new Point(left, top), new Point(right, bottom));
|
|
|
|
var itemsToProcess = _canvas.Children.OfType<UserControl>().Where(child => child is IDataContainer).ToList();
|
|
|
|
foreach (var child in itemsToProcess)
|
|
{
|
|
var childBounds = GetElementBounds(child);
|
|
if (selectionBounds.Contains(childBounds) || selectionBounds.IntersectsWith(childBounds))
|
|
{
|
|
if (child.DataContext is osBase osObject)
|
|
{
|
|
SelectObject(osObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
_canvas.Children.Remove(_selectionRectangle);
|
|
_selectionRectangle = null;
|
|
|
|
UpdateSelectionVisuals();
|
|
}
|
|
}
|
|
|
|
private Rect GetElementBounds(FrameworkElement element)
|
|
{
|
|
var bounds = VisualTreeHelper.GetDescendantBounds(element);
|
|
var transform = element.TransformToAncestor(_canvas);
|
|
return transform.TransformBounds(bounds);
|
|
}
|
|
|
|
public void UpdateRectangleSelection(Point currentPoint)
|
|
{
|
|
if (_selectionRectangle != null)
|
|
{
|
|
UpdateSelectionRectangle(currentPoint);
|
|
}
|
|
}
|
|
|
|
// Métodos del sistema de Undo
|
|
public void CaptureUndoState()
|
|
{
|
|
// Solo capturar estado si no estamos aplicando un undo y si es permitido
|
|
if (_isApplyingUndo || !CanPerformUndo())
|
|
return;
|
|
|
|
// Solo capturar estado si hay objetos seleccionados
|
|
if (_selectedObjects.Count == 0)
|
|
return;
|
|
|
|
if (_mainWindow.DataContext is MainViewModel viewModel)
|
|
{
|
|
// Capturar solo el estado de los objetos seleccionados
|
|
var undoState = new UndoState(_selectedObjects);
|
|
|
|
// Mantener solo los últimos MaxUndoSteps estados
|
|
if (_undoHistory.Count >= MaxUndoSteps)
|
|
{
|
|
// Convertir a lista, remover el más antiguo, y volver a crear el stack
|
|
var tempList = _undoHistory.ToList();
|
|
tempList.Reverse(); // Invertir para mantener el orden correcto
|
|
tempList.RemoveAt(0); // Remover el más antiguo
|
|
_undoHistory.Clear();
|
|
foreach (var state in tempList)
|
|
{
|
|
_undoHistory.Push(state);
|
|
}
|
|
}
|
|
|
|
_undoHistory.Push(undoState);
|
|
Console.WriteLine($"Estado capturado para undo de {_selectedObjects.Count} objetos. Historial: {_undoHistory.Count} estados");
|
|
}
|
|
}
|
|
|
|
// Método para capturar el estado de objetos específicos
|
|
public void CaptureUndoStateForObjects(IEnumerable<osBase> objects)
|
|
{
|
|
// Solo capturar estado si no estamos aplicando un undo y si es permitido
|
|
if (_isApplyingUndo || !CanPerformUndo())
|
|
return;
|
|
|
|
var objectsList = objects.ToList();
|
|
if (objectsList.Count == 0)
|
|
return;
|
|
|
|
if (_mainWindow.DataContext is MainViewModel viewModel)
|
|
{
|
|
// Capturar solo el estado de los objetos especificados
|
|
var undoState = new UndoState(objectsList);
|
|
|
|
// Mantener solo los últimos MaxUndoSteps estados
|
|
if (_undoHistory.Count >= MaxUndoSteps)
|
|
{
|
|
// Convertir a lista, remover el más antiguo, y volver a crear el stack
|
|
var tempList = _undoHistory.ToList();
|
|
tempList.Reverse(); // Invertir para mantener el orden correcto
|
|
tempList.RemoveAt(0); // Remover el más antiguo
|
|
_undoHistory.Clear();
|
|
foreach (var state in tempList)
|
|
{
|
|
_undoHistory.Push(state);
|
|
}
|
|
}
|
|
|
|
_undoHistory.Push(undoState);
|
|
Console.WriteLine($"Estado capturado para undo de {objectsList.Count} objetos específicos. Historial: {_undoHistory.Count} estados");
|
|
}
|
|
}
|
|
|
|
public bool CanPerformUndo()
|
|
{
|
|
if (_mainWindow.DataContext is MainViewModel viewModel)
|
|
{
|
|
// Solo permitir undo si no estamos en simulación ni conectados
|
|
return !viewModel.IsSimulationRunning && !viewModel.IsConnected;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public bool CanUndo()
|
|
{
|
|
return CanPerformUndo() && _undoHistory.Count > 0;
|
|
}
|
|
|
|
public void PerformUndo()
|
|
{
|
|
if (!CanUndo())
|
|
return;
|
|
|
|
if (_mainWindow.DataContext is MainViewModel viewModel)
|
|
{
|
|
_isApplyingUndo = true;
|
|
|
|
try
|
|
{
|
|
var undoState = _undoHistory.Pop();
|
|
Console.WriteLine($"Aplicando undo. Estados restantes: {_undoHistory.Count}");
|
|
|
|
// Aplicar el estado a cada objeto
|
|
foreach (var objectState in undoState.ObjectStates)
|
|
{
|
|
var obj = viewModel.ObjetosSimulables.FirstOrDefault(o => o.Id.Value == objectState.ObjectId);
|
|
if (obj != null)
|
|
{
|
|
// Aplicar solo los parámetros básicos
|
|
obj.Left = objectState.Left;
|
|
obj.Top = objectState.Top;
|
|
obj.Angulo = objectState.Angle;
|
|
obj.Ancho = objectState.Width;
|
|
obj.Alto = objectState.Height;
|
|
|
|
// Actualizar la posición visual
|
|
obj.OnMoveResizeRotate();
|
|
}
|
|
}
|
|
|
|
// Actualizar los visuales de selección
|
|
UpdateSelectionVisuals();
|
|
|
|
// Marcar como cambios sin guardar
|
|
viewModel.HasUnsavedChanges = true;
|
|
|
|
Console.WriteLine("Undo aplicado exitosamente");
|
|
}
|
|
finally
|
|
{
|
|
_isApplyingUndo = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void ClearUndoHistory()
|
|
{
|
|
_undoHistory.Clear();
|
|
Console.WriteLine("Historial de undo limpiado");
|
|
}
|
|
|
|
// Método de conveniencia para obtener información del historial
|
|
public int GetUndoHistoryCount()
|
|
{
|
|
return _undoHistory.Count;
|
|
}
|
|
}
|
|
} |