85 lines
2.9 KiB
C#
85 lines
2.9 KiB
C#
using System.Collections;
|
||
using System.Collections.Generic;
|
||
using System;
|
||
using UnityEngine;
|
||
using Ichni.RhythmGame;
|
||
|
||
namespace Ichni.Editor.Commands
|
||
{
|
||
public class ChangeValueCommand : ICommand
|
||
{
|
||
private IBaseElement target;
|
||
private string path;
|
||
private object oldValue;
|
||
private object newValue;
|
||
private float timestamp;
|
||
|
||
// 合并窗口期:同一字段 1 秒内的接连覆盖,只算作一次编辑行为
|
||
private const float MERGE_WINDOW = 1f;
|
||
|
||
public ChangeValueCommand(IBaseElement target, string path, object newValue)
|
||
{
|
||
this.target = target;
|
||
this.path = path;
|
||
this.newValue = newValue;
|
||
this.oldValue = ReflectionHelper.GetDeepValue(target, path);
|
||
this.timestamp = Time.realtimeSinceStartup;
|
||
}
|
||
|
||
public void Execute()
|
||
{
|
||
if (target == null) return;
|
||
ReflectionHelper.SetDeepValue(target, path, newValue);
|
||
}
|
||
|
||
public void Undo()
|
||
{
|
||
// 防暴毙机制:如果所附着的对象因为其它原因在场景中已被销毁(如删除该 Note),自动拦截撤销并停止访问底层数据。
|
||
if (target == null) return;
|
||
|
||
ReflectionHelper.SetDeepValue(target, path, oldValue);
|
||
target.Refresh();
|
||
|
||
|
||
}
|
||
|
||
public void Redo()
|
||
{
|
||
if (target == null) return;
|
||
|
||
ReflectionHelper.SetDeepValue(target, path, newValue);
|
||
target.Refresh();
|
||
|
||
|
||
}
|
||
|
||
public bool TryMerge(ICommand other)
|
||
{
|
||
if (other is ChangeValueCommand otherCommand)
|
||
{
|
||
// 若对象、路径完全一致,则尝试吞并它的新值
|
||
if (System.Object.ReferenceEquals(this.target, otherCommand.target) &&
|
||
this.path == otherCommand.path &&
|
||
(otherCommand.timestamp - this.timestamp) < MERGE_WINDOW)
|
||
{
|
||
this.newValue = otherCommand.newValue;
|
||
this.timestamp = otherCommand.timestamp;
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
private void RefreshInspectorIfMatched()
|
||
{
|
||
var inspector = EditorManager.instance.uiManager.inspector;
|
||
if (inspector != null && inspector.connectedGameElement != null)
|
||
{
|
||
// 由于目前 DynamicUI 未实现细粒度的数据绑定事件派发,发生撤回时,我们重新装载选择一次右侧属性窗面板以完成显示同步。
|
||
// 借由我们之前的对象池机制,这个 SetInspector 消耗会在低于 1ms 的时间内无感重构。
|
||
inspector.SetInspector(inspector.connectedGameElement);
|
||
}
|
||
}
|
||
}
|
||
}
|