merge
merge and property
This commit is contained in:
@@ -30,6 +30,8 @@ namespace SampleProject
|
||||
[SerializeField]
|
||||
private TopMenuController sideMenu;
|
||||
|
||||
[SerializeField]
|
||||
private PropertyWindow propertyWindow;
|
||||
public Action Initialized;
|
||||
|
||||
/// <summary>
|
||||
@@ -182,7 +184,7 @@ namespace SampleProject
|
||||
topMenu.AddMenuItem(new MenuItemData("Settings", "Settings", new SettingOpenCommand()));
|
||||
topMenu.AddMenuItem(new MenuItemData("PropertyWindow", "PropertyWindow", new ActionCommand(async () =>
|
||||
{
|
||||
PropertyWindow.Instance.Show();
|
||||
propertyWindow.Show();
|
||||
})));
|
||||
|
||||
topMenu.Initialize();
|
||||
@@ -238,7 +240,7 @@ namespace SampleProject
|
||||
|
||||
private void SetupPropertyWindow()
|
||||
{
|
||||
PropertyWindow.Instance.LoadProperties(new List<IPropertyItem>
|
||||
propertyWindow.LoadProperties(new List<IPropertyItem>
|
||||
{
|
||||
new StringProperty("prop1", "String Property", "Initial Value")
|
||||
{
|
||||
@@ -338,7 +340,7 @@ namespace SampleProject
|
||||
}
|
||||
});
|
||||
|
||||
PropertyWindow.Instance.PropertyValueChanged += (sender, e) =>
|
||||
propertyWindow.PropertyValueChanged += (sender, e) =>
|
||||
{
|
||||
Debug.Log($"Property Id:{e.PropertyId}, type:{e.PropertyType}, newValue: {e.NewValue}");
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ComponentBase : MonoBehaviour,IPointerClickHandler
|
||||
protected ComponentDataBase data;
|
||||
protected ModelDataBase modelData;
|
||||
public ComponentType componentType;
|
||||
public event Action<ComponentType,ComponentDataBase> onComponentClicked;
|
||||
public Action<ComponentType,ComponentDataBase> onComponentClicked;
|
||||
|
||||
public void SetPosition()
|
||||
{
|
||||
@@ -61,8 +61,8 @@ public class ComponentBase : MonoBehaviour,IPointerClickHandler
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
{
|
||||
onComponentClicked?.Invoke(componentType, data);
|
||||
Debug.Log(componentType);
|
||||
GameObject clickedObject = eventData.pointerCurrentRaycast.gameObject;
|
||||
Debug.Log(clickedObject.name);
|
||||
getpath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -246,6 +246,7 @@ namespace Simulator.Data
|
||||
{
|
||||
var data = await HttpRequester.RequestGet<Totaljson>($"{Constants.HTTP_DOMAIN}/simulation/logics/{SimulationConfig.logicId}", null, null, true);
|
||||
logicDetailData = data.data.data;
|
||||
PathIndexer.Build(logicDetailData);
|
||||
onProjectNameRecieved?.Invoke(data.data.name);
|
||||
SpawnComponents(data.data.data);
|
||||
if (logicDetailData.templates != null)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using sc.modeling.splines.runtime;
|
||||
using Simulator.Data;
|
||||
using System.Collections.Generic;
|
||||
using Unity.VisualScripting;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using UVC.Data.Core;
|
||||
@@ -23,6 +24,8 @@ public class ConveyorComponent : ComponentBase
|
||||
public float borderEpsilon = 1e-4f;
|
||||
bool[] occupancy;
|
||||
|
||||
ConveyorPath conveyorPath;
|
||||
public event Action<ConveyorPath> onConveyorClicked;
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
@@ -71,7 +74,13 @@ public class ConveyorComponent : ComponentBase
|
||||
MoveOnConveyor();
|
||||
}
|
||||
|
||||
|
||||
public void SetComponent(ConveyorPath conveyorData)
|
||||
{
|
||||
this.conveyorPath = conveyorData;
|
||||
onConveyorClicked += FindAnyObjectByType<ConveyorProperty>().SetProertyWindow;
|
||||
var box=gameObject.AddComponent<BoxCollider>();
|
||||
box.isTrigger = true;
|
||||
}
|
||||
void SetConveyorEntity(Entity entity)
|
||||
{
|
||||
var cTarget = entity.gameObject.GetComponent<ConveyorTarget>();
|
||||
@@ -215,4 +224,9 @@ public class ConveyorComponent : ComponentBase
|
||||
m.Rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
public override void getpath()
|
||||
{
|
||||
onConveyorClicked?.Invoke(conveyorPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,21 +83,23 @@ namespace Simulator.Data
|
||||
{
|
||||
if (datas == null || datas.Count <= 0) return;
|
||||
|
||||
var data = datas[0];
|
||||
if (rootParent == null) rootParent = transform;
|
||||
|
||||
Nodes.Clear();
|
||||
Paths.Clear();
|
||||
_stationAdj.Clear(); // ▲ 추가: 초기화
|
||||
|
||||
if (data.nodes != null)
|
||||
foreach (var data in datas)
|
||||
{
|
||||
foreach (var n in data.nodes)
|
||||
Nodes[n.name] = n;
|
||||
if (data.nodes != null)
|
||||
{
|
||||
foreach (var n in data.nodes)
|
||||
Nodes[n.name] = n;
|
||||
}
|
||||
foreach (var path in data.paths)
|
||||
CreatePathGOWithSpline(path, Nodes[path.from_node], Nodes[path.to_node]);
|
||||
}
|
||||
|
||||
foreach (var path in data.paths)
|
||||
CreatePathGOWithSpline(path, Nodes[path.from_node], Nodes[path.to_node]);
|
||||
|
||||
SpawnJunction();
|
||||
|
||||
@@ -172,6 +174,7 @@ namespace Simulator.Data
|
||||
|
||||
if (string.Equals(toNode.node_type, "station"))
|
||||
AddStationAdj(toNode.name, (fromNode.name, toNode.name), capIndex: 1, otherNode: fromNode.name);
|
||||
go.SetComponent(path);
|
||||
}
|
||||
|
||||
// ▼ 추가: station 인접 정보 수집
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Simulator.Data;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public class NodeComponent : ComponentBase
|
||||
{
|
||||
NodeComponent conveyorNode;
|
||||
public event Action<NodeComponent> onConveyorClicked;
|
||||
|
||||
public void SetComponent(NodeComponent conveyorNode)
|
||||
{
|
||||
this.conveyorNode = conveyorNode;
|
||||
onComponentClicked += FindAnyObjectByType<SourceProperty>().SetProertyWindow;
|
||||
FitCollider();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fda7e059098ae841b4b84e5a3703e3d
|
||||
@@ -1,17 +1,22 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Security;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Simulator.Data
|
||||
{
|
||||
[Serializable]
|
||||
public class SourceDataClass:ComponentDataBase
|
||||
public class SourceDataClass : ComponentDataBase
|
||||
{
|
||||
public string name;
|
||||
public string label;
|
||||
public string modelType;
|
||||
public string prefab;
|
||||
[JsonConverter(typeof(PolicyConverter))]
|
||||
public Policy_Base spawn_time_policy;
|
||||
[JsonConverter(typeof(PolicyConverter))]
|
||||
public Policy_Base spawn_count_policy;
|
||||
[JsonConverter(typeof(PolicyConverter))]
|
||||
public Policy_Base defect_rate_policy;
|
||||
public int max_spawn_limit;
|
||||
public string output_store;
|
||||
public bool is_unlimited;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ public class ProcessorComponent : ComponentBase
|
||||
{
|
||||
this.processorData = processorData;
|
||||
data = processorData;
|
||||
onComponentClicked += FindAnyObjectByType<ProcessorProperty>().SetProertyWindow;
|
||||
FitCollider();
|
||||
}
|
||||
|
||||
@@ -43,4 +44,8 @@ public class ProcessorComponent : ComponentBase
|
||||
break;
|
||||
}
|
||||
}
|
||||
public override void getpath()
|
||||
{
|
||||
onComponentClicked?.Invoke(componentType, processorData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@ namespace Simulator.Data
|
||||
{
|
||||
public string name;
|
||||
public string label;
|
||||
public string processor_type;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@ public class SinkComponent : ComponentBase
|
||||
{
|
||||
this.sourceData = sourceData;
|
||||
data = sourceData;
|
||||
onComponentClicked += FindAnyObjectByType<SinkProperty>().SetProertyWindow;
|
||||
FitCollider();
|
||||
}
|
||||
|
||||
public override void GetModelData(DataObject modelData)
|
||||
@@ -22,4 +24,8 @@ public class SinkComponent : ComponentBase
|
||||
var sinkdata = datas["entity_ids"].ConvertTo<List<string>>();
|
||||
EntityManager.Instance.DestroyEnity(sinkdata);
|
||||
}
|
||||
public override void getpath()
|
||||
{
|
||||
onComponentClicked?.Invoke(componentType, sourceData);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace Simulator.Data
|
||||
{
|
||||
this.sourceData = sourceData;
|
||||
data = sourceData;
|
||||
onComponentClicked += FindAnyObjectByType<SimulatorProperty>().SetProertyWindow;
|
||||
onComponentClicked += FindAnyObjectByType<SourceProperty>().SetProertyWindow;
|
||||
FitCollider();
|
||||
}
|
||||
|
||||
@@ -158,9 +158,7 @@ namespace Simulator.Data
|
||||
|
||||
public override void getpath()
|
||||
{
|
||||
PathIndexer.Build(ComponentsManager.Instance.logicDetailData);
|
||||
var path = PathIndexer.GetNodePath(sourceData);
|
||||
Debug.Log(path);
|
||||
onComponentClicked?.Invoke(componentType, sourceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Assets/Scripts/Simulator/Policy_Base.cs
Normal file
134
Assets/Scripts/Simulator/Policy_Base.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering.Universal;
|
||||
|
||||
[Serializable]
|
||||
public abstract class Policy_Base
|
||||
{
|
||||
public string type;
|
||||
public abstract SpawnPolicy policy { get; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Policy_Constant : Policy_Base
|
||||
{
|
||||
public float value;
|
||||
public override SpawnPolicy policy => SpawnPolicy.Constant;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Policy_Normal : Policy_Base
|
||||
{
|
||||
public float mean;
|
||||
public float stddev;
|
||||
public override SpawnPolicy policy => SpawnPolicy.Normal;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Policy_Uniform : Policy_Base
|
||||
{
|
||||
public float min_val;
|
||||
public float max_val;
|
||||
public override SpawnPolicy policy => SpawnPolicy.Uniform;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Policy_Exponential : Policy_Base
|
||||
{
|
||||
public float mean;
|
||||
public override SpawnPolicy policy => SpawnPolicy.Exponential;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Policy_Triangular : Policy_Base
|
||||
{
|
||||
public float min_val;
|
||||
public float mode;
|
||||
public float max_val;
|
||||
public override SpawnPolicy policy => SpawnPolicy.Triangular;
|
||||
}
|
||||
|
||||
public sealed class PolicyConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
=> objectType == typeof(Policy_Base);
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var jo = JObject.Load(reader);
|
||||
var typeStr = (jo["type"]?.ToString() ?? "").Trim().ToLowerInvariant();
|
||||
|
||||
Policy_Base target = typeStr switch
|
||||
{
|
||||
"constant" => new Policy_Constant(),
|
||||
"normal" => new Policy_Normal(),
|
||||
"uniform" => new Policy_Uniform(),
|
||||
"exponential" => new Policy_Exponential(),
|
||||
"triangular" => new Policy_Triangular(),
|
||||
_ => new Policy_Constant()
|
||||
};
|
||||
|
||||
// jo 내용을 target에 채워 넣음
|
||||
serializer.Populate(jo.CreateReader(), target);
|
||||
return target;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
// 필요 시 구현 (서버로 다시 보낼 때)
|
||||
serializer.Serialize(writer, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PolicyFactory
|
||||
{
|
||||
public static Policy_Base Create(string typeLower)
|
||||
{
|
||||
switch (typeLower)
|
||||
{
|
||||
case "Constant": return new Policy_Constant { type = "constant", value = 0f };
|
||||
case "Normal": return new Policy_Normal { type = "normal", mean = 0f, stddev = 0f };
|
||||
case "Uniform": return new Policy_Uniform { type = "uniform", min_val = 0f, max_val = 0f };
|
||||
case "Exponential": return new Policy_Exponential { type = "exponential", mean = 0f };
|
||||
case "Triangular": return new Policy_Triangular { type = "triangular", min_val = 0f, mode = 0f, max_val = 0f };
|
||||
default: throw new ArgumentException($"Unknown policy type: {typeLower}");
|
||||
}
|
||||
}
|
||||
|
||||
public static Policy_Base ChangeType(Policy_Base oldPolicy, string newTypeLower)
|
||||
{
|
||||
var next = Create(newTypeLower);
|
||||
|
||||
// 의미 있는 값 일부 이관(원하시는 정책 변환 규칙으로 조정)
|
||||
// “대표값”을 mean 계열로 최대한 유지하는 예시입니다.
|
||||
float representative = oldPolicy switch
|
||||
{
|
||||
Policy_Exponential e => e.mean,
|
||||
Policy_Normal n => n.mean,
|
||||
Policy_Constant c => c.value,
|
||||
Policy_Triangular t => t.mode,
|
||||
Policy_Uniform u => (u.min_val + u.max_val) * 0.5f,
|
||||
_ => 0f
|
||||
};
|
||||
|
||||
switch (next)
|
||||
{
|
||||
case Policy_Exponential e: e.mean = representative; break;
|
||||
case Policy_Normal n: n.mean = representative; n.stddev = 1f; break;
|
||||
case Policy_Constant c: c.value = representative; break;
|
||||
case Policy_Uniform u:
|
||||
u.min_val = Mathf.Max(0f, representative - 0.5f);
|
||||
u.max_val = representative + 0.5f;
|
||||
break;
|
||||
case Policy_Triangular t:
|
||||
t.mode = representative;
|
||||
t.min_val = Mathf.Max(0f, representative - 1f);
|
||||
t.max_val = representative + 1f;
|
||||
break;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Simulator/Policy_Base.cs.meta
Normal file
2
Assets/Scripts/Simulator/Policy_Base.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f0adbe757fba3a4285506d22907c0cb
|
||||
63
Assets/Scripts/Simulator/PropertyWindow/ConveyorProperty.cs
Normal file
63
Assets/Scripts/Simulator/PropertyWindow/ConveyorProperty.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Simulator.Data;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.UI.Window.PropertyWindow;
|
||||
using static Unity.VisualScripting.Member;
|
||||
|
||||
public class ConveyorProperty : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private PropertyWindow propertyWindow;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
//propertyWindow.PropertyValueChanged += OnPropertyValueChanged;
|
||||
}
|
||||
|
||||
public void SetProertyWindow(ConveyorPath data)
|
||||
{
|
||||
Debug.Log(data);
|
||||
InitConveyorProperty(data);
|
||||
}
|
||||
|
||||
void SaveChange(object source, object value, string name)
|
||||
{
|
||||
var path = PathIndexer.GetNodePath(source);
|
||||
Patch updateData = new Patch();
|
||||
updateData.value = value;
|
||||
UpdateValueStack.AddPatch($"{path}.{name}", value);
|
||||
//UpdateValueStack.Save();
|
||||
}
|
||||
public void InitConveyorProperty(ConveyorPath conveyor)
|
||||
{
|
||||
List<IPropertyEntry> entries = new List<IPropertyEntry>
|
||||
{
|
||||
new StringProperty("name", "이름", conveyor.name)
|
||||
{
|
||||
IsReadOnly = true
|
||||
},
|
||||
new StringProperty("from_node", "시작 노드", conveyor.from_node)
|
||||
{
|
||||
IsReadOnly = true
|
||||
},
|
||||
new StringProperty("to_node", "도착 노드", conveyor.to_node)
|
||||
{
|
||||
IsReadOnly = true
|
||||
},
|
||||
new FloatProperty("speed", "컨베이어 속도", conveyor.speed)
|
||||
{
|
||||
IsReadOnly = false
|
||||
}.Bind(
|
||||
setter: v => {conveyor.speed = v;SaveChange(conveyor,v,"speed"); }
|
||||
),
|
||||
new IntProperty("capacity", "최대 수용 개체 수", conveyor.capacity)
|
||||
{
|
||||
IsReadOnly = false
|
||||
}.Bind(
|
||||
setter: v => {conveyor.capacity = v;SaveChange(conveyor,v,"capacity"); }
|
||||
),
|
||||
};
|
||||
|
||||
propertyWindow.LoadMixedProperties(entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0abd224908e33b43aa20ba22230309d
|
||||
16
Assets/Scripts/Simulator/PropertyWindow/NodeProperty.cs
Normal file
16
Assets/Scripts/Simulator/PropertyWindow/NodeProperty.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using UnityEngine;
|
||||
|
||||
public class NodeProperty : MonoBehaviour
|
||||
{
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d5383949dbc6ea46bd5666e0731db9c
|
||||
101
Assets/Scripts/Simulator/PropertyWindow/ProcessorProperty.cs
Normal file
101
Assets/Scripts/Simulator/PropertyWindow/ProcessorProperty.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Simulator.Data;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.UI.Window.PropertyWindow;
|
||||
|
||||
public class ProcessorProperty : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private PropertyWindow propertyWindow;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
propertyWindow.PropertyValueChanged += OnPropertyValueChanged;
|
||||
}
|
||||
|
||||
public void SetProertyWindow(ComponentType type, ComponentDataBase data)
|
||||
{
|
||||
Debug.Log(data);
|
||||
switch (type)
|
||||
{
|
||||
case ComponentType.Processor:
|
||||
InitSourceProperty(data as ProcessorDataClass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveChange(object source, object value, string name)
|
||||
{
|
||||
var path = PathIndexer.GetNodePath(source);
|
||||
Patch updateData = new Patch();
|
||||
updateData.value = value;
|
||||
UpdateValueStack.AddPatch($"{path}.{name}", value);
|
||||
}
|
||||
|
||||
public void InitSourceProperty(ProcessorDataClass processor)
|
||||
{
|
||||
List<IPropertyEntry> entries = new List<IPropertyEntry>
|
||||
{
|
||||
new StringProperty("name", "이름", processor.name)
|
||||
{
|
||||
IsReadOnly = true
|
||||
},
|
||||
new StringProperty("label", "라벨", processor.label)
|
||||
{
|
||||
IsReadOnly = false
|
||||
}.Bind(
|
||||
setter: v => { processor.label = v; SaveChange(processor, v, "label"); }
|
||||
),
|
||||
new Vector2Property("Position", "X,Y 좌표(m)", new Vector2(processor.physical.position.x, processor.physical.position.z))
|
||||
{
|
||||
IsReadOnly = false
|
||||
}.Bind(
|
||||
setter: v => { processor.physical.position.x = v.x; processor.physical.position.z = v.y; SaveChange(processor, v, "physical.position"); }
|
||||
),
|
||||
new ListProperty("processing_type","처리 유형",new List<string>(){ "combine","seperate","transform" },processor.processor_type)
|
||||
{
|
||||
IsReadOnly = false
|
||||
}.Bind(
|
||||
setter: v => { processor.processor_type=v; SaveChange(processor, v, "processing_type"); }
|
||||
),
|
||||
CreateDynamicVisibilityTestGroup()
|
||||
|
||||
};
|
||||
|
||||
propertyWindow.LoadMixedProperties(entries);
|
||||
}
|
||||
|
||||
private void OnPropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
|
||||
{
|
||||
Debug.Log($"[PropertyChanged] Id:{e.PropertyId}, Type:{e.PropertyType}, Value:{e.NewValue}");
|
||||
|
||||
// 동적 그룹 표시/비표시 테스트
|
||||
if (e.PropertyId == "display_mode_TimePolicy")
|
||||
{
|
||||
string selectedMode = e.NewValue.ToString();
|
||||
// HandleDisplayModeChanged_TimePolicy(selectedMode);
|
||||
}
|
||||
if (e.PropertyId == "display_mode_CountPolicy")
|
||||
{
|
||||
string selectedMode = e.NewValue.ToString();
|
||||
//HandleDisplayModeChanged_CountPolicy(selectedMode);
|
||||
}
|
||||
if (e.PropertyId == "display_mode_DefectPolicy")
|
||||
{
|
||||
string selectedMode = e.NewValue.ToString();
|
||||
//HandleDisplayModeChanged_DefectPolicy(selectedMode);
|
||||
}
|
||||
}
|
||||
|
||||
private PropertyGroup CreateDynamicVisibilityTestGroup()
|
||||
{
|
||||
var group = new PropertyGroup("dynamic_test", "투입 개체", isExpanded: true);
|
||||
int count = 0;
|
||||
foreach(var prefab in PrefabManager.Instance.prefabDict)
|
||||
{
|
||||
var property = new BoolProperty($"prefab{count}", prefab.Key, false);
|
||||
group.AddItem(property);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfcfbe4843bea5f40981c317f9bea4f2
|
||||
@@ -1,63 +0,0 @@
|
||||
using Simulator.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.UI.Window.PropertyWindow;
|
||||
|
||||
public class SimulatorProperty : MonoBehaviour
|
||||
{
|
||||
public Dictionary<ComponentType, List<IPropertyItem>> propertyDict = new Dictionary<ComponentType, List<IPropertyItem>>();
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetProertyWindow(ComponentType type,ComponentDataBase data)
|
||||
{
|
||||
Debug.Log(data);
|
||||
switch (type)
|
||||
{
|
||||
case ComponentType.Source:
|
||||
InitSourceProperty(data as SourceDataClass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void InitSourceProperty(SourceDataClass source)
|
||||
{
|
||||
PropertyWindow.Instance.LoadProperties(
|
||||
new List<IPropertyItem>()
|
||||
{
|
||||
new StringProperty("name", "이름", source.name)
|
||||
{
|
||||
IsReadOnly=true
|
||||
},
|
||||
new StringProperty("label", "라벨", source.label)
|
||||
{
|
||||
IsReadOnly=false
|
||||
},
|
||||
new Vector2Property("Position","X,Y 좌표(m)", new Vector2(source.physical.position.x,source.physical.position.z))
|
||||
{
|
||||
IsReadOnly=false
|
||||
},
|
||||
new ListProperty("prefabType","생성될 개체 유형",new List<string>(PrefabManager.Instance.prefabDict.Keys),source.prefab)
|
||||
{
|
||||
IsReadOnly=false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
PropertyWindow.Instance.PropertyValueChanged += (sender, e) =>
|
||||
{
|
||||
PathIndexer.Build(ComponentsManager.Instance.logicDetailData);
|
||||
var path = PathIndexer.GetNodePath(source);
|
||||
Debug.Log($"{path}.{e.PropertyId}, type:{e.PropertyType}, newValue: {e.NewValue}");
|
||||
Patch updateData = new Patch();
|
||||
updateData.value = e.NewValue;
|
||||
UpdateValueStack.AddPatch($"{path}.{e.PropertyId}", e.NewValue);
|
||||
UpdateValueStack.Save();
|
||||
//CursorManager.Instance.SetCursor(CursorType.HandPoint);
|
||||
};
|
||||
}
|
||||
}
|
||||
58
Assets/Scripts/Simulator/PropertyWindow/SinkProperty.cs
Normal file
58
Assets/Scripts/Simulator/PropertyWindow/SinkProperty.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Simulator.Data;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.UI.Window.PropertyWindow;
|
||||
|
||||
public class SinkProperty : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private PropertyWindow propertyWindow;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
//propertyWindow.PropertyValueChanged += OnPropertyValueChanged;
|
||||
}
|
||||
|
||||
public void SetProertyWindow(ComponentType type, ComponentDataBase data)
|
||||
{
|
||||
Debug.Log(data);
|
||||
switch (type)
|
||||
{
|
||||
case ComponentType.Sink:
|
||||
InitSinkProperty(data as SinkDataClass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveChange(object source, object value, string name)
|
||||
{
|
||||
var path = PathIndexer.GetNodePath(source);
|
||||
Patch updateData = new Patch();
|
||||
updateData.value = value;
|
||||
UpdateValueStack.AddPatch($"{path}.{name}", value);
|
||||
}
|
||||
|
||||
public void InitSinkProperty(SinkDataClass sink)
|
||||
{
|
||||
List<IPropertyEntry> entries = new List<IPropertyEntry>
|
||||
{
|
||||
new StringProperty("name", "이름", sink.name)
|
||||
{
|
||||
IsReadOnly=true
|
||||
},
|
||||
new StringProperty("label", "라벨", sink.label)
|
||||
{
|
||||
IsReadOnly=false
|
||||
}.Bind(
|
||||
setter: v => {sink.label = v;SaveChange(sink,v,"label"); }
|
||||
),
|
||||
new Vector2Property("Position","X,Y 좌표(m)", new Vector2(sink.physical.position.x,sink.physical.position.z))
|
||||
{
|
||||
IsReadOnly=false
|
||||
}.Bind(
|
||||
setter: v => {sink.physical.position.x = v.x;sink.physical.position.z = v.y; SaveChange(sink,v,"physical.position"); }
|
||||
)
|
||||
};
|
||||
propertyWindow.LoadMixedProperties(entries);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0768809a7d49096438588c00517df09b
|
||||
492
Assets/Scripts/Simulator/PropertyWindow/SourceProperty.cs
Normal file
492
Assets/Scripts/Simulator/PropertyWindow/SourceProperty.cs
Normal file
@@ -0,0 +1,492 @@
|
||||
|
||||
using Simulator.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.UI.Window.PropertyWindow;
|
||||
public enum SpawnPolicy
|
||||
{
|
||||
Constant,
|
||||
Normal,
|
||||
Uniform,
|
||||
Exponential,
|
||||
Triangular
|
||||
}
|
||||
public class SourceProperty : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private PropertyWindow propertyWindow;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
propertyWindow.PropertyValueChanged += OnPropertyValueChanged;
|
||||
}
|
||||
|
||||
public void SetProertyWindow(ComponentType type, ComponentDataBase data)
|
||||
{
|
||||
Debug.Log(data);
|
||||
switch (type)
|
||||
{
|
||||
case ComponentType.Source:
|
||||
InitSourceProperty(data as SourceDataClass);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SaveChange(object source,object value,string name)
|
||||
{
|
||||
var path = PathIndexer.GetNodePath(source);
|
||||
Patch updateData = new Patch();
|
||||
updateData.value = value;
|
||||
UpdateValueStack.AddPatch($"{path}.{name}", value);
|
||||
//UpdateValueStack.Save();
|
||||
}
|
||||
|
||||
public void InitSourceProperty(SourceDataClass source)
|
||||
{
|
||||
List<IPropertyEntry> entries = new List<IPropertyEntry>
|
||||
{
|
||||
new StringProperty("name", "이름", source.name)
|
||||
{
|
||||
IsReadOnly=true
|
||||
},
|
||||
new StringProperty("label", "라벨", source.label)
|
||||
{
|
||||
IsReadOnly=false
|
||||
}.Bind(
|
||||
setter: v => {source.label = v;SaveChange(source,v,"label"); }
|
||||
),
|
||||
new Vector2Property("Position","X,Y 좌표(m)", new Vector2(source.physical.position.x,source.physical.position.z))
|
||||
{
|
||||
IsReadOnly=false
|
||||
}.Bind(
|
||||
setter: v => {source.physical.position.x = v.x;source.physical.position.z = v.y; SaveChange(source,v,"physical.position"); }
|
||||
),
|
||||
new ListProperty("prefabType","생성될 개체 유형",new List<string>(PrefabManager.Instance.prefabDict.Keys),source.prefab)
|
||||
{
|
||||
IsReadOnly=false
|
||||
}.Bind(
|
||||
setter: v => {source.prefab=v; SaveChange(source,v,"prefab"); }
|
||||
),
|
||||
new EnumProperty("display_mode_TimePolicy", "생산 간격 정책",source.spawn_time_policy.policy).Bind(
|
||||
setter: v=>{source.spawn_time_policy=PolicyFactory.Create(v.ToString());SaveChange(source,v.ToString(),"spawn_time_policy.type");}
|
||||
),
|
||||
CreateSpawnTimePolicy_Constant(source),
|
||||
CreateSpawnTimePolicy_Normal(source),
|
||||
CreateSpawnTimePolicy_Uniform(source),
|
||||
CreateSpawnTimePolicy_Exponential(source),
|
||||
CreateSpawnTimePolicy_Triangular(source),
|
||||
|
||||
new EnumProperty("display_mode_CountPolicy", "생산 개수 정책",source.spawn_count_policy.policy).Bind(
|
||||
setter: v=>{source.spawn_count_policy=PolicyFactory.Create(v.ToString());SaveChange(source,v.ToString(),"spawn_count_policy.type");}
|
||||
),
|
||||
CreateSpawnCountPolicy_Constant(source),
|
||||
CreateSpawnCountPolicy_Normal(source),
|
||||
CreateSpawnCountPolicy_Uniform(source),
|
||||
CreateSpawnCountPolicy_Exponential(source),
|
||||
CreateSpawnCountPolicy_Triangular(source),
|
||||
|
||||
new BoolProperty("Is_Unlimited","무제한 모드",source.is_unlimited),
|
||||
|
||||
new EnumProperty("display_mode_DefectPolicy", "불량률 정책",source.defect_rate_policy.policy).Bind(
|
||||
setter: v=>{source.defect_rate_policy=PolicyFactory.Create(v.ToString());SaveChange(source,v.ToString(),"defect_rate_policy.type");}
|
||||
),
|
||||
CreateSpawnDefectPolicy_Constant(source),
|
||||
CreateSpawnDefectPolicy_Normal(source),
|
||||
CreateSpawnDefectPolicy_Uniform(source),
|
||||
CreateSpawnDefectPolicy_Exponential(source),
|
||||
CreateSpawnDefectPolicy_Triangular(source),
|
||||
};
|
||||
propertyWindow.LoadMixedProperties(entries);
|
||||
|
||||
HandleDisplayModeChanged_TimePolicy(source.spawn_time_policy.policy.ToString());
|
||||
HandleDisplayModeChanged_CountPolicy(source.spawn_count_policy.policy.ToString());
|
||||
HandleDisplayModeChanged_DefectPolicy(source.defect_rate_policy.policy.ToString());
|
||||
}
|
||||
|
||||
private void OnPropertyValueChanged(object sender, PropertyValueChangedEventArgs e)
|
||||
{
|
||||
Debug.Log($"[PropertyChanged] Id:{e.PropertyId}, Type:{e.PropertyType}, Value:{e.NewValue}");
|
||||
|
||||
// 동적 그룹 표시/비표시 테스트
|
||||
if (e.PropertyId == "display_mode_TimePolicy")
|
||||
{
|
||||
string selectedMode = e.NewValue.ToString();
|
||||
HandleDisplayModeChanged_TimePolicy(selectedMode);
|
||||
}
|
||||
if (e.PropertyId == "display_mode_CountPolicy")
|
||||
{
|
||||
string selectedMode = e.NewValue.ToString();
|
||||
HandleDisplayModeChanged_CountPolicy(selectedMode);
|
||||
}
|
||||
if (e.PropertyId == "display_mode_DefectPolicy")
|
||||
{
|
||||
string selectedMode = e.NewValue.ToString();
|
||||
HandleDisplayModeChanged_DefectPolicy(selectedMode);
|
||||
}
|
||||
}
|
||||
|
||||
#region 생산 간격 정책
|
||||
private PropertyGroup CreateSpawnTimePolicy_Constant(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnTimePolicy_Constant", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnTimePolicy_Constant_Value", "상수 값", (source.spawn_time_policy as Policy_Constant) ?.value ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Constant).value = v;SaveChange(source,v,"Spawn_time_Policy.value"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnTimePolicy_Normal(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnTimePolicy_Normal", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnTimePolicy_Normal_Mean", "정규 분포 표준치", (source.spawn_time_policy as Policy_Normal) ?.mean ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Normal).mean = v;SaveChange(source,v,"Spawn_time_Policy.mean"); }
|
||||
),
|
||||
new FloatProperty("SpawnTimePolicy_Normal_Gap", "정규 분포 표준 편차", (source.spawn_time_policy as Policy_Normal) ?.stddev ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Normal).stddev = v;SaveChange(source,v,"Spawn_time_Policy.stddev"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnTimePolicy_Uniform(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnTimePolicy_Uniform", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnTimePolicy_Uniform_Min", "균등 분포 최소값", (source.spawn_time_policy as Policy_Uniform) ?.min_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Uniform).min_val = v;SaveChange(source,v,"Spawn_time_Policy.min_val"); }
|
||||
),
|
||||
new FloatProperty("SpawnTimePolicy_Uniform_Max", "균등 분포 최대값", (source.spawn_time_policy as Policy_Uniform) ?.max_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Uniform).max_val = v;SaveChange(source,v,"Spawn_time_Policy.max_val"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnTimePolicy_Exponential(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnTimePolicy_Exponential", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnTimePolicy_Exponential_Mean", "지수 분포 평균치", (source.spawn_time_policy as Policy_Exponential) ?.mean ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Exponential).mean = v;SaveChange(source,v,"Spawn_time_Policy.mean"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnTimePolicy_Triangular(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnTimePolicy_Triangular", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnTimePolicy_Triangular_Min", "지수 분포 최소값", (source.spawn_time_policy as Policy_Triangular) ?.min_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Triangular).min_val = v;SaveChange(source,v,"Spawn_time_Policy.min_val"); }
|
||||
),
|
||||
new FloatProperty("SpawnTimePolicy_Triangular_Mean", "지수 분포 최빈값", (source.spawn_time_policy as Policy_Triangular) ?.mode ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Triangular).mode = v;SaveChange(source,v,"Spawn_time_Policy.mode"); }
|
||||
),
|
||||
new FloatProperty("SpawnTimePolicy_Triangular_Max", "지수 분포 최대값", (source.spawn_time_policy as Policy_Triangular) ?.max_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_time_policy as Policy_Triangular).max_val = v;SaveChange(source,v,"Spawn_time_Policy.max_val"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private void HandleDisplayModeChanged_TimePolicy(string mode)
|
||||
{
|
||||
// 모든 조건부 그룹 숨김
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Constant", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Normal", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Uniform", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Exponential", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Triangular", false);
|
||||
|
||||
// 선택된 모드에 따라 해당 그룹만 표시
|
||||
switch (mode)
|
||||
{
|
||||
case "Constant":
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Constant", true);
|
||||
break;
|
||||
case "Normal":
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Normal", true);
|
||||
break;
|
||||
case "Uniform":
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Uniform", true);
|
||||
break;
|
||||
case "Exponential":
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Exponential", true);
|
||||
break;
|
||||
case "Triangular":
|
||||
propertyWindow.SetGroupVisibility("SpawnTimePolicy_Triangular", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 생산 개수 정책
|
||||
private PropertyGroup CreateSpawnCountPolicy_Constant(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnCountPolicy_Constant", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnCountPolicy_Constant_Value", "상수 값", (source.spawn_count_policy as Policy_Constant)?.value??0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Constant).value = v;SaveChange(source,v,"spawn_count_policy.value"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnCountPolicy_Normal(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnCountPolicy_Normal", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnCountPolicy_Normal_Mean", "정규 분포 표준치", (source.spawn_count_policy as Policy_Normal) ?.mean ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Normal).mean = v;SaveChange(source,v,"spawn_count_policy.mean"); }
|
||||
),
|
||||
new FloatProperty("SpawnCountPolicy_Normal_Gap", "정규 분포 표준 편차", (source.spawn_count_policy as Policy_Normal) ?.stddev ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Normal).stddev = v;SaveChange(source,v,"spawn_count_policy.stddev"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnCountPolicy_Uniform(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnCountPolicy_Uniform", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnCountPolicy_Uniform_Min", "균등 분포 최소값", (source.spawn_count_policy as Policy_Uniform) ?.min_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Uniform).min_val = v;SaveChange(source,v,"spawn_count_policy.min_val"); }
|
||||
),
|
||||
new FloatProperty("SpawnCountPolicy_Uniform_Max", "균등 분포 최대값", (source.spawn_count_policy as Policy_Uniform) ?.max_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Uniform).max_val = v;SaveChange(source,v,"spawn_count_policy.max_val"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnCountPolicy_Exponential(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnCountPolicy_Exponential", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnCountPolicy_Exponential_Mean", "지수 분포 평균치", (source.spawn_count_policy as Policy_Exponential) ?.mean ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Exponential).mean = v;SaveChange(source,v,"spawn_count_policy.mean"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnCountPolicy_Triangular(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnCountPolicy_Triangular", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnCountPolicy_Triangular_Min", "지수 분포 최소값", (source.spawn_count_policy as Policy_Triangular) ?.min_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Triangular).min_val = v;SaveChange(source,v,"spawn_count_policy.min_val"); }
|
||||
),
|
||||
new FloatProperty("SpawnCountPolicy_Triangular_Mean", "지수 분포 최빈값", (source.spawn_count_policy as Policy_Triangular) ?.mode ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Triangular).mode = v;SaveChange(source,v,"spawn_count_policy.mode"); }
|
||||
),
|
||||
new FloatProperty("SpawnCountPolicy_Triangular_Max", "지수 분포 최대값", (source.spawn_count_policy as Policy_Triangular) ?.max_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.spawn_count_policy as Policy_Triangular).max_val = v;SaveChange(source,v,"spawn_count_policy.max_val"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
private void HandleDisplayModeChanged_CountPolicy(string mode)
|
||||
{
|
||||
// 모든 조건부 그룹 숨김
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Constant", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Normal", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Uniform", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Exponential", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Triangular", false);
|
||||
|
||||
// 선택된 모드에 따라 해당 그룹만 표시
|
||||
switch (mode)
|
||||
{
|
||||
case "Constant":
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Constant", true);
|
||||
break;
|
||||
case "Normal":
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Normal", true);
|
||||
break;
|
||||
case "Uniform":
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Uniform", true);
|
||||
break;
|
||||
case "Exponential":
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Exponential", true);
|
||||
break;
|
||||
case "Triangular":
|
||||
propertyWindow.SetGroupVisibility("SpawnCountPolicy_Triangular", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 불량률 정책
|
||||
private PropertyGroup CreateSpawnDefectPolicy_Constant(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnDefectPolicy_Constant", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnDefectPolicy_Constant_Value", "상수 값", (source.defect_rate_policy as Policy_Constant)?.value??0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Constant).value = v;SaveChange(source,v,"defect_rate_policy.value"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnDefectPolicy_Normal(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnDefectPolicy_Normal", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnDefectPolicy_Normal_Mean", "정규 분포 표준치", (source.defect_rate_policy as Policy_Normal)?.mean??0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Normal).mean = v;SaveChange(source,v,"defect_rate_policy.mean"); }
|
||||
),
|
||||
new FloatProperty("SpawnDefectPolicy_Normal_Gap", "정규 분포 표준 편차", (source.defect_rate_policy as Policy_Normal)?.stddev??0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Normal).stddev = v;SaveChange(source,v,"defect_rate_policy.stddev"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnDefectPolicy_Uniform(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnDefectPolicy_Uniform", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnDefectPolicy_Uniform_Min", "균등 분포 최소값", (source.defect_rate_policy as Policy_Uniform) ?.min_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Uniform).min_val = v;SaveChange(source,v,"defect_rate_policy.min_val"); }
|
||||
),
|
||||
new FloatProperty("SpawnDefectPolicy_Uniform_Max", "균등 분포 최대값", (source.defect_rate_policy as Policy_Uniform) ?.max_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Uniform).max_val = v;SaveChange(source,v,"defect_rate_policy.max_val"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnDefectPolicy_Exponential(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnDefectPolicy_Exponential", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnDefectPolicy_Exponential_Mean", "지수 분포 평균치", (source.defect_rate_policy as Policy_Exponential) ?.mean ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Exponential).mean = v;SaveChange(source,v,"defect_rate_policy.mean"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
|
||||
private PropertyGroup CreateSpawnDefectPolicy_Triangular(SourceDataClass source)
|
||||
{
|
||||
var group = new PropertyGroup("SpawnDefectPolicy_Triangular", "", isExpanded: true);
|
||||
group.AddItems(new IPropertyItem[]
|
||||
{
|
||||
new FloatProperty("SpawnDefectPolicy_Triangular_Min", "지수 분포 최소값", (source.defect_rate_policy as Policy_Triangular) ?.min_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Triangular).min_val = v;SaveChange(source,v,"defect_rate_policy.min_val"); }
|
||||
),
|
||||
new FloatProperty("SpawnDefectPolicy_Triangular_Mean", "지수 분포 최빈값", (source.defect_rate_policy as Policy_Triangular) ?.mode ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Triangular).mode = v;SaveChange(source,v,"defect_rate_policy.mode"); }
|
||||
),
|
||||
new FloatProperty("SpawnDefectPolicy_Triangular_Max", "지수 분포 최대값", (source.defect_rate_policy as Policy_Triangular) ?.max_val ?? 0f)
|
||||
{
|
||||
}.Bind(
|
||||
setter: v => {(source.defect_rate_policy as Policy_Triangular).max_val = v;SaveChange(source,v,"defect_rate_policy.max_val"); }
|
||||
),
|
||||
});
|
||||
return group;
|
||||
}
|
||||
private void HandleDisplayModeChanged_DefectPolicy(string mode)
|
||||
{
|
||||
// 모든 조건부 그룹 숨김
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Constant", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Normal", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Uniform", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Exponential", false);
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Triangular", false);
|
||||
|
||||
// 선택된 모드에 따라 해당 그룹만 표시
|
||||
switch (mode)
|
||||
{
|
||||
case "Constant":
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Constant", true);
|
||||
break;
|
||||
case "Normal":
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Normal", true);
|
||||
break;
|
||||
case "Uniform":
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Uniform", true);
|
||||
break;
|
||||
case "Exponential":
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Exponential", true);
|
||||
break;
|
||||
case "Triangular":
|
||||
propertyWindow.SetGroupVisibility("SpawnDefectPolicy_Triangular", true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -27,6 +27,9 @@ namespace Simulator
|
||||
|
||||
public event Action<LogicDetailData> onRequestDataReceived;
|
||||
|
||||
[SerializeField]
|
||||
private PropertyWindow propertyWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 초기 화 메서드입니다.
|
||||
/// Awake 메서드에서 호출되며, MonoBehaviour가 생성될 때 한 번만 실행됩니다.
|
||||
@@ -103,8 +106,8 @@ namespace Simulator
|
||||
{
|
||||
new MenuItemData("explorer_window", "탐색창", new ActionCommand(()=> Debug.Log("탐색창"))),
|
||||
new MenuItemData("property_window", "속성창", new ActionCommand(()=> {
|
||||
if(PropertyWindow.Instance.IsVisible) PropertyWindow.Instance.Hide();
|
||||
else PropertyWindow.Instance.Show();
|
||||
if(propertyWindow.IsVisible) propertyWindow.Hide();
|
||||
else propertyWindow.Show();
|
||||
})),
|
||||
}),
|
||||
new MenuItemData("help", "도움말", subMenuItems: new List<MenuItemData>
|
||||
@@ -118,7 +121,7 @@ namespace Simulator
|
||||
|
||||
private void SetupPropertyWindow()
|
||||
{
|
||||
PropertyWindow.Instance.LoadProperties(new List<IPropertyItem>
|
||||
propertyWindow.LoadProperties(new List<IPropertyItem>
|
||||
{
|
||||
new StringProperty("prop1", "String Property", "Initial Value")
|
||||
{
|
||||
@@ -218,7 +221,7 @@ namespace Simulator
|
||||
}
|
||||
});
|
||||
|
||||
PropertyWindow.Instance.PropertyValueChanged += (sender, e) =>
|
||||
propertyWindow.PropertyValueChanged += (sender, e) =>
|
||||
{
|
||||
Debug.Log($"Property Id:{e.PropertyId}, type:{e.PropertyType}, newValue: {e.NewValue}");
|
||||
};
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework.Constraints;
|
||||
using Simulator.Config;
|
||||
using Simulator.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.Network;
|
||||
|
||||
[Serializable]
|
||||
public class Patches
|
||||
@@ -29,7 +32,7 @@ public class UpdateValueStack : MonoBehaviour
|
||||
patches.Add(path, value);
|
||||
}
|
||||
}
|
||||
public static void Save()
|
||||
public static async void Save()
|
||||
{
|
||||
Patches change = new Patches();
|
||||
foreach (var patch in patches)
|
||||
@@ -41,7 +44,9 @@ public class UpdateValueStack : MonoBehaviour
|
||||
}
|
||||
var data=JsonConvert.SerializeObject(change);
|
||||
Debug.Log(data);
|
||||
|
||||
var request = await HttpRequester.Request_<Totaljson>($"{Constants.HTTP_DOMAIN}/simulation/logics/{SimulationConfig.logicId}/data", data, methodString:"patch", null,true);
|
||||
Debug.Log(request.code);
|
||||
|
||||
patches.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ namespace Simulator
|
||||
{
|
||||
onParameterRecived += ComponentsManager.Instance.testRequest;
|
||||
#if UNITY_EDITOR
|
||||
SimulationConfig.projectId = 278;
|
||||
SimulationConfig.logicId = 292;
|
||||
SimulationConfig.projectId = 292;
|
||||
SimulationConfig.logicId = 306;
|
||||
onParameterRecived?.Invoke();
|
||||
#else
|
||||
Application.ExternalCall("loadingComplete");
|
||||
|
||||
@@ -205,6 +205,11 @@ namespace UVC.Network
|
||||
return await Request_<T>(url, body == null ? null : JsonHelper.ToJson(body), "get", header, useAuth);
|
||||
}
|
||||
|
||||
public static async UniTask<T> RequestPut<T>(string url, Dictionary<string, object> body = null, Dictionary<string, string> header = null, bool useAuth = false)
|
||||
{
|
||||
return await Request_<T>(url, body == null ? null : JsonHelper.ToJson(body), "put", header, useAuth);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// HTTP 요청을 처리하는 내부 메소드
|
||||
/// </summary>
|
||||
@@ -223,15 +228,15 @@ namespace UVC.Network
|
||||
/// <param name="header">추가할 헤더 정보</param>
|
||||
/// <param name="useAuth">인증 토큰 사용 여부</param>
|
||||
/// <returns>지정된 타입으로 변환된 응답 데이터</returns>
|
||||
private static async UniTask<T> Request_<T>(string url, string body = null, string methodString = "post", Dictionary<string, string> header = null, bool useAuth = false)
|
||||
public static async UniTask<T> Request_<T>(string url, string body = null, string methodString = "post", Dictionary<string, string> header = null, bool useAuth = false)
|
||||
{
|
||||
|
||||
HTTPMethods method = StringToMethod(methodString);
|
||||
Debug.Log(method);
|
||||
|
||||
if (!url.Contains("http")) url = $"{Domain}{url}";
|
||||
|
||||
var request = SelectHTTPRequest(method, url);
|
||||
|
||||
request.DownloadSettings = new Best.HTTP.Request.Settings.DownloadSettings() { ContentStreamMaxBuffered = 1024 * 1024 * 200 };
|
||||
request.MethodType = method;
|
||||
request.SetHeader("Content-Type", "application/json; charset=utf-8");
|
||||
@@ -643,13 +648,16 @@ namespace UVC.Network
|
||||
switch (methods)
|
||||
{
|
||||
case HTTPMethods.Get:
|
||||
|
||||
if (onRequest != null)
|
||||
return HTTPRequest.CreateGet(url, onRequest);
|
||||
else
|
||||
return HTTPRequest.CreateGet(url);
|
||||
case HTTPMethods.Post:
|
||||
return HTTPRequest.CreatePost(url);
|
||||
case HTTPMethods.Put:
|
||||
return HTTPRequest.CreatePut(url);
|
||||
case HTTPMethods.Patch:
|
||||
return HTTPRequest.CreatePatch(url);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -667,6 +675,8 @@ namespace UVC.Network
|
||||
{
|
||||
"get" => HTTPMethods.Get,
|
||||
"post" => HTTPMethods.Post,
|
||||
"put" => HTTPMethods.Put,
|
||||
"patch"=>HTTPMethods.Patch,
|
||||
_ => throw new ArgumentException($"Unsupported HTTP method: {method}"),
|
||||
};
|
||||
}
|
||||
|
||||
17
Assets/Scripts/UVC/TopUI/SimulatorSaveCommand.cs
Normal file
17
Assets/Scripts/UVC/TopUI/SimulatorSaveCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Simulator;
|
||||
using Simulator.Config;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UVC.UI.Commands;
|
||||
|
||||
public class SimulatorSaveCommand : ICommand
|
||||
{
|
||||
public SimulatorSaveCommand(object? parameter = null)
|
||||
{
|
||||
}
|
||||
|
||||
public async void Execute(object? parameter = null)
|
||||
{
|
||||
UpdateValueStack.Save();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/TopUI/SimulatorSaveCommand.cs.meta
Normal file
2
Assets/Scripts/UVC/TopUI/SimulatorSaveCommand.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b03eb6f71f6f49b42a0a4f5f9ead3cae
|
||||
17
Assets/Scripts/UVC/TopUI/SimulatorSaveCommandMono.cs
Normal file
17
Assets/Scripts/UVC/TopUI/SimulatorSaveCommandMono.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Simulator.UI.Commands;
|
||||
using UnityEngine;
|
||||
using UVC.UI.Commands.Mono;
|
||||
|
||||
public class SimulatorSaveCommandMono : MonoBehaviourCommand
|
||||
{
|
||||
SimulatorSaveCommand command;
|
||||
private void Start()
|
||||
{
|
||||
command = new SimulatorSaveCommand();
|
||||
}
|
||||
public override void Execute()
|
||||
{
|
||||
Debug.Log("asdf");
|
||||
command.Execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 592e9e72ea21a984599385e593d4a596
|
||||
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/Core.meta
Normal file
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/Core.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ec534859551f9c429fd3b50b8a1cf47
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// PropertyWindow에 추가할 수 있는 항목의 통합 인터페이스입니다.
|
||||
/// 그룹(IPropertyGroup) 또는 개별 아이템(IPropertyItem) 모두 이 타입으로 처리됩니다.
|
||||
/// </summary>
|
||||
public interface IPropertyEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 렌더링 순서를 결정하는 값입니다.
|
||||
/// 낮은 값이 먼저 표시됩니다.
|
||||
/// </summary>
|
||||
int Order { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 86e2cde5cc1d5cb4d88c1937d95bae42
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// 속성 아이템들을 그룹으로 묶어서 관리하는 인터페이스입니다.
|
||||
/// Unity Inspector 스타일의 접이식 그룹(Foldout Group)을 지원합니다.
|
||||
/// </summary>
|
||||
public interface IPropertyGroup : IPropertyEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 그룹의 고유 식별자
|
||||
/// </summary>
|
||||
string GroupId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// UI에 표시될 그룹 이름
|
||||
/// </summary>
|
||||
string GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 접힘/펼침 상태
|
||||
/// true: 펼쳐진 상태, false: 접힌 상태
|
||||
/// </summary>
|
||||
bool IsExpanded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 포함된 속성 아이템들의 읽기 전용 목록
|
||||
/// </summary>
|
||||
IReadOnlyList<IPropertyItem> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 속성 아이템을 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="item">추가할 속성 아이템</param>
|
||||
void AddItem(IPropertyItem item);
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 여러 속성 아이템을 한번에 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="items">추가할 속성 아이템들</param>
|
||||
void AddItems(IEnumerable<IPropertyItem> items);
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에서 특정 ID의 속성 아이템을 제거합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">제거할 아이템의 ID</param>
|
||||
/// <returns>제거 성공 여부</returns>
|
||||
bool RemoveItem(string itemId);
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 모든 속성 아이템을 제거합니다.
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 포함된 아이템 수를 반환합니다.
|
||||
/// </summary>
|
||||
int Count { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d407d231edf6e3f4cbe8630fc9651bcc
|
||||
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// IPropertyGroup 인터페이스의 기본 구현 클래스입니다.
|
||||
/// 속성 아이템들을 그룹으로 묶어서 관리합니다.
|
||||
/// </summary>
|
||||
public class PropertyGroup : IPropertyGroup
|
||||
{
|
||||
private readonly List<IPropertyItem> _items = new List<IPropertyItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 고유 식별자
|
||||
/// </summary>
|
||||
public string GroupId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// UI에 표시될 그룹 이름
|
||||
/// </summary>
|
||||
public string GroupName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 접힘/펼침 상태
|
||||
/// </summary>
|
||||
public bool IsExpanded { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 렌더링 순서
|
||||
/// </summary>
|
||||
public int Order { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 포함된 속성 아이템들의 읽기 전용 목록
|
||||
/// </summary>
|
||||
public IReadOnlyList<IPropertyItem> Items => _items.AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 포함된 아이템 수
|
||||
/// </summary>
|
||||
public int Count => _items.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 추가되었을 때 발생하는 이벤트
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyGroupItemEventArgs> ItemAdded;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 제거되었을 때 발생하는 이벤트
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyGroupItemEventArgs> ItemRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// 그룹이 비워졌을 때 발생하는 이벤트
|
||||
/// </summary>
|
||||
public event EventHandler Cleared;
|
||||
|
||||
/// <summary>
|
||||
/// PropertyGroup을 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 고유 ID</param>
|
||||
/// <param name="groupName">그룹 표시명</param>
|
||||
/// <param name="order">렌더링 순서 (기본값: 0)</param>
|
||||
/// <param name="isExpanded">초기 펼침 상태 (기본값: true)</param>
|
||||
public PropertyGroup(string groupId, string groupName, int order = 0, bool isExpanded = true)
|
||||
{
|
||||
GroupId = groupId ?? throw new ArgumentNullException(nameof(groupId));
|
||||
GroupName = groupName ?? throw new ArgumentNullException(nameof(groupName));
|
||||
Order = order;
|
||||
IsExpanded = isExpanded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 속성 아이템을 추가합니다.
|
||||
/// </summary>
|
||||
public void AddItem(IPropertyItem item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
// 중복 ID 체크
|
||||
if (_items.Any(i => i.Id == item.Id))
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"[PropertyGroup] 이미 존재하는 아이템 ID입니다: {item.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
item.GroupId = GroupId;
|
||||
_items.Add(item);
|
||||
ItemAdded?.Invoke(this, new PropertyGroupItemEventArgs(GroupId, item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에 여러 속성 아이템을 한번에 추가합니다.
|
||||
/// </summary>
|
||||
public void AddItems(IEnumerable<IPropertyItem> items)
|
||||
{
|
||||
if (items == null)
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
AddItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹에서 특정 ID의 속성 아이템을 제거합니다.
|
||||
/// </summary>
|
||||
public bool RemoveItem(string itemId)
|
||||
{
|
||||
var item = _items.FirstOrDefault(i => i.Id == itemId);
|
||||
if (item != null)
|
||||
{
|
||||
item.GroupId = null;
|
||||
_items.Remove(item);
|
||||
ItemRemoved?.Invoke(this, new PropertyGroupItemEventArgs(GroupId, item));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 모든 속성 아이템을 제거합니다.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var item in _items)
|
||||
{
|
||||
item.GroupId = null;
|
||||
}
|
||||
_items.Clear();
|
||||
Cleared?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID의 아이템을 가져옵니다.
|
||||
/// </summary>
|
||||
public IPropertyItem GetItem(string itemId)
|
||||
{
|
||||
return _items.FirstOrDefault(i => i.Id == itemId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID의 아이템이 존재하는지 확인합니다.
|
||||
/// </summary>
|
||||
public bool ContainsItem(string itemId)
|
||||
{
|
||||
return _items.Any(i => i.Id == itemId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyGroup 아이템 관련 이벤트 인자
|
||||
/// </summary>
|
||||
public class PropertyGroupItemEventArgs : EventArgs
|
||||
{
|
||||
public string GroupId { get; }
|
||||
public IPropertyItem Item { get; }
|
||||
|
||||
public PropertyGroupItemEventArgs(string groupId, IPropertyItem item)
|
||||
{
|
||||
GroupId = groupId;
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a8f10eca2b388e4bbfa893f415783da
|
||||
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/Events.meta
Normal file
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/Events.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6732ae9ae5e1c7d4cb457b92ab4efdb6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// 그룹 추가/제거 이벤트 인자
|
||||
/// </summary>
|
||||
public class PropertyGroupEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 이벤트 대상 그룹
|
||||
/// </summary>
|
||||
public IPropertyGroup Group { get; }
|
||||
|
||||
public PropertyGroupEventArgs(IPropertyGroup group)
|
||||
{
|
||||
Group = group;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 펼침/접힘 상태 변경 이벤트 인자
|
||||
/// </summary>
|
||||
public class PropertyGroupExpandedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 이벤트 대상 그룹
|
||||
/// </summary>
|
||||
public IPropertyGroup Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 변경 전 펼침 상태
|
||||
/// </summary>
|
||||
public bool WasExpanded { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 변경 후 펼침 상태
|
||||
/// </summary>
|
||||
public bool IsExpanded { get; }
|
||||
|
||||
public PropertyGroupExpandedEventArgs(IPropertyGroup group, bool wasExpanded, bool isExpanded)
|
||||
{
|
||||
Group = group;
|
||||
WasExpanded = wasExpanded;
|
||||
IsExpanded = isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 엔트리(그룹 또는 아이템) 추가/제거 이벤트 인자
|
||||
/// </summary>
|
||||
public class PropertyEntryEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 이벤트 대상 엔트리
|
||||
/// </summary>
|
||||
public IPropertyEntry Entry { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 엔트리가 그룹인지 여부
|
||||
/// </summary>
|
||||
public bool IsGroup => Entry is IPropertyGroup;
|
||||
|
||||
/// <summary>
|
||||
/// 엔트리가 아이템인지 여부
|
||||
/// </summary>
|
||||
public bool IsItem => Entry is IPropertyItem;
|
||||
|
||||
public PropertyEntryEventArgs(IPropertyEntry entry)
|
||||
{
|
||||
Entry = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1e0750845e63f443b44c64062b7015b
|
||||
@@ -11,5 +11,11 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
/// <param name="item">표시할 속성 데이터</param>
|
||||
/// <param name="controller">상호작용할 컨트롤러</param>
|
||||
void Setup(IPropertyItem item, PropertyWindow controller);
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
void SetReadOnly(bool isReadOnly);
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/Pool.meta
Normal file
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/Pool.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9f56f3d625d0d543b0ff282a1a9f35a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,353 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// PropertyUI 오브젝트들을 풀링하여 재사용하는 시스템입니다.
|
||||
/// 매번 Instantiate/Destroy 대신 풀에서 가져오고 반환하여 성능을 향상시킵니다.
|
||||
/// </summary>
|
||||
public class PropertyUIPool : MonoBehaviour
|
||||
{
|
||||
[Header("Pool Settings")]
|
||||
[SerializeField] private int _defaultPoolSize = 5;
|
||||
[SerializeField] private Transform _poolContainer;
|
||||
|
||||
[Header("Prefabs")]
|
||||
[SerializeField] private GameObject _groupPrefab;
|
||||
[SerializeField] private GameObject _stringPropertyPrefab;
|
||||
[SerializeField] private GameObject _numberPropertyPrefab;
|
||||
[SerializeField] private GameObject _boolPropertyPrefab;
|
||||
[SerializeField] private GameObject _vector2PropertyPrefab;
|
||||
[SerializeField] private GameObject _vector3PropertyPrefab;
|
||||
[SerializeField] private GameObject _colorPropertyPrefab;
|
||||
[SerializeField] private GameObject _datePropertyPrefab;
|
||||
[SerializeField] private GameObject _dateTimePropertyPrefab;
|
||||
[SerializeField] private GameObject _enumPropertyPrefab;
|
||||
[SerializeField] private GameObject _listPropertyPrefab;
|
||||
[SerializeField] private GameObject _radioGroupPropertyPrefab;
|
||||
[SerializeField] private GameObject _numberRangePropertyPrefab;
|
||||
[SerializeField] private GameObject _dateRangePropertyPrefab;
|
||||
[SerializeField] private GameObject _dateTimeRangePropertyPrefab;
|
||||
|
||||
/// <summary>
|
||||
/// PropertyType별 UI 오브젝트 풀
|
||||
/// </summary>
|
||||
private readonly Dictionary<PropertyType, Queue<GameObject>> _itemPools = new Dictionary<PropertyType, Queue<GameObject>>();
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 UI 오브젝트 풀
|
||||
/// </summary>
|
||||
private readonly Queue<PropertyGroupView> _groupPool = new Queue<PropertyGroupView>();
|
||||
|
||||
/// <summary>
|
||||
/// PropertyType별 프리팹 매핑
|
||||
/// </summary>
|
||||
private Dictionary<PropertyType, GameObject> _prefabMap;
|
||||
|
||||
private bool _isInitialized = false;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀을 초기화합니다.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
// 풀 컨테이너 생성 (없으면)
|
||||
if (_poolContainer == null)
|
||||
{
|
||||
var containerObj = new GameObject("PropertyUIPool_Container");
|
||||
containerObj.transform.SetParent(transform);
|
||||
containerObj.SetActive(false);
|
||||
_poolContainer = containerObj.transform;
|
||||
}
|
||||
|
||||
// 프리팹 맵 초기화
|
||||
InitializePrefabMap();
|
||||
|
||||
// 각 타입별 풀 초기화
|
||||
foreach (PropertyType type in System.Enum.GetValues(typeof(PropertyType)))
|
||||
{
|
||||
if (!_itemPools.ContainsKey(type))
|
||||
{
|
||||
_itemPools[type] = new Queue<GameObject>();
|
||||
}
|
||||
}
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyType별 프리팹 매핑을 초기화합니다.
|
||||
/// </summary>
|
||||
private void InitializePrefabMap()
|
||||
{
|
||||
_prefabMap = new Dictionary<PropertyType, GameObject>
|
||||
{
|
||||
{ PropertyType.String, _stringPropertyPrefab },
|
||||
{ PropertyType.Int, _numberPropertyPrefab },
|
||||
{ PropertyType.Float, _numberPropertyPrefab },
|
||||
{ PropertyType.Bool, _boolPropertyPrefab },
|
||||
{ PropertyType.Vector2, _vector2PropertyPrefab },
|
||||
{ PropertyType.Vector3, _vector3PropertyPrefab },
|
||||
{ PropertyType.Color, _colorPropertyPrefab },
|
||||
{ PropertyType.Date, _datePropertyPrefab },
|
||||
{ PropertyType.DateTime, _dateTimePropertyPrefab },
|
||||
{ PropertyType.Enum, _enumPropertyPrefab },
|
||||
{ PropertyType.DropdownList, _listPropertyPrefab },
|
||||
{ PropertyType.RadioGroup, _radioGroupPropertyPrefab },
|
||||
{ PropertyType.IntRange, _numberRangePropertyPrefab },
|
||||
{ PropertyType.FloatRange, _numberRangePropertyPrefab },
|
||||
{ PropertyType.DateRange, _dateRangePropertyPrefab },
|
||||
{ PropertyType.DateTimeRange, _dateTimeRangePropertyPrefab }
|
||||
};
|
||||
}
|
||||
|
||||
#region Item Pool Methods
|
||||
|
||||
/// <summary>
|
||||
/// 풀에서 PropertyUI 오브젝트를 가져옵니다.
|
||||
/// 풀이 비어있으면 새로 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="type">속성 타입</param>
|
||||
/// <returns>UI GameObject 또는 프리팹이 없으면 null</returns>
|
||||
public GameObject GetItemUI(PropertyType type)
|
||||
{
|
||||
if (!_isInitialized) Initialize();
|
||||
|
||||
// 풀에서 가져오기 시도
|
||||
if (_itemPools.TryGetValue(type, out var pool) && pool.Count > 0)
|
||||
{
|
||||
var obj = pool.Dequeue();
|
||||
obj.SetActive(true);
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 풀이 비어있으면 새로 생성
|
||||
return CreateItemUI(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyUI 오브젝트를 풀에 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="type">속성 타입</param>
|
||||
/// <param name="obj">반환할 오브젝트</param>
|
||||
public void ReturnItemUI(PropertyType type, GameObject obj)
|
||||
{
|
||||
if (obj == null) return;
|
||||
|
||||
// UI 컴포넌트 정리
|
||||
var propertyUI = obj.GetComponent<IPropertyUI>();
|
||||
if (propertyUI is IPoolable poolable)
|
||||
{
|
||||
poolable.OnReturnToPool();
|
||||
}
|
||||
|
||||
obj.SetActive(false);
|
||||
obj.transform.SetParent(_poolContainer, false);
|
||||
|
||||
if (_itemPools.TryGetValue(type, out var pool))
|
||||
{
|
||||
pool.Enqueue(obj);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 새로운 PropertyUI 오브젝트를 생성합니다.
|
||||
/// </summary>
|
||||
private GameObject CreateItemUI(PropertyType type)
|
||||
{
|
||||
if (_prefabMap == null || !_prefabMap.TryGetValue(type, out var prefab) || prefab == null)
|
||||
{
|
||||
Debug.LogWarning($"[PropertyUIPool] '{type}' 타입에 대한 프리팹이 설정되지 않았습니다.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = Instantiate(prefab);
|
||||
return obj;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Group Pool Methods
|
||||
|
||||
/// <summary>
|
||||
/// 풀에서 PropertyGroupView를 가져옵니다.
|
||||
/// </summary>
|
||||
/// <returns>PropertyGroupView 또는 프리팹이 없으면 null</returns>
|
||||
public PropertyGroupView GetGroupUI()
|
||||
{
|
||||
if (!_isInitialized) Initialize();
|
||||
|
||||
// 풀에서 가져오기 시도
|
||||
if (_groupPool.Count > 0)
|
||||
{
|
||||
var groupView = _groupPool.Dequeue();
|
||||
groupView.gameObject.SetActive(true);
|
||||
return groupView;
|
||||
}
|
||||
|
||||
// 풀이 비어있으면 새로 생성
|
||||
return CreateGroupUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyGroupView를 풀에 반환합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupView">반환할 그룹 뷰</param>
|
||||
public void ReturnGroupUI(PropertyGroupView groupView)
|
||||
{
|
||||
if (groupView == null) return;
|
||||
|
||||
groupView.Reset();
|
||||
groupView.gameObject.SetActive(false);
|
||||
groupView.transform.SetParent(_poolContainer, false);
|
||||
_groupPool.Enqueue(groupView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 새로운 PropertyGroupView를 생성합니다.
|
||||
/// </summary>
|
||||
private PropertyGroupView CreateGroupUI()
|
||||
{
|
||||
if (_groupPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("[PropertyUIPool] 그룹 프리팹이 설정되지 않았습니다.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var obj = Instantiate(_groupPrefab);
|
||||
return obj.GetComponent<PropertyGroupView>();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Pool Management
|
||||
|
||||
/// <summary>
|
||||
/// 특정 타입의 UI를 미리 생성하여 풀에 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="type">속성 타입</param>
|
||||
/// <param name="count">미리 생성할 개수</param>
|
||||
public void Prewarm(PropertyType type, int count)
|
||||
{
|
||||
if (!_isInitialized) Initialize();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var obj = CreateItemUI(type);
|
||||
if (obj != null)
|
||||
{
|
||||
ReturnItemUI(type, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 UI를 미리 생성하여 풀에 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="count">미리 생성할 개수</param>
|
||||
public void PrewarmGroups(int count)
|
||||
{
|
||||
if (!_isInitialized) Initialize();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var groupView = CreateGroupUI();
|
||||
if (groupView != null)
|
||||
{
|
||||
ReturnGroupUI(groupView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 풀을 비우고 오브젝트를 파괴합니다.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
// 아이템 풀 정리
|
||||
foreach (var pool in _itemPools.Values)
|
||||
{
|
||||
while (pool.Count > 0)
|
||||
{
|
||||
var obj = pool.Dequeue();
|
||||
if (obj != null)
|
||||
{
|
||||
Destroy(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
_itemPools.Clear();
|
||||
|
||||
// 그룹 풀 정리
|
||||
while (_groupPool.Count > 0)
|
||||
{
|
||||
var groupView = _groupPool.Dequeue();
|
||||
if (groupView != null)
|
||||
{
|
||||
Destroy(groupView.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// 풀 컨테이너의 모든 자식 삭제
|
||||
if (_poolContainer != null)
|
||||
{
|
||||
foreach (Transform child in _poolContainer)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 풀 상태를 반환합니다 (디버그용).
|
||||
/// </summary>
|
||||
public string GetPoolStatus()
|
||||
{
|
||||
var status = new System.Text.StringBuilder();
|
||||
status.AppendLine("[PropertyUIPool Status]");
|
||||
status.AppendLine($"Groups in pool: {_groupPool.Count}");
|
||||
|
||||
foreach (var kvp in _itemPools)
|
||||
{
|
||||
if (kvp.Value.Count > 0)
|
||||
{
|
||||
status.AppendLine($"{kvp.Key}: {kvp.Value.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
return status.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀링 가능한 UI 컴포넌트가 구현해야 하는 인터페이스입니다.
|
||||
/// </summary>
|
||||
public interface IPoolable
|
||||
{
|
||||
/// <summary>
|
||||
/// 풀에서 가져올 때 호출됩니다.
|
||||
/// </summary>
|
||||
void OnGetFromPool();
|
||||
|
||||
/// <summary>
|
||||
/// 풀에 반환될 때 호출됩니다.
|
||||
/// 이벤트 해제 및 상태 초기화를 수행해야 합니다.
|
||||
/// </summary>
|
||||
void OnReturnToPool();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a9acf4264efbf44e957f7c7870280dc
|
||||
@@ -65,8 +65,9 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
|
||||
/// <summary>
|
||||
/// 모든 속성 항목이 구현해야 하는 기본 인터페이스입니다.
|
||||
/// IPropertyEntry를 상속하여 그룹과 혼용하여 사용할 수 있습니다.
|
||||
/// </summary>
|
||||
public interface IPropertyItem
|
||||
public interface IPropertyItem : IPropertyEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// 속성의 고유 식별자 (필수)
|
||||
@@ -108,6 +109,12 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
/// 속성의 데이터 타입
|
||||
/// </summary>
|
||||
PropertyType PropertyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 속한 그룹의 ID입니다.
|
||||
/// null이면 그룹에 속하지 않은 독립 아이템입니다.
|
||||
/// </summary>
|
||||
string GroupId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,11 +131,23 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
public bool IsReadOnly { get; set; } = false;
|
||||
public abstract PropertyType PropertyType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 렌더링 순서를 결정하는 값입니다. (IPropertyEntry 구현)
|
||||
/// </summary>
|
||||
public int Order { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 속한 그룹의 ID입니다. null이면 그룹에 속하지 않습니다.
|
||||
/// </summary>
|
||||
public string GroupId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 실제 데이터가 저장되는 필드
|
||||
/// </summary>
|
||||
protected T _value;
|
||||
|
||||
private Action<T>? _setter;
|
||||
|
||||
/// <summary>
|
||||
/// 속성의 현재 값 (제네릭 타입)
|
||||
/// </summary>
|
||||
@@ -145,6 +164,12 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
_value = initialValue;
|
||||
}
|
||||
|
||||
public PropertyItem<T> Bind(Action<T>? setter)
|
||||
{
|
||||
_setter = setter; // 읽기전용이면 null 가능
|
||||
return this;
|
||||
}
|
||||
|
||||
// IPropertyItem 인터페이스 구현
|
||||
public object GetValue() => _value;
|
||||
public void SetValue(object value)
|
||||
@@ -153,6 +178,10 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
if (value is T typedValue)
|
||||
{
|
||||
_value = typedValue;
|
||||
if (_setter != null)
|
||||
{
|
||||
_setter(typedValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
@@ -5,19 +7,19 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
/// <summary>
|
||||
/// 속성창의 UI를 담당하는 View 클래스입니다.
|
||||
/// Controller로부터 데이터를 받아와 동적으로 UI 요소들을 생성하고 관리합니다.
|
||||
/// 이 클래스는 MonoBehaviour를 상속받아 Unity 씬에 배치될 수 있습니다.
|
||||
/// 그룹과 개별 아이템을 혼용하여 렌더링할 수 있습니다.
|
||||
/// </summary>
|
||||
public class PropertyView : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// UI 요소들이 생성될 부모 컨테이너입니다.
|
||||
/// Unity 에디터에서 Vertical Layout Group 컴포넌트가 추가된 Panel 등을 연결합니다.
|
||||
/// </summary>
|
||||
[Tooltip("속성 UI들이 생성될 부모 Transform (예: Vertical Layout Group이 있는 Panel)")]
|
||||
[SerializeField] private Transform _container;
|
||||
|
||||
// 각 속성 타입에 맞는 UI 프리팹들입니다.
|
||||
// 실제 프로젝트에서는 이 프리팹들을 만들고 여기에 연결해야 합니다.
|
||||
[Header("Group UI Prefab")]
|
||||
[SerializeField] private GameObject _groupPrefab;
|
||||
|
||||
[Header("Property UI Prefabs")]
|
||||
[SerializeField] private GameObject _stringPropertyPrefab;
|
||||
[SerializeField] private GameObject _numberPropertyPrefab;
|
||||
@@ -39,63 +41,216 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
/// </summary>
|
||||
private PropertyWindow _controller;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 표시 중인 그룹 뷰들의 캐시
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, PropertyGroupView> _groupViews = new Dictionary<string, PropertyGroupView>();
|
||||
|
||||
/// <summary>
|
||||
/// 현재 표시 중인 아이템 UI들의 캐시 (아이템 ID -> GameObject)
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, GameObject> _itemViews = new Dictionary<string, GameObject>();
|
||||
|
||||
/// <summary>
|
||||
/// Controller를 View에 설정하고 UI를 초기화합니다.
|
||||
/// </summary>
|
||||
/// <param name="controller">사용할 PropertyWindow</param>
|
||||
public void Initialize(PropertyWindow controller)
|
||||
{
|
||||
// 기존 컨트롤러 이벤트 해제
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.PropertyValueChanged -= OnPropertyValueChanged;
|
||||
_controller.GroupExpandedChanged -= OnGroupExpandedChanged;
|
||||
}
|
||||
|
||||
_controller = controller;
|
||||
|
||||
// Controller가 null이 아니면, 이벤트 핸들러를 등록하고 UI를 그립니다.
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.PropertyValueChanged += OnPropertyValueChanged;
|
||||
DrawProperties();
|
||||
_controller.GroupExpandedChanged += OnGroupExpandedChanged;
|
||||
DrawEntries();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controller에 있는 속성 목록을 기반으로 UI를 생성합니다.
|
||||
/// Controller에 있는 엔트리 목록을 기반으로 UI를 생성합니다.
|
||||
/// 그룹과 개별 아이템을 혼용하여 렌더링합니다.
|
||||
/// </summary>
|
||||
private void DrawProperties()
|
||||
private void DrawEntries()
|
||||
{
|
||||
// UI를 다시 그리기 전에 기존에 생성된 모든 자식 오브젝트를 삭제합니다.
|
||||
foreach (Transform child in _container)
|
||||
{
|
||||
DestroyImmediate(child.gameObject);
|
||||
}
|
||||
// 기존 UI 정리
|
||||
ClearAllViews();
|
||||
|
||||
if (_controller == null) return;
|
||||
|
||||
// 각 속성 항목에 대해 적절한 UI를 생성합니다.
|
||||
foreach (var propertyItem in _controller.Properties)
|
||||
{
|
||||
// 속성 타입에 맞는 UI 프리팹을 찾습니다.
|
||||
GameObject prefab = GetPrefabForProperty(propertyItem.PropertyType);
|
||||
if (prefab != null)
|
||||
{
|
||||
// 프리팹을 인스턴스화하여 컨테이너의 자식으로 추가합니다.
|
||||
GameObject uiInstance = Instantiate(prefab, _container);
|
||||
// Order 순으로 정렬된 엔트리들을 렌더링
|
||||
var sortedEntries = _controller.Entries;
|
||||
|
||||
// 생성된 UI 인스턴스에서 IPropertyUI 컴포넌트를 찾아 Setup을 호출합니다.
|
||||
var propertyUI = uiInstance.GetComponent<IPropertyUI>();
|
||||
if (propertyUI != null)
|
||||
foreach (var entry in sortedEntries)
|
||||
{
|
||||
if (entry is IPropertyGroup group)
|
||||
{
|
||||
DrawGroup(group);
|
||||
}
|
||||
else if (entry is IPropertyItem item && item.GroupId == null)
|
||||
{
|
||||
// 그룹에 속하지 않은 개별 아이템만 직접 렌더링
|
||||
DrawPropertyItem(item, _container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 UI를 생성합니다.
|
||||
/// </summary>
|
||||
private void DrawGroup(IPropertyGroup group)
|
||||
{
|
||||
if (_groupPrefab == null)
|
||||
{
|
||||
Debug.LogWarning("[PropertyView] 그룹 프리팹이 설정되지 않았습니다. 그룹 내 아이템만 표시합니다.");
|
||||
// 그룹 프리팹이 없으면 아이템들만 직접 렌더링
|
||||
foreach (var item in group.Items)
|
||||
{
|
||||
DrawPropertyItem(item, _container);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 그룹 UI 생성
|
||||
GameObject groupInstance = Instantiate(_groupPrefab, _container);
|
||||
var groupView = groupInstance.GetComponent<PropertyGroupView>();
|
||||
|
||||
if (groupView != null)
|
||||
{
|
||||
groupView.Setup(group, _controller);
|
||||
_groupViews[group.GroupId] = groupView;
|
||||
|
||||
// 그룹이 펼쳐진 상태면 자식 아이템들 렌더링
|
||||
if (group.IsExpanded)
|
||||
{
|
||||
foreach (var item in group.Items)
|
||||
{
|
||||
propertyUI.Setup(propertyItem, _controller);
|
||||
DrawPropertyItem(item, groupView.ItemContainer);
|
||||
}
|
||||
else
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[PropertyView] 그룹 프리팹 '{_groupPrefab.name}'에 PropertyGroupView 컴포넌트가 없습니다.");
|
||||
Destroy(groupInstance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 개별 속성 아이템 UI를 생성합니다.
|
||||
/// </summary>
|
||||
private void DrawPropertyItem(IPropertyItem item, Transform container)
|
||||
{
|
||||
GameObject prefab = GetPrefabForProperty(item.PropertyType);
|
||||
if (prefab != null)
|
||||
{
|
||||
GameObject uiInstance = Instantiate(prefab, container);
|
||||
|
||||
var propertyUI = uiInstance.GetComponent<IPropertyUI>();
|
||||
if (propertyUI != null)
|
||||
{
|
||||
propertyUI.Setup(item, _controller);
|
||||
_itemViews[item.Id] = uiInstance;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[PropertyView] 프리팹 '{prefab.name}'에 IPropertyUI를 구현한 스크립트가 없습니다.");
|
||||
Destroy(uiInstance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[PropertyView] '{item.PropertyType}' 타입에 대한 UI 프리팹이 지정되지 않았습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 View를 정리합니다.
|
||||
/// </summary>
|
||||
private void ClearAllViews()
|
||||
{
|
||||
// 그룹 뷰 정리
|
||||
foreach (var groupView in _groupViews.Values)
|
||||
{
|
||||
if (groupView != null)
|
||||
{
|
||||
groupView.Reset();
|
||||
Destroy(groupView.gameObject);
|
||||
}
|
||||
}
|
||||
_groupViews.Clear();
|
||||
|
||||
// 아이템 뷰 정리
|
||||
foreach (var itemView in _itemViews.Values)
|
||||
{
|
||||
if (itemView != null)
|
||||
{
|
||||
Destroy(itemView);
|
||||
}
|
||||
}
|
||||
_itemViews.Clear();
|
||||
|
||||
// 컨테이너의 모든 자식 삭제 (혹시 누락된 것이 있을 경우)
|
||||
foreach (Transform child in _container)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 펼침/접힘 상태가 변경되었을 때 호출됩니다.
|
||||
/// </summary>
|
||||
private void OnGroupExpandedChanged(object sender, PropertyGroupExpandedEventArgs e)
|
||||
{
|
||||
if (_groupViews.TryGetValue(e.Group.GroupId, out var groupView))
|
||||
{
|
||||
groupView.UpdateExpandedState();
|
||||
|
||||
// 펼쳐진 경우 자식 아이템들 렌더링
|
||||
if (e.IsExpanded)
|
||||
{
|
||||
foreach (var item in e.Group.Items)
|
||||
{
|
||||
Debug.LogError($"[PropertyView] 프리팹 '{prefab.name}'에 IPropertyUI를 구현한 스크립트가 없습니다.");
|
||||
// 이미 존재하는 아이템은 건너뜀
|
||||
if (!_itemViews.ContainsKey(item.Id))
|
||||
{
|
||||
DrawPropertyItem(item, groupView.ItemContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[PropertyView] '{propertyItem.PropertyType}' 타입에 대한 UI 프리팹이 지정되지 않았습니다.");
|
||||
// 접힌 경우 자식 아이템들 제거
|
||||
groupView.ClearItems();
|
||||
foreach (var item in e.Group.Items)
|
||||
{
|
||||
if (_itemViews.TryGetValue(item.Id, out var itemView))
|
||||
{
|
||||
Destroy(itemView);
|
||||
_itemViews.Remove(item.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [하위 호환성] 기존 방식으로 속성 목록을 그립니다.
|
||||
/// </summary>
|
||||
[System.Obsolete("Use DrawEntries() instead. This method is kept for backward compatibility.")]
|
||||
private void DrawProperties()
|
||||
{
|
||||
DrawEntries();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 속성 타입에 맞는 UI 프리팹을 반환합니다.
|
||||
/// 실제 구현에서는 더 많은 case가 필요합니다.
|
||||
@@ -160,7 +315,63 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
if (_controller != null)
|
||||
{
|
||||
_controller.PropertyValueChanged -= OnPropertyValueChanged;
|
||||
_controller.GroupExpandedChanged -= OnGroupExpandedChanged;
|
||||
}
|
||||
|
||||
// 캐시 정리
|
||||
_groupViews.Clear();
|
||||
_itemViews.Clear();
|
||||
}
|
||||
|
||||
#region Property/Group Visibility
|
||||
|
||||
/// <summary>
|
||||
/// 특정 속성 아이템의 가시성을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">속성 아이템의 ID</param>
|
||||
/// <param name="visible">가시성 여부</param>
|
||||
public void SetPropertyVisibility(string propertyId, bool visible)
|
||||
{
|
||||
if (_itemViews.TryGetValue(propertyId, out var itemView) && itemView != null)
|
||||
{
|
||||
itemView.SetActive(visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 그룹의 가시성을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <param name="visible">가시성 여부</param>
|
||||
public void SetGroupVisibility(string groupId, bool visible)
|
||||
{
|
||||
if (_groupViews.TryGetValue(groupId, out var groupView) && groupView != null)
|
||||
{
|
||||
groupView.gameObject.SetActive(visible);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Property/Group ReadOnly
|
||||
|
||||
/// <summary>
|
||||
/// 특정 속성 아이템의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">속성 아이템의 ID</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public void SetPropertyReadOnly(string propertyId, bool isReadOnly)
|
||||
{
|
||||
if (_itemViews.TryGetValue(propertyId, out var itemView) && itemView != null)
|
||||
{
|
||||
var propertyUI = itemView.GetComponent<IPropertyUI>();
|
||||
if (propertyUI != null)
|
||||
{
|
||||
propertyUI.SetReadOnly(isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,44 +4,421 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UVC.Core;
|
||||
using UVC.Factory;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// 속성 데이터를 관리하고, 데이터 변경 시 이벤트를 발생시키는 컨트롤러 클래스입니다.
|
||||
/// Model과 View 사이의 중재자 역할을 합니다.
|
||||
/// 그룹과 개별 아이템을 혼용하여 사용할 수 있습니다.
|
||||
/// </summary>
|
||||
public class PropertyWindow: SingletonScene<PropertyWindow>, IPointerEnterHandler, IPointerExitHandler
|
||||
public class PropertyWindow : MonoBehaviour
|
||||
{
|
||||
|
||||
[SerializeField]
|
||||
private PropertyView _view;
|
||||
|
||||
#region Internal Data Structures
|
||||
|
||||
/// <summary>
|
||||
/// 통합 엔트리 목록 (그룹과 개별 아이템 혼합 저장)
|
||||
/// </summary>
|
||||
private readonly List<IPropertyEntry> _entries = new List<IPropertyEntry>();
|
||||
|
||||
/// <summary>
|
||||
/// 빠른 그룹 조회를 위한 인덱스
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, IPropertyGroup> _groupIndex = new Dictionary<string, IPropertyGroup>();
|
||||
|
||||
/// <summary>
|
||||
/// 빠른 아이템 조회를 위한 인덱스
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, IPropertyItem> _itemIndex = new Dictionary<string, IPropertyItem>();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// 현재 컨트롤러가 관리하는 모든 속성 항목의 목록입니다.
|
||||
/// 하위 호환성을 위해 유지됩니다. 그룹에 속한 아이템도 포함됩니다.
|
||||
/// </summary>
|
||||
public List<IPropertyItem> Properties { get; private set; } = new List<IPropertyItem>();
|
||||
public List<IPropertyItem> Properties
|
||||
{
|
||||
get
|
||||
{
|
||||
var allItems = new List<IPropertyItem>();
|
||||
foreach (var entry in _entries)
|
||||
{
|
||||
if (entry is IPropertyItem item && item.GroupId == null)
|
||||
{
|
||||
allItems.Add(item);
|
||||
}
|
||||
else if (entry is IPropertyGroup group)
|
||||
{
|
||||
allItems.AddRange(group.Items);
|
||||
}
|
||||
}
|
||||
return allItems;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 정렬된 엔트리 목록을 반환합니다.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IPropertyEntry> Entries => _entries.OrderBy(e => e.Order).ToList().AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// 모든 그룹의 읽기 전용 목록을 반환합니다.
|
||||
/// </summary>
|
||||
public IReadOnlyList<IPropertyGroup> Groups => _groupIndex.Values.ToList().AsReadOnly();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
/// <summary>
|
||||
/// 속성 값이 변경될 때 발생하는 이벤트입니다.
|
||||
/// View는 이 이벤트를 구독하여 UI를 업데이트할 수 있습니다.
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyValueChangedEventArgs>? PropertyValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 새로운 속성 목록을 로드하고 초기화합니다.
|
||||
/// 그룹이 추가되었을 때 발생하는 이벤트입니다.
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyGroupEventArgs>? GroupAdded;
|
||||
|
||||
/// <summary>
|
||||
/// 그룹이 제거되었을 때 발생하는 이벤트입니다.
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyGroupEventArgs>? GroupRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 펼침/접힘 상태가 변경되었을 때 발생하는 이벤트입니다.
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyGroupExpandedEventArgs>? GroupExpandedChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 엔트리가 추가되었을 때 발생하는 이벤트입니다.
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyEntryEventArgs>? EntryAdded;
|
||||
|
||||
/// <summary>
|
||||
/// 엔트리가 제거되었을 때 발생하는 이벤트입니다.
|
||||
/// </summary>
|
||||
public event EventHandler<PropertyEntryEventArgs>? EntryRemoved;
|
||||
|
||||
/// <summary>
|
||||
/// 모든 엔트리가 제거되었을 때 발생하는 이벤트입니다.
|
||||
/// </summary>
|
||||
public event EventHandler? EntriesCleared;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Load Methods (기존 호환 + 그룹 + 혼용)
|
||||
|
||||
/// <summary>
|
||||
/// [기존 방식] 그룹 없이 속성 목록을 로드합니다.
|
||||
/// 모든 아이템이 flat하게 표시됩니다.
|
||||
/// </summary>
|
||||
/// <param name="items">표시할 속성 항목들의 목록</param>
|
||||
public void LoadProperties(List<IPropertyItem> items)
|
||||
{
|
||||
Properties = items ?? new List<IPropertyItem>();
|
||||
// 필요하다면 여기서 추가적인 초기화 로직을 수행할 수 있습니다.
|
||||
if(_view != null) _view.Initialize(this);
|
||||
Clear();
|
||||
if (items != null)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
item.GroupId = null; // 그룹 없음 명시
|
||||
AddEntryInternal(item);
|
||||
}
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [그룹 방식] 그룹화된 속성 목록을 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="groups">표시할 속성 그룹들의 목록</param>
|
||||
public void LoadGroupedProperties(List<IPropertyGroup> groups)
|
||||
{
|
||||
Clear();
|
||||
if (groups != null)
|
||||
{
|
||||
foreach (var group in groups)
|
||||
{
|
||||
AddEntryInternal(group);
|
||||
}
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// [혼용 방식] 그룹과 개별 아이템을 함께 로드합니다.
|
||||
/// </summary>
|
||||
/// <param name="entries">표시할 엔트리들의 목록 (그룹 또는 아이템)</param>
|
||||
public void LoadMixedProperties(List<IPropertyEntry> entries)
|
||||
{
|
||||
Clear();
|
||||
if (entries != null)
|
||||
{
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
AddEntryInternal(entry);
|
||||
}
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Group Management
|
||||
|
||||
/// <summary>
|
||||
/// 그룹을 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="group">추가할 그룹</param>
|
||||
public void AddGroup(IPropertyGroup group)
|
||||
{
|
||||
if (group == null)
|
||||
throw new ArgumentNullException(nameof(group));
|
||||
|
||||
if (_groupIndex.ContainsKey(group.GroupId))
|
||||
{
|
||||
Debug.LogWarning($"[PropertyWindow] 이미 존재하는 그룹 ID입니다: {group.GroupId}");
|
||||
return;
|
||||
}
|
||||
|
||||
AddEntryInternal(group);
|
||||
GroupAdded?.Invoke(this, new PropertyGroupEventArgs(group));
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹을 제거합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">제거할 그룹의 ID</param>
|
||||
public void RemoveGroup(string groupId)
|
||||
{
|
||||
if (_groupIndex.TryGetValue(groupId, out var group))
|
||||
{
|
||||
// 그룹 내 모든 아이템의 GroupId 초기화 및 인덱스에서 제거
|
||||
foreach (var item in group.Items)
|
||||
{
|
||||
item.GroupId = null;
|
||||
_itemIndex.Remove(item.Id);
|
||||
}
|
||||
group.Clear();
|
||||
|
||||
_groupIndex.Remove(groupId);
|
||||
_entries.Remove(group);
|
||||
|
||||
GroupRemoved?.Invoke(this, new PropertyGroupEventArgs(group));
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID의 그룹을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <returns>그룹 또는 null</returns>
|
||||
public IPropertyGroup? GetGroup(string groupId)
|
||||
{
|
||||
_groupIndex.TryGetValue(groupId, out var group);
|
||||
return group;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 펼침/접힘 상태를 변경합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <param name="isExpanded">펼침 상태</param>
|
||||
public void SetGroupExpanded(string groupId, bool isExpanded)
|
||||
{
|
||||
if (_groupIndex.TryGetValue(groupId, out var group))
|
||||
{
|
||||
bool wasExpanded = group.IsExpanded;
|
||||
if (wasExpanded != isExpanded)
|
||||
{
|
||||
group.IsExpanded = isExpanded;
|
||||
// 이벤트를 통해 PropertyView.OnGroupExpandedChanged()가 호출되어
|
||||
// 부분적으로 UI를 업데이트합니다. 전체 Refresh()는 스크롤 위치를 초기화하므로 호출하지 않습니다.
|
||||
GroupExpandedChanged?.Invoke(this, new PropertyGroupExpandedEventArgs(group, wasExpanded, isExpanded));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹의 펼침/접힘 상태를 토글합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
public void ToggleGroupExpanded(string groupId)
|
||||
{
|
||||
if (_groupIndex.TryGetValue(groupId, out var group))
|
||||
{
|
||||
SetGroupExpanded(groupId, !group.IsExpanded);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Property Management
|
||||
|
||||
/// <summary>
|
||||
/// 개별 속성 아이템을 추가합니다 (그룹 없이).
|
||||
/// </summary>
|
||||
/// <param name="item">추가할 속성 아이템</param>
|
||||
public void AddProperty(IPropertyItem item)
|
||||
{
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
item.GroupId = null;
|
||||
AddEntryInternal(item);
|
||||
EntryAdded?.Invoke(this, new PropertyEntryEventArgs(item));
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 그룹에 속성 아이템을 추가합니다.
|
||||
/// 그룹이 없으면 새로 생성합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <param name="item">추가할 속성 아이템</param>
|
||||
/// <param name="groupNameIfNew">그룹이 새로 생성될 경우 사용할 이름 (null이면 groupId 사용)</param>
|
||||
public void AddPropertyToGroup(string groupId, IPropertyItem item, string? groupNameIfNew = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupId))
|
||||
throw new ArgumentNullException(nameof(groupId));
|
||||
if (item == null)
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
|
||||
if (!_groupIndex.TryGetValue(groupId, out var group))
|
||||
{
|
||||
// 그룹이 없으면 새로 생성
|
||||
group = new PropertyGroup(groupId, groupNameIfNew ?? groupId);
|
||||
AddEntryInternal(group);
|
||||
GroupAdded?.Invoke(this, new PropertyGroupEventArgs(group));
|
||||
}
|
||||
|
||||
group.AddItem(item);
|
||||
_itemIndex[item.Id] = item;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 여러 속성 아이템을 한번에 그룹에 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <param name="items">추가할 속성 아이템들</param>
|
||||
/// <param name="groupNameIfNew">그룹이 새로 생성될 경우 사용할 이름</param>
|
||||
public void AddPropertiesToGroup(string groupId, IEnumerable<IPropertyItem> items, string? groupNameIfNew = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(groupId))
|
||||
throw new ArgumentNullException(nameof(groupId));
|
||||
if (items == null)
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
|
||||
if (!_groupIndex.TryGetValue(groupId, out var group))
|
||||
{
|
||||
group = new PropertyGroup(groupId, groupNameIfNew ?? groupId);
|
||||
AddEntryInternal(group);
|
||||
GroupAdded?.Invoke(this, new PropertyGroupEventArgs(group));
|
||||
}
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
group.AddItem(item);
|
||||
_itemIndex[item.Id] = item;
|
||||
}
|
||||
Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 속성 아이템을 그룹에서 제거하고 독립 아이템으로 변경합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">아이템 ID</param>
|
||||
public void UngroupProperty(string itemId)
|
||||
{
|
||||
if (_itemIndex.TryGetValue(itemId, out var item) && item.GroupId != null)
|
||||
{
|
||||
if (_groupIndex.TryGetValue(item.GroupId, out var group))
|
||||
{
|
||||
group.RemoveItem(itemId);
|
||||
}
|
||||
item.GroupId = null;
|
||||
AddEntryInternal(item); // 독립 엔트리로 추가
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 속성 아이템을 제거합니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">제거할 아이템 ID</param>
|
||||
public void RemoveProperty(string itemId)
|
||||
{
|
||||
if (_itemIndex.TryGetValue(itemId, out var item))
|
||||
{
|
||||
if (item.GroupId != null && _groupIndex.TryGetValue(item.GroupId, out var group))
|
||||
{
|
||||
group.RemoveItem(itemId);
|
||||
}
|
||||
else
|
||||
{
|
||||
_entries.Remove(item);
|
||||
}
|
||||
|
||||
_itemIndex.Remove(itemId);
|
||||
EntryRemoved?.Invoke(this, new PropertyEntryEventArgs(item));
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID의 속성 아이템을 가져옵니다.
|
||||
/// </summary>
|
||||
/// <param name="itemId">아이템 ID</param>
|
||||
/// <returns>속성 아이템 또는 null</returns>
|
||||
public IPropertyItem? GetProperty(string itemId)
|
||||
{
|
||||
_itemIndex.TryGetValue(itemId, out var item);
|
||||
return item;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Clear and Refresh
|
||||
|
||||
/// <summary>
|
||||
/// 모든 엔트리를 제거합니다.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var group in _groupIndex.Values)
|
||||
{
|
||||
group.Clear();
|
||||
}
|
||||
_entries.Clear();
|
||||
_groupIndex.Clear();
|
||||
_itemIndex.Clear();
|
||||
EntriesCleared?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// View를 갱신합니다.
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
if (_view != null)
|
||||
{
|
||||
_view.Initialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value Update
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID를 가진 속성의 값을 업데이트합니다.
|
||||
/// 이 메서드는 주로 View에서 사용자 입력이 발생했을 때 호출됩니다.
|
||||
@@ -51,45 +428,71 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
/// <param name="newValue">새로운 값</param>
|
||||
public void UpdatePropertyValue(string propertyId, PropertyType propertyType, object newValue)
|
||||
{
|
||||
// ID에 해당하는 속성을 찾습니다.
|
||||
var propertyItem = Properties.FirstOrDefault(p => p.Id == propertyId);
|
||||
|
||||
if (propertyItem == null)
|
||||
if (!_itemIndex.TryGetValue(propertyId, out var propertyItem))
|
||||
{
|
||||
// 해당 ID의 속성이 없으면 오류를 기록하고 반환합니다.
|
||||
UnityEngine.Debug.LogError($"[PropertyWindow] ID '{propertyId}'에 해당하는 속성을 찾을 수 없습니다.");
|
||||
Debug.LogError($"[PropertyWindow] ID '{propertyId}'에 해당하는 속성을 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 이전 값을 저장합니다.
|
||||
object oldValue = propertyItem.GetValue();
|
||||
object? oldValue = propertyItem.GetValue();
|
||||
|
||||
// 값 타입일 때 새 값과 이전 값이 같은지 확인합니다. 참조 타입은 PropertyUI에서 필터링 (불필요한 이벤트 발생 방지)
|
||||
if (oldValue.GetType().IsValueType && Equals(oldValue, newValue))
|
||||
// 값 타입일 때 새 값과 이전 값이 같은지 확인합니다.
|
||||
if (oldValue != null && oldValue.GetType().IsValueType && Equals(oldValue, newValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 속성 객체의 값을 새로운 값으로 설정합니다.
|
||||
propertyItem.SetValue(newValue);
|
||||
|
||||
// 값이 변경되었음을 알리는 이벤트를 발생시킵니다.
|
||||
OnPropertyValueChanged(propertyId, propertyType, oldValue, newValue);
|
||||
OnPropertyValueChanged(propertyId, propertyType, oldValue!, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PropertyValueChanged 이벤트를 안전하게 발생시키는 보호된 가상 메서드입니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">변경된 속성 ID</param>
|
||||
/// <param name="oldValue">이전 값</param>
|
||||
/// <param name="newValue">새로운 값</param>
|
||||
protected virtual void OnPropertyValueChanged(string propertyId, PropertyType propertyType, object oldValue, object newValue)
|
||||
{
|
||||
// 이벤트 핸들러가 등록되어 있는지 확인하고 이벤트를 발생시킵니다.
|
||||
PropertyValueChanged?.Invoke(this, new PropertyValueChangedEventArgs(propertyId, propertyType, oldValue, newValue));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers
|
||||
|
||||
/// <summary>
|
||||
/// 엔트리를 내부 컬렉션에 추가합니다.
|
||||
/// </summary>
|
||||
private void AddEntryInternal(IPropertyEntry entry)
|
||||
{
|
||||
if (entry is IPropertyGroup group)
|
||||
{
|
||||
if (!_groupIndex.ContainsKey(group.GroupId))
|
||||
{
|
||||
_groupIndex[group.GroupId] = group;
|
||||
_entries.Add(group);
|
||||
|
||||
// 그룹 내 아이템들도 인덱스에 추가
|
||||
foreach (var item in group.Items)
|
||||
{
|
||||
_itemIndex[item.Id] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (entry is IPropertyItem item)
|
||||
{
|
||||
if (!_itemIndex.ContainsKey(item.Id))
|
||||
{
|
||||
_itemIndex[item.Id] = item;
|
||||
if (item.GroupId == null)
|
||||
{
|
||||
_entries.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Visibility
|
||||
|
||||
public bool IsVisible => gameObject.activeSelf;
|
||||
|
||||
@@ -101,26 +504,195 @@ namespace UVC.UI.Window.PropertyWindow
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
FactoryCameraController.Instance.Enable = true; // 카메라 컨트롤러 활성화
|
||||
}
|
||||
|
||||
public void ToggleVisibility()
|
||||
{
|
||||
gameObject.SetActive(!gameObject.activeSelf);
|
||||
}
|
||||
|
||||
public void SetVisibility(bool visible)
|
||||
{
|
||||
gameObject.SetActive(visible);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Property/Group Visibility
|
||||
|
||||
/// <summary>
|
||||
/// 특정 속성 아이템의 가시성을 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">속성 아이템의 ID</param>
|
||||
/// <param name="visible">가시성 여부</param>
|
||||
public void SetPropertyVisibility(string propertyId, bool visible)
|
||||
{
|
||||
if (_view != null)
|
||||
{
|
||||
_view.SetPropertyVisibility(propertyId, visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마우스 포인터가 이 UI 요소의 영역 안으로 들어왔을 때 호출됩니다.
|
||||
/// UI와 상호작용하는 동안 3D 뷰의 카메라가 움직이지 않도록 컨트롤러를 비활성화합니다.
|
||||
/// 여러 속성 아이템의 가시성을 한번에 설정합니다.
|
||||
/// </summary>
|
||||
public virtual void OnPointerEnter(PointerEventData eventData)
|
||||
/// <param name="propertyIds">속성 아이템 ID 목록</param>
|
||||
/// <param name="visible">가시성 여부</param>
|
||||
public void SetPropertiesVisibility(IEnumerable<string> propertyIds, bool visible)
|
||||
{
|
||||
FactoryCameraController.Instance.Enable = false; // 카메라 컨트롤러 비활성화
|
||||
if (_view != null && propertyIds != null)
|
||||
{
|
||||
foreach (var propertyId in propertyIds)
|
||||
{
|
||||
_view.SetPropertyVisibility(propertyId, visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마우스 포인터가 이 UI 요소의 영역 밖으로 나갔을 때 호출됩니다.
|
||||
/// 카메라 컨트롤을 다시 활성화하여 3D 뷰를 조작할 수 있도록 합니다.
|
||||
/// 특정 그룹의 가시성을 설정합니다.
|
||||
/// </summary>
|
||||
public virtual void OnPointerExit(PointerEventData eventData)
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <param name="visible">가시성 여부</param>
|
||||
public void SetGroupVisibility(string groupId, bool visible)
|
||||
{
|
||||
FactoryCameraController.Instance.Enable = true; // 카메라 컨트롤러 활성화
|
||||
if (_view != null)
|
||||
{
|
||||
_view.SetGroupVisibility(groupId, visible);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 여러 그룹의 가시성을 한번에 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupIds">그룹 ID 목록</param>
|
||||
/// <param name="visible">가시성 여부</param>
|
||||
public void SetGroupsVisibility(IEnumerable<string> groupIds, bool visible)
|
||||
{
|
||||
if (_view != null && groupIds != null)
|
||||
{
|
||||
foreach (var groupId in groupIds)
|
||||
{
|
||||
_view.SetGroupVisibility(groupId, visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Property/Group ReadOnly (Enable/Disable)
|
||||
|
||||
/// <summary>
|
||||
/// 특정 속성 아이템의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">속성 아이템의 ID</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetPropertyReadOnly(string propertyId, bool isReadOnly)
|
||||
{
|
||||
if (_itemIndex.TryGetValue(propertyId, out var item))
|
||||
{
|
||||
item.IsReadOnly = isReadOnly;
|
||||
if (_view != null)
|
||||
{
|
||||
_view.SetPropertyReadOnly(propertyId, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 속성 아이템의 활성화 상태를 설정합니다.
|
||||
/// SetPropertyReadOnly의 반대 동작입니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyId">속성 아이템의 ID</param>
|
||||
/// <param name="enabled">활성화 여부 (true: 활성화, false: 비활성화)</param>
|
||||
public void SetPropertyEnabled(string propertyId, bool enabled)
|
||||
{
|
||||
SetPropertyReadOnly(propertyId, !enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 여러 속성 아이템의 읽기 전용 상태를 한번에 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyIds">속성 아이템 ID 목록</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public void SetPropertiesReadOnly(IEnumerable<string> propertyIds, bool isReadOnly)
|
||||
{
|
||||
if (propertyIds != null)
|
||||
{
|
||||
foreach (var propertyId in propertyIds)
|
||||
{
|
||||
SetPropertyReadOnly(propertyId, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 여러 속성 아이템의 활성화 상태를 한번에 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="propertyIds">속성 아이템 ID 목록</param>
|
||||
/// <param name="enabled">활성화 여부</param>
|
||||
public void SetPropertiesEnabled(IEnumerable<string> propertyIds, bool enabled)
|
||||
{
|
||||
SetPropertiesReadOnly(propertyIds, !enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 그룹 내 모든 속성 아이템의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public void SetGroupReadOnly(string groupId, bool isReadOnly)
|
||||
{
|
||||
if (_groupIndex.TryGetValue(groupId, out var group))
|
||||
{
|
||||
foreach (var item in group.Items)
|
||||
{
|
||||
item.IsReadOnly = isReadOnly;
|
||||
if (_view != null)
|
||||
{
|
||||
_view.SetPropertyReadOnly(item.Id, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 그룹 내 모든 속성 아이템의 활성화 상태를 설정합니다.
|
||||
/// SetGroupReadOnly의 반대 동작입니다.
|
||||
/// </summary>
|
||||
/// <param name="groupId">그룹 ID</param>
|
||||
/// <param name="enabled">활성화 여부</param>
|
||||
public void SetGroupEnabled(string groupId, bool enabled)
|
||||
{
|
||||
SetGroupReadOnly(groupId, !enabled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 여러 그룹 내 모든 속성 아이템의 읽기 전용 상태를 한번에 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupIds">그룹 ID 목록</param>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
public void SetGroupsReadOnly(IEnumerable<string> groupIds, bool isReadOnly)
|
||||
{
|
||||
if (groupIds != null)
|
||||
{
|
||||
foreach (var groupId in groupIds)
|
||||
{
|
||||
SetGroupReadOnly(groupId, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 여러 그룹 내 모든 속성 아이템의 활성화 상태를 한번에 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="groupIds">그룹 ID 목록</param>
|
||||
/// <param name="enabled">활성화 여부</param>
|
||||
public void SetGroupsEnabled(IEnumerable<string> groupIds, bool enabled)
|
||||
{
|
||||
SetGroupsReadOnly(groupIds, !enabled);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,22 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_valueToggle != null)
|
||||
{
|
||||
_valueToggle.interactable = !isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// 메모리 누수를 방지하기 위해 등록된 리스너를 제거합니다.
|
||||
|
||||
@@ -130,6 +130,20 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
openningColorPickered = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_colorLabel != null) _colorLabel.interactable = !isReadOnly;
|
||||
if (_colorPickerButton != null) _colorPickerButton.gameObject.SetActive(!isReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -110,6 +110,24 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newDate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_dateText != null) _dateText.interactable = !isReadOnly;
|
||||
if (_datePickerButton != null)
|
||||
{
|
||||
_datePickerButton.interactable = !isReadOnly;
|
||||
_datePickerButton.gameObject.SetActive(!isReadOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -147,6 +147,30 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_startDateText != null) _startDateText.interactable = !isReadOnly;
|
||||
if (_endDateText != null) _endDateText.interactable = !isReadOnly;
|
||||
if (_startDatePickerButton != null)
|
||||
{
|
||||
_startDatePickerButton.interactable = !isReadOnly;
|
||||
_startDatePickerButton.gameObject.SetActive(!isReadOnly);
|
||||
}
|
||||
if (_endDatePickerButton != null)
|
||||
{
|
||||
_endDatePickerButton.interactable = !isReadOnly;
|
||||
_endDatePickerButton.gameObject.SetActive(!isReadOnly);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -172,6 +172,26 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newDateTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_dateText != null) _dateText.interactable = !isReadOnly;
|
||||
if (_dateTimePickerButton != null)
|
||||
{
|
||||
_dateTimePickerButton.interactable = !isReadOnly;
|
||||
_dateTimePickerButton.gameObject.SetActive(!isReadOnly);
|
||||
}
|
||||
if (_hourDropDown != null) _hourDropDown.interactable = !isReadOnly;
|
||||
if (_miniteDropDown != null) _miniteDropDown.interactable = !isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -275,6 +275,34 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_startDateText != null) _startDateText.interactable = !isReadOnly;
|
||||
if (_endDateText != null) _endDateText.interactable = !isReadOnly;
|
||||
if (_startDatePickerButton != null)
|
||||
{
|
||||
_startDatePickerButton.interactable = !isReadOnly;
|
||||
_startDatePickerButton.gameObject.SetActive(!isReadOnly);
|
||||
}
|
||||
if (_endDatePickerButton != null)
|
||||
{
|
||||
_endDatePickerButton.interactable = !isReadOnly;
|
||||
_endDatePickerButton.gameObject.SetActive(!isReadOnly);
|
||||
}
|
||||
if (_startHourDropDown != null) _startHourDropDown.interactable = !isReadOnly;
|
||||
if (_startMiniteDropDown != null) _startMiniteDropDown.interactable = !isReadOnly;
|
||||
if (_endHourDropDown != null) _endHourDropDown.interactable = !isReadOnly;
|
||||
if (_endMiniteDropDown != null) _endMiniteDropDown.interactable = !isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -110,6 +110,19 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_dropdown != null) _dropdown.interactable = !isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -107,6 +107,19 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_dropdown != null) _dropdown.interactable = !isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -195,6 +195,26 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.interactable = !isReadOnly;
|
||||
}
|
||||
if (_slider != null)
|
||||
{
|
||||
_slider.interactable = !isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 오브젝트가 파괴될 때 리스너를 확실히 제거합니다.
|
||||
|
||||
@@ -181,6 +181,20 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_minInputField.text = newMin.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_minInputField != null) _minInputField.interactable = !isReadOnly;
|
||||
if (_maxInputField != null) _maxInputField.interactable = !isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -140,6 +140,25 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, selectedValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
foreach (var toggle in _toggles)
|
||||
{
|
||||
if (toggle != null)
|
||||
{
|
||||
toggle.interactable = !isReadOnly;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -88,6 +88,22 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.interactable = !isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 오브젝트가 파괴될 때 리스너를 확실히 제거합니다.
|
||||
|
||||
@@ -110,6 +110,20 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_xInputField != null) _xInputField.interactable = !isReadOnly;
|
||||
if (_yInputField != null) _yInputField.interactable = !isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
@@ -121,6 +121,21 @@ namespace UVC.UI.Window.PropertyWindow.UI
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
if (_xInputField != null) _xInputField.interactable = !isReadOnly;
|
||||
if (_yInputField != null) _yInputField.interactable = !isReadOnly;
|
||||
if (_zInputField != null) _zInputField.interactable = !isReadOnly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
|
||||
/// </summary>
|
||||
|
||||
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/View.meta
Normal file
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/View.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 290d4b3761c68414b8afb4350b257340
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,151 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// 속성 그룹의 UI를 담당하는 View 클래스입니다.
|
||||
/// 접이식 헤더와 자식 아이템들의 컨테이너를 관리합니다.
|
||||
/// </summary>
|
||||
public class PropertyGroupView : MonoBehaviour
|
||||
{
|
||||
[Header("Header")]
|
||||
[SerializeField] private Button _headerButton;
|
||||
[SerializeField] private TextMeshProUGUI _groupNameLabel;
|
||||
[SerializeField] private Image _expandIcon;
|
||||
|
||||
[Header("Icons")]
|
||||
[SerializeField] private Sprite _expandedIcon;
|
||||
[SerializeField] private Sprite _collapsedIcon;
|
||||
|
||||
[Header("Content")]
|
||||
[SerializeField] private Transform _itemContainer;
|
||||
[SerializeField] private GameObject _contentPanel;
|
||||
|
||||
private IPropertyGroup _group;
|
||||
private PropertyWindow _controller;
|
||||
|
||||
/// <summary>
|
||||
/// 자식 PropertyItem UI들이 생성될 컨테이너입니다.
|
||||
/// </summary>
|
||||
public Transform ItemContainer => _itemContainer;
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 데이터
|
||||
/// </summary>
|
||||
public IPropertyGroup Group => _group;
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 ID
|
||||
/// </summary>
|
||||
public string GroupId => _group?.GroupId;
|
||||
|
||||
/// <summary>
|
||||
/// PropertyGroupView를 초기화합니다.
|
||||
/// </summary>
|
||||
/// <param name="group">표시할 그룹 데이터</param>
|
||||
/// <param name="controller">상호작용할 컨트롤러</param>
|
||||
public void Setup(IPropertyGroup group, PropertyWindow controller)
|
||||
{
|
||||
_group = group;
|
||||
_controller = controller;
|
||||
|
||||
// 그룹명 설정
|
||||
if (_groupNameLabel != null)
|
||||
{
|
||||
_groupNameLabel.text = group.GroupName;
|
||||
}
|
||||
|
||||
// 헤더 버튼 이벤트 등록
|
||||
if (_headerButton != null)
|
||||
{
|
||||
_headerButton.onClick.RemoveAllListeners();
|
||||
_headerButton.onClick.AddListener(OnHeaderClicked);
|
||||
}
|
||||
|
||||
// 펼침/접힘 상태 반영
|
||||
UpdateExpandedState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 헤더 클릭 시 호출됩니다.
|
||||
/// </summary>
|
||||
private void OnHeaderClicked()
|
||||
{
|
||||
if (_controller != null && _group != null)
|
||||
{
|
||||
_controller.ToggleGroupExpanded(_group.GroupId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 펼침/접힘 상태를 UI에 반영합니다.
|
||||
/// </summary>
|
||||
public void UpdateExpandedState()
|
||||
{
|
||||
if (_group == null) return;
|
||||
|
||||
bool isExpanded = _group.IsExpanded;
|
||||
|
||||
// 컨텐츠 패널 표시/숨김
|
||||
if (_contentPanel != null)
|
||||
{
|
||||
_contentPanel.SetActive(isExpanded);
|
||||
}
|
||||
|
||||
// 아이콘 변경
|
||||
if (_expandIcon != null)
|
||||
{
|
||||
if (isExpanded && _expandedIcon != null)
|
||||
{
|
||||
_expandIcon.sprite = _expandedIcon;
|
||||
}
|
||||
else if (!isExpanded && _collapsedIcon != null)
|
||||
{
|
||||
_expandIcon.sprite = _collapsedIcon;
|
||||
}
|
||||
|
||||
// 아이콘 회전으로 표현할 경우
|
||||
_expandIcon.transform.rotation = Quaternion.Euler(0, 0, isExpanded ? 0 : -90);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 그룹 내 모든 아이템 UI를 제거합니다.
|
||||
/// </summary>
|
||||
public void ClearItems()
|
||||
{
|
||||
if (_itemContainer == null) return;
|
||||
|
||||
foreach (Transform child in _itemContainer)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀에 반환하기 전에 정리합니다.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_group = null;
|
||||
_controller = null;
|
||||
|
||||
if (_headerButton != null)
|
||||
{
|
||||
_headerButton.onClick.RemoveAllListeners();
|
||||
}
|
||||
|
||||
ClearItems();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_headerButton != null)
|
||||
{
|
||||
_headerButton.onClick.RemoveAllListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1292fe635565fff43a6bd494f3f597b8
|
||||
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Extention;
|
||||
using UVC.UI.Tooltip;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// 모든 PropertyUI의 공통 베이스 클래스입니다.
|
||||
/// 공통 기능을 제공하고 IPropertyUI, IPoolable, IDisposable을 구현합니다.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(LayoutElement))]
|
||||
public abstract class PropertyUIBase<T> : MonoBehaviour, IPropertyUI, IPoolable, IDisposable
|
||||
where T : class, IPropertyItem
|
||||
{
|
||||
[Header("Common UI Elements")]
|
||||
[SerializeField] protected TextMeshProUGUI _nameLabel;
|
||||
[SerializeField] protected TextMeshProUGUI _descriptionLabel;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 표시 중인 속성 아이템
|
||||
/// </summary>
|
||||
protected T _propertyItem;
|
||||
|
||||
/// <summary>
|
||||
/// 상호작용할 컨트롤러
|
||||
/// </summary>
|
||||
protected PropertyWindow _controller;
|
||||
|
||||
/// <summary>
|
||||
/// 이미 해제되었는지 여부
|
||||
/// </summary>
|
||||
protected bool _isDisposed = false;
|
||||
|
||||
#region IPropertyUI Implementation
|
||||
|
||||
/// <summary>
|
||||
/// PropertyView에 의해 호출되어 UI를 초기화합니다.
|
||||
/// </summary>
|
||||
public virtual void Setup(IPropertyItem item, PropertyWindow controller)
|
||||
{
|
||||
// 기존 상태 정리
|
||||
Cleanup();
|
||||
|
||||
// 타입 체크
|
||||
if (!(item is T typedItem))
|
||||
{
|
||||
Debug.LogError($"[{GetType().Name}] 잘못된 타입의 PropertyItem이 전달되었습니다. 예상: {typeof(T).Name}, 실제: {item.GetType().Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
_propertyItem = typedItem;
|
||||
_controller = controller;
|
||||
_isDisposed = false;
|
||||
|
||||
// 공통 UI 설정
|
||||
SetupCommonUI();
|
||||
|
||||
// 파생 클래스의 추가 설정
|
||||
SetupValueUI();
|
||||
|
||||
// 이벤트 구독
|
||||
SubscribeEvents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI의 읽기 전용 상태를 설정합니다.
|
||||
/// 파생 클래스에서 재정의하여 구체적인 UI 요소에 적용해야 합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
|
||||
public virtual void SetReadOnly(bool isReadOnly)
|
||||
{
|
||||
if (_propertyItem != null)
|
||||
{
|
||||
_propertyItem.IsReadOnly = isReadOnly;
|
||||
}
|
||||
// 파생 클래스에서 구체적인 UI 요소 비활성화 로직을 구현
|
||||
ApplyReadOnlyStateToUI(isReadOnly);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 파생 클래스에서 구체적인 UI 요소에 읽기 전용 상태를 적용합니다.
|
||||
/// </summary>
|
||||
/// <param name="isReadOnly">읽기 전용 여부</param>
|
||||
protected virtual void ApplyReadOnlyStateToUI(bool isReadOnly)
|
||||
{
|
||||
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Common UI Setup
|
||||
|
||||
/// <summary>
|
||||
/// 공통 UI 요소들을 설정합니다.
|
||||
/// </summary>
|
||||
protected virtual void SetupCommonUI()
|
||||
{
|
||||
if (_propertyItem == null) return;
|
||||
|
||||
// 이름 설정
|
||||
if (_nameLabel != null)
|
||||
{
|
||||
_nameLabel.text = _propertyItem.Name;
|
||||
|
||||
// 툴팁 설정
|
||||
var tooltipHandler = _nameLabel.GetComponent<TooltipHandler>();
|
||||
if (tooltipHandler != null && !_propertyItem.Tooltip.IsNullOrEmpty())
|
||||
{
|
||||
tooltipHandler.Tooltip = _propertyItem.Tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
// 설명 설정
|
||||
if (_descriptionLabel != null)
|
||||
{
|
||||
if (_propertyItem.Description.IsNullOrEmpty())
|
||||
{
|
||||
_descriptionLabel.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_descriptionLabel.gameObject.SetActive(true);
|
||||
_descriptionLabel.text = _propertyItem.Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 파생 클래스에서 값 관련 UI를 설정합니다.
|
||||
/// </summary>
|
||||
protected abstract void SetupValueUI();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Management
|
||||
|
||||
/// <summary>
|
||||
/// 이벤트를 구독합니다. 파생 클래스에서 재정의할 수 있습니다.
|
||||
/// </summary>
|
||||
protected virtual void SubscribeEvents()
|
||||
{
|
||||
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이벤트 구독을 해제합니다. 파생 클래스에서 재정의할 수 있습니다.
|
||||
/// </summary>
|
||||
protected virtual void UnsubscribeEvents()
|
||||
{
|
||||
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Value Update
|
||||
|
||||
/// <summary>
|
||||
/// 컨트롤러를 통해 값 변경을 통지합니다.
|
||||
/// </summary>
|
||||
/// <param name="newValue">새로운 값</param>
|
||||
protected void NotifyValueChanged(object newValue)
|
||||
{
|
||||
if (_controller == null || _propertyItem == null) return;
|
||||
|
||||
// 값이 변경되지 않았으면 무시
|
||||
var oldValue = _propertyItem.GetValue();
|
||||
if (Equals(oldValue, newValue)) return;
|
||||
|
||||
_controller.UpdatePropertyValue(_propertyItem.Id, _propertyItem.PropertyType, newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 읽기 전용 상태를 설정합니다.
|
||||
/// </summary>
|
||||
/// <param name="interactable">UI 요소</param>
|
||||
protected void ApplyReadOnlyState(Selectable interactable)
|
||||
{
|
||||
if (interactable != null && _propertyItem != null)
|
||||
{
|
||||
interactable.interactable = !_propertyItem.IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 읽기 전용 상태를 설정합니다 (TMP_InputField용).
|
||||
/// </summary>
|
||||
/// <param name="inputField">입력 필드</param>
|
||||
protected void ApplyReadOnlyState(TMP_InputField inputField)
|
||||
{
|
||||
if (inputField != null && _propertyItem != null)
|
||||
{
|
||||
inputField.interactable = !_propertyItem.IsReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cleanup and Disposal
|
||||
|
||||
/// <summary>
|
||||
/// 상태를 정리합니다.
|
||||
/// </summary>
|
||||
protected virtual void Cleanup()
|
||||
{
|
||||
UnsubscribeEvents();
|
||||
_propertyItem = null;
|
||||
_controller = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IDisposable 구현
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
|
||||
Cleanup();
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPoolable Implementation
|
||||
|
||||
/// <summary>
|
||||
/// 풀에서 가져올 때 호출됩니다.
|
||||
/// </summary>
|
||||
public virtual void OnGetFromPool()
|
||||
{
|
||||
_isDisposed = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 풀에 반환될 때 호출됩니다.
|
||||
/// </summary>
|
||||
public virtual void OnReturnToPool()
|
||||
{
|
||||
Cleanup();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97fb1aaa6e72f9742b99c8d8ddf43ebc
|
||||
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/View/UI.meta
Normal file
8
Assets/Scripts/UVC/UI/Window/PropertyWindow/View/UI.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba2f83247bcb33a45b3e8e656b4d3ed0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,87 @@
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.Window.PropertyWindow
|
||||
{
|
||||
/// <summary>
|
||||
/// StringProperty를 위한 PropertyUIBase 기반 UI 스크립트입니다.
|
||||
/// 기존 StringPropertyUI의 리팩토링 버전입니다.
|
||||
/// </summary>
|
||||
public class StringPropertyUIBase : PropertyUIBase<StringProperty>
|
||||
{
|
||||
[Header("String Property UI")]
|
||||
[SerializeField] private TMP_InputField _valueInput;
|
||||
|
||||
#region PropertyUIBase Implementation
|
||||
|
||||
/// <summary>
|
||||
/// 값 관련 UI를 설정합니다.
|
||||
/// </summary>
|
||||
protected override void SetupValueUI()
|
||||
{
|
||||
if (_valueInput == null || _propertyItem == null) return;
|
||||
|
||||
// 값 설정
|
||||
_valueInput.text = _propertyItem.Value;
|
||||
|
||||
// 읽기 전용 상태 설정
|
||||
ApplyReadOnlyState(_valueInput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이벤트를 구독합니다.
|
||||
/// </summary>
|
||||
protected override void SubscribeEvents()
|
||||
{
|
||||
base.SubscribeEvents();
|
||||
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.onEndEdit.AddListener(OnValueSubmitted);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이벤트 구독을 해제합니다.
|
||||
/// </summary>
|
||||
protected override void UnsubscribeEvents()
|
||||
{
|
||||
base.UnsubscribeEvents();
|
||||
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.onEndEdit.RemoveListener(OnValueSubmitted);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// 사용자가 InputField 수정 완료 후 Enter를 누르거나 포커스를 잃었을 때 호출됩니다.
|
||||
/// </summary>
|
||||
/// <param name="newValue">InputField에 입력된 새로운 문자열</param>
|
||||
private void OnValueSubmitted(string newValue)
|
||||
{
|
||||
NotifyValueChanged(newValue);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPoolable Implementation
|
||||
|
||||
public override void OnReturnToPool()
|
||||
{
|
||||
base.OnReturnToPool();
|
||||
|
||||
// 입력 필드 초기화
|
||||
if (_valueInput != null)
|
||||
{
|
||||
_valueInput.text = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cab478cef001a8c43afe2fe8389124ae
|
||||
Reference in New Issue
Block a user