2026-03-22 12:05:32 -04:00
|
|
|
|
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();
|
|
|
|
|
|
|
2026-03-26 14:48:04 -04:00
|
|
|
|
|
2026-03-22 12:05:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Redo()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (target == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
ReflectionHelper.SetDeepValue(target, path, newValue);
|
|
|
|
|
|
target.Refresh();
|
|
|
|
|
|
|
2026-03-26 14:48:04 -04:00
|
|
|
|
|
2026-03-22 12:05:32 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|