Se implementó un sistema de gestión de historial de deshacer (undo) en la aplicación, permitiendo capturar y restaurar estados de objetos seleccionados. Se añadieron métodos para limpiar el historial y se mejoró la interfaz de usuario para mostrar información sobre el estado del historial de deshacer. Además, se realizaron ajustes en la lógica de manipulación de objetos para asegurar la correcta captura de estados antes de movimientos y redimensionamientos.

This commit is contained in:
Miguel 2025-06-14 16:47:25 +02:00
parent 20467c88ae
commit 94b11cf068
4 changed files with 288 additions and 19 deletions

View File

@ -447,6 +447,9 @@ namespace CtrEditor
{
OnVisFilterChanged(MainWindow.VisFilter.FilterViewModel);
}
// Limpiar historial de undo al cargar un estado desde archivo
MainWindow?.ClearUndoHistory();
}
}
@ -455,6 +458,9 @@ namespace CtrEditor
{
// Suponiendo que "SelectedImage" es una propiedad que al establecerse dispara "ImageSelected"
directorioTrabajo = EstadoPersistente.Instance.directorio;
// Limpiar historial de undo al cargar datos iniciales
MainWindow?.ClearUndoHistory();
}
// Crear un nuevo Objeto
@ -794,6 +800,9 @@ namespace CtrEditor
// Restaurar los rectángulos de selección si hay objetos seleccionados
_objectManager.UpdateSelectionVisuals();
// Limpiar historial de undo al detener la simulación
MainWindow?.ClearUndoHistory();
}
/// <summary>
@ -869,6 +878,8 @@ namespace CtrEditor
foreach (var objetoSimulable in ObjetosSimulables)
objetoSimulable.SetPLC(null);
// Limpiar historial de undo al desconectar del PLC
MainWindow?.ClearUndoHistory();
}
private List<osBase> objetosSimulablesLlamados = new List<osBase>();

View File

@ -413,6 +413,9 @@ namespace CtrEditor
private void MoveSelectedObjects(float deltaX, float deltaY)
{
// Capturar estado antes de mover
_objectManager.CaptureUndoState();
// Mover todos los objetos primero
foreach (var obj in _objectManager.SelectedObjects)
{
@ -521,6 +524,11 @@ namespace CtrEditor
debugWindow.Show();
}
public void ClearUndoHistory()
{
_objectManager.ClearUndoHistory();
}
private void Canvas_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
// Aceptar el evento si viene del canvas o de la imagen de fondo
@ -672,8 +680,11 @@ namespace CtrEditor
contextMenu.Items.Add(pasteReplaceMenuItem);
contextMenu.Items.Add(new Separator());
}
// Opciones de bloqueo/desbloqueo
// Opciones de bloqueo/desbloqueo - mostrar siempre que haya objetos seleccionados
if (_objectManager.SelectedObjects.Count > 0)
{
var lockSubmenu = new MenuItem { Header = "Bloqueo" };
// Verificar el estado actual de los objetos
@ -702,6 +713,17 @@ namespace CtrEditor
contextMenu.Items.Add(lockSubmenu);
contextMenu.Items.Add(new Separator());
}
// Agregar información del sistema de undo
var undoHistoryCount = _objectManager.GetUndoHistoryCount();
var canUndo = _objectManager.CanUndo();
var undoInfoItem = new MenuItem
{
Header = $"Undo: {undoHistoryCount}/3 estados ({(canUndo ? "Ctrl+Z disponible" : "No disponible")})",
IsEnabled = false,
FontStyle = FontStyles.Italic
};
contextMenu.Items.Add(undoInfoItem);
contextMenu.IsOpen = true;
contextMenu.PlacementTarget = ImagenEnTrabajoCanvas;
@ -755,6 +777,11 @@ namespace CtrEditor
PasteObjectsFromJson();
e.Handled = true;
}
else if (Keyboard.Modifiers == ModifierKeys.Control && e.Key == Key.Z)
{
_objectManager.PerformUndo();
e.Handled = true;
}
else if (_objectManager.SelectedObjects.Any())
{
const float moveDistance = 0.01f;

View File

@ -10,6 +10,58 @@ 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,
@ -76,6 +128,11 @@ namespace CtrEditor
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;
@ -615,6 +672,9 @@ namespace CtrEditor
return;
}
// Capturar estado antes de iniciar redimensionamiento
CaptureUndoState();
_currentDraggingRectangle = sender as Rectangle;
_lastMousePosition = e.GetPosition(_canvas);
_currentDraggingRectangle.CaptureMouse();
@ -625,25 +685,49 @@ namespace CtrEditor
}
var userControl = sender as UserControl;
if (userControl != null)
if (userControl != null && userControl.DataContext is osBase datos)
{
if (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)
{
HandleObjectSelection(userControl, datos);
// 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);
}
// Actualizar el estado de objetos bloqueados después de la selección
hasLockedObjects = _selectedObjects.Any(obj => obj.Lock_movement);
// 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);
}
}
// Solo iniciar el arrastre si no se presionó Ctrl y no hay objetos bloqueados
if (!(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) && !hasLockedObjects)
{
userControl.CaptureMouse();
_currentDraggingControl = userControl;
_isMovingUserControl = true;
InitializeDrag(e);
}
else if (hasLockedObjects)
// 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;
@ -929,6 +1013,9 @@ namespace CtrEditor
return; // No alinear si hay objetos bloqueados
}
// Capturar estado antes de alinear
CaptureUndoState();
var alignment = new ObjectAlignment(_selectedObjects, _selectedObjects.FirstOrDefault());
switch (alignmentType)
@ -1090,5 +1177,149 @@ namespace CtrEditor
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;
}
}
}

View File

@ -5,12 +5,13 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:vm="clr-namespace:CtrEditor.ObjetosSim"
mc:Ignorable="d">
<UserControl.DataContext>
<vm:osFramePlate Color_Titulo="Black" Alto_Titulo="0.1" Color="WhiteSmoke" />
</UserControl.DataContext>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="{Binding Angulo}" />
</Grid.RenderTransform>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
@ -24,6 +25,5 @@
Fill="{Binding Color, Converter={StaticResource ColorToBrushConverter}}" Stroke="Blue"
StrokeThickness="0.2"
Visibility="{Binding ShowPlate, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>
</UserControl>