Files
XRLib/Assets/Scripts/Simulator/PropertyWindow/InitailizePopup/InitialInventoryWindow.cs
2026-01-19 14:33:40 +09:00

472 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Simulator.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
public class InitialInventoryWindow : MonoBehaviour
{
[Header("Top/Left")]
[SerializeField] private TMP_Text dimLabel;
[SerializeField] private Transform rackContainer; // ScrollRect Content
[SerializeField] private GridView rackSectionPrefab; // Rack 섹션 프리팹
[Header("Right Controls")]
[SerializeField] private TMP_Dropdown prefabDropdown;
[SerializeField] private TMP_Text countText;
[Header("Table")]
[SerializeField] private Transform tableContent; // ScrollRect Content
[SerializeField] private InitRow rowPrefab;
[Header("Bottom Buttons")]
[SerializeField] private Button btnApply; // Apply 버튼 (요구사항 추가)
[SerializeField] private Button btnCancel; // 선택: 닫기
[SerializeField] private Button btnReset; // 선택: 초기화
private ASRSDataClass asrs;
// rack views
private readonly List<GridView> rackViews = new();
// ====== Click + Hover selection state ======
private readonly Dictionary<int, Vector2Int> anchors = new();
private readonly Dictionary<int, AsrsRect> previewRects = new();
// ====== Entry / Occupancy ======
private sealed class Entry
{
public int id;
public string prefabId;
public string prefabDisplayName;
public int count;
public Vector3Int from; // inclusive
public Vector3Int to; // inclusive
}
private readonly List<Entry> entries = new();
private readonly Dictionary<(int rack, int x, int y), int> occupancy = new(); // (rack,x,y) -> entryId
private int nextEntryId = 1;
private int? selectedEntryId = null;
// prefab dropdown cache
private List<PrefabCatalogFromManager.Item> prefabItems = new();
private int count = 1;
// 외부 버튼으로 모드 토글 가능
public bool IsSelectionMode { get; private set; } = true;
// ===== Public API =====
public void Open(ASRSDataClass asrs)
{
gameObject.SetActive(true);
this.asrs = asrs;
BuildDimLabel();
BuildPrefabDropdown();
BuildRacks();
// 기존 initialize 로딩
LoadInitialize(asrs.initialize);
HookButtons();
RefreshAll();
}
public void Close()
{
gameObject.SetActive(false);
}
public void SetSelectionMode(bool on)
{
IsSelectionMode = on;
if (!on)
{
anchors.Clear();
previewRects.Clear();
RefreshSelectionVisuals();
}
}
// ===== Build UI =====
private void BuildDimLabel()
{
if (dimLabel == null || asrs?.asrs_layout == null) return;
var l = asrs.asrs_layout;
float xM = l.x * l.x_length;
float yM = l.z * l.z_length;
float zM = l.y * l.y_length;
dimLabel.text = $"{xM:0.##} m (x) × {yM:0.##} m (y) × {zM:0.##} m (z)";
}
private void BuildPrefabDropdown()
{
if (prefabDropdown == null) return;
prefabItems = PrefabCatalogFromManager.GetSortedItems();
prefabDropdown.ClearOptions();
prefabDropdown.AddOptions(prefabItems.Select(i => i.DisplayName).ToList());
prefabDropdown.value = 0;
prefabDropdown.RefreshShownValue();
SetCount(count);
}
private void BuildRacks()
{
foreach (Transform child in rackContainer)
Destroy(child.gameObject);
rackViews.Clear();
var l = asrs.asrs_layout;
int rackCount = Mathf.Clamp(l.z, 1, 2);
for (int rack = 1; rack <= rackCount; rack++)
{
var view = Instantiate(rackSectionPrefab, rackContainer);
view.Build(this, rack, l.x, l.y);
rackViews.Add(view);
}
}
private void HookButtons()
{
if (btnApply != null)
{
btnApply.onClick.RemoveAllListeners();
btnApply.onClick.AddListener(ApplyToAsrsInitialize);
}
if (btnCancel != null)
{
btnCancel.onClick.RemoveAllListeners();
btnCancel.onClick.AddListener(Close);
}
if (btnReset != null)
{
btnReset.onClick.RemoveAllListeners();
btnReset.onClick.AddListener(() =>
{
entries.Clear();
occupancy.Clear();
anchors.Clear();
previewRects.Clear();
selectedEntryId = null;
nextEntryId = 1;
RefreshAll();
});
}
}
private void SetCount(int newCount)
{
count = Mathf.Clamp(newCount, 1, 999999);
if (countText != null) countText.text = count.ToString();
}
private string GetSelectedPrefabId()
{
if (prefabItems == null || prefabItems.Count == 0) return null;
int idx = prefabDropdown != null ? prefabDropdown.value : 0;
idx = Mathf.Clamp(idx, 0, prefabItems.Count - 1);
return prefabItems[idx].Id;
}
private string ResolvePrefabDisplayName(string prefabId)
=> PrefabCatalogFromManager.ResolveDisplayName(prefabId);
// ===== initialize load (기존 데이터 반영) =====
private void LoadInitialize(List<InitializeEntry> initList)
{
entries.Clear();
occupancy.Clear();
selectedEntryId = null;
nextEntryId = 1;
anchors.Clear();
previewRects.Clear();
if (initList == null) return;
foreach (var dto in initList)
{
if (dto == null) continue;
var fromV = AsrsPositionUtil.ToGridInt(dto.from_position);
var toV = AsrsPositionUtil.ToGridInt(dto.to_position);
(fromV, toV) = AsrsPositionUtil.Normalize(fromV, toV);
if (!CanPlace(fromV, toV))
continue;
var entry = new Entry
{
id = nextEntryId++,
prefabId = dto.prefab,
prefabDisplayName = ResolvePrefabDisplayName(dto.prefab),
count = dto.count,
from = fromV,
to = toV
};
entries.Add(entry);
MarkOccupancy(entry.id, entry.from, entry.to, true);
}
}
// ============================================================
// GridView -> Window 콜백 (Click + Hover)
// ============================================================
public void OnCellHover(int rackIndex, int x, int y)
{
if (!IsSelectionMode) return;
if (!anchors.TryGetValue(rackIndex, out var anchor)) return;
previewRects[rackIndex] = AsrsRect.FromTwoPoints(anchor, new Vector2Int(x, y));
RefreshSelectionVisuals();
}
public void OnCellClick(int rackIndex, int x, int y)
{
if (!IsSelectionMode) return;
// ✅ (추가) 앵커를 아직 안 잡은 상태에서, 이미 설정된 셀을 클릭하면 "그 영역(Entry) 삭제"
// 사용 예: 1,3,1~4,4,1로 설정된 상태에서 3,4,1 클릭 -> 해당 Entry 전체 삭제
if (!anchors.ContainsKey(rackIndex) && occupancy.TryGetValue((rackIndex, x, y), out int entryId))
{
DeleteEntryById(entryId);
// 삭제 후에는 선택 관련 미리보기/앵커도 정리(안전)
anchors.Remove(rackIndex);
previewRects.Remove(rackIndex);
RefreshAll();
return;
}
// 1차 클릭: anchor 설정 + 1셀 미리보기 시작
if (!anchors.ContainsKey(rackIndex))
{
anchors[rackIndex] = new Vector2Int(x, y);
previewRects[rackIndex] = AsrsRect.FromTwoPoints(anchors[rackIndex], anchors[rackIndex]);
selectedEntryId = null;
RefreshSelectionVisuals();
RefreshTableSelection();
return;
}
// 2차 클릭: anchor ~ 현재까지 확정
var rect = AsrsRect.FromTwoPoints(anchors[rackIndex], new Vector2Int(x, y));
bool committed = CommitRectToEntries(rackIndex, rect);
if (committed)
{
anchors.Remove(rackIndex);
previewRects.Remove(rackIndex);
RefreshAll();
}
else
{
// 실패 시: 미리보기 유지(앵커 유지)
previewRects[rackIndex] = rect;
RefreshSelectionVisuals();
}
}
private bool CommitRectToEntries(int rackIndex, AsrsRect rect)
{
string prefabId = GetSelectedPrefabId();
if (string.IsNullOrWhiteSpace(prefabId))
return false;
var from = new Vector3Int(rect.MinX, rect.MinY, rackIndex);
var to = new Vector3Int(rect.MaxX, rect.MaxY, rackIndex);
(from, to) = AsrsPositionUtil.Normalize(from, to);
if (!CanPlace(from, to))
return false;
var entry = new Entry
{
id = nextEntryId++,
prefabId = prefabId,
prefabDisplayName = ResolvePrefabDisplayName(prefabId),
count = count,
from = from,
to = to
};
entries.Add(entry);
MarkOccupancy(entry.id, entry.from, entry.to, true);
return true;
}
private bool CanPlace(Vector3Int from, Vector3Int to)
{
(from, to) = AsrsPositionUtil.Normalize(from, to);
int minRack = Mathf.Min(from.z, to.z);
int maxRack = Mathf.Max(from.z, to.z);
for (int rack = minRack; rack <= maxRack; rack++)
{
for (int y = from.y; y <= to.y; y++)
{
for (int x = from.x; x <= to.x; x++)
{
if (occupancy.ContainsKey((rack, x, y)))
return false;
}
}
}
return true;
}
private void MarkOccupancy(int entryId, Vector3Int from, Vector3Int to, bool occupy)
{
(from, to) = AsrsPositionUtil.Normalize(from, to);
int minRack = Mathf.Min(from.z, to.z);
int maxRack = Mathf.Max(from.z, to.z);
for (int rack = minRack; rack <= maxRack; rack++)
{
for (int y = from.y; y <= to.y; y++)
{
for (int x = from.x; x <= to.x; x++)
{
var key = (rack, x, y);
if (occupy) occupancy[key] = entryId;
else occupancy.Remove(key);
}
}
}
}
// ===== Apply: 현재 변경사항을 asrs.initialize에 반영 =====
private void ApplyToAsrsInitialize()
{
if (asrs == null) return;
// 현재 엔트리 -> InitializeEntry 리스트로 변환
asrs.initialize = BuildInitializeEntries();
// 필요하면 여기서 닫거나, “저장됨” 토스트 등을 띄우면 됩니다.
// Close();
}
private List<InitializeEntry> BuildInitializeEntries()
{
// 중요: InitializeEntry의 from/to는 Position 타입이므로 변환 필요
// 사용자님 기존 코드 방식 그대로 사용
return entries.Select(e => new InitializeEntry
{
count = e.count,
prefab = e.prefabId,
from_position = AsrsPositionUtil.ToPosition(e.from),
to_position = AsrsPositionUtil.ToPosition(e.to)
}).ToList();
}
// ===== UI Refresh =====
private void RefreshAll()
{
RefreshSelectionVisuals();
RefreshOccupiedVisuals();
RefreshTable();
RefreshTableSelection();
}
private void RefreshSelectionVisuals()
{
foreach (var view in rackViews)
{
if (previewRects.TryGetValue(view.RackIndex, out var rect))
view.SetSelectionVisual(rect);
else
view.ClearSelectionVisual();
}
}
private void RefreshOccupiedVisuals()
{
var occupiedSet = occupancy.Keys.Select(k => (k.rack, k.x, k.y)).ToHashSet();
foreach (var view in rackViews)
view.SetOccupiedCells(occupiedSet);
}
private void RefreshTable()
{
if (tableContent == null || rowPrefab == null) return;
foreach (Transform child in tableContent)
Destroy(child.gameObject);
foreach (var e in entries)
{
var row = Instantiate(rowPrefab, tableContent);
// InitRow.Bind 시그니처는 사용자님 프로젝트 기준
row.Bind(
e.id,
e.prefabDisplayName,
e.count,
e.from,
e.to,
onClickRow: OnClickRow
);
if (selectedEntryId.HasValue && selectedEntryId.Value == e.id)
row.SetSelected(true);
}
}
private void OnClickRow(int entryId)
{
selectedEntryId = entryId;
RefreshTableSelection();
}
private void RefreshTableSelection()
{
if (tableContent == null) return;
var rows = tableContent.GetComponentsInChildren<InitRow>(true);
foreach (var r in rows)
r.SetSelected(selectedEntryId.HasValue && r.EntryId == selectedEntryId.Value);
}
private void DeleteEntryById(int entryId)
{
// entries에서 엔트리 찾기
var entry = entries.FirstOrDefault(e => e.id == entryId);
if (entry == null) return;
// occupancy 해제 (범위 전체)
MarkOccupancy(entry.id, entry.from, entry.to, false);
// entries 제거
entries.Remove(entry);
// 테이블 선택 상태도 정리
if (selectedEntryId.HasValue && selectedEntryId.Value == entryId)
selectedEntryId = null;
}
}