88 lines
3.2 KiB
C#
88 lines
3.2 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);
|
|||
|
|
// 修复:必须主动唤醒目标通知其数据已完成覆写,否则拖拽属性将无法立刻映射到真实世界物体
|
|||
|
|
target.Refresh();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Undo()
|
|||
|
|
{
|
|||
|
|
// 防暴毙机制:如果所附着的对象因为其它原因在场景中已被销毁(如删除该 Note),自动拦截撤销并停止访问底层数据。
|
|||
|
|
if (target == null) return;
|
|||
|
|
|
|||
|
|
ReflectionHelper.SetDeepValue(target, path, oldValue);
|
|||
|
|
target.Refresh();
|
|||
|
|
|
|||
|
|
// 通知 UI 面板重载(保证数据被回滚后,界面的参数显示不再是之前打错的值)
|
|||
|
|
RefreshInspectorIfMatched();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Redo()
|
|||
|
|
{
|
|||
|
|
if (target == null) return;
|
|||
|
|
|
|||
|
|
ReflectionHelper.SetDeepValue(target, path, newValue);
|
|||
|
|
target.Refresh();
|
|||
|
|
|
|||
|
|
RefreshInspectorIfMatched();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|