This commit is contained in:
2026-01-16 11:36:54 +09:00
parent 6b78b68229
commit 2d36b4247f
58 changed files with 12198 additions and 292 deletions

View File

@@ -0,0 +1,25 @@
using UnityEngine;
using UnityEngine.EventSystems;
public class InitializeCellPointer : MonoBehaviour, IPointerDownHandler, IPointerEnterHandler, IPointerUpHandler
{
private InitializePopupCellButton cell;
private System.Action<InitializePopupCellButton> onDown;
private System.Action<InitializePopupCellButton> onEnter;
private System.Action onUp;
public void Bind(InitializePopupCellButton cell,
System.Action<InitializePopupCellButton> onDown,
System.Action<InitializePopupCellButton> onEnter,
System.Action onUp)
{
this.cell = cell;
this.onDown = onDown;
this.onEnter = onEnter;
this.onUp = onUp;
}
public void OnPointerDown(PointerEventData eventData) => onDown?.Invoke(cell);
public void OnPointerEnter(PointerEventData eventData) => onEnter?.Invoke(cell);
public void OnPointerUp(PointerEventData eventData) => onUp?.Invoke();
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cd40ac7562a71324587a41d17a99920c

View File

@@ -0,0 +1,17 @@
using TMPro;
using UnityEngine;
public class InitializeLayerView : MonoBehaviour
{
[SerializeField] private TMP_Text title;
[SerializeField] private Transform gridRoot;
public int Z { get; private set; }
public Transform GridRoot => gridRoot;
public void SetLayer(int z)
{
Z = z;
if (title) title.text = $"{z}번 랙";
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 044e2d8fec641e447b6cb5b8c371c591

View File

@@ -0,0 +1,44 @@
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class InitializePopupCellButton : MonoBehaviour
{
[SerializeField] private Button button;
[SerializeField] private Image background;
[SerializeField] private TMP_Text text; // 필요 없으면 제거
public int X { get; private set; }
public int Y { get; private set; }
public int Z { get; private set; }
public void Setup(int x, int y, int z, System.Action<InitializePopupCellButton> onDown, System.Action<InitializePopupCellButton> onEnter, System.Action onUp)
{
X = x; Y = y; Z = z;
// 버튼 이벤트(단순화): Pointer 이벤트가 더 정확하지만, 여기서는 구성 예시로 단순화
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() => onDown?.Invoke(this)); // 클릭 시작으로 취급
// 드래그/호버는 EventTrigger 또는 IPointerEnterHandler로 확장 권장
// 여기서는 창 코드에서 추가로 연결할 수 있게 메서드를 남겨둡니다.
}
public void SetEmpty()
{
// background.color = ...
if (text) text.text = "";
}
public void SetOccupied(string prefab)
{
// background.color = ...
if (text) text.text = "■"; // 아이콘/이미지로 바꾸셔도 됨
}
public void SetHighlight(bool on)
{
// 하이라이트 색/아웃라인 처리
// 예: background.color = on ? highlightColor : normalColor;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f30a17680ef568a46a5904393cc225b3

View File

@@ -0,0 +1,343 @@
using Simulator.Data;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class InitializePopupWindow : MonoBehaviour
{
[Header("Prefabs / Roots")]
[SerializeField] private InitializeLayerView layerPrefab;
[SerializeField] private InitializePopupCellButton cellPrefab;
[SerializeField] private Transform layersParent;
[Header("Right Panel Controls")]
[SerializeField] private TMP_Dropdown prefabDropdown;
[SerializeField] private TMP_InputField countInput; // 숫자 입력
[SerializeField] private Button applyButton;
[SerializeField] private Button deleteButton;
[Header("Z Layer Toggles (max 2 selected)")]
[SerializeField] private Toggle z1Toggle;
[SerializeField] private Toggle z2Toggle;
// z가 더 늘 수 있으면 Toggle 리스트로 확장 권장(지금은 최대 2층 요구에 맞춰 간단히)
[Header("Table")]
[SerializeField] private Transform tableContent; // row parent
[SerializeField] private InitializeRow rowPrefab;
private ASRSDataClass asrs;
// grid lookup
private readonly Dictionary<(int x, int y, int z), InitializePopupCellButton> cellMap = new();
// occupied lookup: each cell -> entry
private readonly Dictionary<(int x, int y, int z), InitializeEntry> occupied = new();
// selection
private bool dragging;
private (int x, int y) dragStart;
private (int x, int y) dragEnd;
// highlight diff (성능)
private readonly HashSet<(int x, int y, int z)> lastHighlighted = new();
private readonly HashSet<(int x, int y, int z)> newHighlighted = new();
// table selection
private InitializeEntry selectedRowEntry;
private void Awake()
{
applyButton.onClick.AddListener(ApplySelection);
deleteButton.onClick.AddListener(DeleteSelectedRow);
z1Toggle.onValueChanged.AddListener(_ => OnZToggleChanged());
z2Toggle.onValueChanged.AddListener(_ => OnZToggleChanged());
}
public void Open(ASRSDataClass asrsData, int sizeX, int sizeY, int sizeZ)
{
asrs = asrsData;
asrs.initialize ??= new List<InitializeEntry>();
BuildPrefabDropdown();
BuildLayerGrids(sizeX, sizeY, sizeZ);
RebuildOccupiedIndex();
RefreshAllCellsFromOccupied();
RefreshTableFromInitialize();
// 초기 상태: 1번 랙만 체크
z1Toggle.isOn = true;
z2Toggle.isOn = false;
OnZToggleChanged();
dragging = false;
deleteButton.interactable = false;
}
private void BuildPrefabDropdown()
{
prefabDropdown.ClearOptions();
var options = new List<string>(PrefabManager.Instance.prefabDict.Keys);
prefabDropdown.AddOptions(options);
}
private void BuildLayerGrids(int sizeX, int sizeY, int sizeZ)
{
// 기존 UI 삭제
foreach (Transform child in layersParent) Destroy(child.gameObject);
cellMap.Clear();
for (int z = 1; z <= sizeZ; z++)
{
var layer = Instantiate(layerPrefab, layersParent);
layer.SetLayer(z);
// y는 화면 상단이 큰 값(이미지처럼)
for (int y = sizeY; y >= 1; y--)
for (int x = 1; x <= sizeX; x++)
{
var cell = Instantiate(cellPrefab, layer.GridRoot);
// Pointer 이벤트 기반 드래그 지원용 컴포넌트 부착
var trigger = cell.gameObject.GetComponent<InitializeCellPointer>();
if (trigger == null) trigger = cell.gameObject.AddComponent<InitializeCellPointer>();
trigger.Bind(
cell,
onDown: OnCellPointerDown,
onEnter: OnCellPointerEnter,
onUp: OnCellPointerUp
);
cellMap[(x, y, z)] = cell;
}
}
}
#region Pointer / Drag Handlers
private void OnCellPointerDown(InitializePopupCellButton cell)
{
dragging = true;
dragStart = (cell.X, cell.Y);
dragEnd = (cell.X, cell.Y);
UpdateHighlights();
}
private void OnCellPointerEnter(InitializePopupCellButton cell)
{
if (!dragging) return;
dragEnd = (cell.X, cell.Y);
UpdateHighlights();
}
private void OnCellPointerUp()
{
if (!dragging) return;
dragging = false;
// 드래그 확정 상태 유지(하이라이트 유지)
UpdateHighlights();
}
#endregion
private void OnZToggleChanged()
{
// 최대 2개만 허용(현재 토글이 2개라 사실상 자동 충족)
// z가 늘면: 체크 수가 2 초과 시 방금 체크한 토글을 되돌리는 식으로 처리
UpdateHighlights(); // z 선택 바뀌면 하이라이트도 즉시 반영
}
private List<int> GetSelectedZLayers()
{
var list = new List<int>(2);
if (z1Toggle.isOn) list.Add(1);
if (z2Toggle.isOn) list.Add(2);
return list;
}
private void UpdateHighlights()
{
// 선택된 XY rect 계산
int minX = Mathf.Min(dragStart.x, dragEnd.x);
int maxX = Mathf.Max(dragStart.x, dragEnd.x);
int minY = Mathf.Min(dragStart.y, dragEnd.y);
int maxY = Mathf.Max(dragStart.y, dragEnd.y);
var zs = GetSelectedZLayers();
newHighlighted.Clear();
// 새 하이라이트 집합 구성
foreach (int z in zs)
{
for (int y = minY; y <= maxY; y++)
for (int x = minX; x <= maxX; x++)
{
newHighlighted.Add((x, y, z));
}
}
// diff 적용(전체 리셋 방지)
foreach (var key in lastHighlighted)
{
if (!newHighlighted.Contains(key) && cellMap.TryGetValue(key, out var cell))
cell.SetHighlight(false);
}
foreach (var key in newHighlighted)
{
if (!lastHighlighted.Contains(key) && cellMap.TryGetValue(key, out var cell))
cell.SetHighlight(true);
}
lastHighlighted.Clear();
foreach (var k in newHighlighted) lastHighlighted.Add(k);
}
#region Apply / Delete
private void ApplySelection()
{
var zs = GetSelectedZLayers();
if (zs.Count == 0) return;
// prefab
string prefab = prefabDropdown.options[prefabDropdown.value].text;
// count
int count = 1;
if (!int.TryParse(countInput.text, out count)) count = 1;
count = Mathf.Max(1, count);
// XY rect
int minX = Mathf.Min(dragStart.x, dragEnd.x);
int maxX = Mathf.Max(dragStart.x, dragEnd.x);
int minY = Mathf.Min(dragStart.y, dragEnd.y);
int maxY = Mathf.Max(dragStart.y, dragEnd.y);
// 1) 중복 검사(선택된 z 각각)
foreach (int z in zs)
{
if (HasOverlap(minX, minY, maxX, maxY, z))
{
// 여기서 경고 UI 띄우시면 됩니다(Toast/Popup)
return;
}
}
// 2) initialize 추가
// z가 2개이고 연속(1,2)인 경우: 하나의 entry로 z 범위를 담는 것도 가능
// 단, 삭제/표시가 더 쉬운 구조는 “z마다 entry 하나씩”입니다(권장).
foreach (int z in zs)
{
var entry = new InitializeEntry
{
prefab = prefab,
count = count,
from_position = new Position { x = minX, y = maxY, z = z }, // y축 방향은 프로젝트 좌표계에 맞춰 조정
to_position = new Position { x = maxX, y = minY, z = z },
};
asrs.initialize.Add(entry);
}
// 3) 점유 인덱스/그리드/테이블 갱신
RebuildOccupiedIndex();
RefreshAllCellsFromOccupied();
RefreshTableFromInitialize();
// 4) 모델 반영(패치 저장 필요 시 여기서)
// SaveChange(asrs, asrs.initialize, "initialize");
}
private bool HasOverlap(int minX, int minY, int maxX, int maxY, int z)
{
for (int y = minY; y <= maxY; y++)
for (int x = minX; x <= maxX; x++)
{
if (occupied.ContainsKey((x, y, z)))
return true;
}
return false;
}
private void DeleteSelectedRow()
{
if (selectedRowEntry == null) return;
asrs.initialize.Remove(selectedRowEntry);
selectedRowEntry = null;
deleteButton.interactable = false;
RebuildOccupiedIndex();
RefreshAllCellsFromOccupied();
RefreshTableFromInitialize();
// SaveChange(asrs, asrs.initialize, "initialize");
}
#endregion
#region Occupied / Refresh
private void RebuildOccupiedIndex()
{
occupied.Clear();
if (asrs.initialize == null) return;
foreach (var e in asrs.initialize)
{
if (e?.from_position == null || e?.to_position == null) continue;
// normalize
int minX = (int)Mathf.Min(e.from_position.x, e.to_position.x);
int maxX = (int)Mathf.Max(e.from_position.x, e.to_position.x);
int minY = (int)Mathf.Min(e.from_position.y, e.to_position.y);
int maxY = (int)Mathf.Max(e.from_position.y, e.to_position.y);
int minZ = (int)Mathf.Min(e.from_position.z, e.to_position.z);
int maxZ = (int)Mathf.Max(e.from_position.z, e.to_position.z);
for (int z = minZ; z <= maxZ; z++)
for (int y = minY; y <= maxY; y++)
for (int x = minX; x <= maxX; x++)
{
occupied[(x, y, z)] = e;
}
}
}
private void RefreshAllCellsFromOccupied()
{
foreach (var kv in cellMap)
{
var key = kv.Key;
var cell = kv.Value;
if (occupied.TryGetValue(key, out var entry))
cell.SetOccupied(entry.prefab);
else
cell.SetEmpty();
}
}
private void RefreshTableFromInitialize()
{
foreach (Transform child in tableContent) Destroy(child.gameObject);
if (asrs.initialize == null || asrs.initialize.Count == 0)
return;
foreach (var e in asrs.initialize)
{
var row = Instantiate(rowPrefab, tableContent);
row.Bind(e, onSelect: OnRowSelected);
}
}
private void OnRowSelected(InitializeEntry entry, bool selected)
{
selectedRowEntry = selected ? entry : null;
deleteButton.interactable = selectedRowEntry != null;
// 선택된 row의 범위를 하이라이트로 보여주고 싶으면 여기서 lastHighlighted를 해당 entry 범위로 재구성하면 됩니다.
}
#endregion
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d555b3ddff999f74796a726059e53e51

View File

@@ -0,0 +1,30 @@
using Simulator.Data;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class InitializeRow : MonoBehaviour
{
[SerializeField] private Toggle selectToggle;
[SerializeField] private TMP_Text prefabText;
[SerializeField] private TMP_Text countText;
[SerializeField] private TMP_Text fromText;
[SerializeField] private TMP_Text toText;
private InitializeEntry entry;
private System.Action<InitializeEntry, bool> onSelect;
public void Bind(InitializeEntry e, System.Action<InitializeEntry, bool> onSelect)
{
entry = e;
this.onSelect = onSelect;
prefabText.text = e.prefab;
countText.text = e.count.ToString();
fromText.text = $"{e.from_position.x}, {e.from_position.y}, {e.from_position.z}";
toText.text = $"{e.to_position.x}, {e.to_position.y}, {e.to_position.z}";
selectToggle.onValueChanged.RemoveAllListeners();
selectToggle.onValueChanged.AddListener(v => this.onSelect?.Invoke(entry, v));
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b976ee14e46fcfc4c9a120e720899aff