단축키, UndoRedo 개발중

This commit is contained in:
logonkhi
2025-12-22 19:49:36 +09:00
parent ac071813f4
commit 94dd7782ae
61 changed files with 5433 additions and 1628 deletions

View File

@@ -0,0 +1,22 @@
#nullable enable
namespace UVC.UI.Commands
{
/// <summary>
/// Undo/Redo 가능한 Command 인터페이스
/// ICommand + IUndoable 통합
/// </summary>
public interface IUndoableCommand : ICommand, IUndoable
{
/// <summary>작업 설명 (UI 표시용, 예: "객체 복제", "Node 삭제")</summary>
string Description { get; }
/// <summary>다시 실행</summary>
void Redo();
/// <summary>연속 동작 병합 가능 여부 (예: 연속 Transform 변경)</summary>
bool CanMerge(IUndoableCommand other);
/// <summary>연속 동작 병합</summary>
void Merge(IUndoableCommand other);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 269f16afecf878047ae53d4bac753753

View File

@@ -3,21 +3,23 @@ using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UVC.UI.Modal.DatePicker;
namespace UVC.UI.List.Tree
{
/// <summary>
/// 트리 구조의 리스트를 관리하고 제어하는 클래스입니다.
///
///
/// 주요 기능:
/// - 계층 구조 아이템의 추가/제거 관리
/// - 단일 및 다중 선택 지원 (파일 탐색기와 동일)
/// - 키보드 입력(Delete, 화살표, Ctrl, Shift) 처리
/// - 드래그 & 드롭 기능 지원
/// - 선택 상태 변경 이벤트 제공
///
///
/// 선택 동작:
/// - 클릭: 한 항목 선택
/// - Ctrl+클릭: 여러 항목 선택 (기존 선택 유지)
@@ -135,6 +137,12 @@ namespace UVC.UI.List.Tree
/// </summary>
public Action<TreeListItemData, bool>? OnItemVisibilityChanged;
/// <summary>
/// 아이템 더블클릭 이벤트 (data)
/// TreeListItem이 더블클릭될 때 호출됩니다.
/// </summary>
public Action<TreeListItemData>? OnItemDoubleClicked;
/// <summary>
/// 현재 선택된 아이템 목록 (읽기 전용)
/// </summary>
@@ -320,6 +328,9 @@ namespace UVC.UI.List.Tree
protected void Update()
{
// 입력 필드에 포커스가 있으면 키보드 입력 무시
if (IsInputFieldFocused()) return;
// Escape 키 입력 감지 - 선택 해제
if (Input.GetKeyDown(KeyCode.Escape))
{
@@ -355,6 +366,36 @@ namespace UVC.UI.List.Tree
}
}
/// <summary>
/// 입력 필드에 포커스가 있는지 확인합니다.
/// </summary>
private bool IsInputFieldFocused()
{
var eventSystem = EventSystem.current;
if (eventSystem == null) return false;
var selected = eventSystem.currentSelectedGameObject;
if (selected == null) return false;
// TMP_InputField 확인 (자기 자신 및 부모 계층에서 검색)
var tmpInput = selected.GetComponent<TMP_InputField>();
if (tmpInput == null)
{
tmpInput = selected.GetComponentInParent<TMP_InputField>();
}
if (tmpInput != null && tmpInput.isFocused) return true;
// Legacy InputField 확인 (자기 자신 및 부모 계층에서 검색)
var legacyInput = selected.GetComponent<UnityEngine.UI.InputField>();
if (legacyInput == null)
{
legacyInput = selected.GetComponentInParent<UnityEngine.UI.InputField>();
}
if (legacyInput != null && legacyInput.isFocused) return true;
return false;
}
/// <summary>
/// View 등록(데이터↔View 맵). Init 시점에 호출됩니다.
/// </summary>
@@ -444,9 +485,11 @@ namespace UVC.UI.List.Tree
// 디버그 로그
//Debug.Log($"Delete key pressed. Removing {itemsToDelete.Count} selected item(s).");
// 각 선택된 아이템 삭제
// 각 선택된 아이템에 대해 삭제 이벤트 발생 후 삭제
foreach (var item in itemsToDelete)
{
// 삭제 전 이벤트 발생 (View 업데이트 + 외부에서 연관 데이터 정리 가능)
NotifyDataChanged(item, ChangedType.Delete, item);
DeleteItem(item);
}

View File

@@ -100,6 +100,16 @@ namespace UVC.UI.List.Tree
protected VerticalLayoutGroup? childRootLayoutGroup = null;
/// <summary>
/// 마지막 클릭 시간 (더블클릭 감지용)
/// </summary>
protected float _lastClickTime = 0f;
/// <summary>
/// 더블클릭 감지 시간 간격 (초)
/// </summary>
protected const float DoubleClickThreshold = 0.3f;
#endregion
#region (Initialization)
@@ -456,11 +466,23 @@ namespace UVC.UI.List.Tree
/// - TreeList의 OnItemClickAction 이벤트 실행
/// - Ctrl/Shift 상태를 읽어 TreeList에 전달
/// (일반/토글/범위 선택)
/// - 더블클릭 감지 시 OnItemDoubleClicked 이벤트 실행
/// </summary>
protected void OnItemClicked()
{
if (data == null) return;
// 더블클릭 감지
float currentTime = Time.unscaledTime;
if (currentTime - _lastClickTime <= DoubleClickThreshold)
{
// 더블클릭 이벤트 발생
treeList.OnItemDoubleClicked?.Invoke(data);
_lastClickTime = 0f; // 연속 더블클릭 방지를 위해 초기화
return; // 더블클릭 시 일반 클릭 처리 스킵
}
_lastClickTime = currentTime;
// 1. TreeList의 클릭 액션 이벤트 실행
treeList.OnItemClickAction?.Invoke(data);

View File

@@ -351,5 +351,6 @@ namespace UVC.UI.List.Tree
AddCloneAtChild,
SwapChild,
TailButtons,// 향후 버튼 관련 변경 추가 가능
Delete, // 아이템 삭제 (Delete 키로 삭제 시)
}
}

View File

@@ -199,6 +199,7 @@ namespace UVC.UI.Menu
/// <see cref="TopMenuView.OnMenuItemClicked"/> 이벤트가 발생했을 때 호출되는 핸들러입니다.
/// 클릭된 메뉴 아이템(<paramref name="clickedItemData"/>)의 유효성을 검사하고,
/// 연결된 <see cref="ICommand"/>를 실행합니다.
/// IUndoableCommand인 경우 Undo/Redo 히스토리에 기록됩니다.
/// </summary>
/// <param name="clickedItemData">사용자가 클릭한 메뉴 아이템의 데이터입니다.</param>
protected virtual void HandleMenuItemClicked(MenuItemData clickedItemData)
@@ -223,9 +224,35 @@ namespace UVC.UI.Menu
// 클릭된 메뉴 아이템 정보 로그 (디버깅 목적)
ULog.Debug($"메뉴 아이템 클릭됨: {clickedItemData.ItemId} (표시 키: {clickedItemData.DisplayName})");
// 메뉴 아이템에 연결된 Command가 있다면 실행
// Command가 null일 수 있으므로 null 조건부 연산자(?.) 사용
clickedItemData.Command?.Execute(clickedItemData.CommandParameter);
// 메뉴 아이템에 연결된 Command 실행
ExecuteCommand(clickedItemData.Command, clickedItemData.CommandParameter);
}
/// <summary>
/// Command를 실행합니다.
/// IUndoableCommand인 경우 UndoRedoManager를 통해 실행하여 히스토리에 기록합니다.
/// 일반 ICommand인 경우 직접 실행합니다.
/// </summary>
/// <param name="command">실행할 Command</param>
/// <param name="parameter">Command 파라미터</param>
protected virtual void ExecuteCommand(ICommand command, object parameter = null)
{
if (command == null) return;
// IUndoableCommand인 경우 UndoRedoManager를 통해 실행
if (command is IUndoableCommand undoableCommand)
{
// UndoRedoManager가 존재하는지 확인 (Studio 씬에서만 사용 가능)
var undoRedoManager = FindAnyObjectByType<UVC.Studio.Manager.UndoRedoManager>();
if (undoRedoManager != null)
{
undoRedoManager.ExecuteCommand(undoableCommand, parameter);
return;
}
}
// 일반 ICommand이거나 UndoRedoManager가 없는 경우 직접 실행
command.Execute(parameter);
}
/// <summary>

View File

@@ -5,6 +5,7 @@ using UnityEngine;
using UnityEngine.UI;
using UVC.Extention;
using UVC.Log;
using UVC.Studio.Manager;
namespace UVC.UI.Modal
{
@@ -119,6 +120,12 @@ namespace UVC.UI.Modal
{
//ULog.Debug($"[ModalView] {gameObject.name} OnOpen called.");
// 모달이 열리면 단축키 비활성화
if (ShortcutManager.Instance != null)
{
ShortcutManager.Instance.GlobalEnabled = false;
}
// ModalContent 레시피에 적힌 대로 UI 요소들을 설정해요.
if (titleText != null && content != null)
{
@@ -177,6 +184,13 @@ namespace UVC.UI.Modal
public virtual async UniTask OnClose(ModalContent content)
{
//ULog.Debug($"[ModalView] {gameObject.name} OnClose called.");
// 모달이 닫히면 단축키 다시 활성화
if (ShortcutManager.Instance != null)
{
ShortcutManager.Instance.GlobalEnabled = true;
}
// 예: 모달에서 사용했던 리소스를 해제하거나, UI 상태를 초기화하는 코드를 여기에 넣을 수 있어요.
await UniTask.CompletedTask;
}

View File

@@ -192,6 +192,7 @@ namespace UVC.UI.Toolbar.Model
/// 버튼 클릭 로직을 실행합니다.
/// 이 메서드는 일반적으로 UI 시스템(예: Unity UI의 Button.onClick 이벤트)에 의해 호출되도록 설계됩니다.
/// 버튼이 활성화(IsEnabled == true)되어 있고 ClickCommand가 할당되어 있다면, 해당 커맨드를 실행합니다.
/// IUndoableCommand인 경우 UndoRedoManager를 통해 실행하여 Undo/Redo 히스토리에 기록됩니다.
/// 파생 클래스에서 이 메서드를 재정의하여 특정 버튼 타입에 맞는 추가적인 클릭 동작을 구현할 수 있습니다.
/// </summary>
/// <param name="parameter">ClickCommand에 전달할 선택적 파라미터입니다.</param>
@@ -199,7 +200,30 @@ namespace UVC.UI.Toolbar.Model
{
if (!IsEnabled) return;
ClickCommand?.Execute(parameter); // 커맨드에 파라미터 전달
if (ClickCommand != null)
{
// IUndoableCommand인 경우 UndoRedoManager를 통해 실행
if (ClickCommand is IUndoableCommand undoableCommand)
{
// UndoRedoManager가 존재하는지 확인 (Studio 씬에서만 사용 가능)
var undoRedoManager = UnityEngine.Object.FindAnyObjectByType<UVC.Studio.Manager.UndoRedoManager>();
if (undoRedoManager != null)
{
undoRedoManager.ExecuteCommand(undoableCommand, parameter);
}
else
{
// UndoRedoManager가 없으면 직접 실행
ClickCommand.Execute(parameter);
}
}
else
{
// 일반 ICommand는 직접 실행
ClickCommand.Execute(parameter);
}
}
OnClicked?.Invoke(); // 클릭 이벤트 발생
}

View File

@@ -13,13 +13,13 @@ namespace UVC.UI.Window
{
/// <summary>
/// 계층 데이터를 표시/검색/선택하는 창(View)입니다.
///
///
/// 책임:
/// - 메인 트리(`treeList`)와 검색 트리(`treeListSearch`)를 관리
/// - 입력창으로 검색을 수행하고 결과를 검색 트리에 표시(청크 처리 + 로딩 애니메이션)
/// - `TreeList.OnItemSelectionChanged`를 구독해 외부로 선택/해제 이벤트를 전달
/// - 외부에서 호출 가능한 간단한 항목 추가/삭제 API 제공(실제 렌더링/상태는 `TreeList`가 담당)
///
///
/// 사용 예:
/// <example>
/// <![CDATA[
@@ -65,6 +65,16 @@ namespace UVC.UI.Window
/// </summary>
public System.Action<TreeListItemData, bool>? OnItemVisibilityChanged;
/// <summary>
/// 메인/검색 리스트에서 항목이 삭제될 때 발생합니다 (Delete 키).
/// </summary>
public System.Action<TreeListItemData>? OnItemDeleted;
/// <summary>
/// 메인/검색 리스트에서 항목이 더블클릭될 때 발생합니다.
/// </summary>
public System.Action<TreeListItemData>? OnItemDoubleClicked;
// 검색 목록에서 선택된 항목(클론된 데이터)
protected TreeListItemData? selectedSearchItem;
@@ -97,6 +107,8 @@ namespace UVC.UI.Window
{
treeList.OnItemSelectionChanged += HandleMainSelectionChanged;
treeList.OnItemVisibilityChanged += HandleMainVisibilityChanged;
treeList.OnItemDataChanged += HandleMainDataChanged;
treeList.OnItemDoubleClicked += HandleMainDoubleClicked;
}
// 검색 리스트의 선택 변경을 감지 (선택 결과를 원본 트리에 반영하는 용도)
@@ -104,6 +116,8 @@ namespace UVC.UI.Window
{
treeListSearch.OnItemSelectionChanged += OnSearchSelectionChanged;
treeListSearch.OnItemVisibilityChanged += HandleMainVisibilityChanged;
treeListSearch.OnItemDataChanged += HandleMainDataChanged;
treeListSearch.OnItemDoubleClicked += HandleMainDoubleClicked;
}
clearTextButton.onClick.AddListener(() =>
@@ -362,6 +376,20 @@ namespace UVC.UI.Window
OnItemVisibilityChanged?.Invoke(data, isVisible);
}
protected void HandleMainDataChanged(ChangedType changedType, TreeListItemData data, int index)
{
// Delete 키로 삭제된 경우 외부 이벤트 발생
if (changedType == ChangedType.Delete)
{
OnItemDeleted?.Invoke(data);
}
}
protected void HandleMainDoubleClicked(TreeListItemData data)
{
OnItemDoubleClicked?.Invoke(data);
}
protected void OnInputFieldSubmit(string text)
{
// 검색어가 있으면 검색 결과 목록 표시
@@ -502,17 +530,23 @@ namespace UVC.UI.Window
{
treeListSearch.OnItemSelectionChanged -= OnSearchSelectionChanged;
treeListSearch.OnItemVisibilityChanged -= HandleMainVisibilityChanged;
treeListSearch.OnItemDataChanged -= HandleMainDataChanged;
treeListSearch.OnItemDoubleClicked -= HandleMainDoubleClicked;
}
if (treeList != null)
{
treeList.OnItemSelectionChanged -= HandleMainSelectionChanged;
treeList.OnItemVisibilityChanged -= HandleMainVisibilityChanged;
treeList.OnItemDataChanged -= HandleMainDataChanged;
treeList.OnItemDoubleClicked -= HandleMainDoubleClicked;
}
// 4. 외부 이벤트 핸들러 정리
OnItemSelected = null;
OnItemDeselected = null;
OnItemDeleted = null;
OnItemDoubleClicked = null;
// 5. 참조 정리
selectedSearchItem = null;

View File

@@ -17,5 +17,12 @@ namespace UVC.UI.Window.PropertyWindow
/// </summary>
/// <param name="isReadOnly">읽기 전용 여부 (true: 비활성화, false: 활성화)</param>
void SetReadOnly(bool isReadOnly);
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// Undo/Redo 시 PropertyValueChanged 이벤트 없이 값을 반영할 때 사용됩니다.
/// </summary>
/// <param name="value">새로운 값</param>
void UpdateValue(object value);
}
}

View File

@@ -376,5 +376,27 @@ namespace UVC.UI.Window.PropertyWindow
}
#endregion
#region Property Value Update
/// <summary>
/// 특정 속성 아이템의 값을 UI에 반영합니다.
/// Undo/Redo 시 PropertyValueChanged 이벤트 없이 값을 반영할 때 사용됩니다.
/// </summary>
/// <param name="propertyId">속성 아이템의 ID</param>
/// <param name="value">새로운 값</param>
public void UpdatePropertyValue(string propertyId, object value)
{
if (_itemViews.TryGetValue(propertyId, out var itemView) && itemView != null)
{
var propertyUI = itemView.GetComponent<IPropertyUI>();
if (propertyUI != null)
{
propertyUI.UpdateValue(value);
}
}
}
#endregion
}
}

View File

@@ -457,6 +457,36 @@ namespace UVC.UI.Window.PropertyWindow
PropertyValueChanged?.Invoke(this, new PropertyValueChangedEventArgs(propertyId, propertyType, oldValue, newValue));
}
/// <summary>
/// 특정 ID를 가진 속성의 값을 설정합니다.
/// 이 메서드는 Undo/Redo 시 값을 복원할 때 사용됩니다.
/// PropertyValueChanged 이벤트를 발생시키지 않습니다.
/// </summary>
/// <param name="propertyId">값을 변경할 속성의 고유 ID</param>
/// <param name="value">새로운 값</param>
/// <returns>값이 성공적으로 설정되었는지 여부</returns>
public bool SetPropertyValue(string propertyId, object? value)
{
if (!_itemIndex.TryGetValue(propertyId, out var propertyItem))
{
Debug.LogWarning($"[PropertyWindow] ID '{propertyId}'에 해당하는 속성을 찾을 수 없습니다.");
return false;
}
if (value != null)
{
propertyItem.SetValue(value);
// View에 값 반영 (UI 업데이트)
if (_view != null)
{
_view.UpdatePropertyValue(propertyId, value);
}
}
return true;
}
#endregion
#region Internal Helpers

View File

@@ -111,6 +111,18 @@ namespace UVC.UI.Window.PropertyWindow.UI
}
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (_valueToggle != null && value is bool boolValue)
{
_valueToggle.SetIsOnWithoutNotify(boolValue);
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// 메모리 누수를 방지하기 위해 등록된 리스너를 제거합니다.

View File

@@ -144,6 +144,19 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_colorPickerButton != null) _colorPickerButton.gameObject.SetActive(!isReadOnly);
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Color colorValue)
{
if (_colorPreviewImage != null) _colorPreviewImage.color = colorValue;
if (_colorLabel != null) _colorLabel.SetTextWithoutNotify(ColorUtil.ToHex(colorValue, true, false));
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -204,6 +204,23 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_colorPickerButton != null) _colorPickerButton.gameObject.SetActive(!isReadOnly);
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Tuple<string, string, Color?> colorStateValue)
{
if (_stateLabel != null) _stateLabel.SetTextWithoutNotify(colorStateValue.Item2);
if (colorStateValue.Item3.HasValue)
{
if (_colorPreviewImage != null) _colorPreviewImage.color = colorStateValue.Item3.Value;
if (_colorLabel != null) _colorLabel.SetTextWithoutNotify(ColorUtil.ToHex(colorStateValue.Item3.Value, true, false));
}
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -128,6 +128,18 @@ namespace UVC.UI.Window.PropertyWindow.UI
}
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is DateTime dateValue && _dateText != null)
{
_dateText.SetTextWithoutNotify(dateValue.ToString(DateFormat));
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -171,6 +171,19 @@ namespace UVC.UI.Window.PropertyWindow.UI
}
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Tuple<DateTime, DateTime> dateRange)
{
if (_startDateText != null) _startDateText.SetTextWithoutNotify(dateRange.Item1.ToString(DateFormat));
if (_endDateText != null) _endDateText.SetTextWithoutNotify(dateRange.Item2.ToString(DateFormat));
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -192,6 +192,20 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_miniteDropDown != null) _miniteDropDown.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is DateTime dateTimeValue)
{
if (_dateText != null) _dateText.SetTextWithoutNotify(dateTimeValue.ToString(DateFormat));
if (_hourDropDown != null) _hourDropDown.SetValueWithoutNotify(dateTimeValue.Hour);
if (_miniteDropDown != null) _miniteDropDown.SetValueWithoutNotify(dateTimeValue.Minute);
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -303,6 +303,23 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_endMiniteDropDown != null) _endMiniteDropDown.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Tuple<DateTime, DateTime> dateTimeRange)
{
if (_startDateText != null) _startDateText.SetTextWithoutNotify(dateTimeRange.Item1.ToString(DateFormat));
if (_endDateText != null) _endDateText.SetTextWithoutNotify(dateTimeRange.Item2.ToString(DateFormat));
if (_startHourDropDown != null) _startHourDropDown.SetValueWithoutNotify(dateTimeRange.Item1.Hour);
if (_startMiniteDropDown != null) _startMiniteDropDown.SetValueWithoutNotify(dateTimeRange.Item1.Minute);
if (_endHourDropDown != null) _endHourDropDown.SetValueWithoutNotify(dateTimeRange.Item2.Hour);
if (_endMiniteDropDown != null) _endMiniteDropDown.SetValueWithoutNotify(dateTimeRange.Item2.Minute);
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -123,6 +123,22 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_dropdown != null) _dropdown.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (_dropdown != null && _enumValues != null && value != null)
{
int index = Array.IndexOf(_enumValues, value);
if (index >= 0)
{
_dropdown.SetValueWithoutNotify(index);
}
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -120,6 +120,22 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_dropdown != null) _dropdown.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (_dropdown != null && _propertyItem?.ItemsSource != null && value is string stringValue)
{
int index = _propertyItem.ItemsSource.IndexOf(stringValue);
if (index >= 0)
{
_dropdown.SetValueWithoutNotify(index);
}
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -215,6 +215,32 @@ namespace UVC.UI.Window.PropertyWindow.UI
}
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value == null) return;
string textValue = value.ToString();
if (_valueInput != null)
{
_valueInput.SetTextWithoutNotify(textValue);
}
if (_slider != null && _slider.gameObject.activeSelf)
{
if (value is int intValue)
{
_slider.SetValueWithoutNotify(intValue);
}
else if (value is float floatValue)
{
_slider.SetValueWithoutNotify(floatValue);
}
}
}
private void OnDestroy()
{
// 오브젝트가 파괴될 때 리스너를 확실히 제거합니다.

View File

@@ -195,6 +195,24 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_maxInputField != null) _maxInputField.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Tuple<int, int> intRange)
{
if (_minInputField != null) _minInputField.SetTextWithoutNotify(intRange.Item1.ToString(CultureInfo.InvariantCulture));
if (_maxInputField != null) _maxInputField.SetTextWithoutNotify(intRange.Item2.ToString(CultureInfo.InvariantCulture));
}
else if (value is Tuple<float, float> floatRange)
{
if (_minInputField != null) _minInputField.SetTextWithoutNotify(floatRange.Item1.ToString(CultureInfo.InvariantCulture));
if (_maxInputField != null) _maxInputField.SetTextWithoutNotify(floatRange.Item2.ToString(CultureInfo.InvariantCulture));
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -159,6 +159,21 @@ namespace UVC.UI.Window.PropertyWindow.UI
}
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (_propertyItem?.ItemsSource == null || value is not string stringValue) return;
int index = _propertyItem.ItemsSource.IndexOf(stringValue);
if (index >= 0 && index < _toggles.Count)
{
_toggles[index].SetIsOnWithoutNotify(true);
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -104,6 +104,18 @@ namespace UVC.UI.Window.PropertyWindow.UI
}
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (_valueInput != null && value != null)
{
_valueInput.SetTextWithoutNotify(value.ToString());
}
}
private void OnDestroy()
{
// 오브젝트가 파괴될 때 리스너를 확실히 제거합니다.

View File

@@ -183,6 +183,19 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_yInputField != null) _yInputField.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Vector2 vector2Value)
{
if (_xInputField != null) _xInputField.SetTextWithoutNotify(vector2Value.x.ToString(CultureInfo.InvariantCulture));
if (_yInputField != null) _yInputField.SetTextWithoutNotify(vector2Value.y.ToString(CultureInfo.InvariantCulture));
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -216,6 +216,20 @@ namespace UVC.UI.Window.PropertyWindow.UI
if (_zInputField != null) _zInputField.interactable = !isReadOnly;
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// </summary>
/// <param name="value">새로운 값</param>
public void UpdateValue(object value)
{
if (value is Vector3 vector3Value)
{
if (_xInputField != null) _xInputField.SetTextWithoutNotify(vector3Value.x.ToString(CultureInfo.InvariantCulture));
if (_yInputField != null) _yInputField.SetTextWithoutNotify(vector3Value.y.ToString(CultureInfo.InvariantCulture));
if (_zInputField != null) _zInputField.SetTextWithoutNotify(vector3Value.z.ToString(CultureInfo.InvariantCulture));
}
}
/// <summary>
/// 이 UI 오브젝트가 파괴될 때 Unity에 의해 호출됩니다.
/// </summary>

View File

@@ -89,6 +89,26 @@ namespace UVC.UI.Window.PropertyWindow
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
}
/// <summary>
/// UI에 표시되는 값을 업데이트합니다.
/// Undo/Redo 시 PropertyValueChanged 이벤트 없이 값을 반영할 때 사용됩니다.
/// </summary>
/// <param name="value">새로운 값</param>
public virtual void UpdateValue(object value)
{
// 파생 클래스에서 구체적인 구현을 제공해야 합니다.
ApplyValueToUI(value);
}
/// <summary>
/// 파생 클래스에서 UI에 값을 적용합니다.
/// </summary>
/// <param name="value">적용할 값</param>
protected virtual void ApplyValueToUI(object value)
{
// 기본 구현 없음 - 파생 클래스에서 필요시 구현
}
#endregion
#region Common UI Setup