UTKShortcutList 개발 완료. Modal 개선
This commit is contained in:
@@ -35,7 +35,8 @@
|
||||
"Bash(/bin/mkdir -p:*)",
|
||||
"Bash(/bin/rm:*)",
|
||||
"WebFetch(domain:docs.unity3d.com)",
|
||||
"Bash(ls:*)"
|
||||
"Bash(ls:*)",
|
||||
"WebFetch(domain:discussions.unity.com)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
8
Assets/Resources/Studio/UIToolkit.meta
Normal file
8
Assets/Resources/Studio/UIToolkit.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 036922f6ebd1fc246b7f9dcbed76951d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Resources/Studio/UIToolkit/Modal.meta
Normal file
8
Assets/Resources/Studio/UIToolkit/Modal.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 523651d33563a6e4eb5a39945fe0017f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
|
||||
<ui:VisualElement name="root" style="padding-left: 20px;">
|
||||
<utk:UTKInputField label="Database IP" value="127.0.0.3" label-min-width="200" style="width: 400px;" />
|
||||
<utk:UTKInputField label="Database PORT" value="3304" label-min-width="200" style="width: 400px;" />
|
||||
<utk:UTKInputField label="Database ID" value="" label-min-width="200" style="width: 400px;" />
|
||||
<utk:UTKInputField label="Database PASSWORD" value="" is-password-field="true" label-min-width="200" style="width: 400px;" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d27b20f78aec0354b8b97b9af0765701
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,24 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
|
||||
<ui:VisualElement name="root" style="padding-left: 20px;">
|
||||
<ui:VisualElement style="flex-direction: row;">
|
||||
<utk:UTKIntegerField label="Auto Save Time" value="5" label-min-width="200" style="width: 400px;margin-right: 5px;" />
|
||||
<utk:UTKLabel text="Minutes" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement style="flex-direction: row;">
|
||||
<utk:UTKFloatField label="Grid Spacing" value="1" label-min-width="200" style="width: 400px;margin-right: 5px;" />
|
||||
<utk:UTKLabel text="Meters" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="VisualElement" style="flex-direction: row;">
|
||||
<utk:UTKFloatField label="Position Snap" value="0.5" label-min-width="200" style="width: 400px;margin-right: 5px;" />
|
||||
<utk:UTKLabel text="Meters" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement style="flex-direction: row;">
|
||||
<utk:UTKFloatField label="Rotation Snap" value="10" label-min-width="200" style="width: 400px;margin-right: 5px;" />
|
||||
<utk:UTKLabel text="Meters" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement style="flex-direction: row;">
|
||||
<utk:UTKFloatField label="Scale Snap" value="0.5" label-min-width="200" style="width: 400px;margin-right: 5px;" />
|
||||
<utk:UTKLabel text="Meters" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a709a335acffbd4c9d4358c153fad81
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -0,0 +1,5 @@
|
||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
|
||||
<ui:VisualElement name="root" style="padding-left: 10px;">
|
||||
<utk:UTKShortcutList name="window" style="background-color: transparent;" />
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcdd51dd452d3e94ca374da0307608a4
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -35,7 +35,7 @@
|
||||
.utk-input > .unity-base-text-field__input {
|
||||
flex-grow: 1;
|
||||
height: var(--size-input-height);
|
||||
min-width: 120px;
|
||||
min-width: 60px;
|
||||
padding-left: var(--space-m);
|
||||
padding-right: var(--space-m);
|
||||
background-color: var(--color-bg-input);
|
||||
|
||||
56
Assets/Resources/UIToolkit/List/UTKShortcutList.uxml
Normal file
56
Assets/Resources/UIToolkit/List/UTKShortcutList.uxml
Normal file
@@ -0,0 +1,56 @@
|
||||
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:ui="UnityEngine.UIElements"
|
||||
xmlns:utk="UVC.UIToolkit"
|
||||
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
|
||||
editor-extension-mode="False">
|
||||
<!--
|
||||
UTKShortcutList.uxml
|
||||
단축키 설정 리스트 컴포넌트 메인 레이아웃.
|
||||
|
||||
구조:
|
||||
- container (.utk-shortcut-list)
|
||||
- search-field : UTKInputField (Command 이름 검색)
|
||||
- header : 컬럼 헤더 행
|
||||
- header-command : 명령 이름 컬럼 헤더 (flex-grow)
|
||||
- header-ctrl : Ctrl 컬럼 헤더
|
||||
- header-shift : Shift 컬럼 헤더
|
||||
- header-alt : Alt 컬럼 헤더
|
||||
- header-key : Key 컬럼 헤더
|
||||
- list-view : UTKListView (가상화 지원)
|
||||
|
||||
Item 레이아웃: UTKShortcutListItem.uxml 참조
|
||||
스타일: Resources/UIToolkit/List/UTKShortcutListUss.uss (C#에서 로드)
|
||||
-->
|
||||
<ui:VisualElement name="container" class="utk-shortcut-list-container">
|
||||
|
||||
<!-- 검색 영역 -->
|
||||
<ui:VisualElement name="search-container" class="utk-shortcut-list__search-container">
|
||||
<utk:UTKInputField name="search-field"
|
||||
placeholder="Search command..."
|
||||
class="utk-shortcut-list__search" />
|
||||
<utk:UTKButton name="clear-btn"
|
||||
class="utk-shortcut-list__clear-btn"
|
||||
variant="Text"
|
||||
icon="Close"
|
||||
icon-size="12"
|
||||
icon-only="true" />
|
||||
</ui:VisualElement>
|
||||
|
||||
<!-- 컬럼 헤더 행 -->
|
||||
<ui:VisualElement name="header" class="utk-shortcut-list__header">
|
||||
<ui:Label name="header-command" text="" class="utk-shortcut-list__header-command" />
|
||||
<ui:Label name="header-ctrl" text="Ctrl" class="utk-shortcut-list__header-modifier" />
|
||||
<ui:Label name="header-shift" text="Shift" class="utk-shortcut-list__header-modifier" />
|
||||
<ui:Label name="header-alt" text="Alt" class="utk-shortcut-list__header-modifier" />
|
||||
<ui:Label name="header-key" text="Key" class="utk-shortcut-list__header-key" />
|
||||
</ui:VisualElement>
|
||||
|
||||
<!-- ListView (가상화 지원) -->
|
||||
<utk:UTKListView name="list-view"
|
||||
class="utk-shortcut-list__listview"
|
||||
fixed-item-height="36"
|
||||
selection-type="None"
|
||||
show-alternating-row-backgrounds="None" />
|
||||
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
10
Assets/Resources/UIToolkit/List/UTKShortcutList.uxml.meta
Normal file
10
Assets/Resources/UIToolkit/List/UTKShortcutList.uxml.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 009b695fb58d74b43a48be6fb5a00895
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
40
Assets/Resources/UIToolkit/List/UTKShortcutListItem.uxml
Normal file
40
Assets/Resources/UIToolkit/List/UTKShortcutListItem.uxml
Normal file
@@ -0,0 +1,40 @@
|
||||
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:ui="UnityEngine.UIElements"
|
||||
xmlns:utk="UVC.UIToolkit"
|
||||
noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd"
|
||||
editor-extension-mode="False">
|
||||
<!--
|
||||
UTKShortcutListItem.uxml
|
||||
단축키 리스트 단일 행(아이템) 레이아웃.
|
||||
UTKShortcutList.cs 의 makeItem/bindItem 에서 사용됩니다.
|
||||
|
||||
구조 (flex-direction: row):
|
||||
- item-container (.utk-shortcut-list-item)
|
||||
- command-label : UTKLabel - 명령 이름 (flex-grow: 1)
|
||||
- ctrl-checkbox : UTKCheckBox - Ctrl 수정자 토글
|
||||
- shift-checkbox : UTKCheckBox - Shift 수정자 토글
|
||||
- alt-checkbox : UTKCheckBox - Alt 수정자 토글
|
||||
- key-field : UTKInputField - 주요 키 (읽기전용 + 클릭 캡처)
|
||||
|
||||
스타일: UTKShortcutListUss.uss (상위 컴포넌트에서 cascade)
|
||||
USS는 UXML에 지정하지 않고 C#에서 로드합니다 (테마 적용 순서 보장).
|
||||
-->
|
||||
<ui:VisualElement name="item-container" class="utk-shortcut-list-item">
|
||||
|
||||
<!-- 명령 이름 (좌측, 남은 공간 전부 사용) -->
|
||||
<utk:UTKLabel name="command-label" class="utk-shortcut-list-item__command" />
|
||||
|
||||
<!-- Ctrl 수정자 체크박스 (텍스트 없음, 아이콘만) -->
|
||||
<utk:UTKCheckBox name="ctrl-checkbox" class="utk-shortcut-list-item__modifier" />
|
||||
|
||||
<!-- Shift 수정자 체크박스 -->
|
||||
<utk:UTKCheckBox name="shift-checkbox" class="utk-shortcut-list-item__modifier" />
|
||||
|
||||
<!-- Alt 수정자 체크박스 -->
|
||||
<utk:UTKCheckBox name="alt-checkbox" class="utk-shortcut-list-item__modifier" />
|
||||
|
||||
<!-- 주요 키 입력 필드 (읽기전용 표시 + 클릭 시 키 캡처 모드 진입) -->
|
||||
<utk:UTKInputField name="key-field" class="utk-shortcut-list-item__key" />
|
||||
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d18cdf18ee19874aaf8decc2b8805ff
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
262
Assets/Resources/UIToolkit/List/UTKShortcutListUss.uss
Normal file
262
Assets/Resources/UIToolkit/List/UTKShortcutListUss.uss
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* ===================================
|
||||
* UTKShortcutListUss.uss
|
||||
* 단축키 설정 리스트 컴포넌트 전용 스타일
|
||||
* ===================================
|
||||
*
|
||||
* 이 파일은 UTKThemeManager 의 CSS 변수를 참조합니다.
|
||||
* - 색상 : var(--color-*)
|
||||
* - 간격 : var(--space-*)
|
||||
* - 반지름: var(--radius-*)
|
||||
* - 폰트 : var(--font-size-*)
|
||||
*
|
||||
* 컬럼 너비 (컨테이너 · 헤더 · 아이템 공통으로 맞춤):
|
||||
* command flex-grow:1 명령 이름 (나머지 공간 전부)
|
||||
* modifier 52px Ctrl / Shift / Alt 체크박스 셀
|
||||
* key 76px 주요 키 입력 셀
|
||||
*
|
||||
* 관련 파일:
|
||||
* UTKShortcutList.uxml – 메인 레이아웃
|
||||
* UTKShortcutListItem.uxml – 아이템 행 레이아웃
|
||||
* UTKShortcutList.cs – 컴포넌트 로직
|
||||
*/
|
||||
|
||||
/* =============================================
|
||||
루트 컨테이너
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
background-color: var(--color-bg-panel);
|
||||
}
|
||||
|
||||
.utk-shortcut-list-container {
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
검색 영역 (검색 필드 + Clear 버튼)
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list__search-container {
|
||||
position: relative;
|
||||
margin: var(--space-m) var(--space-m) 0 var(--space-m);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.utk-shortcut-list__search {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* 검색어 있을 때 텍스트가 버튼 뒤로 가리지 않도록 오른쪽 패딩 확보 */
|
||||
.utk-shortcut-list__search .unity-text-field__input {
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.utk-shortcut-list__clear-btn {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
min-width: 16px;
|
||||
min-height: 16px;
|
||||
border-width: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
align-self: center;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.utk-shortcut-list__search .unity-text-field__input {
|
||||
background-color: var(--color-bg-input);
|
||||
border-width: var(--border-width);
|
||||
border-color: var(--color-border);
|
||||
border-radius: var(--radius-s);
|
||||
padding: var(--space-xs) var(--space-m);
|
||||
font-size: var(--font-size-body2);
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.utk-shortcut-list__search:focus .unity-text-field__input {
|
||||
border-color: var(--color-border-focus);
|
||||
}
|
||||
|
||||
/* UTKInputField 내부 label 숨김 (placeholder 모드에서 label 없이 사용) */
|
||||
.utk-shortcut-list__search .unity-label {
|
||||
display: none;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
컬럼 헤더 행
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list__header {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: var(--space-s) var(--space-m);
|
||||
margin-top: var(--space-s);
|
||||
border-bottom-width: 1px;
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.utk-shortcut-list__header-command {
|
||||
flex-grow: 1;
|
||||
font-size: var(--font-size-body2);
|
||||
color: var(--color-text-secondary);
|
||||
-unity-font-style: bold;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.utk-shortcut-list__header-modifier {
|
||||
width: 52px;
|
||||
flex-shrink: 0;
|
||||
font-size: var(--font-size-body2);
|
||||
color: var(--color-text-secondary);
|
||||
-unity-text-align: middle-center;
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
.utk-shortcut-list__header-key {
|
||||
width: 90px;
|
||||
flex-shrink: 0;
|
||||
font-size: var(--font-size-body2);
|
||||
color: var(--color-text-secondary);
|
||||
-unity-text-align: middle-center;
|
||||
-unity-font-style: bold;
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
UTKListView 컨테이너
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list__listview {
|
||||
flex-grow: 1;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* ListView 자체의 테두리·배경 제거 (디자인 가이드: 패널 배경 사용) */
|
||||
.utk-shortcut-list__listview.utk-listview {
|
||||
border-width: 0;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
아이템 행 컨테이너
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-m);
|
||||
min-height: 36px;
|
||||
flex-grow: 1;
|
||||
border-bottom-width: 1px;
|
||||
border-color: var(--color-border);
|
||||
transition-duration: var(--anim-fast);
|
||||
transition-property: background-color;
|
||||
}
|
||||
|
||||
.utk-shortcut-list-item:hover {
|
||||
background-color: var(--color-btn-hover);
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
아이템 – 명령 이름 레이블
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list-item__command {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* UTKLabel 내부 Label 텍스트 스타일 */
|
||||
.utk-shortcut-list-item__command .unity-label {
|
||||
font-size: var(--font-size-body2);
|
||||
color: var(--color-text-primary);
|
||||
-unity-text-align: middle-left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
아이템 – 수정자 키 체크박스 (Ctrl / Shift / Alt)
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list-item__modifier {
|
||||
width: 52px;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* UTKCheckBox 내부 컨테이너를 중앙 정렬 */
|
||||
.utk-shortcut-list-item__modifier .utk-checkbox {
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 체크박스 레이블(텍스트) 숨김 – 헤더가 컬럼 레이블 역할을 담당 */
|
||||
.utk-shortcut-list-item__modifier .utk-checkbox__label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* =============================================
|
||||
아이템 – 주요 키 입력 필드
|
||||
============================================= */
|
||||
|
||||
.utk-shortcut-list-item__key {
|
||||
width: 90px;
|
||||
flex-shrink: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* UTKInputField label 숨김 */
|
||||
.utk-shortcut-list-item__key .unity-label {
|
||||
display: none;
|
||||
min-width: 0;
|
||||
width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* 텍스트 입력 영역: 키 뱃지 스타일 */
|
||||
.utk-shortcut-list-item__key .unity-text-field__input {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border-width: var(--border-width);
|
||||
border-color: var(--color-border);
|
||||
border-radius: var(--radius-s);
|
||||
-unity-text-align: middle-center;
|
||||
font-size: var(--font-size-body2);
|
||||
color: var(--color-text-primary);
|
||||
padding: 0 var(--space-s);
|
||||
min-height: 24px;
|
||||
cursor: link;
|
||||
transition-duration: var(--anim-fast);
|
||||
transition-property: border-color, background-color;
|
||||
}
|
||||
|
||||
.utk-shortcut-list-item__key .unity-text-field__input:hover {
|
||||
border-color: var(--color-border-focus);
|
||||
}
|
||||
|
||||
/* 캡처 모드: 키 입력 대기 중 */
|
||||
.utk-shortcut-list-item__key--capturing .unity-text-field__input {
|
||||
border-color: var(--color-border-focus);
|
||||
background-color: var(--color-bg-input);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
/* 텍스트 커서(caret) 숨김 – 읽기전용이므로 커서 불필요 */
|
||||
.utk-shortcut-list-item__key .unity-text-field__input > TextElement {
|
||||
cursor: link;
|
||||
}
|
||||
11
Assets/Resources/UIToolkit/List/UTKShortcutListUss.uss.meta
Normal file
11
Assets/Resources/UIToolkit/List/UTKShortcutListUss.uss.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9fafe6cd359d2c4899d36f1fb6ad99a
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
@@ -69,10 +69,12 @@
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.utk-alert__buttons .last-child {
|
||||
margin-left: var(--space-s);
|
||||
}
|
||||
|
||||
.utk-alert__btn {
|
||||
min-width: 80px;
|
||||
margin-left: var(--space-s);
|
||||
margin-right: var(--space-s);
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
}
|
||||
|
||||
.utk-modal--large {
|
||||
width: 640px;
|
||||
width: 800px;
|
||||
height: 600px;
|
||||
max-height: 800px;
|
||||
}
|
||||
|
||||
@@ -112,6 +113,14 @@
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: var(--space-m) var(--space-l);
|
||||
border-top-width: var(--border-width);
|
||||
border-top-width: 0;
|
||||
border-top-color: var(--color-border);
|
||||
}
|
||||
|
||||
.utk-modal__footer--has-children {
|
||||
border-top-width: var(--border-width);
|
||||
}
|
||||
|
||||
.utk-modal__footer .last-child {
|
||||
margin-left: var(--space-s);
|
||||
}
|
||||
@@ -131,3 +131,7 @@
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--space-s);
|
||||
}
|
||||
|
||||
.utk-notification__actions .last-child {
|
||||
margin-left: var(--space-s);
|
||||
}
|
||||
@@ -29,3 +29,8 @@
|
||||
height: 150px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.utk-sample-shortcut-list {
|
||||
height: 320px;
|
||||
width: 620px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit">
|
||||
<Style src="../UTKSampleCommon.uss" />
|
||||
<Style src="UTKListSample.uss" />
|
||||
|
||||
<VisualElement class="utk-sample-container">
|
||||
<Label class="utk-sample-desc" text="단축키(Command + Modifier Keys + Key) 매핑을 설정하는 리스트 컴포넌트" />
|
||||
|
||||
<VisualElement class="utk-sample-section">
|
||||
<Label class="utk-sample-section__title" text="ShortcutList" />
|
||||
<utk:UTKShortcutList name="shortcut-list-sample" class="utk-sample-shortcut-list" />
|
||||
</VisualElement>
|
||||
|
||||
<!-- Code Sample -->
|
||||
<VisualElement class="utk-code-sample-container">
|
||||
<utk:UTKCodeBlock name="code-csharp" title="C#" />
|
||||
<utk:UTKCodeBlock name="code-uxml" title="UXML" />
|
||||
</VisualElement>
|
||||
</VisualElement>
|
||||
</UXML>
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db17838bfbe1351449a155173abfc075
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -61,6 +61,7 @@
|
||||
margin-right: var(--space-s);
|
||||
transition-duration: var(--anim-fast);
|
||||
transition-property: background-color;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.utk-tabview .unity-tab__header:hover {
|
||||
@@ -150,11 +151,13 @@
|
||||
border-bottom-width: 0;
|
||||
border-top-width: 0;
|
||||
border-left-width: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.utk-tabview.utk-tabview--align-left .unity-tab__header {
|
||||
margin-right: 0;
|
||||
margin-bottom: var(--space-s);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.utk-tabview.utk-tabview--align-left .unity-tab__header-underline {
|
||||
@@ -183,6 +186,7 @@
|
||||
.utk-tabview.utk-tabview--align-right .unity-tab__header {
|
||||
margin-right: 0;
|
||||
margin-bottom: var(--space-s);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.utk-tabview.utk-tabview--align-right .unity-tab__header-underline {
|
||||
|
||||
@@ -166,7 +166,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument
|
||||
m_PanelSettings: {fileID: 11400000, guid: 5ad7007b08a97b54d927c352279a18b6, type: 2}
|
||||
m_ParentUI: {fileID: 0}
|
||||
sourceAsset: {fileID: 9197481963319205126, guid: 54e4f33c8b08cb54f97dbdb5edd79e1e, type: 3}
|
||||
sourceAsset: {fileID: 9197481963319205126, guid: 6c8eae7ee21b96245b325f08111b214b, type: 3}
|
||||
m_SortingOrder: 1
|
||||
m_Position: 0
|
||||
m_WorldSpaceSizeMode: 1
|
||||
@@ -184,9 +184,9 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 1097328750}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 39265a781c40bdb4a90aa56b0fbf44a6, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: e88ad13f58976fb4a837242a7e1c8282, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::UVC.Sample.UIToolkit.UTKToolBarSample
|
||||
m_EditorClassIdentifier: Assembly-CSharp::UVC.Sample.UIToolkit.UTKSettingModalSample
|
||||
_uiDocument: {fileID: 1097328754}
|
||||
initialTheme: 0
|
||||
--- !u!1 &1331954412
|
||||
|
||||
@@ -5,6 +5,7 @@ using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit;
|
||||
using UVC.UI.Commands;
|
||||
using UVC.Log;
|
||||
using UVC.Studio.UIToolkit.Modal;
|
||||
|
||||
namespace UVC.Sample.UIToolkit
|
||||
{
|
||||
@@ -21,6 +22,10 @@ namespace UVC.Sample.UIToolkit
|
||||
|
||||
private UTKToggle? _themeToggle;
|
||||
private VisualElement? _root;
|
||||
|
||||
private UTKButton? _openButton0;
|
||||
private UTKButton? _openButton1;
|
||||
private UTKButton? _openButton2;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
@@ -33,7 +38,7 @@ namespace UVC.Sample.UIToolkit
|
||||
}
|
||||
_uiDocument = doc;
|
||||
_root = _uiDocument.rootVisualElement;
|
||||
|
||||
UTKModal.SetRoot(_root);
|
||||
UTKThemeManager.Instance.RegisterRoot(_root);
|
||||
UTKThemeManager.Instance.SetTheme(initialTheme);
|
||||
|
||||
@@ -47,6 +52,39 @@ namespace UVC.Sample.UIToolkit
|
||||
};
|
||||
}
|
||||
|
||||
_openButton0 = _root.Q<UTKButton>("openButton0");
|
||||
if (_openButton0 != null) {
|
||||
_openButton0.OnClicked += async () =>
|
||||
{
|
||||
var modal = UTKModal.Create("Settings", UTKModal.ModalSize.Large);
|
||||
var content = new UTKSettingModalContent(0); // 초기 탭 인덱스 설정
|
||||
modal.Add(content);
|
||||
await modal.ShowAsync();
|
||||
};
|
||||
}
|
||||
|
||||
_openButton1 = _root.Q<UTKButton>("openButton1");
|
||||
if (_openButton1 != null) {
|
||||
_openButton1.OnClicked += async () =>
|
||||
{
|
||||
var modal = UTKModal.Create("Settings", UTKModal.ModalSize.Large);
|
||||
var content = new UTKSettingModalContent(1); // 초기 탭 인덱스 설정
|
||||
modal.Add(content);
|
||||
await modal.ShowAsync();
|
||||
};
|
||||
}
|
||||
|
||||
_openButton2 = _root.Q<UTKButton>("openButton2");
|
||||
if (_openButton2 != null) {
|
||||
_openButton2.OnClicked += async () =>
|
||||
{
|
||||
var modal = UTKModal.Create("Settings", UTKModal.ModalSize.Large);
|
||||
var content = new UTKSettingModalContent(2); // 초기 탭 인덱스 설정
|
||||
modal.Add(content);
|
||||
await modal.ShowAsync();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
|
||||
<VisualElement style="width: 100%; height: 100%; flex-direction: column;">
|
||||
<VisualElement name="toolbar-area" style="flex-grow: 1; flex-direction: column; padding: 8px;" />
|
||||
<utk:UTKToggle name="toggle" label="테마 변경" style="position: absolute; top: 8px; right: 10px; z-index: 10;" />
|
||||
<utk:UTKButton name="openButton0" text="Open0" style="position: absolute; top: 8px; left: 10px; z-index: 10;" />
|
||||
<utk:UTKButton name="openButton1" text="Open1" style="position: absolute; top: 38px; left: 10px; z-index: 10;" />
|
||||
<utk:UTKButton name="openButton2" text="Open2" style="position: absolute; top: 68px; left: 10px; z-index: 10;" />
|
||||
</VisualElement>
|
||||
</UXML>
|
||||
|
||||
497
Assets/Sample/UIToolkit/UTKShortcutList.unity
Normal file
497
Assets/Sample/UIToolkit/UTKShortcutList.unity
Normal file
@@ -0,0 +1,497 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!29 &1
|
||||
OcclusionCullingSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_OcclusionBakeSettings:
|
||||
smallestOccluder: 5
|
||||
smallestHole: 0.25
|
||||
backfaceThreshold: 100
|
||||
m_SceneGUID: 00000000000000000000000000000000
|
||||
m_OcclusionCullingData: {fileID: 0}
|
||||
--- !u!104 &2
|
||||
RenderSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 10
|
||||
m_Fog: 0
|
||||
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
|
||||
m_FogMode: 3
|
||||
m_FogDensity: 0.01
|
||||
m_LinearFogStart: 0
|
||||
m_LinearFogEnd: 300
|
||||
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
|
||||
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
|
||||
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
|
||||
m_AmbientIntensity: 1
|
||||
m_AmbientMode: 0
|
||||
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
|
||||
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_HaloStrength: 0.5
|
||||
m_FlareStrength: 1
|
||||
m_FlareFadeSpeed: 3
|
||||
m_HaloTexture: {fileID: 0}
|
||||
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_DefaultReflectionMode: 0
|
||||
m_DefaultReflectionResolution: 128
|
||||
m_ReflectionBounces: 1
|
||||
m_ReflectionIntensity: 1
|
||||
m_CustomReflection: {fileID: 0}
|
||||
m_Sun: {fileID: 0}
|
||||
m_UseRadianceAmbientProbe: 0
|
||||
--- !u!157 &3
|
||||
LightmapSettings:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 13
|
||||
m_BakeOnSceneLoad: 0
|
||||
m_GISettings:
|
||||
serializedVersion: 2
|
||||
m_BounceScale: 1
|
||||
m_IndirectOutputScale: 1
|
||||
m_AlbedoBoost: 1
|
||||
m_EnvironmentLightingMode: 0
|
||||
m_EnableBakedLightmaps: 1
|
||||
m_EnableRealtimeLightmaps: 0
|
||||
m_LightmapEditorSettings:
|
||||
serializedVersion: 12
|
||||
m_Resolution: 2
|
||||
m_BakeResolution: 40
|
||||
m_AtlasSize: 1024
|
||||
m_AO: 0
|
||||
m_AOMaxDistance: 1
|
||||
m_CompAOExponent: 1
|
||||
m_CompAOExponentDirect: 0
|
||||
m_ExtractAmbientOcclusion: 0
|
||||
m_Padding: 2
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_LightmapsBakeMode: 1
|
||||
m_TextureCompression: 1
|
||||
m_ReflectionCompression: 2
|
||||
m_MixedBakeMode: 2
|
||||
m_BakeBackend: 1
|
||||
m_PVRSampling: 1
|
||||
m_PVRDirectSampleCount: 32
|
||||
m_PVRSampleCount: 512
|
||||
m_PVRBounces: 2
|
||||
m_PVREnvironmentSampleCount: 256
|
||||
m_PVREnvironmentReferencePointCount: 2048
|
||||
m_PVRFilteringMode: 1
|
||||
m_PVRDenoiserTypeDirect: 1
|
||||
m_PVRDenoiserTypeIndirect: 1
|
||||
m_PVRDenoiserTypeAO: 1
|
||||
m_PVRFilterTypeDirect: 0
|
||||
m_PVRFilterTypeIndirect: 0
|
||||
m_PVRFilterTypeAO: 0
|
||||
m_PVREnvironmentMIS: 1
|
||||
m_PVRCulling: 1
|
||||
m_PVRFilteringGaussRadiusDirect: 1
|
||||
m_PVRFilteringGaussRadiusIndirect: 1
|
||||
m_PVRFilteringGaussRadiusAO: 1
|
||||
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
|
||||
m_PVRFilteringAtrousPositionSigmaIndirect: 2
|
||||
m_PVRFilteringAtrousPositionSigmaAO: 1
|
||||
m_ExportTrainingData: 0
|
||||
m_TrainingDataDestination: TrainingData
|
||||
m_LightProbeSampleCountMultiplier: 4
|
||||
m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_LightingSettings: {fileID: 0}
|
||||
--- !u!196 &4
|
||||
NavMeshSettings:
|
||||
serializedVersion: 2
|
||||
m_ObjectHideFlags: 0
|
||||
m_BuildSettings:
|
||||
serializedVersion: 3
|
||||
agentTypeID: 0
|
||||
agentRadius: 0.5
|
||||
agentHeight: 2
|
||||
agentSlope: 45
|
||||
agentClimb: 0.4
|
||||
ledgeDropHeight: 0
|
||||
maxJumpAcrossDistance: 0
|
||||
minRegionArea: 2
|
||||
manualCellSize: 0
|
||||
cellSize: 0.16666667
|
||||
manualTileSize: 0
|
||||
tileSize: 256
|
||||
buildHeightMesh: 0
|
||||
maxJobWorkers: 0
|
||||
preserveTilesOutsideBounds: 0
|
||||
debug:
|
||||
m_Flags: 0
|
||||
m_NavMeshData: {fileID: 0}
|
||||
--- !u!1 &1097328750
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1097328752}
|
||||
- component: {fileID: 1097328754}
|
||||
- component: {fileID: 1097328755}
|
||||
m_Layer: 0
|
||||
m_Name: Sample
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &1097328752
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1097328750}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!114 &1097328754
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1097328750}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument
|
||||
m_PanelSettings: {fileID: 11400000, guid: 5ad7007b08a97b54d927c352279a18b6, type: 2}
|
||||
m_ParentUI: {fileID: 0}
|
||||
sourceAsset: {fileID: 9197481963319205126, guid: 274352955998a6e478eb57d04c49969b, type: 3}
|
||||
m_SortingOrder: 0
|
||||
m_Position: 0
|
||||
m_WorldSpaceSizeMode: 1
|
||||
m_WorldSpaceWidth: 1920
|
||||
m_WorldSpaceHeight: 1080
|
||||
m_PivotReferenceSize: 0
|
||||
m_Pivot: 0
|
||||
m_WorldSpaceCollider: {fileID: 0}
|
||||
--- !u!114 &1097328755
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1097328750}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 85433d2caa763104f86b4b44ded2fc2d, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Assembly-CSharp::UTKShortcutListSample
|
||||
uiDocument: {fileID: 1097328754}
|
||||
initialTheme: 0
|
||||
--- !u!1 &1331954412
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1331954415}
|
||||
- component: {fileID: 1331954414}
|
||||
- component: {fileID: 1331954413}
|
||||
m_Layer: 0
|
||||
m_Name: EventSystem
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!114 &1331954413
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1331954412}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 01614664b831546d2ae94a42149d80ac, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_SendPointerHoverToParent: 1
|
||||
m_MoveRepeatDelay: 0.5
|
||||
m_MoveRepeatRate: 0.1
|
||||
m_XRTrackingOrigin: {fileID: 0}
|
||||
m_ActionsAsset: {fileID: -944628639613478452, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_PointAction: {fileID: -1654692200621890270, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_MoveAction: {fileID: -8784545083839296357, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_SubmitAction: {fileID: 392368643174621059, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_CancelAction: {fileID: 7727032971491509709, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_LeftClickAction: {fileID: 3001919216989983466, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_MiddleClickAction: {fileID: -2185481485913320682, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_RightClickAction: {fileID: -4090225696740746782, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_ScrollWheelAction: {fileID: 6240969308177333660, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_TrackedDevicePositionAction: {fileID: 6564999863303420839, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_TrackedDeviceOrientationAction: {fileID: 7970375526676320489, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3}
|
||||
m_DeselectOnBackgroundClick: 0
|
||||
m_PointerBehavior: 0
|
||||
m_CursorLockBehavior: 0
|
||||
m_ScrollDeltaPerTick: 6
|
||||
--- !u!114 &1331954414
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1331954412}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_FirstSelected: {fileID: 0}
|
||||
m_sendNavigationEvents: 1
|
||||
m_DragThreshold: 10
|
||||
--- !u!4 &1331954415
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1331954412}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1 &1414861612
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 1414861614}
|
||||
- component: {fileID: 1414861613}
|
||||
- component: {fileID: 1414861615}
|
||||
m_Layer: 0
|
||||
m_Name: Directional Light
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!108 &1414861613
|
||||
Light:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1414861612}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 11
|
||||
m_Type: 1
|
||||
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
|
||||
m_Intensity: 1
|
||||
m_Range: 10
|
||||
m_SpotAngle: 30
|
||||
m_InnerSpotAngle: 21.80208
|
||||
m_CookieSize: 10
|
||||
m_Shadows:
|
||||
m_Type: 2
|
||||
m_Resolution: -1
|
||||
m_CustomResolution: -1
|
||||
m_Strength: 1
|
||||
m_Bias: 0.05
|
||||
m_NormalBias: 0.4
|
||||
m_NearPlane: 0.2
|
||||
m_CullingMatrixOverride:
|
||||
e00: 1
|
||||
e01: 0
|
||||
e02: 0
|
||||
e03: 0
|
||||
e10: 0
|
||||
e11: 1
|
||||
e12: 0
|
||||
e13: 0
|
||||
e20: 0
|
||||
e21: 0
|
||||
e22: 1
|
||||
e23: 0
|
||||
e30: 0
|
||||
e31: 0
|
||||
e32: 0
|
||||
e33: 1
|
||||
m_UseCullingMatrixOverride: 0
|
||||
m_Cookie: {fileID: 0}
|
||||
m_DrawHalo: 0
|
||||
m_Flare: {fileID: 0}
|
||||
m_RenderMode: 0
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingLayerMask: 1
|
||||
m_Lightmapping: 4
|
||||
m_LightShadowCasterMode: 0
|
||||
m_AreaSize: {x: 1, y: 1}
|
||||
m_BounceIntensity: 1
|
||||
m_ColorTemperature: 6570
|
||||
m_UseColorTemperature: 0
|
||||
m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_UseBoundingSphereOverride: 0
|
||||
m_UseViewFrustumForShadowCasterCull: 1
|
||||
m_ForceVisible: 0
|
||||
m_ShadowRadius: 0
|
||||
m_ShadowAngle: 0
|
||||
m_LightUnit: 1
|
||||
m_LuxAtDistance: 1
|
||||
m_EnableSpotReflector: 1
|
||||
--- !u!4 &1414861614
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1414861612}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
|
||||
m_LocalPosition: {x: 0, y: 3, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
|
||||
--- !u!114 &1414861615
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1414861612}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_UsePipelineSettings: 1
|
||||
m_AdditionalLightsShadowResolutionTier: 2
|
||||
m_CustomShadowLayers: 0
|
||||
m_LightCookieSize: {x: 1, y: 1}
|
||||
m_LightCookieOffset: {x: 0, y: 0}
|
||||
m_SoftShadowQuality: 0
|
||||
m_RenderingLayersMask:
|
||||
serializedVersion: 0
|
||||
m_Bits: 1
|
||||
m_ShadowRenderingLayersMask:
|
||||
serializedVersion: 0
|
||||
m_Bits: 1
|
||||
m_Version: 4
|
||||
m_LightLayerMask: 1
|
||||
m_ShadowLayerMask: 1
|
||||
m_RenderingLayers: 1
|
||||
m_ShadowRenderingLayers: 1
|
||||
--- !u!1 &2136621999
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2136622002}
|
||||
- component: {fileID: 2136622001}
|
||||
- component: {fileID: 2136622000}
|
||||
m_Layer: 0
|
||||
m_Name: Main Camera
|
||||
m_TagString: MainCamera
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!81 &2136622000
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2136621999}
|
||||
m_Enabled: 1
|
||||
--- !u!20 &2136622001
|
||||
Camera:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2136621999}
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_ClearFlags: 1
|
||||
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
|
||||
m_projectionMatrixMode: 1
|
||||
m_GateFitMode: 2
|
||||
m_FOVAxisMode: 0
|
||||
m_Iso: 200
|
||||
m_ShutterSpeed: 0.005
|
||||
m_Aperture: 16
|
||||
m_FocusDistance: 10
|
||||
m_FocalLength: 50
|
||||
m_BladeCount: 5
|
||||
m_Curvature: {x: 2, y: 11}
|
||||
m_BarrelClipping: 0.25
|
||||
m_Anamorphism: 0
|
||||
m_SensorSize: {x: 36, y: 24}
|
||||
m_LensShift: {x: 0, y: 0}
|
||||
m_NormalizedViewPortRect:
|
||||
serializedVersion: 2
|
||||
x: 0
|
||||
y: 0
|
||||
width: 1
|
||||
height: 1
|
||||
near clip plane: 0.3
|
||||
far clip plane: 1000
|
||||
field of view: 60
|
||||
orthographic: 0
|
||||
orthographic size: 5
|
||||
m_Depth: -1
|
||||
m_CullingMask:
|
||||
serializedVersion: 2
|
||||
m_Bits: 4294967295
|
||||
m_RenderingPath: -1
|
||||
m_TargetTexture: {fileID: 0}
|
||||
m_TargetDisplay: 0
|
||||
m_TargetEye: 3
|
||||
m_HDR: 1
|
||||
m_AllowMSAA: 1
|
||||
m_AllowDynamicResolution: 0
|
||||
m_ForceIntoRT: 0
|
||||
m_OcclusionCulling: 1
|
||||
m_StereoConvergence: 10
|
||||
m_StereoSeparation: 0.022
|
||||
--- !u!4 &2136622002
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2136621999}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 1, z: -10}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1660057539 &9223372036854775807
|
||||
SceneRoots:
|
||||
m_ObjectHideFlags: 0
|
||||
m_Roots:
|
||||
- {fileID: 2136622002}
|
||||
- {fileID: 1414861614}
|
||||
- {fileID: 1331954415}
|
||||
- {fileID: 1097328752}
|
||||
7
Assets/Sample/UIToolkit/UTKShortcutList.unity.meta
Normal file
7
Assets/Sample/UIToolkit/UTKShortcutList.unity.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f46f8f4e748fbd34ab6be590a5584d89
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
213
Assets/Sample/UIToolkit/UTKShortcutListSample.cs
Normal file
213
Assets/Sample/UIToolkit/UTKShortcutListSample.cs
Normal file
@@ -0,0 +1,213 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
/// <summary>
|
||||
/// UTKShortcutList의 기능을 테스트하기 위한 샘플 MonoBehaviour입니다.
|
||||
/// <list type="bullet">
|
||||
/// <item>Studio 단축키 목록 SetData</item>
|
||||
/// <item>OnDataChanged 이벤트 핸들링</item>
|
||||
/// <item>GetData / 리셋 / 항목 추가 버튼</item>
|
||||
/// <item>라이트/다크 테마 토글</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class UTKShortcutListSample : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
public UIDocument uiDocument;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("시작 시 적용할 테마")]
|
||||
private UTKTheme initialTheme = UTKTheme.Dark;
|
||||
|
||||
private UTKToggle? _themeToggle;
|
||||
private UTKShortcutList? _shortcutList;
|
||||
|
||||
// ── 샘플 데이터 원본 (리셋용) ──────────────────────────────────────────
|
||||
private static readonly List<UTKShortcutItemData> DefaultShortcuts = new()
|
||||
{
|
||||
new() { Id = "file.new_project", CommandName = "File > New Project", UseCtrl = true, UseShift = false, UseAlt = false, Key = "N" },
|
||||
new() { Id = "file.open_project", CommandName = "File > Open Project", UseCtrl = true, UseShift = true, UseAlt = false, Key = "O" },
|
||||
new() { Id = "file.save_project", CommandName = "File > Save Project", UseCtrl = true, UseShift = false, UseAlt = true, Key = "S" },
|
||||
new() { Id = "file.save_as", CommandName = "File > Save As...", UseCtrl = true, UseShift = true, UseAlt = true, Key = "S" },
|
||||
new() { Id = "file.insert_database", CommandName = "File > Insert Database", UseCtrl = true, UseShift = true, UseAlt = false, Key = "I" },
|
||||
new() { Id = "file.export_layout", CommandName = "File > Export > Layout", UseCtrl = true, UseShift = true, UseAlt = false, Key = "L" },
|
||||
new() { Id = "file.export_metadata", CommandName = "File > Export > Metadata",UseCtrl = true, UseShift = true, UseAlt = false, Key = "M" },
|
||||
new() { Id = "file.export_gltf", CommandName = "File > Export > glTF", UseCtrl = true, UseShift = true, UseAlt = false, Key = "G" },
|
||||
new() { Id = "edit.undo", CommandName = "Edit > Undo", UseCtrl = true, UseShift = true, UseAlt = false, Key = "Z" },
|
||||
new() { Id = "edit.redo", CommandName = "Edit > Redo", UseCtrl = true, UseShift = true, UseAlt = false, Key = "Y" },
|
||||
new() { Id = "edit.duplicate", CommandName = "Edit > Duplicate", UseCtrl = true, UseShift = true, UseAlt = false, Key = "D" },
|
||||
new() { Id = "edit.delete", CommandName = "Edit > Delete", UseCtrl = false, UseShift = true, UseAlt = false, Key = "Delete" },
|
||||
new() { Id = "create.plane", CommandName = "Create > Plane", UseCtrl = true, UseShift = true, UseAlt = false, Key = "V" },
|
||||
new() { Id = "tool.select", CommandName = "Select Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "1" },
|
||||
new() { Id = "tool.move", CommandName = "Move Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "2" },
|
||||
new() { Id = "tool.rotate", CommandName = "Rotate Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "3" },
|
||||
new() { Id = "tool.scale", CommandName = "Scale Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "4" },
|
||||
new() { Id = "view.focus", CommandName = "View > Focus Selected", UseCtrl = false, UseShift = false, UseAlt = false, Key = "F" },
|
||||
new() { Id = "view.toggle_grid", CommandName = "View > Toggle Grid", UseCtrl = false, UseShift = false, UseAlt = false, Key = "G" },
|
||||
new() { Id = "view.toggle_wireframe", CommandName = "View > Toggle Wireframe", UseCtrl = false, UseShift = false, UseAlt = false, Key = "W" },
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// UIDocument 참조
|
||||
var doc = GetComponent<UIDocument>();
|
||||
if (doc == null)
|
||||
{
|
||||
Debug.LogError("[UTKShortcutListSample] UIDocument 컴포넌트가 없습니다.");
|
||||
return;
|
||||
}
|
||||
uiDocument = doc;
|
||||
|
||||
var root = uiDocument.rootVisualElement;
|
||||
|
||||
// 테마 토글
|
||||
_themeToggle = root.Q<UTKToggle>("toggle");
|
||||
if (_themeToggle == null)
|
||||
Debug.LogWarning("[UTKShortcutListSample] UXML에서 UTKToggle(name='toggle')을 찾을 수 없습니다.");
|
||||
|
||||
// ShortcutList
|
||||
_shortcutList = root.Q<UTKShortcutList>("shortcut-list");
|
||||
if (_shortcutList == null)
|
||||
{
|
||||
// 이름을 못 찾으면 타입으로 검색
|
||||
_shortcutList = root.Q<UTKShortcutList>();
|
||||
}
|
||||
if (_shortcutList == null)
|
||||
{
|
||||
Debug.LogError("[UTKShortcutListSample] UXML에서 UTKShortcutList를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 테마 초기화
|
||||
UTKThemeManager.Instance.RegisterRoot(root);
|
||||
UTKThemeManager.Instance.SetTheme(initialTheme);
|
||||
|
||||
_themeToggle!.OnValueChanged += (isOn) =>
|
||||
{
|
||||
UTKThemeManager.Instance.SetTheme(isOn ? UTKTheme.Light : UTKTheme.Dark);
|
||||
};
|
||||
|
||||
// 이벤트 핸들러
|
||||
_shortcutList.OnDataChanged += OnShortcutDataChanged;
|
||||
|
||||
// 기본 데이터 로드 (DeepCopy 로 원본 보존)
|
||||
LoadDefaultData();
|
||||
|
||||
// 테스트 버튼 생성
|
||||
CreateButtons(root);
|
||||
}
|
||||
|
||||
// ── 이벤트 핸들러 ─────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>단축키 데이터가 변경될 때 호출됩니다.</summary>
|
||||
private void OnShortcutDataChanged(UTKShortcutItemData item)
|
||||
{
|
||||
Debug.Log($"[UTKShortcutListSample] 변경 → id={item.Id} " +
|
||||
$"Ctrl={item.UseCtrl} Shift={item.UseShift} Alt={item.UseAlt} Key={item.Key}");
|
||||
}
|
||||
|
||||
// ── 데이터 관리 ───────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>DefaultShortcuts 의 복사본을 리스트에 로드합니다.</summary>
|
||||
private void LoadDefaultData()
|
||||
{
|
||||
var copy = new List<UTKShortcutItemData>(DefaultShortcuts.Count);
|
||||
foreach (var src in DefaultShortcuts)
|
||||
{
|
||||
copy.Add(new UTKShortcutItemData
|
||||
{
|
||||
Id = src.Id,
|
||||
CommandName = src.CommandName,
|
||||
UseCtrl = src.UseCtrl,
|
||||
UseShift = src.UseShift,
|
||||
UseAlt = src.UseAlt,
|
||||
Key = src.Key,
|
||||
});
|
||||
}
|
||||
_shortcutList?.SetData(copy);
|
||||
Debug.Log($"[UTKShortcutListSample] {copy.Count}개 단축키 로드 완료");
|
||||
}
|
||||
|
||||
// ── 테스트 버튼 ───────────────────────────────────────────────────────────
|
||||
|
||||
private void CreateButtons(VisualElement root)
|
||||
{
|
||||
var bar = new VisualElement();
|
||||
bar.style.flexDirection = FlexDirection.Row;
|
||||
bar.style.justifyContent = Justify.Center;
|
||||
bar.style.paddingTop = 8;
|
||||
bar.style.paddingBottom = 8;
|
||||
bar.style.flexWrap = Wrap.Wrap;
|
||||
|
||||
// GetData 버튼: 현재 데이터를 콘솔에 출력
|
||||
var getDataBtn = new UTKButton("GetData", variant: UTKButton.ButtonVariant.Primary);
|
||||
getDataBtn.OnClicked += OnGetDataClicked;
|
||||
getDataBtn.style.marginRight = 4;
|
||||
bar.Add(getDataBtn);
|
||||
|
||||
// 리셋 버튼: 기본값으로 복원
|
||||
var resetBtn = new UTKButton("리셋", variant: UTKButton.ButtonVariant.Normal);
|
||||
resetBtn.OnClicked += LoadDefaultData;
|
||||
resetBtn.style.marginRight = 4;
|
||||
bar.Add(resetBtn);
|
||||
|
||||
// 항목 추가 버튼: 빈 항목 하나 추가
|
||||
var addBtn = new UTKButton("항목 추가", variant: UTKButton.ButtonVariant.OutlinePrimary);
|
||||
addBtn.OnClicked += OnAddItemClicked;
|
||||
bar.Add(addBtn);
|
||||
|
||||
root.Add(bar);
|
||||
}
|
||||
|
||||
/// <summary>현재 최종 데이터를 콘솔에 출력합니다.</summary>
|
||||
private void OnGetDataClicked()
|
||||
{
|
||||
if (_shortcutList == null) return;
|
||||
|
||||
var data = _shortcutList.GetData();
|
||||
Debug.Log($"[UTKShortcutListSample] ── GetData ({data.Count}개) ──");
|
||||
foreach (var item in data)
|
||||
{
|
||||
var mods = $"{(item.UseCtrl ? "Ctrl+" : "")}" +
|
||||
$"{(item.UseShift ? "Shift+" : "")}" +
|
||||
$"{(item.UseAlt ? "Alt+" : "")}";
|
||||
Debug.Log($" [{item.Id}] {item.CommandName} → {mods}{item.Key}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>빈 항목을 목록 끝에 추가하고 리스트를 갱신합니다.</summary>
|
||||
private void OnAddItemClicked()
|
||||
{
|
||||
if (_shortcutList == null) return;
|
||||
|
||||
var current = _shortcutList.GetData();
|
||||
var newItem = new UTKShortcutItemData
|
||||
{
|
||||
Id = $"custom.{current.Count + 1}",
|
||||
CommandName = $"Custom Command {current.Count + 1}",
|
||||
UseCtrl = false,
|
||||
UseShift = false,
|
||||
UseAlt = false,
|
||||
Key = "",
|
||||
};
|
||||
current.Add(newItem);
|
||||
_shortcutList.SetData(current);
|
||||
Debug.Log($"[UTKShortcutListSample] 항목 추가: {newItem.CommandName}");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_shortcutList != null)
|
||||
{
|
||||
_shortcutList.OnDataChanged -= OnShortcutDataChanged;
|
||||
_shortcutList.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Sample/UIToolkit/UTKShortcutListSample.cs.meta
Normal file
2
Assets/Sample/UIToolkit/UTKShortcutListSample.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85433d2caa763104f86b4b44ded2fc2d
|
||||
6
Assets/Sample/UIToolkit/UTKShortcutListUXML.uxml
Normal file
6
Assets/Sample/UIToolkit/UTKShortcutListUXML.uxml
Normal file
@@ -0,0 +1,6 @@
|
||||
<UXML xmlns="UnityEngine.UIElements" xmlns:utk="UVC.UIToolkit" editor-extension-mode="False">
|
||||
<VisualElement style="width: 100%; height: 100%;">
|
||||
<utk:UTKShortcutList name="window" style="width: 500px;" />
|
||||
<utk:UTKToggle name="toggle" label="테마 변경" style="position: absolute; top: 10px; right: 10px;" />
|
||||
</VisualElement>
|
||||
</UXML>
|
||||
10
Assets/Sample/UIToolkit/UTKShortcutListUXML.uxml.meta
Normal file
10
Assets/Sample/UIToolkit/UTKShortcutListUXML.uxml.meta
Normal file
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 274352955998a6e478eb57d04c49969b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
@@ -102,7 +102,11 @@ input.ErrorMessage = """"; // 오류 제거
|
||||
// 비활성화 / 읽기 전용
|
||||
input.IsEnabled = false;
|
||||
input.isReadOnly = true;
|
||||
input.multiline = true;",
|
||||
input.multiline = true;
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
// label이 있을 때 .unity-label의 min-width를 설정
|
||||
input.LabelMinWidth = 120f; // 120px (-1이면 미설정)",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -124,6 +128,9 @@ input.multiline = true;",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKInputField label=""읽기전용"" is-enabled=""false"" value=""수정 불가"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKInputField label=""이름"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -200,7 +207,10 @@ ageField.ClearError();
|
||||
|
||||
// 5) 에러 메시지 직접 설정 (Validation 없이, 서버 오류 등)
|
||||
intField.ErrorMessage = ""서버 오류가 발생했습니다."";
|
||||
intField.ErrorMessage = """"; // 오류 제거",
|
||||
intField.ErrorMessage = """"; // 오류 제거
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
intField.LabelMinWidth = 120f; // label의 min-width 설정",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -213,6 +223,9 @@ intField.ErrorMessage = """"; // 오류 제거",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKIntegerField label=""읽기전용"" is-enabled=""false"" value=""50"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKIntegerField label=""수량"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -276,7 +289,10 @@ sizeField.Validation = () => sizeField.Value >= 0 && sizeField.Value <= 1000000;
|
||||
bool isValid = sizeField.Validate();
|
||||
|
||||
// 3) 에러 수동 해제
|
||||
sizeField.ClearError();",
|
||||
sizeField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
longField.LabelMinWidth = 120f; // label의 min-width 설정",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -289,6 +305,9 @@ sizeField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKLongField label=""읽기전용"" is-enabled=""false"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKLongField label=""ID"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -353,7 +372,10 @@ speedField.Validation = () => speedField.Value >= 0f && speedField.Value <= 100f
|
||||
bool isValid = speedField.Validate();
|
||||
|
||||
// 3) 에러 수동 해제
|
||||
speedField.ClearError();",
|
||||
speedField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
floatField.LabelMinWidth = 120f; // label의 min-width 설정",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -366,6 +388,9 @@ speedField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKFloatField label=""읽기전용"" is-enabled=""false"" value=""3.14"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKFloatField label=""속도"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -430,7 +455,10 @@ percentField.Validation = () => percentField.Value >= 0.0 && percentField.Value
|
||||
bool isValid = percentField.Validate();
|
||||
|
||||
// 3) 에러 수동 해제
|
||||
percentField.ClearError();",
|
||||
percentField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
doubleField.LabelMinWidth = 120f; // label의 min-width 설정",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -443,6 +471,9 @@ percentField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKDoubleField label=""읽기전용"" is-enabled=""false"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKDoubleField label=""정밀도"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -695,7 +726,11 @@ dirField.ErrorMessage = ""방향 벡터는 (0,0)이 될 수 없습니다."";
|
||||
dirField.Validation = () => dirField.Value != Vector2.zero;
|
||||
|
||||
bool isValid = dirField.Validate();
|
||||
dirField.ClearError();",
|
||||
dirField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
// label이 있을 때 .unity-label의 min-width를 설정
|
||||
positionField.LabelMinWidth = 120f; // 120px (-1이면 미설정)",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -708,6 +743,9 @@ dirField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKVector2Field label=""ReadOnly"" is-enabled=""false"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKVector2Field label=""Position"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -767,7 +805,11 @@ scaleField.ErrorMessage = ""스케일 벡터는 (0,0,0)이 될 수 없습니다.
|
||||
scaleField.Validation = () => scaleField.Value != Vector3.zero;
|
||||
|
||||
bool isValid = scaleField.Validate();
|
||||
scaleField.ClearError();",
|
||||
scaleField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
// label이 있을 때 .unity-label의 min-width를 설정
|
||||
positionField.LabelMinWidth = 120f; // 120px (-1이면 미설정)",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -780,6 +822,9 @@ scaleField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKVector3Field label=""Disabled"" is-enabled=""false"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKVector3Field label=""Position"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -853,7 +898,11 @@ rgbaField.Validation = () =>
|
||||
};
|
||||
|
||||
bool isValid = rgbaField.Validate();
|
||||
rgbaField.ClearError();",
|
||||
rgbaField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
// label이 있을 때 .unity-label의 min-width를 설정
|
||||
colorField.LabelMinWidth = 120f; // 120px (-1이면 미설정)",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -866,6 +915,9 @@ rgbaField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKVector4Field label=""Disabled"" is-enabled=""false"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKVector4Field label=""Color"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -927,7 +979,11 @@ viewportField.Validation = () =>
|
||||
viewportField.Value.width > 0 && viewportField.Value.height > 0;
|
||||
|
||||
bool isValid = viewportField.Validate();
|
||||
viewportField.ClearError();",
|
||||
viewportField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
// label이 있을 때 .unity-label의 min-width를 설정
|
||||
areaField.LabelMinWidth = 120f; // 120px (-1이면 미설정)",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -940,6 +996,9 @@ viewportField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKRectField label=""Disabled"" is-enabled=""false"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKRectField label=""Area"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
@@ -1009,7 +1068,11 @@ colliderField.Validation = () =>
|
||||
};
|
||||
|
||||
bool isValid = colliderField.Validate();
|
||||
colliderField.ClearError();",
|
||||
colliderField.ClearError();
|
||||
|
||||
// ── Label Min-Width ──────────────────────────
|
||||
// label이 있을 때 .unity-label의 min-width를 설정
|
||||
boundsField.LabelMinWidth = 120f; // 120px (-1이면 미설정)",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -1022,6 +1085,9 @@ colliderField.ClearError();",
|
||||
<!-- 비활성화 -->
|
||||
<utk:UTKBoundsField label=""Disabled"" is-enabled=""false"" />
|
||||
|
||||
<!-- label min-width 설정 -->
|
||||
<utk:UTKBoundsField label=""Bounds"" label-min-width=""120"" />
|
||||
|
||||
</ui:UXML>");
|
||||
}
|
||||
|
||||
|
||||
@@ -456,6 +456,72 @@ treeView.OnItemSelected += (index) => Debug.Log($""선택된 항목: {index}"");
|
||||
<!-- C#에서 컴럼과 데이터 설정 필요 -->");
|
||||
}
|
||||
|
||||
private void InitializeShortcutListSample(VisualElement root)
|
||||
{
|
||||
var shortcutList = root.Q<UTKShortcutList>("shortcut-list-sample");
|
||||
if (shortcutList != null)
|
||||
{
|
||||
shortcutList.SetData(new System.Collections.Generic.List<UTKShortcutItemData>
|
||||
{
|
||||
new() { Id = "file.new_project", CommandName = "File > New Project", UseCtrl = true, UseShift = false, UseAlt = false, Key = "N" },
|
||||
new() { Id = "file.open_project", CommandName = "File > Open Project", UseCtrl = true, UseShift = true, UseAlt = false, Key = "O" },
|
||||
new() { Id = "file.save_project", CommandName = "File > Save Project", UseCtrl = true, UseShift = false, UseAlt = true, Key = "S" },
|
||||
new() { Id = "file.save_as", CommandName = "File > Save As...", UseCtrl = true, UseShift = true, UseAlt = true, Key = "S" },
|
||||
new() { Id = "file.export_layout", CommandName = "File > Export > Layout", UseCtrl = true, UseShift = true, UseAlt = false, Key = "L" },
|
||||
new() { Id = "file.export_gltf", CommandName = "File > Export > glTF", UseCtrl = true, UseShift = true, UseAlt = false, Key = "G" },
|
||||
new() { Id = "edit.undo", CommandName = "Edit > Undo", UseCtrl = true, UseShift = true, UseAlt = false, Key = "Z" },
|
||||
new() { Id = "edit.redo", CommandName = "Edit > Redo", UseCtrl = true, UseShift = true, UseAlt = false, Key = "Y" },
|
||||
new() { Id = "edit.duplicate", CommandName = "Edit > Duplicate", UseCtrl = true, UseShift = true, UseAlt = false, Key = "D" },
|
||||
new() { Id = "edit.delete", CommandName = "Edit > Delete", UseCtrl = false, UseShift = true, UseAlt = false, Key = "Delete" },
|
||||
new() { Id = "create.plane", CommandName = "Create > Plane", UseCtrl = true, UseShift = true, UseAlt = false, Key = "V" },
|
||||
new() { Id = "tool.select", CommandName = "Select Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "1" },
|
||||
});
|
||||
|
||||
shortcutList.OnDataChanged += item =>
|
||||
Debug.Log($"[StyleGuide] 단축키 변경: {item.CommandName} → {(item.UseCtrl ? "Ctrl+" : "")}{(item.UseShift ? "Shift+" : "")}{(item.UseAlt ? "Alt+" : "")}{item.Key}");
|
||||
}
|
||||
|
||||
SetCodeSamples(root,
|
||||
csharpCode:
|
||||
@"// 데이터 생성
|
||||
var data = new List<UTKShortcutItemData>
|
||||
{
|
||||
new() { Id = ""file.new"", CommandName = ""File > New Project"", UseCtrl = true, UseShift = false, UseAlt = false, Key = ""N"" },
|
||||
new() { Id = ""edit.undo"", CommandName = ""Edit > Undo"", UseCtrl = true, UseShift = true, UseAlt = false, Key = ""Z"" },
|
||||
new() { Id = ""edit.delete"", CommandName = ""Edit > Delete"", UseCtrl = false, UseShift = true, UseAlt = false, Key = ""Delete"" },
|
||||
};
|
||||
|
||||
// 리스트 생성 & 데이터 설정
|
||||
var list = new UTKShortcutList();
|
||||
list.SetData(data);
|
||||
|
||||
// 변경 이벤트 (Ctrl/Shift/Alt 체크 또는 Key 캡처 시 발생)
|
||||
list.OnDataChanged += item =>
|
||||
{
|
||||
Debug.Log($""{item.CommandName}: Ctrl={item.UseCtrl} Key={item.Key}"");
|
||||
};
|
||||
|
||||
// Key 캡처 방법:
|
||||
// 1. Key 필드 클릭 → ""···"" 표시
|
||||
// 2. 원하는 키 입력 → 자동 저장
|
||||
// 3. Escape → 취소 (이전 값 복원)
|
||||
|
||||
// 현재 데이터 가져오기
|
||||
var current = list.GetData();
|
||||
|
||||
// 업데이트된 데이터 다시 적용
|
||||
current[0].Key = ""F1"";
|
||||
list.SetData(current);",
|
||||
uxmlCode:
|
||||
@"<!-- 기본 사용 -->
|
||||
<utk:UTKShortcutList name=""shortcut-list"" />
|
||||
|
||||
<!-- 고정 크기 -->
|
||||
<utk:UTKShortcutList style=""height: 300px;"" />
|
||||
|
||||
<!-- 데이터와 이벤트는 C#에서 설정 -->");
|
||||
}
|
||||
|
||||
private void InitializeScrollViewSample(VisualElement root)
|
||||
{
|
||||
// Horizontal scroll
|
||||
|
||||
@@ -430,7 +430,7 @@ for (int i = 0; i < buttons.Length; i++)
|
||||
var btnFooter = root.Q<UTKButton>("btn-modal-footer");
|
||||
btnFooter?.RegisterCallback<ClickEvent>(_ =>
|
||||
{
|
||||
var modal = UTKModal.Show("푸터 버튼 모달", UTKModal.ModalSize.Medium);
|
||||
var modal = UTKModal.Create("푸터 버튼 모달", UTKModal.ModalSize.Medium);
|
||||
modal.Add(new Label("확인 또는 취소를 선택하세요."));
|
||||
|
||||
var confirmBtn = new UTKButton("확인") { Variant = UTKButton.ButtonVariant.Primary };
|
||||
@@ -449,29 +449,33 @@ for (int i = 0; i < buttons.Length; i++)
|
||||
|
||||
modal.AddToFooter(cancelBtn);
|
||||
modal.AddToFooter(confirmBtn);
|
||||
modal.Show();
|
||||
});
|
||||
|
||||
var btnNoClose = root.Q<UTKButton>("btn-modal-no-close");
|
||||
btnNoClose?.RegisterCallback<ClickEvent>(_ =>
|
||||
{
|
||||
var modal = UTKModal.Show("닫기 버튼 없음", UTKModal.ModalSize.Small);
|
||||
var modal = UTKModal.Create("닫기 버튼 없음", UTKModal.ModalSize.Small);
|
||||
modal.ShowCloseButton = false;
|
||||
modal.CloseOnBackdropClick = true;
|
||||
modal.Add(new Label("닫기 버튼이 없습니다.\n배경을 클릭하면 닫힙니다."));
|
||||
modal.Show();
|
||||
});
|
||||
|
||||
var btnNoBackdrop = root.Q<UTKButton>("btn-modal-no-backdrop");
|
||||
btnNoBackdrop?.RegisterCallback<ClickEvent>(_ =>
|
||||
{
|
||||
var modal = UTKModal.Show("배경 클릭 비활성화", UTKModal.ModalSize.Small);
|
||||
var modal = UTKModal.Create("배경 클릭 비활성화", UTKModal.ModalSize.Small);
|
||||
modal.CloseOnBackdropClick = false;
|
||||
modal.Add(new Label("배경을 클릭해도 닫히지 않습니다.\nX 버튼으로만 닫을 수 있습니다."));
|
||||
modal.Show();
|
||||
});
|
||||
|
||||
// Custom Content - Form Modal
|
||||
var btnForm = root.Q<UTKButton>("btn-modal-form");
|
||||
btnForm?.RegisterCallback<ClickEvent>(_ =>
|
||||
{
|
||||
var modal = UTKModal.Show("사용자 정보 입력", UTKModal.ModalSize.Medium);
|
||||
var modal = UTKModal.Create("사용자 정보 입력", UTKModal.ModalSize.Medium);
|
||||
|
||||
var nameField = new UTKInputField("이름");
|
||||
var emailField = new UTKInputField("이메일");
|
||||
@@ -490,6 +494,7 @@ for (int i = 0; i < buttons.Length; i++)
|
||||
|
||||
modal.AddToFooter(cancelBtn);
|
||||
modal.AddToFooter(submitBtn);
|
||||
modal.Show();
|
||||
});
|
||||
|
||||
SetCodeSamples(root,
|
||||
@@ -500,33 +505,40 @@ UTKModal.SetRoot(rootVisualElement);
|
||||
// 1. 기본 모달 표시
|
||||
// ========================================
|
||||
|
||||
var modal = UTKModal.Show(""제목"", UTKModal.ModalSize.Medium);
|
||||
var modal = UTKModal.Create(""제목"", UTKModal.ModalSize.Medium);
|
||||
modal.Add(new Label(""모달 내용""));
|
||||
|
||||
// 닫힘 이벤트
|
||||
modal.OnClosed += () => Debug.Log(""모달 닫힘"");
|
||||
|
||||
// 화면에 표시
|
||||
modal.Show();
|
||||
|
||||
// ========================================
|
||||
// 2. 모달 크기
|
||||
// ========================================
|
||||
|
||||
// Small
|
||||
UTKModal.Show(""Small"", UTKModal.ModalSize.Small);
|
||||
var small = UTKModal.Create(""Small"", UTKModal.ModalSize.Small);
|
||||
small.Show();
|
||||
|
||||
// Medium (기본)
|
||||
UTKModal.Show(""Medium"", UTKModal.ModalSize.Medium);
|
||||
var medium = UTKModal.Create(""Medium"", UTKModal.ModalSize.Medium);
|
||||
medium.Show();
|
||||
|
||||
// Large
|
||||
UTKModal.Show(""Large"", UTKModal.ModalSize.Large);
|
||||
var large = UTKModal.Create(""Large"", UTKModal.ModalSize.Large);
|
||||
large.Show();
|
||||
|
||||
// FullScreen
|
||||
UTKModal.Show(""FullScreen"", UTKModal.ModalSize.FullScreen);
|
||||
var full = UTKModal.Create(""FullScreen"", UTKModal.ModalSize.FullScreen);
|
||||
full.Show();
|
||||
|
||||
// ========================================
|
||||
// 3. 옵션 설정
|
||||
// ========================================
|
||||
|
||||
var modal = UTKModal.Show(""설정"");
|
||||
var modal = UTKModal.Create(""설정"");
|
||||
|
||||
// 닫기 버튼 숨기기
|
||||
modal.ShowCloseButton = false;
|
||||
@@ -537,11 +549,14 @@ modal.CloseOnBackdropClick = false;
|
||||
// 푸터 숨기기
|
||||
modal.SetFooterVisible(false);
|
||||
|
||||
// 옵션 설정 후 표시
|
||||
modal.Show();
|
||||
|
||||
// ========================================
|
||||
// 4. 푸터 버튼 추가
|
||||
// ========================================
|
||||
|
||||
var modal = UTKModal.Show(""확인 모달"");
|
||||
var modal = UTKModal.Create(""확인 모달"");
|
||||
modal.Add(new Label(""정말 삭제하시겠습니까?""));
|
||||
|
||||
var confirmBtn = new UTKButton(""삭제"") { Variant = UTKButton.ButtonVariant.Danger };
|
||||
@@ -556,12 +571,13 @@ cancelBtn.RegisterCallback<ClickEvent>(_ => modal.Close());
|
||||
|
||||
modal.AddToFooter(cancelBtn);
|
||||
modal.AddToFooter(confirmBtn);
|
||||
modal.Show();
|
||||
|
||||
// ========================================
|
||||
// 5. 폼 모달
|
||||
// ========================================
|
||||
|
||||
var modal = UTKModal.Show(""사용자 정보"");
|
||||
var modal = UTKModal.Create(""사용자 정보"");
|
||||
|
||||
var nameField = new UTKInputField(""이름"");
|
||||
var emailField = new UTKInputField(""이메일"");
|
||||
@@ -575,18 +591,83 @@ submitBtn.RegisterCallback<ClickEvent>(_ =>
|
||||
modal.Close();
|
||||
});
|
||||
modal.AddToFooter(submitBtn);
|
||||
modal.Show();
|
||||
|
||||
// ========================================
|
||||
// 6. 프로그래밍 방식 닫기
|
||||
// ========================================
|
||||
|
||||
var modal = UTKModal.Show(""타이머 모달"");
|
||||
var modal = UTKModal.Create(""타이머 모달"");
|
||||
modal.Add(new Label(""3초 후 자동으로 닫힙니다.""));
|
||||
modal.ShowCloseButton = false;
|
||||
modal.CloseOnBackdropClick = false;
|
||||
modal.Show();
|
||||
|
||||
// 3초 후 닫기
|
||||
modal.schedule.Execute(() => modal.Close()).StartingIn(3000);",
|
||||
modal.schedule.Execute(() => modal.Close()).StartingIn(3000);
|
||||
|
||||
// ========================================
|
||||
// 7. Async/Await 방식 (닫힐 때까지 대기)
|
||||
// ========================================
|
||||
|
||||
var modal = UTKModal.Create(""설정"");
|
||||
modal.Add(new Label(""모달 내용""));
|
||||
|
||||
var closeBtn = new UTKButton(""닫기"") { Variant = UTKButton.ButtonVariant.Primary };
|
||||
closeBtn.OnClicked += () => modal.Close();
|
||||
modal.AddToFooter(closeBtn);
|
||||
|
||||
// 모달이 닫힐 때까지 대기
|
||||
await modal.ShowAsync();
|
||||
Debug.Log(""모달이 닫혔습니다."");
|
||||
|
||||
// Async 폼 예시
|
||||
var formModal = UTKModal.Create(""사용자 정보"");
|
||||
var nameField = new UTKInputField(""이름"");
|
||||
formModal.Add(nameField);
|
||||
|
||||
var submitBtn = new UTKButton(""제출"") { Variant = UTKButton.ButtonVariant.Primary };
|
||||
submitBtn.OnClicked += () => formModal.Close();
|
||||
formModal.AddToFooter(submitBtn);
|
||||
|
||||
await formModal.ShowAsync();
|
||||
Debug.Log($""입력된 이름: {nameField.value}"");
|
||||
|
||||
// ========================================
|
||||
// 8. IUTKModalContent<T> 결과 반환
|
||||
// ========================================
|
||||
|
||||
// IUTKModalContent<T>를 구현한 콘텐츠 클래스 정의
|
||||
// public class UserFormContent : VisualElement, IUTKModalContent<UserData>
|
||||
// {
|
||||
// private UTKInputField _nameField = new(""이름"");
|
||||
// private UTKInputField _emailField = new(""이메일"");
|
||||
//
|
||||
// public UserFormContent()
|
||||
// {
|
||||
// Add(_nameField);
|
||||
// Add(_emailField);
|
||||
// }
|
||||
//
|
||||
// public UserData? GetResult()
|
||||
// {
|
||||
// return new UserData(_nameField.value, _emailField.value);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 사용
|
||||
var modal = UTKModal.Create(""사용자 정보"");
|
||||
var form = new UserFormContent();
|
||||
modal.Add(form);
|
||||
|
||||
var submitBtn = new UTKButton(""제출"") { Variant = UTKButton.ButtonVariant.Primary };
|
||||
submitBtn.OnClicked += () => modal.Close();
|
||||
modal.AddToFooter(submitBtn);
|
||||
|
||||
// Close() 시 자동으로 form.GetResult() 호출
|
||||
UserData? result = await modal.ShowAsync<UserData>();
|
||||
if (result != null)
|
||||
Debug.Log($""이름: {result.Name}, 이메일: {result.Email}"");",
|
||||
uxmlCode: @"<!-- 네임스페이스 선언 -->
|
||||
<ui:UXML xmlns:utk=""UVC.UIToolkit"">
|
||||
|
||||
@@ -612,9 +693,10 @@ modal.schedule.Execute(() => modal.Close()).StartingIn(3000);",
|
||||
{
|
||||
if (_root == null) return;
|
||||
|
||||
var modal = UTKModal.Show($"{size} Modal", size);
|
||||
var modal = UTKModal.Create($"{size} Modal", size);
|
||||
modal.Add(new Label($"이것은 {size} 크기의 모달입니다."));
|
||||
modal.OnClosed += () => Debug.Log($"{size} modal closed");
|
||||
modal.Show();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -88,6 +88,7 @@ public partial class UTKStyleGuideSample : MonoBehaviour
|
||||
["UTKMultiColumnTreeView"] = "UIToolkit/Sample/List/UTKMultiColumnTreeViewSample",
|
||||
["UTKFoldout"] = "UIToolkit/Sample/List/UTKFoldoutSample",
|
||||
["UTKScrollView"] = "UIToolkit/Sample/List/UTKScrollViewSample",
|
||||
["UTKShortcutList"] = "UIToolkit/Sample/List/UTKShortcutListSample",
|
||||
// Card
|
||||
["UTKCard"] = "UIToolkit/Sample/Card/UTKCardSample",
|
||||
["UTKPanel"] = "UIToolkit/Sample/Card/UTKPanelSample",
|
||||
@@ -123,7 +124,7 @@ public partial class UTKStyleGuideSample : MonoBehaviour
|
||||
["Slider"] = new[] { "UTKSlider", "UTKSliderInt", "UTKMinMaxSlider", "UTKProgressBar" },
|
||||
["Dropdown"] = new[] { "UTKDropdown", "UTKEnumDropDown", "UTKMultiSelectDropdown" },
|
||||
["Label"] = new[] { "UTKLabel", "UTKHelpBox" },
|
||||
["List"] = new[] { "UTKListView", "UTKTreeView", "UTKMultiColumnListView", "UTKMultiColumnTreeView", "UTKFoldout", "UTKScrollView" },
|
||||
["List"] = new[] { "UTKListView", "UTKTreeView", "UTKMultiColumnListView", "UTKMultiColumnTreeView", "UTKFoldout", "UTKScrollView", "UTKShortcutList" },
|
||||
["Card"] = new[] { "UTKCard", "UTKPanel" },
|
||||
["Tab"] = new[] { "UTKTabView" },
|
||||
["Modal"] = new[] { "UTKAlert", "UTKToast", "UTKTooltip", "UTKNotification", "UTKModal" },
|
||||
@@ -527,6 +528,9 @@ public partial class UTKStyleGuideSample : MonoBehaviour
|
||||
case "UTKScrollView":
|
||||
InitializeScrollViewSample(root);
|
||||
break;
|
||||
case "UTKShortcutList":
|
||||
InitializeShortcutListSample(root);
|
||||
break;
|
||||
// Card
|
||||
case "UTKCard":
|
||||
InitializeCardSample(root);
|
||||
|
||||
8
Assets/Scripts/Factory/UIToolkit.meta
Normal file
8
Assets/Scripts/Factory/UIToolkit.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a61045a867dc1e942a2cd33b53f96d50
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Factory/UIToolkit/Modal.meta
Normal file
8
Assets/Scripts/Factory/UIToolkit/Modal.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fcce7ee7cafeaf439e7e492d93cc565
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Studio/UIToolkit.meta
Normal file
8
Assets/Scripts/Studio/UIToolkit.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64f4ac483a97a0f4b84735c007f9fabb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Scripts/Studio/UIToolkit/Modal.meta
Normal file
8
Assets/Scripts/Studio/UIToolkit/Modal.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 83909bf9f7c75a148b50d4efc03855b4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.Studio.UIToolkit.Modal
|
||||
{
|
||||
|
||||
[UxmlElement]
|
||||
public partial class UTKSettingModalContent : VisualElement, IDisposable, IUTKModalContent<object>
|
||||
{
|
||||
private UTKTabView tabView;
|
||||
|
||||
public UTKSettingModalContent()
|
||||
{
|
||||
style.flexGrow = 1;
|
||||
|
||||
tabView = new UTKTabView();
|
||||
tabView.style.flexGrow = 1;
|
||||
tabView.TabWidth = 140; // 탭 너비 설정
|
||||
|
||||
// 탭 정렬 방향 설정
|
||||
tabView.Align = TabAlign.Left; // 탭을 왼쪽에 세로로 배치
|
||||
|
||||
// 탭 추가
|
||||
var tab1 = tabView.AddUTKTab("Database");
|
||||
tab1.Add(new UTKSettingModalContentDB());
|
||||
|
||||
var tab2 = tabView.AddUTKTab("General");
|
||||
tab2.Add(new UTKSettingModalContentGeneral());
|
||||
|
||||
var tab3 = tabView.AddUTKTab("Shortcut");
|
||||
tab3.Add(new UTKSettingModalContentShortcut());
|
||||
|
||||
tabView.OnTabChanged += OnTabChanged;
|
||||
tabView.tabClosed += OnTabClosed;
|
||||
Add(tabView);
|
||||
}
|
||||
|
||||
public UTKSettingModalContent(int tabIndex = 0, object? data = null) : this()
|
||||
{
|
||||
// 생성자에서 탭 인덱스를 받아서 초기 탭 설정
|
||||
tabView.SelectedIndex = tabIndex;
|
||||
}
|
||||
|
||||
private void OnTabChanged(int index, UnityEngine.UIElements.Tab? tab)
|
||||
{
|
||||
Debug.Log($"Selected Tab Index: {index}");
|
||||
|
||||
}
|
||||
|
||||
private void OnTabClosed(UnityEngine.UIElements.Tab tab, int index)
|
||||
{
|
||||
Debug.Log($"Closed Tab Index: {index}");
|
||||
}
|
||||
|
||||
public object? GetResult()
|
||||
{
|
||||
IUTKTabContent? content = UTKTabView.FindTabContent(tabView.activeTab);
|
||||
if (content != null) {
|
||||
return content.Hide();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// 필요한 경우 리소스 정리
|
||||
tabView.OnTabChanged -= OnTabChanged;
|
||||
tabView.tabClosed -= OnTabClosed;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2fa8a8673a16331449fef869f3ea9575
|
||||
@@ -0,0 +1,97 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.Studio.UIToolkit.Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// 설정 모달 - Database 탭 콘텐츠
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKSettingModalContentDB : VisualElement, IDisposable, IUTKTabContent
|
||||
{
|
||||
#region Constants
|
||||
private const string UXML_PATH = "Studio/UIToolkit/Modal/UTKSettingModalContentDBUXML";
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
private bool _disposed;
|
||||
private UTKInputField? _dbIpField;
|
||||
private UTKInputField? _dbPortField;
|
||||
private UTKInputField? _dbIdField;
|
||||
private UTKInputField? _dbPasswordField;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>Database IP</summary>
|
||||
public string DbIp => _dbIpField?.value ?? "";
|
||||
|
||||
/// <summary>Database PORT</summary>
|
||||
public string DbPort => _dbPortField?.value ?? "";
|
||||
|
||||
/// <summary>Database ID</summary>
|
||||
public string DbId => _dbIdField?.value ?? "";
|
||||
|
||||
/// <summary>Database PASSWORD</summary>
|
||||
public string DbPassword => _dbPasswordField?.value ?? "";
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKSettingModalContentDB()
|
||||
{
|
||||
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
||||
if (asset != null)
|
||||
{
|
||||
var root = asset.Instantiate();
|
||||
root.style.flexGrow = 1;
|
||||
|
||||
// UTKInputField 참조 가져오기
|
||||
var inputFields = root.Query<UTKInputField>().ToList();
|
||||
foreach (var field in inputFields)
|
||||
{
|
||||
switch (field.label)
|
||||
{
|
||||
case "Database IP":
|
||||
_dbIpField = field;
|
||||
break;
|
||||
case "Database PORT":
|
||||
_dbPortField = field;
|
||||
break;
|
||||
case "Database ID":
|
||||
_dbIdField = field;
|
||||
break;
|
||||
case "Database PASSWORD":
|
||||
_dbPasswordField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Add(root);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IUTKTabContent
|
||||
public void Show(object? data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async UniTask Hide()
|
||||
{
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c11147285831da84cb395f5a2f564174
|
||||
@@ -0,0 +1,113 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.Studio.UIToolkit.Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// 설정 모달 - General 탭 콘텐츠
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKSettingModalContentGeneral : VisualElement, IDisposable, IUTKTabContent
|
||||
{
|
||||
#region Constants
|
||||
private const string UXML_PATH = "Studio/UIToolkit/Modal/UTKSettingModalContentGeneralUXML";
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
private bool _disposed;
|
||||
private UTKIntegerField? _autoSaveTimeField;
|
||||
private UTKFloatField? _gridSpacingField;
|
||||
private UTKFloatField? _positionSnapField;
|
||||
private UTKFloatField? _rotationSnapField;
|
||||
private UTKFloatField? _scaleSnapField;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
/// <summary>Auto Save Time 값 (분)</summary>
|
||||
public int AutoSaveTime => _autoSaveTimeField?.value ?? 5;
|
||||
|
||||
/// <summary>Grid Spacing 값</summary>
|
||||
public float GridSpacing => _gridSpacingField?.value ?? 1f;
|
||||
|
||||
/// <summary>Position Snap 값</summary>
|
||||
public float PositionSnap => _positionSnapField?.value ?? 0.5f;
|
||||
|
||||
/// <summary>Rotation Snap 값</summary>
|
||||
public float RotationSnap => _rotationSnapField?.value ?? 10f;
|
||||
|
||||
/// <summary>Scale Snap 값</summary>
|
||||
public float ScaleSnap => _scaleSnapField?.value ?? 0.5f;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKSettingModalContentGeneral()
|
||||
{
|
||||
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
||||
if (asset != null)
|
||||
{
|
||||
var root = asset.Instantiate();
|
||||
root.style.flexGrow = 1;
|
||||
|
||||
// UTKIntegerField 참조 가져오기
|
||||
var intFields = root.Query<UTKIntegerField>().ToList();
|
||||
foreach (var field in intFields)
|
||||
{
|
||||
switch (field.label)
|
||||
{
|
||||
case "Auto Save Time":
|
||||
_autoSaveTimeField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// UTKFloatField 참조 가져오기
|
||||
var floatFields = root.Query<UTKFloatField>().ToList();
|
||||
foreach (var field in floatFields)
|
||||
{
|
||||
switch (field.label)
|
||||
{
|
||||
case "Grid Spacing":
|
||||
_gridSpacingField = field;
|
||||
break;
|
||||
case "Position Snap":
|
||||
_positionSnapField = field;
|
||||
break;
|
||||
case "Rotation Snap":
|
||||
_rotationSnapField = field;
|
||||
break;
|
||||
case "Scale Snap":
|
||||
_scaleSnapField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Add(root);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
public void Show(object? data)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public async UniTask Hide()
|
||||
{
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5f618e56a387b84399cb67c340cbbfc
|
||||
@@ -0,0 +1,111 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit;
|
||||
|
||||
namespace UVC.Studio.UIToolkit.Modal
|
||||
{
|
||||
/// <summary>
|
||||
/// 설정 모달 - Database 탭 콘텐츠
|
||||
/// </summary>
|
||||
[UxmlElement]
|
||||
public partial class UTKSettingModalContentShortcut : VisualElement, IDisposable, IUTKTabContent
|
||||
{
|
||||
#region Constants
|
||||
private const string UXML_PATH = "Studio/UIToolkit/Modal/UTKSettingModalContentShortcutUXML";
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
private bool _disposed;
|
||||
private UTKShortcutList? _shortcutList;
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKSettingModalContentShortcut()
|
||||
{
|
||||
var asset = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
||||
if (asset != null)
|
||||
{
|
||||
var root = asset.Instantiate();
|
||||
root.style.flexGrow = 1;
|
||||
|
||||
// 참조 가져오기
|
||||
_shortcutList = root.Q<UTKShortcutList>("window");
|
||||
if (_shortcutList != null)
|
||||
{
|
||||
// 변경 이벤트 구독
|
||||
_shortcutList.OnDataChanged += OnDataChanged;
|
||||
}
|
||||
Add(root);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void OnDataChanged(UTKShortcutItemData item)
|
||||
{
|
||||
Debug.Log($"{item.CommandName}: Ctrl={item.UseCtrl} Key={item.Key}");
|
||||
}
|
||||
|
||||
#region IUTKTabContent
|
||||
public void Show(object? data)
|
||||
{
|
||||
Debug.Log($"[UTKSettingModalContentShortcut] Show called with data: {data}");
|
||||
if (_shortcutList != null)
|
||||
{
|
||||
if(data is List<UTKShortcutItemData> shortcuts)
|
||||
{
|
||||
_shortcutList.SetData(shortcuts);
|
||||
return;
|
||||
}
|
||||
// 예시 데이터 설정
|
||||
var tempData = new List<UTKShortcutItemData>()
|
||||
{
|
||||
new() { Id = "file.new_project", CommandName = "File > New Project", UseCtrl = true, UseShift = false, UseAlt = false, Key = "N" },
|
||||
new() { Id = "file.open_project", CommandName = "File > Open Project", UseCtrl = true, UseShift = true, UseAlt = false, Key = "O" },
|
||||
new() { Id = "file.save_project", CommandName = "File > Save Project", UseCtrl = true, UseShift = false, UseAlt = true, Key = "S" },
|
||||
new() { Id = "file.save_as", CommandName = "File > Save As...", UseCtrl = true, UseShift = true, UseAlt = true, Key = "S" },
|
||||
new() { Id = "file.insert_database", CommandName = "File > Insert Database", UseCtrl = true, UseShift = true, UseAlt = false, Key = "I" },
|
||||
new() { Id = "file.export_layout", CommandName = "File > Export > Layout", UseCtrl = true, UseShift = true, UseAlt = false, Key = "L" },
|
||||
new() { Id = "file.export_metadata", CommandName = "File > Export > Metadata",UseCtrl = true, UseShift = true, UseAlt = false, Key = "M" },
|
||||
new() { Id = "file.export_gltf", CommandName = "File > Export > glTF", UseCtrl = true, UseShift = true, UseAlt = false, Key = "G" },
|
||||
new() { Id = "edit.undo", CommandName = "Edit > Undo", UseCtrl = true, UseShift = true, UseAlt = false, Key = "Z" },
|
||||
new() { Id = "edit.redo", CommandName = "Edit > Redo", UseCtrl = true, UseShift = true, UseAlt = false, Key = "Y" },
|
||||
new() { Id = "edit.duplicate", CommandName = "Edit > Duplicate", UseCtrl = true, UseShift = true, UseAlt = false, Key = "D" },
|
||||
new() { Id = "edit.delete", CommandName = "Edit > Delete", UseCtrl = false, UseShift = true, UseAlt = false, Key = "Delete" },
|
||||
new() { Id = "create.plane", CommandName = "Create > Plane", UseCtrl = true, UseShift = true, UseAlt = false, Key = "V" },
|
||||
new() { Id = "tool.select", CommandName = "Select Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "1" },
|
||||
new() { Id = "tool.move", CommandName = "Move Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "2" },
|
||||
new() { Id = "tool.rotate", CommandName = "Rotate Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "3" },
|
||||
new() { Id = "tool.scale", CommandName = "Scale Tool", UseCtrl = false, UseShift = false, UseAlt = false, Key = "4" },
|
||||
};
|
||||
_shortcutList.SetData(tempData);
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask Hide()
|
||||
{
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
if (_shortcutList != null)
|
||||
{
|
||||
_shortcutList.OnDataChanged -= OnDataChanged;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cb9bc28b2f8f5b48b3e969c05a8a86e
|
||||
@@ -97,8 +97,17 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 에러 메시지 (C#에서 Validation 설정 권장) -->
|
||||
/// <utk:UTKBoundsField label="경계" error-message="크기는 양수여야 합니다." />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKBoundsField label="경계" label-min-width="120" />
|
||||
/// </UXML>
|
||||
/// </code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var boundsField = new UTKBoundsField("경계");
|
||||
/// boundsField.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <code>
|
||||
/// // 게임 오브젝트의 콜라이더 경계 설정
|
||||
@@ -130,6 +139,7 @@ namespace UVC.UIToolkit
|
||||
private string _yLabel = "Y";
|
||||
private string _zLabel = "Z";
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -253,6 +263,18 @@ namespace UVC.UIToolkit
|
||||
get => _validation;
|
||||
set => _validation = value;
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -283,7 +305,11 @@ namespace UVC.UIToolkit
|
||||
AddToClassList("utk-boundsfield");
|
||||
|
||||
// 초기 라벨 설정
|
||||
schedule.Execute(() => UpdateAxisLabels());
|
||||
schedule.Execute(() =>
|
||||
{
|
||||
UpdateAxisLabels();
|
||||
ApplyLabelMinWidth();
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -348,6 +374,22 @@ namespace UVC.UIToolkit
|
||||
textInput.isReadOnly = _isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -67,7 +67,16 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 비활성화 상태 -->
|
||||
/// <utk:UTKDoubleField label="PI" value="3.141592653589793" is-enabled="false" />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKDoubleField label="경도" label-min-width="120" />
|
||||
/// ]]></code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var doubleField = new UTKDoubleField("경도");
|
||||
/// doubleField.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시 (GPS 좌표):</b></para>
|
||||
/// <code>
|
||||
/// // GPS 좌표 입력 (높은 정밀도 필요)
|
||||
@@ -89,6 +98,7 @@ namespace UVC.UIToolkit
|
||||
private bool _disposed;
|
||||
private bool _isEnabled = true;
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -140,7 +150,19 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>읽기 전용</summary>
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>읽기 전용</summary>
|
||||
public new bool isReadOnly
|
||||
{
|
||||
get => base.isReadOnly;
|
||||
@@ -180,6 +202,9 @@ namespace UVC.UIToolkit
|
||||
private void SetupStyles()
|
||||
{
|
||||
AddToClassList("utk-double-field");
|
||||
|
||||
// label 설정 후 LabelMinWidth 적용
|
||||
schedule.Execute(() => ApplyLabelMinWidth());
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -212,6 +237,22 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -78,7 +78,16 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 비활성화 상태 -->
|
||||
/// <utk:UTKFloatField label="고정 값" value="3.14" is-enabled="false" />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKFloatField label="속도" label-min-width="120" />
|
||||
/// ]]></code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var floatField = new UTKFloatField("속도");
|
||||
/// floatField.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시 (캐릭터 스탯):</b></para>
|
||||
/// <code>
|
||||
/// // 캐릭터 이동 속도 편집
|
||||
@@ -101,6 +110,7 @@ namespace UVC.UIToolkit
|
||||
private bool _disposed;
|
||||
private bool _isEnabled = true;
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -152,6 +162,18 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>읽기 전용</summary>
|
||||
public new bool isReadOnly
|
||||
{
|
||||
@@ -191,6 +213,9 @@ namespace UVC.UIToolkit
|
||||
private void SetupStyles()
|
||||
{
|
||||
AddToClassList("utk-float-field");
|
||||
|
||||
// label 설정 후 LabelMinWidth 적용
|
||||
schedule.Execute(() => ApplyLabelMinWidth());
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -223,6 +248,22 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -80,8 +80,18 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 비활성화 -->
|
||||
/// <utk:UTKInputField label="읽기전용" is-enabled="false" value="수정 불가" />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKInputField label="이름" label-min-width="120" />
|
||||
/// </ui:UXML>
|
||||
/// ]]></code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var input = new UTKInputField("이름");
|
||||
/// input.LabelMinWidth = 120f; // 120px
|
||||
/// // -1이면 미설정 (기본값)
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class UTKInputField : TextField, IDisposable
|
||||
@@ -94,6 +104,7 @@ namespace UVC.UIToolkit
|
||||
private bool _disposed;
|
||||
private bool _isEnabled = true;
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private InputFieldVariant _variant = InputFieldVariant.Default;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
@@ -160,6 +171,18 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>읽기 전용</summary>
|
||||
public new bool isReadOnly
|
||||
{
|
||||
@@ -237,6 +260,9 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
AddToClassList("utk-input");
|
||||
UpdateVariant();
|
||||
|
||||
// label 설정 후 LabelMinWidth 적용
|
||||
schedule.Execute(() => ApplyLabelMinWidth());
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -271,6 +297,22 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -76,7 +76,16 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 비활성화 상태 -->
|
||||
/// <utk:UTKIntegerField label="고정 값" value="100" is-enabled="false" />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKIntegerField label="수량" label-min-width="120" />
|
||||
/// ]]></code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var intField = new UTKIntegerField("수량");
|
||||
/// intField.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시 (인벤토리 수량):</b></para>
|
||||
/// <code>
|
||||
/// // 인벤토리 아이템 수량 편집
|
||||
@@ -100,6 +109,7 @@ namespace UVC.UIToolkit
|
||||
private bool _disposed;
|
||||
private bool _isEnabled = true;
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -151,6 +161,18 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 읽기 전용
|
||||
/// </summary>
|
||||
@@ -192,6 +214,9 @@ namespace UVC.UIToolkit
|
||||
private void SetupStyles()
|
||||
{
|
||||
AddToClassList("utk-integer-field");
|
||||
|
||||
// label 설정 후 LabelMinWidth 적용
|
||||
schedule.Execute(() => ApplyLabelMinWidth());
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -224,6 +249,22 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -68,7 +68,16 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 비활성화 상태 -->
|
||||
/// <utk:UTKLongField label="고정 ID" is-enabled="false" />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKLongField label="ID" label-min-width="120" />
|
||||
/// ]]></code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var longField = new UTKLongField("ID");
|
||||
/// longField.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시 (파일 정보):</b></para>
|
||||
/// <code>
|
||||
/// // 파일 크기 표시
|
||||
@@ -87,6 +96,7 @@ namespace UVC.UIToolkit
|
||||
private bool _disposed;
|
||||
private bool _isEnabled = true;
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -138,6 +148,18 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 읽기 전용
|
||||
/// </summary>
|
||||
@@ -179,6 +201,9 @@ namespace UVC.UIToolkit
|
||||
private void SetupStyles()
|
||||
{
|
||||
AddToClassList("utk-long-field");
|
||||
|
||||
// label 설정 후 LabelMinWidth 적용
|
||||
schedule.Execute(() => ApplyLabelMinWidth());
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -211,6 +236,22 @@ namespace UVC.UIToolkit
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -84,7 +84,16 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 읽기 전용 -->
|
||||
/// <utk:UTKRectField label="고정 영역" is-readonly="true" />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKRectField label="영역" label-min-width="120" />
|
||||
/// ]]></code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var rectField = new UTKRectField("영역");
|
||||
/// rectField.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시 (스프라이트 영역 편집):</b></para>
|
||||
/// <code>
|
||||
/// // 스프라이트 UV 영역 편집기
|
||||
@@ -114,6 +123,7 @@ namespace UVC.UIToolkit
|
||||
private string _wLabel = "W";
|
||||
private string _hLabel = "H";
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -213,6 +223,18 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>읽기 전용 상태</summary>
|
||||
[UxmlAttribute("is-readonly")]
|
||||
public bool IsReadOnly
|
||||
@@ -263,7 +285,11 @@ namespace UVC.UIToolkit
|
||||
AddToClassList("utk-rectfield");
|
||||
|
||||
// 초기 라벨 설정
|
||||
schedule.Execute(() => UpdateAxisLabels());
|
||||
schedule.Execute(() =>
|
||||
{
|
||||
UpdateAxisLabels();
|
||||
ApplyLabelMinWidth();
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -318,6 +344,22 @@ namespace UVC.UIToolkit
|
||||
textInput.isReadOnly = _isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -89,8 +89,17 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 에러 메시지 (C#에서 Validation 설정 권장) -->
|
||||
/// <utk:UTKVector2Field label="크기" error-message="크기는 양수여야 합니다." />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKVector2Field label="크기" label-min-width="120" />
|
||||
/// </UXML>
|
||||
/// </code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var vec2Field = new UTKVector2Field("크기");
|
||||
/// vec2Field.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <code>
|
||||
/// // RectTransform 크기 조절
|
||||
@@ -121,6 +130,7 @@ namespace UVC.UIToolkit
|
||||
private string _xLabel = "X";
|
||||
private string _yLabel = "Y";
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -208,6 +218,18 @@ namespace UVC.UIToolkit
|
||||
get => _validation;
|
||||
set => _validation = value;
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -246,7 +268,11 @@ namespace UVC.UIToolkit
|
||||
AddToClassList("utk-vector2-field");
|
||||
|
||||
// 초기 라벨 설정
|
||||
schedule.Execute(() => UpdateAxisLabels());
|
||||
schedule.Execute(() =>
|
||||
{
|
||||
UpdateAxisLabels();
|
||||
ApplyLabelMinWidth();
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -299,6 +325,22 @@ namespace UVC.UIToolkit
|
||||
textInput.isReadOnly = _isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -89,7 +89,16 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 에러 메시지 (C#에서 Validation 설정 권장) -->
|
||||
/// <utk:UTKVector3Field label="위치" error-message="유효하지 않은 좌표입니다." />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKVector3Field label="위치" label-min-width="120" />
|
||||
/// ]]></code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var vec3Field = new UTKVector3Field("위치");
|
||||
/// vec3Field.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시 (Transform 편집기):</b></para>
|
||||
/// <code>
|
||||
/// // GameObject Transform 편집
|
||||
@@ -121,6 +130,7 @@ namespace UVC.UIToolkit
|
||||
private string _yLabel = "Y";
|
||||
private string _zLabel = "Z";
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -229,6 +239,18 @@ namespace UVC.UIToolkit
|
||||
get => _validation;
|
||||
set => _validation = value;
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -267,7 +289,11 @@ namespace UVC.UIToolkit
|
||||
AddToClassList("utk-vector3-field");
|
||||
|
||||
// 초기 라벨 설정
|
||||
schedule.Execute(() => UpdateAxisLabels());
|
||||
schedule.Execute(() =>
|
||||
{
|
||||
UpdateAxisLabels();
|
||||
ApplyLabelMinWidth();
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -321,6 +347,22 @@ namespace UVC.UIToolkit
|
||||
textInput.isReadOnly = _isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
@@ -91,8 +91,17 @@ namespace UVC.UIToolkit
|
||||
///
|
||||
/// <!-- 에러 메시지 (C#에서 Validation 설정 권장) -->
|
||||
/// <utk:UTKVector4Field label="색상" error-message="알파 값은 0~1 사이여야 합니다." />
|
||||
///
|
||||
/// <!-- label min-width 설정 -->
|
||||
/// <utk:UTKVector4Field label="값" label-min-width="120" />
|
||||
/// </UXML>
|
||||
/// </code>
|
||||
/// <para><b>Label Min-Width 설정:</b></para>
|
||||
/// <code>
|
||||
/// // label이 있을 때 .unity-label의 min-width를 설정
|
||||
/// var vec4Field = new UTKVector4Field("값");
|
||||
/// vec4Field.LabelMinWidth = 120f; // 120px
|
||||
/// </code>
|
||||
/// <para><b>실제 활용 예시:</b></para>
|
||||
/// <code>
|
||||
/// // 머티리얼 쉐이더 파라미터 설정
|
||||
@@ -123,6 +132,7 @@ namespace UVC.UIToolkit
|
||||
private string _zLabel = "Z";
|
||||
private string _wLabel = "W";
|
||||
private string _errorMessage = "";
|
||||
private float _labelMinWidth = -1f;
|
||||
private Func<bool>? _validation;
|
||||
private Label? _errorLabel;
|
||||
#endregion
|
||||
@@ -234,6 +244,18 @@ namespace UVC.UIToolkit
|
||||
get => _validation;
|
||||
set => _validation = value;
|
||||
}
|
||||
|
||||
/// <summary>label이 있을 때 .unity-label의 min-width (px). -1이면 미설정</summary>
|
||||
[UxmlAttribute("label-min-width")]
|
||||
public float LabelMinWidth
|
||||
{
|
||||
get => _labelMinWidth;
|
||||
set
|
||||
{
|
||||
_labelMinWidth = value;
|
||||
ApplyLabelMinWidth();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -272,7 +294,11 @@ namespace UVC.UIToolkit
|
||||
AddToClassList("utk-vector4field");
|
||||
|
||||
// 초기 라벨 설정
|
||||
schedule.Execute(() => UpdateAxisLabels());
|
||||
schedule.Execute(() =>
|
||||
{
|
||||
UpdateAxisLabels();
|
||||
ApplyLabelMinWidth();
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupEvents()
|
||||
@@ -327,6 +353,22 @@ namespace UVC.UIToolkit
|
||||
textInput.isReadOnly = _isReadOnly;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyLabelMinWidth()
|
||||
{
|
||||
if (string.IsNullOrEmpty(label)) return;
|
||||
var labelElement = this.Q<Label>(className: "unity-label");
|
||||
if (labelElement == null) return;
|
||||
|
||||
if (_labelMinWidth >= 0)
|
||||
{
|
||||
labelElement.style.minWidth = _labelMinWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
labelElement.style.minWidth = StyleKeyword.Null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
42
Assets/Scripts/UVC/UIToolkit/List/UTKShortcutItemData.cs
Normal file
42
Assets/Scripts/UVC/UIToolkit/List/UTKShortcutItemData.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
#nullable enable
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 단축키 아이템 데이터.
|
||||
/// <see cref="UTKShortcutList"/>의 각 행에 대한 데이터를 담습니다.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var data = new UTKShortcutItemData
|
||||
/// {
|
||||
/// Id = "file.new_project",
|
||||
/// CommandName = "File > New Project",
|
||||
/// UseCtrl = true,
|
||||
/// UseShift = false,
|
||||
/// UseAlt = false,
|
||||
/// Key = "N"
|
||||
/// };
|
||||
/// </code>
|
||||
/// </example>
|
||||
public class UTKShortcutItemData
|
||||
{
|
||||
/// <summary>단축키 고유 ID (예: "file.new_project")</summary>
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
/// <summary>표시 명령 이름 (예: "File > New Project")</summary>
|
||||
public string CommandName { get; set; } = "";
|
||||
|
||||
/// <summary>Ctrl 키 사용 여부</summary>
|
||||
public bool UseCtrl { get; set; }
|
||||
|
||||
/// <summary>Shift 키 사용 여부</summary>
|
||||
public bool UseShift { get; set; }
|
||||
|
||||
/// <summary>Alt 키 사용 여부</summary>
|
||||
public bool UseAlt { get; set; }
|
||||
|
||||
/// <summary>주요 키 표시 텍스트 (예: "N", "Delete", "F1")</summary>
|
||||
public string Key { get; set; } = "";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: caa768e3069c01c49863e091a09d622f
|
||||
639
Assets/Scripts/UVC/UIToolkit/List/UTKShortcutList.cs
Normal file
639
Assets/Scripts/UVC/UIToolkit/List/UTKShortcutList.cs
Normal file
@@ -0,0 +1,639 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 단축키 설정 리스트 컴포넌트.
|
||||
/// Command 이름, Ctrl / Shift / Alt 체크박스, Key 입력(읽기전용 + 클릭 시 키 캡처)으로 구성됩니다.
|
||||
/// </summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para><b>열 구성 (왼쪽 → 오른쪽):</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item>Command 이름 (flex-grow)</item>
|
||||
/// <item>Ctrl 체크박스 (52 px)</item>
|
||||
/// <item>Shift 체크박스 (52 px)</item>
|
||||
/// <item>Alt 체크박스 (52 px)</item>
|
||||
/// <item>Key 입력 필드 (76 px) – 클릭 시 다음 키 자동 캡처</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>Key 캡처 방법:</b></para>
|
||||
/// <list type="number">
|
||||
/// <item>Key 필드 클릭 → 캡처 모드 진입 ("···" 표시)</item>
|
||||
/// <item>원하는 키 입력 → 자동 저장 후 캡처 종료</item>
|
||||
/// <item>Escape 키 → 취소 (이전 값 복원)</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para><b>가상화:</b> UTKListView(ListView) 를 사용하여 대량 항목도 성능 저하 없이 표시합니다.</para>
|
||||
///
|
||||
/// <para><b>관련 리소스:</b></para>
|
||||
/// <list type="bullet">
|
||||
/// <item>Resources/UIToolkit/List/UTKShortcutList.uxml</item>
|
||||
/// <item>Resources/UIToolkit/List/UTKShortcutListItem.uxml</item>
|
||||
/// <item>Resources/UIToolkit/List/UTKShortcutListUss.uss</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
///
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var list = new UTKShortcutList();
|
||||
/// list.OnDataChanged += (item) => Debug.Log($"변경: {item.CommandName}");
|
||||
///
|
||||
/// list.SetData(new List<UTKShortcutItemData>
|
||||
/// {
|
||||
/// new() { Id = "file.new", CommandName = "File > New Project", UseCtrl = true, Key = "N" },
|
||||
/// new() { Id = "edit.undo", CommandName = "Edit > Undo", UseCtrl = true, Key = "Z" },
|
||||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
[UxmlElement]
|
||||
public partial class UTKShortcutList : VisualElement, IDisposable
|
||||
{
|
||||
#region Constants
|
||||
private const string UXML_PATH = "UIToolkit/List/UTKShortcutList";
|
||||
private const string USS_PATH = "UIToolkit/List/UTKShortcutListUss";
|
||||
private const string ITEM_UXML_PATH = "UIToolkit/List/UTKShortcutListItem";
|
||||
private const float ITEM_HEIGHT = 36f;
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
private bool _disposed;
|
||||
|
||||
private UTKListView? _listView;
|
||||
private UTKInputField? _searchField;
|
||||
private UTKButton? _clearButton;
|
||||
|
||||
// 전체 데이터 · 검색 필터링된 데이터 분리
|
||||
private List<UTKShortcutItemData> _allItems = new();
|
||||
private readonly List<UTKShortcutItemData> _filteredItems = new();
|
||||
|
||||
// UXML 캐싱 (makeItem 호출마다 Resources.Load 방지)
|
||||
private VisualTreeAsset? _itemTemplate;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
/// <summary>
|
||||
/// 단축키 데이터가 변경될 때 발생합니다.
|
||||
/// 변경된 <see cref="UTKShortcutItemData"/> 인스턴스를 전달합니다.
|
||||
/// </summary>
|
||||
public event Action<UTKShortcutItemData>? OnDataChanged;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public UTKShortcutList() : base()
|
||||
{
|
||||
// 1. 테마 적용
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
|
||||
// 2. USS 로드 (테마 변수 스타일시트 이후에 로드되어야 변수가 해석됨)
|
||||
var uss = Resources.Load<StyleSheet>(USS_PATH);
|
||||
if (uss != null)
|
||||
styleSheets.Add(uss);
|
||||
else
|
||||
Debug.LogWarning($"[UTKShortcutList] USS not found: {USS_PATH}");
|
||||
|
||||
// 3. UXML 로드 → 요소 구성
|
||||
var visualTree = Resources.Load<VisualTreeAsset>(UXML_PATH);
|
||||
if (visualTree != null)
|
||||
{
|
||||
visualTree.CloneTree(this);
|
||||
InitializeFromUxml();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[UTKShortcutList] UXML not found: {UXML_PATH}, using fallback");
|
||||
CreateFallbackUI();
|
||||
}
|
||||
|
||||
// 4. 테마 변경 구독
|
||||
SubscribeToThemeChanges();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region UI Creation
|
||||
|
||||
/// <summary>UXML 로드 성공 시 – 자식 요소 참조 획득 및 초기화.</summary>
|
||||
private void InitializeFromUxml()
|
||||
{
|
||||
AddToClassList("utk-shortcut-list");
|
||||
|
||||
_searchField = this.Q<UTKInputField>("search-field");
|
||||
_clearButton = this.Q<UTKButton>("clear-btn");
|
||||
_listView = this.Q<UTKListView>("list-view");
|
||||
|
||||
BindSearchField();
|
||||
SetupListView();
|
||||
}
|
||||
|
||||
/// <summary>UXML 로드 실패 시 – 코드로 UI 구성.</summary>
|
||||
private void CreateFallbackUI()
|
||||
{
|
||||
AddToClassList("utk-shortcut-list");
|
||||
|
||||
// 검색 영역
|
||||
var searchContainer = new VisualElement { name = "search-container" };
|
||||
searchContainer.AddToClassList("utk-shortcut-list__search-container");
|
||||
Add(searchContainer);
|
||||
|
||||
_searchField = new UTKInputField { name = "search-field" };
|
||||
_searchField.AddToClassList("utk-shortcut-list__search");
|
||||
searchContainer.Add(_searchField);
|
||||
|
||||
_clearButton = new UTKButton { name = "clear-btn" };
|
||||
_clearButton.Variant = UTKButton.ButtonVariant.Text;
|
||||
_clearButton.SetMaterialIcon(UTKMaterialIcons.Close, 12);
|
||||
_clearButton.AddToClassList("utk-shortcut-list__clear-btn");
|
||||
searchContainer.Add(_clearButton);
|
||||
|
||||
// 컬럼 헤더
|
||||
var header = new VisualElement { name = "header" };
|
||||
header.AddToClassList("utk-shortcut-list__header");
|
||||
header.Add(MakeHeaderLabel("", "utk-shortcut-list__header-command"));
|
||||
header.Add(MakeHeaderLabel("Ctrl", "utk-shortcut-list__header-modifier"));
|
||||
header.Add(MakeHeaderLabel("Shift", "utk-shortcut-list__header-modifier"));
|
||||
header.Add(MakeHeaderLabel("Alt", "utk-shortcut-list__header-modifier"));
|
||||
header.Add(MakeHeaderLabel("Key", "utk-shortcut-list__header-key"));
|
||||
Add(header);
|
||||
|
||||
// ListView
|
||||
_listView = new UTKListView { name = "list-view" };
|
||||
_listView.AddToClassList("utk-shortcut-list__listview");
|
||||
Add(_listView);
|
||||
|
||||
BindSearchField();
|
||||
SetupListView();
|
||||
}
|
||||
|
||||
private static Label MakeHeaderLabel(string text, string className)
|
||||
{
|
||||
var label = new Label(text);
|
||||
label.AddToClassList(className);
|
||||
return label;
|
||||
}
|
||||
|
||||
/// <summary>검색 필드 이벤트 연결.</summary>
|
||||
private void BindSearchField()
|
||||
{
|
||||
// Clear 버튼 초기 숨김
|
||||
if (_clearButton != null)
|
||||
{
|
||||
_clearButton.style.display = DisplayStyle.None;
|
||||
_clearButton.OnClicked += OnClearButtonClicked;
|
||||
}
|
||||
|
||||
if (_searchField == null) return;
|
||||
// Enter 키 또는 포커스 잃을 때 검색 실행 (UTKComponentList 방식)
|
||||
_searchField.OnSubmit += OnSearch;
|
||||
}
|
||||
|
||||
/// <summary>ListView makeItem / bindItem / unbindItem 설정.</summary>
|
||||
private void SetupListView()
|
||||
{
|
||||
if (_listView == null) return;
|
||||
|
||||
_listView.makeItem = MakeItem;
|
||||
_listView.bindItem = BindItem;
|
||||
_listView.unbindItem = UnbindItem;
|
||||
_listView.fixedItemHeight = ITEM_HEIGHT;
|
||||
_listView.selectionType = SelectionType.None;
|
||||
_listView.itemsSource = _filteredItems;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Theme
|
||||
private void SubscribeToThemeChanges()
|
||||
{
|
||||
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
||||
RegisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
||||
RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
||||
}
|
||||
|
||||
private void OnAttachToPanelForTheme(AttachToPanelEvent evt)
|
||||
{
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
UTKThemeManager.Instance.OnThemeChanged += OnThemeChanged;
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
|
||||
private void OnDetachFromPanelForTheme(DetachFromPanelEvent evt)
|
||||
{
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
}
|
||||
|
||||
private void OnThemeChanged(UTKTheme theme)
|
||||
{
|
||||
UTKThemeManager.Instance.ApplyThemeToElement(this);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Search
|
||||
/// <summary>Clear 버튼 클릭 처리 – 검색어 초기화 및 재검색.</summary>
|
||||
private void OnClearButtonClicked()
|
||||
{
|
||||
if (_searchField != null && _searchField.value.Length > 0)
|
||||
{
|
||||
_searchField.value = string.Empty;
|
||||
OnSearch(string.Empty);
|
||||
}
|
||||
if (_clearButton != null)
|
||||
_clearButton.style.display = DisplayStyle.None;
|
||||
}
|
||||
|
||||
/// <summary>검색 실행 (Enter 키 또는 포커스 잃을 때 호출).</summary>
|
||||
private void OnSearch(string query)
|
||||
{
|
||||
// Clear 버튼 표시/숨김
|
||||
if (_clearButton != null)
|
||||
_clearButton.style.display = string.IsNullOrEmpty(query) ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
|
||||
_filteredItems.Clear();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
_filteredItems.AddRange(_allItems);
|
||||
}
|
||||
else
|
||||
{
|
||||
var lower = query.ToLowerInvariant();
|
||||
foreach (var item in _allItems)
|
||||
{
|
||||
if (item.CommandName.ToLowerInvariant().Contains(lower))
|
||||
_filteredItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
_listView?.RefreshItems();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ListView Callbacks
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 VisualElement 생성 (가상화 재사용 요소).
|
||||
/// UXML을 캐싱하여 매 호출마다 Resources.Load 를 방지합니다.
|
||||
/// </summary>
|
||||
private VisualElement MakeItem()
|
||||
{
|
||||
_itemTemplate ??= Resources.Load<VisualTreeAsset>(ITEM_UXML_PATH);
|
||||
|
||||
if (_itemTemplate != null)
|
||||
return _itemTemplate.Instantiate();
|
||||
|
||||
Debug.LogWarning($"[UTKShortcutList] Item UXML not found: {ITEM_UXML_PATH}, using fallback");
|
||||
return CreateItemFallback();
|
||||
}
|
||||
|
||||
/// <summary>UXML 로드 실패 시 코드로 아이템 행 생성.</summary>
|
||||
private static VisualElement CreateItemFallback()
|
||||
{
|
||||
var container = new VisualElement { name = "item-container" };
|
||||
container.AddToClassList("utk-shortcut-list-item");
|
||||
|
||||
var cmd = new UTKLabel { name = "command-label" };
|
||||
cmd.AddToClassList("utk-shortcut-list-item__command");
|
||||
container.Add(cmd);
|
||||
|
||||
foreach (var nm in new[] { "ctrl-checkbox", "shift-checkbox", "alt-checkbox" })
|
||||
{
|
||||
var cb = new UTKCheckBox { name = nm };
|
||||
cb.AddToClassList("utk-shortcut-list-item__modifier");
|
||||
container.Add(cb);
|
||||
}
|
||||
|
||||
var key = new UTKInputField { name = "key-field" };
|
||||
key.AddToClassList("utk-shortcut-list-item__key");
|
||||
container.Add(key);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 데이터를 VisualElement 에 바인딩합니다.
|
||||
/// 이전 콜백을 해제하고 새 콜백을 등록하여 중복 이벤트를 방지합니다.
|
||||
/// </summary>
|
||||
private void BindItem(VisualElement element, int index)
|
||||
{
|
||||
if (index < 0 || index >= _filteredItems.Count) return;
|
||||
|
||||
// ListView 가 내부적으로 flex-grow: 0 을 인라인으로 강제하므로 덮어씁니다.
|
||||
element.style.flexGrow = 1;
|
||||
|
||||
var data = _filteredItems[index];
|
||||
|
||||
// 요소 참조 획득
|
||||
var root = element.Q<VisualElement>("item-container");
|
||||
var cmdLabel = root?.Q<UTKLabel>("command-label");
|
||||
var ctrlBox = root?.Q<UTKCheckBox>("ctrl-checkbox");
|
||||
var shiftBox = root?.Q<UTKCheckBox>("shift-checkbox");
|
||||
var altBox = root?.Q<UTKCheckBox>("alt-checkbox");
|
||||
var keyField = root?.Q<UTKInputField>("key-field");
|
||||
|
||||
if (cmdLabel == null || ctrlBox == null || shiftBox == null || altBox == null || keyField == null)
|
||||
{
|
||||
Debug.LogWarning("[UTKShortcutList] BindItem: 일부 자식 요소를 찾을 수 없습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 이전 바인딩 해제
|
||||
CleanupItemCallbacks(element);
|
||||
|
||||
// ── 값 설정 (notify: false → 이벤트 미발생) ──────────────
|
||||
cmdLabel.Text = data.CommandName;
|
||||
ctrlBox.SetChecked(data.UseCtrl, notify: false);
|
||||
shiftBox.SetChecked(data.UseShift, notify: false);
|
||||
altBox.SetChecked(data.UseAlt, notify: false);
|
||||
keyField.SetValue(data.Key, notify: false);
|
||||
keyField.isReadOnly = true; // 직접 타이핑 방지 (캡처만으로 설정)
|
||||
|
||||
// ── 수정자 키 체크박스 콜백 ──────────────────────────────
|
||||
Action<bool> onCtrl = v => { data.UseCtrl = v; OnDataChanged?.Invoke(data); };
|
||||
Action<bool> onShift = v => { data.UseShift = v; OnDataChanged?.Invoke(data); };
|
||||
Action<bool> onAlt = v => { data.UseAlt = v; OnDataChanged?.Invoke(data); };
|
||||
|
||||
ctrlBox.OnValueChanged += onCtrl;
|
||||
shiftBox.OnValueChanged += onShift;
|
||||
altBox.OnValueChanged += onAlt;
|
||||
|
||||
// ── Key 캡처 콜백 ─────────────────────────────────────────
|
||||
var capture = new KeyCaptureState(data, keyField);
|
||||
|
||||
EventCallback<FocusInEvent> onFocusIn = _ => capture.StartCapture();
|
||||
EventCallback<KeyDownEvent> onKeyDown = evt => capture.HandleKeyDown(evt, () => OnDataChanged?.Invoke(data));
|
||||
EventCallback<FocusOutEvent> onFocusOut = _ => capture.CancelCapture();
|
||||
|
||||
keyField.RegisterCallback(onFocusIn);
|
||||
// TrickleDown 으로 등록 → 내부 TextElement 가 키 이벤트를 받기 전에 가로채기
|
||||
keyField.RegisterCallback(onKeyDown, TrickleDown.TrickleDown);
|
||||
keyField.RegisterCallback(onFocusOut);
|
||||
|
||||
// 해제 정보 저장
|
||||
element.userData = new ShortcutItemCallbackInfo(
|
||||
ctrlBox, shiftBox, altBox, keyField,
|
||||
onCtrl, onShift, onAlt,
|
||||
onFocusIn, onKeyDown, onFocusOut);
|
||||
}
|
||||
|
||||
/// <summary>가상화 재사용 전 콜백 정리.</summary>
|
||||
private void UnbindItem(VisualElement element, int index)
|
||||
{
|
||||
CleanupItemCallbacks(element);
|
||||
}
|
||||
|
||||
/// <summary>element.userData 에 저장된 모든 이벤트 콜백을 해제합니다.</summary>
|
||||
private static void CleanupItemCallbacks(VisualElement element)
|
||||
{
|
||||
if (element.userData is not ShortcutItemCallbackInfo info) return;
|
||||
|
||||
info.CtrlBox.OnValueChanged -= info.OnCtrlHandler;
|
||||
info.ShiftBox.OnValueChanged -= info.OnShiftHandler;
|
||||
info.AltBox.OnValueChanged -= info.OnAltHandler;
|
||||
|
||||
info.KeyField.UnregisterCallback(info.OnFocusIn);
|
||||
info.KeyField.UnregisterCallback(info.OnKeyDown, TrickleDown.TrickleDown);
|
||||
info.KeyField.UnregisterCallback(info.OnFocusOut);
|
||||
|
||||
element.userData = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// 단축키 목록을 설정하고 ListView 를 갱신합니다.
|
||||
/// </summary>
|
||||
/// <param name="items">표시할 단축키 데이터 목록.</param>
|
||||
public void SetData(List<UTKShortcutItemData> items)
|
||||
{
|
||||
_allItems = items ?? new List<UTKShortcutItemData>();
|
||||
OnSearch(_searchField?.value ?? string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 단축키 목록(원본 전체)을 반환합니다.
|
||||
/// </summary>
|
||||
/// <returns>복사본 목록.</returns>
|
||||
public List<UTKShortcutItemData> GetData() => new(_allItems);
|
||||
|
||||
/// <summary>
|
||||
/// ListView 를 강제로 새로고침합니다.
|
||||
/// 외부에서 데이터를 직접 변경한 후 호출하세요.
|
||||
/// </summary>
|
||||
public void RefreshItems() => _listView?.RefreshItems();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Types
|
||||
|
||||
/// <summary>
|
||||
/// Key 캡처 상태 관리.
|
||||
/// FocusIn → 캡처 시작, KeyDown → 키 저장, FocusOut/Escape → 취소.
|
||||
/// </summary>
|
||||
private sealed class KeyCaptureState
|
||||
{
|
||||
private readonly UTKShortcutItemData _data;
|
||||
private readonly UTKInputField _keyField;
|
||||
private bool _isCapturing;
|
||||
private string _originalKey = "";
|
||||
|
||||
/// <summary>캡처 대기 중 표시 문자열.</summary>
|
||||
private const string CAPTURE_PLACEHOLDER = "···";
|
||||
|
||||
public KeyCaptureState(UTKShortcutItemData data, UTKInputField keyField)
|
||||
{
|
||||
_data = data;
|
||||
_keyField = keyField;
|
||||
}
|
||||
|
||||
/// <summary>캡처 모드 진입 – 대기 표시 문자열로 교체.</summary>
|
||||
public void StartCapture()
|
||||
{
|
||||
if (_isCapturing) return;
|
||||
|
||||
_isCapturing = true;
|
||||
_originalKey = _data.Key;
|
||||
|
||||
_keyField.AddToClassList("utk-shortcut-list-item__key--capturing");
|
||||
_keyField.SetValue(CAPTURE_PLACEHOLDER, notify: false);
|
||||
}
|
||||
|
||||
/// <summary>캡처 취소 – 원래 값 복원.</summary>
|
||||
public void CancelCapture()
|
||||
{
|
||||
if (!_isCapturing) return;
|
||||
|
||||
_isCapturing = false;
|
||||
_keyField.RemoveFromClassList("utk-shortcut-list-item__key--capturing");
|
||||
_keyField.SetValue(_originalKey, notify: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// KeyDown 이벤트 처리.
|
||||
/// <list type="bullet">
|
||||
/// <item>Escape → 캡처 취소.</item>
|
||||
/// <item>수정자 키(Ctrl/Shift/Alt 단독) → 무시.</item>
|
||||
/// <item>그 외 → 키 이름 저장 후 캡처 종료.</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public void HandleKeyDown(KeyDownEvent evt, Action onChanged)
|
||||
{
|
||||
if (!_isCapturing) return;
|
||||
|
||||
var code = evt.keyCode;
|
||||
|
||||
// Escape: 취소
|
||||
if (code == KeyCode.Escape)
|
||||
{
|
||||
evt.StopImmediatePropagation();
|
||||
CancelCapture();
|
||||
_keyField.Blur();
|
||||
return;
|
||||
}
|
||||
|
||||
// 수정자 키 단독 입력 → 무시 (Ctrl/Shift/Alt 체크박스로 설정)
|
||||
if (code is KeyCode.LeftControl or KeyCode.RightControl or
|
||||
KeyCode.LeftShift or KeyCode.RightShift or
|
||||
KeyCode.LeftAlt or KeyCode.RightAlt or
|
||||
KeyCode.LeftCommand or KeyCode.RightCommand or
|
||||
KeyCode.None)
|
||||
return;
|
||||
|
||||
// 키 저장
|
||||
_isCapturing = false;
|
||||
_keyField.RemoveFromClassList("utk-shortcut-list-item__key--capturing");
|
||||
|
||||
var keyName = ResolveKeyName(code, evt.character);
|
||||
_data.Key = keyName;
|
||||
_keyField.SetValue(keyName, notify: false);
|
||||
|
||||
onChanged.Invoke();
|
||||
|
||||
evt.StopImmediatePropagation();
|
||||
evt.PreventDefault();
|
||||
|
||||
_keyField.Blur();
|
||||
}
|
||||
|
||||
/// <summary>KeyCode → 표시 문자열 변환.</summary>
|
||||
private static string ResolveKeyName(KeyCode code, char character)
|
||||
{
|
||||
return code switch
|
||||
{
|
||||
KeyCode.Delete => "Delete",
|
||||
KeyCode.Backspace => "Backspace",
|
||||
KeyCode.Return => "Enter",
|
||||
KeyCode.KeypadEnter => "Enter",
|
||||
KeyCode.Tab => "Tab",
|
||||
KeyCode.Space => "Space",
|
||||
KeyCode.Insert => "Insert",
|
||||
KeyCode.Home => "Home",
|
||||
KeyCode.End => "End",
|
||||
KeyCode.PageUp => "PgUp",
|
||||
KeyCode.PageDown => "PgDn",
|
||||
KeyCode.UpArrow => "↑",
|
||||
KeyCode.DownArrow => "↓",
|
||||
KeyCode.LeftArrow => "←",
|
||||
KeyCode.RightArrow => "→",
|
||||
KeyCode.F1 => "F1",
|
||||
KeyCode.F2 => "F2",
|
||||
KeyCode.F3 => "F3",
|
||||
KeyCode.F4 => "F4",
|
||||
KeyCode.F5 => "F5",
|
||||
KeyCode.F6 => "F6",
|
||||
KeyCode.F7 => "F7",
|
||||
KeyCode.F8 => "F8",
|
||||
KeyCode.F9 => "F9",
|
||||
KeyCode.F10 => "F10",
|
||||
KeyCode.F11 => "F11",
|
||||
KeyCode.F12 => "F12",
|
||||
KeyCode.Keypad0 => "Num0",
|
||||
KeyCode.Keypad1 => "Num1",
|
||||
KeyCode.Keypad2 => "Num2",
|
||||
KeyCode.Keypad3 => "Num3",
|
||||
KeyCode.Keypad4 => "Num4",
|
||||
KeyCode.Keypad5 => "Num5",
|
||||
KeyCode.Keypad6 => "Num6",
|
||||
KeyCode.Keypad7 => "Num7",
|
||||
KeyCode.Keypad8 => "Num8",
|
||||
KeyCode.Keypad9 => "Num9",
|
||||
KeyCode.KeypadPlus => "Num+",
|
||||
KeyCode.KeypadMinus => "Num-",
|
||||
KeyCode.KeypadMultiply => "Num*",
|
||||
KeyCode.KeypadDivide => "Num/",
|
||||
KeyCode.KeypadPeriod => "Num.",
|
||||
// 일반 문자 키 – character 우선, 없으면 KeyCode.ToString()
|
||||
_ => character != '\0' && !char.IsControl(character)
|
||||
? character.ToString().ToUpperInvariant()
|
||||
: code.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 바인딩 시 등록하는 이벤트 콜백 참조 보관.
|
||||
/// UnbindItem 에서 정확히 해제하기 위해 사용합니다.
|
||||
/// </summary>
|
||||
private sealed class ShortcutItemCallbackInfo
|
||||
{
|
||||
public readonly UTKCheckBox CtrlBox;
|
||||
public readonly UTKCheckBox ShiftBox;
|
||||
public readonly UTKCheckBox AltBox;
|
||||
public readonly UTKInputField KeyField;
|
||||
|
||||
public readonly Action<bool> OnCtrlHandler;
|
||||
public readonly Action<bool> OnShiftHandler;
|
||||
public readonly Action<bool> OnAltHandler;
|
||||
|
||||
public readonly EventCallback<FocusInEvent> OnFocusIn;
|
||||
public readonly EventCallback<KeyDownEvent> OnKeyDown;
|
||||
public readonly EventCallback<FocusOutEvent> OnFocusOut;
|
||||
|
||||
public ShortcutItemCallbackInfo(
|
||||
UTKCheckBox ctrl, UTKCheckBox shift, UTKCheckBox alt, UTKInputField key,
|
||||
Action<bool> onCtrl, Action<bool> onShift, Action<bool> onAlt,
|
||||
EventCallback<FocusInEvent> onFocusIn,
|
||||
EventCallback<KeyDownEvent> onKeyDown,
|
||||
EventCallback<FocusOutEvent> onFocusOut)
|
||||
{
|
||||
CtrlBox = ctrl; ShiftBox = shift; AltBox = alt; KeyField = key;
|
||||
OnCtrlHandler = onCtrl; OnShiftHandler = onShift; OnAltHandler = onAlt;
|
||||
OnFocusIn = onFocusIn;
|
||||
OnKeyDown = onKeyDown;
|
||||
OnFocusOut = onFocusOut;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
|
||||
// 테마 구독 해제
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
||||
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
||||
|
||||
// 검색 필드 이벤트 해제
|
||||
if (_searchField != null)
|
||||
_searchField.OnSubmit -= OnSearch;
|
||||
|
||||
// Clear 버튼 이벤트 해제
|
||||
if (_clearButton != null)
|
||||
_clearButton.OnClicked -= OnClearButtonClicked;
|
||||
|
||||
// ListView 정리
|
||||
_listView?.Dispose();
|
||||
|
||||
// 이벤트 · 캐시 정리
|
||||
OnDataChanged = null;
|
||||
_itemTemplate = null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 700c91f027891a64c8d4a91e4107a90d
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit.Util;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
@@ -478,6 +479,7 @@ namespace UVC.UIToolkit
|
||||
okBtn.OnClicked += Close;
|
||||
_buttonContainer.Add(okBtn);
|
||||
}
|
||||
UTKChildAnnotator.AnnotateChild(_buttonContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit.Util;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
@@ -21,8 +23,8 @@ namespace UVC.UIToolkit
|
||||
/// // 초기화 (root 설정 필요)
|
||||
/// UTKModal.SetRoot(rootVisualElement);
|
||||
///
|
||||
/// // Static Factory로 표시
|
||||
/// var modal = UTKModal.Show("설정", UTKModal.ModalSize.Medium);
|
||||
/// // Static Factory로 생성
|
||||
/// var modal = UTKModal.Create("설정", UTKModal.ModalSize.Medium);
|
||||
/// modal.OnClosed += () => Debug.Log("모달 닫힘");
|
||||
///
|
||||
/// // 콘텐츠 추가
|
||||
@@ -32,6 +34,37 @@ namespace UVC.UIToolkit
|
||||
/// // 푸터에 버튼 추가
|
||||
/// modal.AddToFooter(new UTKButton("확인", "", UTKButton.ButtonVariant.Primary));
|
||||
/// modal.AddToFooter(new UTKButton("취소", "", UTKButton.ButtonVariant.Normal));
|
||||
///
|
||||
/// // 화면에 표시
|
||||
/// modal.Show();
|
||||
/// </code>
|
||||
/// <para><b>Async/Await 방식:</b></para>
|
||||
/// <code>
|
||||
/// // 모달이 닫힐 때까지 대기
|
||||
/// var modal = UTKModal.Create("설정", UTKModal.ModalSize.Medium);
|
||||
/// modal.Add(new Label("모달 내용"));
|
||||
///
|
||||
/// var closeBtn = new UTKButton("닫기", "", UTKButton.ButtonVariant.Primary);
|
||||
/// closeBtn.OnClicked += () => modal.Close();
|
||||
/// modal.AddToFooter(closeBtn);
|
||||
///
|
||||
/// await modal.ShowAsync();
|
||||
/// Debug.Log("모달이 닫혔습니다.");
|
||||
/// </code>
|
||||
/// <para><b>Async/Await + IUTKModalContent 방식:</b></para>
|
||||
/// <code>
|
||||
/// // IUTKModalContent<T> 구현 콘텐츠에서 결과 반환
|
||||
/// var modal = UTKModal.Create("사용자 정보", UTKModal.ModalSize.Medium);
|
||||
/// var form = new UserFormContent(); // VisualElement + IUTKModalContent<UserData>
|
||||
/// modal.Add(form);
|
||||
///
|
||||
/// var submitBtn = new UTKButton("제출", "", UTKButton.ButtonVariant.Primary);
|
||||
/// submitBtn.OnClicked += () => modal.Close();
|
||||
/// modal.AddToFooter(submitBtn);
|
||||
///
|
||||
/// UserData? result = await modal.ShowAsync<UserData>();
|
||||
/// if (result != null)
|
||||
/// Debug.Log($"이름: {result.Name}");
|
||||
/// </code>
|
||||
/// <para><b>UXML에서 사용:</b></para>
|
||||
/// <code>
|
||||
@@ -42,6 +75,41 @@ namespace UVC.UIToolkit
|
||||
/// </ui:UXML>
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <summary>
|
||||
/// 모달 콘텐츠 인터페이스.
|
||||
/// VisualElement를 상속한 클래스에서 구현하여 모달의 결과 값을 반환합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">결과 타입</typeparam>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public class UserFormContent : VisualElement, IUTKModalContent<UserData>
|
||||
/// {
|
||||
/// private UTKInputField _nameField;
|
||||
/// private UTKInputField _emailField;
|
||||
///
|
||||
/// public UserFormContent()
|
||||
/// {
|
||||
/// _nameField = new UTKInputField("이름");
|
||||
/// _emailField = new UTKInputField("이메일");
|
||||
/// Add(_nameField);
|
||||
/// Add(_emailField);
|
||||
/// }
|
||||
///
|
||||
/// public UserData? GetResult()
|
||||
/// {
|
||||
/// return new UserData(_nameField.value, _emailField.value);
|
||||
/// }
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
public interface IUTKModalContent<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 모달 닫힐 때 호출되어 결과 값을 반환합니다.
|
||||
/// </summary>
|
||||
T? GetResult();
|
||||
}
|
||||
|
||||
[UxmlElement]
|
||||
public partial class UTKModal : VisualElement, IDisposable
|
||||
{
|
||||
@@ -64,6 +132,9 @@ namespace UVC.UIToolkit
|
||||
private bool _showCloseButton = true;
|
||||
private bool _closeOnBackdropClick = false;
|
||||
private ModalSize _size = ModalSize.Medium;
|
||||
|
||||
private UniTaskCompletionSource? _closeTcs;
|
||||
private Action? _onCloseResultHandler;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -104,7 +175,12 @@ namespace UVC.UIToolkit
|
||||
public bool CloseOnBackdropClick
|
||||
{
|
||||
get => _closeOnBackdropClick;
|
||||
set => _closeOnBackdropClick = value;
|
||||
set
|
||||
{
|
||||
if (_closeOnBackdropClick == value) return;
|
||||
_closeOnBackdropClick = value;
|
||||
UpdateBackdropClickHandler();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>모달 크기</summary>
|
||||
@@ -186,31 +262,12 @@ namespace UVC.UIToolkit
|
||||
|
||||
#region Static Factory
|
||||
/// <summary>
|
||||
/// 모달 표시
|
||||
/// 모달 생성 (화면에 표시하려면 Show() 호출 필요)
|
||||
/// </summary>
|
||||
public static UTKModal Show(string title, ModalSize size = ModalSize.Medium)
|
||||
public static UTKModal Create(string title, ModalSize size = ModalSize.Medium)
|
||||
{
|
||||
ValidateRoot();
|
||||
|
||||
var modal = new UTKModal(title, size);
|
||||
|
||||
modal._blocker = UTKModalBlocker.Show(_root!, 0.5f, false);
|
||||
if (modal._closeOnBackdropClick)
|
||||
{
|
||||
modal._blocker.OnBlockerClicked += modal.Close;
|
||||
}
|
||||
|
||||
// panel.visualTree에 직접 추가
|
||||
var root = _root!.panel?.visualTree ?? _root!;
|
||||
root.Add(modal);
|
||||
|
||||
// 중앙 정렬
|
||||
modal.style.position = Position.Absolute;
|
||||
modal.style.left = Length.Percent(50);
|
||||
modal.style.top = Length.Percent(50);
|
||||
modal.style.translate = new Translate(Length.Percent(-50), Length.Percent(-50));
|
||||
|
||||
return modal;
|
||||
return new UTKModal(title, size);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -305,6 +362,22 @@ namespace UVC.UIToolkit
|
||||
AddToClassList(sizeClass);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// backdrop 클릭 핸들러 등록/해제
|
||||
/// </summary>
|
||||
private void UpdateBackdropClickHandler()
|
||||
{
|
||||
if (_blocker == null) return;
|
||||
|
||||
// 항상 먼저 해제하여 중복 등록 방지
|
||||
_blocker.OnBlockerClicked -= Close;
|
||||
|
||||
if (_closeOnBackdropClick)
|
||||
{
|
||||
_blocker.OnBlockerClicked += Close;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠 추가
|
||||
/// </summary>
|
||||
@@ -319,6 +392,8 @@ namespace UVC.UIToolkit
|
||||
public void AddToFooter(VisualElement element)
|
||||
{
|
||||
_footer?.Add(element);
|
||||
_footer?.AddToClassList("utk-modal__footer--has-children");
|
||||
UTKChildAnnotator.AnnotateChild(_footer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -332,11 +407,81 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모달을 화면에 표시
|
||||
/// </summary>
|
||||
public void Show()
|
||||
{
|
||||
ValidateRoot();
|
||||
|
||||
_blocker = UTKModalBlocker.Show(_root!, 0.5f, false);
|
||||
UpdateBackdropClickHandler();
|
||||
|
||||
// panel.visualTree에 직접 추가
|
||||
var root = _root!.panel?.visualTree ?? _root!;
|
||||
root.Add(this);
|
||||
|
||||
// 중앙 정렬
|
||||
style.position = Position.Absolute;
|
||||
style.left = Length.Percent(50);
|
||||
style.top = Length.Percent(50);
|
||||
style.translate = new Translate(Length.Percent(-50), Length.Percent(-50));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모달을 화면에 표시하고 닫힐 때까지 대기
|
||||
/// </summary>
|
||||
public UniTask ShowAsync()
|
||||
{
|
||||
_closeTcs = new UniTaskCompletionSource();
|
||||
Show();
|
||||
return _closeTcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모달을 화면에 표시하고 닫힐 때 IUTKModalContent<T> 콘텐츠의 결과를 반환.
|
||||
/// Add()된 자식 중 IUTKModalContent<T>를 구현한 첫 번째 요소에서 GetResult()를 호출합니다.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">결과 타입</typeparam>
|
||||
/// <returns>콘텐츠의 결과 값. 콘텐츠가 없으면 default(T)</returns>
|
||||
public UniTask<T?> ShowAsync<T>() where T : class
|
||||
{
|
||||
var tcs = new UniTaskCompletionSource<T?>();
|
||||
|
||||
_onCloseResultHandler = () =>
|
||||
{
|
||||
var result = FindModalContent<T>()?.GetResult();
|
||||
tcs.TrySetResult(result);
|
||||
};
|
||||
|
||||
Show();
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 콘텐츠에서 IUTKModalContent<T>를 구현한 첫 번째 요소를 찾습니다.
|
||||
/// </summary>
|
||||
private IUTKModalContent<T>? FindModalContent<T>() where T : class
|
||||
{
|
||||
if (_content == null) return null;
|
||||
|
||||
for (int i = 0; i < _content.childCount; i++)
|
||||
{
|
||||
if (_content[i] is IUTKModalContent<T> modalContent)
|
||||
return modalContent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모달 닫기
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
// 결과 핸들러 먼저 호출 (ShowAsync<T> 사용 시)
|
||||
_onCloseResultHandler?.Invoke();
|
||||
_onCloseResultHandler = null;
|
||||
|
||||
OnClosed?.Invoke();
|
||||
RemoveFromHierarchy();
|
||||
if (_blocker != null)
|
||||
@@ -345,6 +490,8 @@ namespace UVC.UIToolkit
|
||||
_blocker.Hide();
|
||||
}
|
||||
_blocker = null;
|
||||
_closeTcs?.TrySetResult();
|
||||
_closeTcs = null;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UVC.UIToolkit.Util;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
@@ -411,6 +412,7 @@ namespace UVC.UIToolkit
|
||||
btn.Size = UTKButton.ButtonSize.Small;
|
||||
btn.OnClicked += () => OnActionClicked?.Invoke(actionId);
|
||||
_actions.Add(btn);
|
||||
UTKChildAnnotator.AnnotateChild(_actions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
18
Assets/Scripts/UVC/UIToolkit/Tab/IUTKTabContent.cs
Normal file
18
Assets/Scripts/UVC/UIToolkit/Tab/IUTKTabContent.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
#nullable enable
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace UVC.UIToolkit
|
||||
{
|
||||
/// <summary>
|
||||
/// 탭 콘텐츠 인터페이스.
|
||||
/// UTKTabView에서 탭 전환 시 자동으로 Show/Hide를 호출합니다.
|
||||
/// </summary>
|
||||
public interface IUTKTabContent
|
||||
{
|
||||
/// <summary>탭이 선택되어 표시될 때 호출</summary>
|
||||
void Show(object? data);
|
||||
|
||||
/// <summary>탭이 선택 해제되어 숨겨질 때 호출</summary>
|
||||
UniTask Hide();
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/UIToolkit/Tab/IUTKTabContent.cs.meta
Normal file
2
Assets/Scripts/UVC/UIToolkit/Tab/IUTKTabContent.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f5401783ae68ec49b0dca98d448e209
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
@@ -126,6 +127,10 @@ namespace UVC.UIToolkit
|
||||
private bool _disposed;
|
||||
private readonly List<UTKTab> _utkTabs = new();
|
||||
private TabAlign _align = TabAlign.Top;
|
||||
private float _tabWidth = 0;
|
||||
private float _tabHeight = 0;
|
||||
private VisualElement? _contentViewport;
|
||||
private int _previousTabIndex = -1;
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
@@ -156,6 +161,30 @@ namespace UVC.UIToolkit
|
||||
ApplyAlignment();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>탭 콘텐츠 영역 너비 (0 이하이면 미설정)</summary>
|
||||
[UxmlAttribute("tab-width")]
|
||||
public float TabWidth
|
||||
{
|
||||
get => _tabWidth;
|
||||
set
|
||||
{
|
||||
_tabWidth = value;
|
||||
ApplyContentViewportSize();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>탭 콘텐츠 영역 높이 (0 이하이면 미설정)</summary>
|
||||
[UxmlAttribute("tab-height")]
|
||||
public float TabHeight
|
||||
{
|
||||
get => _tabHeight;
|
||||
set
|
||||
{
|
||||
_tabHeight = value;
|
||||
ApplyContentViewportSize();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -185,6 +214,7 @@ namespace UVC.UIToolkit
|
||||
private void SetupEvents()
|
||||
{
|
||||
this.RegisterCallback<ChangeEvent<int>>(OnTabIndexChanged);
|
||||
activeTabChanged += OnActiveTabChanged;
|
||||
}
|
||||
|
||||
private void SubscribeToThemeChanges()
|
||||
@@ -213,12 +243,71 @@ namespace UVC.UIToolkit
|
||||
#endregion
|
||||
|
||||
#region Event Handlers
|
||||
/// <summary>
|
||||
/// 코드에서 selectedTabIndex를 변경했을 때 호출됩니다.
|
||||
/// </summary>
|
||||
private void OnTabIndexChanged(ChangeEvent<int> evt)
|
||||
{
|
||||
_previousTabIndex = evt.previousValue;
|
||||
NotifyTabContent(evt.previousValue, evt.newValue).Forget();
|
||||
UpdateTabSelection();
|
||||
OnTabChanged?.Invoke(evt.newValue, activeTab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마우스 클릭 등으로 탭이 변경되었을 때 호출됩니다.
|
||||
/// </summary>
|
||||
private void OnActiveTabChanged(Tab previousTab, Tab newTab)
|
||||
{
|
||||
int prevIndex = previousTab != null ? _utkTabs.FindIndex(t => t == previousTab) : -1;
|
||||
int newIndex = newTab != null ? _utkTabs.FindIndex(t => t == newTab) : -1;
|
||||
|
||||
// ChangeEvent<int>와 중복 호출 방지
|
||||
if (prevIndex == _previousTabIndex && newIndex == selectedTabIndex)
|
||||
return;
|
||||
|
||||
_previousTabIndex = prevIndex;
|
||||
NotifyTabContent(prevIndex, newIndex).Forget();
|
||||
UpdateTabSelection();
|
||||
OnTabChanged?.Invoke(newIndex, newTab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이전 탭 콘텐츠의 Hide, 새 탭 콘텐츠의 Show를 호출합니다.
|
||||
/// </summary>
|
||||
private async UniTaskVoid NotifyTabContent(int previousIndex, int newIndex)
|
||||
{
|
||||
// 이전 탭 Hide
|
||||
if (previousIndex >= 0 && previousIndex < _utkTabs.Count)
|
||||
{
|
||||
if (FindTabContent(_utkTabs[previousIndex]) is IUTKTabContent prevContent)
|
||||
{
|
||||
await prevContent.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
// 새 탭 Show
|
||||
if (newIndex >= 0 && newIndex < _utkTabs.Count)
|
||||
{
|
||||
if (FindTabContent(_utkTabs[newIndex]) is IUTKTabContent newContent)
|
||||
{
|
||||
newContent.Show(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 탭 내부에서 IUTKTabContent를 구현한 자식 요소를 찾습니다.
|
||||
/// </summary>
|
||||
public static IUTKTabContent? FindTabContent(Tab tab)
|
||||
{
|
||||
for (int i = 0; i < tab.childCount; i++)
|
||||
{
|
||||
if (tab[i] is IUTKTabContent content) return content;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void UpdateTabSelection()
|
||||
{
|
||||
for (int i = 0; i < _utkTabs.Count; i++)
|
||||
@@ -258,6 +347,23 @@ namespace UVC.UIToolkit
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 탭 콘텐츠 영역 크기 적용
|
||||
/// </summary>
|
||||
private void ApplyContentViewportSize()
|
||||
{
|
||||
_contentViewport ??= this.Q(className: "unity-tab-view__content-viewport");
|
||||
if (_contentViewport == null) return;
|
||||
|
||||
_contentViewport.style.width = _tabWidth > 0
|
||||
? new StyleLength(_tabWidth)
|
||||
: new StyleLength(StyleKeyword.Auto);
|
||||
|
||||
_contentViewport.style.height = _tabHeight > 0
|
||||
? new StyleLength(_tabHeight)
|
||||
: new StyleLength(StyleKeyword.Auto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UTK 탭 추가
|
||||
/// </summary>
|
||||
@@ -322,6 +428,8 @@ namespace UVC.UIToolkit
|
||||
UTKThemeManager.Instance.OnThemeChanged -= OnThemeChanged;
|
||||
UnregisterCallback<AttachToPanelEvent>(OnAttachToPanelForTheme);
|
||||
UnregisterCallback<DetachFromPanelEvent>(OnDetachFromPanelForTheme);
|
||||
UnregisterCallback<ChangeEvent<int>>(OnTabIndexChanged);
|
||||
activeTabChanged -= OnActiveTabChanged;
|
||||
|
||||
foreach (var tab in _utkTabs)
|
||||
{
|
||||
|
||||
8
Assets/Scripts/UVC/UIToolkit/Util.meta
Normal file
8
Assets/Scripts/UVC/UIToolkit/Util.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad370f2935493cc439100336b1d0c334
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Assets/Scripts/UVC/UIToolkit/Util/UTKChildAnnotator.cs
Normal file
41
Assets/Scripts/UVC/UIToolkit/Util/UTKChildAnnotator.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Linq;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UVC.UIToolkit.Util
|
||||
{
|
||||
/// <summary>
|
||||
/// UTKChildAnnotator는 VisualElement의 자식 요소들을 순회하며 첫 번째 자식에게 "first-child" 클래스를, 마지막 자식에게 "last-child" 클래스를 추가하는 유틸리티 클래스입니다.
|
||||
/// </summary>
|
||||
public static class UTKChildAnnotator
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 VisualElement의 자식 요소들을 순회하며 첫 번째 자식에게는 "first-child" 클래스를, 마지막 자식에게는 "last-child" 클래스를 추가합니다.
|
||||
/// </summary>
|
||||
/// <param name="parent">클래스를 추가할 VisualElement 부모 요소</param>
|
||||
public static void AnnotateChild(VisualElement parent)
|
||||
{
|
||||
var children = parent.Children().ToList();
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (i == 0)
|
||||
{
|
||||
child.RemoveFromClassList("first-child");
|
||||
child.AddToClassList("first-child");
|
||||
}
|
||||
else if (i == children.Count - 1)
|
||||
{
|
||||
child.RemoveFromClassList("last-child");
|
||||
child.AddToClassList("last-child");
|
||||
}
|
||||
else
|
||||
{
|
||||
child.RemoveFromClassList("first-child");
|
||||
child.RemoveFromClassList("last-child");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26dda967fd6d0d84bb1f40f8a6ad9c5f
|
||||
11
CLAUDE.md
11
CLAUDE.md
@@ -5,6 +5,17 @@
|
||||
|
||||
---
|
||||
|
||||
## 0) 작업 진행 규칙
|
||||
|
||||
**⚠️ 최우선 규칙: 임의로 진행하지 않고, 반드시 사용자에게 확인 후 진행합니다.**
|
||||
|
||||
- 코드 수정, 파일 생성/삭제, 리팩토링 등 **모든 변경 작업은 사전에 계획을 설명하고 승인을 받은 후** 진행합니다.
|
||||
- 요구사항이 모호하거나 여러 접근 방식이 가능한 경우, **추측하지 말고 질문**합니다.
|
||||
- 버그 수정이라도 원인 분석 결과를 먼저 공유하고, 수정 방향에 대해 합의 후 코드를 변경합니다.
|
||||
- 단순한 오타 수정, 한 줄 변경 등 **명백하고 사소한 작업**만 즉시 진행할 수 있습니다.
|
||||
|
||||
---
|
||||
|
||||
## 1) 핵심 원칙
|
||||
|
||||
### UI 프레임워크
|
||||
|
||||
Reference in New Issue
Block a user