Skip to content

Commit

Permalink
implement undo redo for selected actor ui
Browse files Browse the repository at this point in the history
  • Loading branch information
jupahe64 committed Dec 1, 2023
1 parent 9cb85d2 commit 3140462
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 0 deletions.
1 change: 1 addition & 0 deletions Fushigi/Fushigi.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="fasterflect" Version="3.0.0" />
<PackageReference Include="FuzzySharp" Version="2.0.2" />
<PackageReference Include="ImGui.NET" Version="1.89.9.3" />
<PackageReference Include="NativeFileDialogSharp" Version="0.6.0-alpha" />
Expand Down
177 changes: 177 additions & 0 deletions Fushigi/ui/undo/PropertySetUndo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using Fasterflect;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

namespace Fushigi.ui.undo
{
public abstract class PropertyCapture<T>
{
protected readonly T mTarget;

private readonly (string name, object oldValue, object lastCheckpointValue)[] mCapturedProperties;

public PropertyCapture(T target, string[] propertiesToCapture)
{
mTarget = target;
mCapturedProperties = new (string name, object oldValue, object lastCheckpointValue)[propertiesToCapture.Length];

for (int i = 0; i < mCapturedProperties.Length; i++)
{
string name = propertiesToCapture[i];
object value = GetValue(name);
mCapturedProperties[i] = (name, value, value);
}
}

protected abstract object GetValue(string name);
protected abstract void SetValue(string name, object value);

public bool HasChanges() => mCapturedProperties.Any(x => !Equals(GetValue(x.name), x.oldValue));
public bool HasChangesSinceLastCheckpoint() => mCapturedProperties.Any(x => !Equals(GetValue(x.name), x.lastCheckpointValue));
public void MakeCheckpoint()
{
for (int i = 0; i < mCapturedProperties.Length; i++)
{
mCapturedProperties[i].lastCheckpointValue = GetValue(mCapturedProperties[i].name);
}
}

protected abstract IRevertable GetRevertable((string name, object oldValue)[] changedProperties);

public bool TryGetRevertable([NotNullWhen(true)] out IRevertable? revertable,
[NotNullWhen(true)] out string[]? names)
{
(string name, object oldValue)[] changedProperties =
mCapturedProperties
.Where(x => !Equals(GetValue(x.name), x.oldValue))
.Select(x => (x.name, x.oldValue))
.ToArray();

if (changedProperties.Length == 0)
{
revertable = null;
names = null;
return false;
}

revertable = GetRevertable(changedProperties);
names = new string[changedProperties.Length];
for (int i = 0; i < changedProperties.Length; i++)
names[i] = changedProperties[i].name;
return true;
}
}

public class PropertyFieldsCapture : PropertyCapture<object?>
{
public static string[] GetPropertyFieldNames(object target)
{
List<string> names = [];

foreach (var info in target.GetType().Fields(BindingFlags.Public | BindingFlags.Instance))
{
var fieldType = info.Type();
if (!(fieldType.IsValueType || fieldType == typeof(string)))
continue;

names.Add(info.Name);
}

return names.ToArray();
}

public static readonly PropertyFieldsCapture Empty = new PropertyFieldsCapture();

private PropertyFieldsCapture()
: base(null, Array.Empty<string>()) { }

public PropertyFieldsCapture(object target)
: base(target, GetPropertyFieldNames(target)) { }

protected override IRevertable GetRevertable((string name, object oldValue)[] changedProperties)
{
Debug.Assert(mTarget != null);
return new PropertyFieldsSetUndo(mTarget, changedProperties);
}

protected override object GetValue(string name)
{
Debug.Assert(mTarget != null);
return mTarget.GetFieldValue(name);
}

protected override void SetValue(string name, object value)
{
Debug.Assert(mTarget != null);
mTarget.SetFieldValue(name, value);
}
}

public class PropertyFieldsSetUndo(object target, (string name, object oldValue)[] changedProperties) : IRevertable
{
public string Name => "Change properties";

public IRevertable Revert()
{
var newChangedProperties = new (string name, object oldValue)[changedProperties.Length];

for (int i = 0; i < changedProperties.Length; i++)
{
var (name, value) = changedProperties[i];
newChangedProperties[i] = (name, target.GetFieldValue(name));
target.SetFieldValue(name, value);
}

return new PropertyFieldsSetUndo(target, newChangedProperties);
}
}

public class PropertyDictCapture : PropertyCapture<IDictionary<string, object>?>
{
public static readonly PropertyDictCapture Empty = new PropertyDictCapture();

private PropertyDictCapture()
: base(null, Array.Empty<string>()) { }

public PropertyDictCapture(IDictionary<string, object> target): base(target, [.. target.Keys]) { }

protected override IRevertable GetRevertable((string name, object oldValue)[] changedProperties)
{
Debug.Assert(mTarget != null);
return new PropertyDictSetUndo(mTarget, changedProperties);
}

protected override object GetValue(string name)
{
Debug.Assert(mTarget != null);
return mTarget[name];
}

protected override void SetValue(string name, object value)
{
Debug.Assert(mTarget != null);
mTarget[name] = value;
}
}

public class PropertyDictSetUndo(IDictionary<string, object> target,
(string name, object oldValue)[] changedProperties) : IRevertable
{
public string Name => "Change properties";

public IRevertable Revert()
{
var newChangedProperties = new (string name, object oldValue)[changedProperties.Length];

for (int i = 0; i < changedProperties.Length; i++)
{
var (name, value) = changedProperties[i];
newChangedProperties[i] = (name, target[name]);
target[name] = value;
}

return new PropertyDictSetUndo(target, newChangedProperties);
}
}
}
45 changes: 45 additions & 0 deletions Fushigi/ui/widgets/CourseScene.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
using Fushigi.param;
using Fushigi.ui.SceneObjects;
using Fushigi.ui.SceneObjects.bgunit;
using Fushigi.ui.undo;
using Fushigi.util;
using ImGuiNET;
using Silk.NET.OpenGL;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Fushigi.ui.widgets
{
Expand All @@ -21,6 +24,11 @@ class CourseScene
public LevelViewport activeViewport;
UndoWindow undoWindow;

(object? courseObj, PropertyFieldsCapture placementPropCapture, PropertyDictCapture dynamicPropCapture)
propertyCapture = (null,
PropertyFieldsCapture.Empty,
PropertyDictCapture.Empty);

readonly Course course;
CourseArea selectedArea;

Expand Down Expand Up @@ -650,6 +658,14 @@ private void SelectionParameterPanel()

if (editContext.IsSingleObjectSelected(out CourseActor? mSelectedActor))
{
//invalidate current action if there has been external changes
if((propertyCapture.placementPropCapture.HasChangesSinceLastCheckpoint() ||
propertyCapture.dynamicPropCapture.HasChangesSinceLastCheckpoint()))
{
propertyCapture = (null, PropertyFieldsCapture.Empty, PropertyDictCapture.Empty);
}

#region Actor UI
string actorName = mSelectedActor.mActorName;
string name = mSelectedActor.mName;

Expand Down Expand Up @@ -845,7 +861,36 @@ private void SelectionParameterPanel()
ImGui.Separator();

}
#endregion

bool needsRecapture = false;

if (!ImGui.IsAnyItemActive())
{
if (propertyCapture.placementPropCapture.TryGetRevertable(out var revertable, out var names))
{
editContext.CommitAction(revertable);
needsRecapture = true;
}

if (propertyCapture.dynamicPropCapture.TryGetRevertable(out revertable, out names))
{
editContext.CommitAction(revertable);
needsRecapture = true;
}
}
if(needsRecapture || propertyCapture.courseObj != mSelectedActor)
{
Console.WriteLine("Capturing");
propertyCapture = (
mSelectedActor,
new PropertyFieldsCapture(mSelectedActor),
new PropertyDictCapture(mSelectedActor.mActorParameters)
);
}

propertyCapture.placementPropCapture.MakeCheckpoint();
propertyCapture.dynamicPropCapture.MakeCheckpoint();
}
else if (editContext.IsSingleObjectSelected(out CourseUnit? mSelectedUnit))
{
Expand Down

0 comments on commit 3140462

Please sign in to comment.