From e1f2ac5b027f59f7c83821d1ad9a25861a5d9fce Mon Sep 17 00:00:00 2001 From: logonkhi Date: Mon, 12 Jan 2026 20:16:17 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EA=B0=80?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=EC=A0=81=EC=9A=A9=20=EC=99=84=EB=A3=8C.?= =?UTF-8?q?=20UTKCOlorPicker,=20UTKDatePicker=20=ED=99=95=EC=9D=B8?= =?UTF-8?q?=ED=95=B4=EC=95=BC=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 4 +- .../Resources/UIToolkit/Common/UTKFoldout.uss | 22 +- .../UIToolkit/Common/UTKScrollView.uss | 71 +-- .../Resources/UIToolkit/Common/UTKTooltip.uss | 2 +- .../UIToolkit/List/UTKAccordionList.uss | 13 - .../Resources/UIToolkit/List/UTKImageList.uss | 13 - .../Resources/UIToolkit/List/UTKListView.uss | 42 +- .../UIToolkit/List/UTKMultiColumnListView.uss | 39 +- .../UIToolkit/List/UTKMultiColumnTreeView.uss | 42 +- .../Resources/UIToolkit/List/UTKTreeView.uss | 51 +- Assets/Resources/UIToolkit/Modal/UTKAlert.uss | 124 ++-- .../UIToolkit/Modal/UTKColorPicker.uss | 1 + .../UIToolkit/Modal/UTKDatePicker.uss | 61 ++ Assets/Resources/UIToolkit/Modal/UTKPanel.uss | 5 + Assets/Resources/UIToolkit/Modal/UTKToast.uss | 54 +- .../UIToolkit/Style/UTKDefaultStyle.uss | 32 +- .../UIToolkit/Style/UTKThemeDark.uss | 2 + .../UIToolkit/Style/UTKThemeLight.uss | 2 + Assets/Resources/UIToolkit/Tab/UTKTab.uss | 95 ---- .../Resources/UIToolkit/Tab/UTKTab.uss.meta | 11 - Assets/Resources/UIToolkit/Tab/UTKTabView.uss | 63 ++- .../Sample/UIToolkit/UTKColorPickerSample.cs | 33 +- .../Sample/UIToolkit/UTKDatePickerSample.cs | 133 +++++ .../Sample/UIToolkit/UTKStyleGuideSample.cs | 534 ++++++++++++++++-- Assets/Scripts/UVC/UIToolkit/Card/UTKCard.cs | 8 + .../UVC/UIToolkit/Common/UTKTooltipManager.cs | 18 +- .../Scripts/UVC/UIToolkit/Modal/UTKAlert.cs | 335 +++++++++-- .../UVC/UIToolkit/Modal/UTKColorPicker.cs | 181 +++++- .../UVC/UIToolkit/Modal/UTKDatePicker.cs | 419 ++++++++++++-- .../Scripts/UVC/UIToolkit/Modal/UTKToast.cs | 93 ++- Assets/Scripts/UVC/UIToolkit/Tab/UTKTab.cs | 11 - 31 files changed, 1854 insertions(+), 660 deletions(-) delete mode 100644 Assets/Resources/UIToolkit/Tab/UTKTab.uss delete mode 100644 Assets/Resources/UIToolkit/Tab/UTKTab.uss.meta diff --git a/.claude/settings.local.json b/.claude/settings.local.json index c66fbab4..98002d7a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,9 @@ "Bash(del \"d:\\\\works\\\\2025\\\\02.Studio\\\\dev\\\\base\\\\XRBase\\\\Assets\\\\Scripts\\\\UVC\\\\UIToolkit\\\\Input\\\\NumberStepper.cs\")", "Bash(del \"d:\\\\works\\\\2025\\\\02.Studio\\\\dev\\\\base\\\\XRBase\\\\Assets\\\\Scripts\\\\UVC\\\\UIToolkit\\\\Modal\\\\UTKColorPickerHSV.cs\")", "mcp__UnityMCP__read_console", - "Bash(git mv:*)" + "Bash(git mv:*)", + "Bash(del \"d:\\\\works\\\\2025\\\\02.Studio\\\\dev\\\\base\\\\XRBase\\\\Assets\\\\Resources\\\\UIToolkit\\\\Tab\\\\UTKTab.uss\")", + "Bash(cmd //c \"del /f \"\"d:\\\\works\\\\2025\\\\02.Studio\\\\dev\\\\base\\\\XRBase\\\\Assets\\\\Resources\\\\UIToolkit\\\\Tab\\\\UTKTab.uss\"\"\")" ], "deny": [], "ask": [] diff --git a/Assets/Resources/UIToolkit/Common/UTKFoldout.uss b/Assets/Resources/UIToolkit/Common/UTKFoldout.uss index 9d0befff..40c4da9d 100644 --- a/Assets/Resources/UIToolkit/Common/UTKFoldout.uss +++ b/Assets/Resources/UIToolkit/Common/UTKFoldout.uss @@ -37,16 +37,19 @@ =================================== */ .utk-foldout > .unity-foldout__toggle > .unity-toggle__input > .unity-foldout__checkmark { - width: 12px; - height: 12px; - margin-right: var(--space-s); + width: 16px; + height: 16px; -unity-background-image-tint-color: var(--color-text-secondary); transition-duration: var(--anim-fast); transition-property: rotate; + margin-top: 0; + margin-bottom: 0; + margin-left: 0; + margin-right: var(--space-xs); } .utk-foldout > .unity-foldout__toggle:checked > .unity-toggle__input > .unity-foldout__checkmark { - rotate: 90deg; + rotate: 0deg; } /* =================================== @@ -57,13 +60,15 @@ font-size: var(--font-size-body2); color: var(--color-text-primary); -unity-font-style: bold; + margin: 0; + padding: var(--space-s) 0; } /* =================================== Content Container =================================== */ -.utk-foldout > .unity-foldout__content { +.utk-foldout .unity-foldout__content { margin-top: var(--space-s); margin-left: var(--space-m); padding: var(--space-m); @@ -73,6 +78,11 @@ border-color: var(--color-border); } +.utk-foldout .unity-foldout__content { + font-size: var(--font-size-body2); + color: var(--color-text-primary); +} + /* =================================== Collapsed State - Remove content border radius on top =================================== */ @@ -82,7 +92,7 @@ border-bottom-right-radius: 0; } -.utk-foldout:checked > .unity-foldout__content { +.utk-foldout:checked .unity-foldout__content { margin-top: 0; border-top-left-radius: 0; border-top-right-radius: 0; diff --git a/Assets/Resources/UIToolkit/Common/UTKScrollView.uss b/Assets/Resources/UIToolkit/Common/UTKScrollView.uss index bdb02d7b..0daea829 100644 --- a/Assets/Resources/UIToolkit/Common/UTKScrollView.uss +++ b/Assets/Resources/UIToolkit/Common/UTKScrollView.uss @@ -29,78 +29,29 @@ padding: 0; } +/* =================================== + Content Text + =================================== */ + +.utk-scrollview .unity-label { + font-size: var(--font-size-body2); + color: var(--color-text-primary); +} + /* =================================== Vertical Scroller =================================== */ -.utk-scrollview .unity-scroller--vertical { - width: 8px; - margin-left: var(--space-xs); -} - -.utk-scrollview .unity-scroller--vertical > .unity-scroller__low-button, -.utk-scrollview .unity-scroller--vertical > .unity-scroller__high-button { - display: none; -} - .utk-scrollview .unity-scroller--vertical > .unity-scroller__slider { - width: 8px; + width: 6px; margin: 0; } -.utk-scrollview .unity-scroller--vertical .unity-base-slider__tracker { - background-color: var(--color-bg-secondary); - border-radius: 4px; - border-width: 0; -} - -.utk-scrollview .unity-scroller--vertical .unity-base-slider__dragger { - width: 8px; - border-radius: 4px; - background-color: var(--color-text-disabled); - border-width: 0; - transition-duration: var(--anim-fast); - transition-property: background-color; -} - -.utk-scrollview .unity-scroller--vertical .unity-base-slider__dragger:hover { - background-color: var(--color-text-secondary); -} - /* =================================== Horizontal Scroller =================================== */ -.utk-scrollview .unity-scroller--horizontal { - height: 8px; - margin-top: var(--space-xs); -} - -.utk-scrollview .unity-scroller--horizontal > .unity-scroller__low-button, -.utk-scrollview .unity-scroller--horizontal > .unity-scroller__high-button { - display: none; -} - .utk-scrollview .unity-scroller--horizontal > .unity-scroller__slider { - height: 8px; + height: 6px; margin: 0; } - -.utk-scrollview .unity-scroller--horizontal .unity-base-slider__tracker { - background-color: var(--color-bg-secondary); - border-radius: 4px; - border-width: 0; -} - -.utk-scrollview .unity-scroller--horizontal .unity-base-slider__dragger { - height: 8px; - border-radius: 4px; - background-color: var(--color-text-disabled); - border-width: 0; - transition-duration: var(--anim-fast); - transition-property: background-color; -} - -.utk-scrollview .unity-scroller--horizontal .unity-base-slider__dragger:hover { - background-color: var(--color-text-secondary); -} diff --git a/Assets/Resources/UIToolkit/Common/UTKTooltip.uss b/Assets/Resources/UIToolkit/Common/UTKTooltip.uss index becf1514..c3cba5cf 100644 --- a/Assets/Resources/UIToolkit/Common/UTKTooltip.uss +++ b/Assets/Resources/UIToolkit/Common/UTKTooltip.uss @@ -11,7 +11,7 @@ } .utk-tooltip-label { - color: var(--color-text-primary); + color: #FFFFFF; font-size: var(--font-size-body2); white-space: normal; -unity-text-align: middle-left; diff --git a/Assets/Resources/UIToolkit/List/UTKAccordionList.uss b/Assets/Resources/UIToolkit/List/UTKAccordionList.uss index c16487b7..0bdeaa55 100644 --- a/Assets/Resources/UIToolkit/List/UTKAccordionList.uss +++ b/Assets/Resources/UIToolkit/List/UTKAccordionList.uss @@ -71,19 +71,6 @@ background-color: transparent; } -.accordion-tree-view .unity-scroller--vertical { - width: 8px; -} - -.accordion-tree-view .unity-scroller--vertical .unity-base-slider__dragger { - background-color: var(--color-scroller-dragger-normal); - border-radius: var(--radius-m); -} - -.accordion-tree-view .unity-scroller--vertical .unity-base-slider__dragger:hover { - background-color: var(--color-scroller-dragger-hover); -} - .accordion-tree-view .unity-tree-view__item { padding: 0; margin: 0; diff --git a/Assets/Resources/UIToolkit/List/UTKImageList.uss b/Assets/Resources/UIToolkit/List/UTKImageList.uss index ca3d1652..0c3e024b 100644 --- a/Assets/Resources/UIToolkit/List/UTKImageList.uss +++ b/Assets/Resources/UIToolkit/List/UTKImageList.uss @@ -60,19 +60,6 @@ background-color: transparent; } -#main-list-view .unity-scroller--vertical { - width: 8px; -} - -#main-list-view .unity-scroller--vertical .unity-base-slider__dragger { - background-color: rgb(80, 80, 82); - border-radius: 4px; -} - -#main-list-view .unity-scroller--vertical .unity-base-slider__dragger:hover { - background-color: rgb(100, 100, 102); -} - .image-list-row { flex-direction: row; justify-content: flex-start; diff --git a/Assets/Resources/UIToolkit/List/UTKListView.uss b/Assets/Resources/UIToolkit/List/UTKListView.uss index 065f3b23..bd219176 100644 --- a/Assets/Resources/UIToolkit/List/UTKListView.uss +++ b/Assets/Resources/UIToolkit/List/UTKListView.uss @@ -30,8 +30,8 @@ =================================== */ .utk-listview .unity-list-view__item { - padding-top: var(--space-s); - padding-bottom: var(--space-s); + padding-top: 0; + padding-bottom: 0; padding-left: var(--space-m); padding-right: var(--space-m); margin: 0; @@ -40,14 +40,10 @@ transition-property: background-color; font-size: var(--font-size-body2); height: auto; - min-height: 28px; + min-height: 24px; -unity-text-align: middle-left; } -.utk-listview .unity-collection-view__item--selected { - color: var(--color-base-01); -} - /* Unity USS does not support :last-child pseudo-class */ /* .utk-listview .unity-list-view__item:last-child { border-bottom-width: 0; @@ -57,10 +53,13 @@ background-color: var(--color-btn-hover); } +/* Unity ListView uses unity-collection-view__item--selected class internally */ +.utk-listview .unity-collection-view__item--selected, .utk-listview .unity-list-view__item--selected { background-color: var(--color-btn-primary); } +.utk-listview .unity-collection-view__item--selected:hover, .utk-listview .unity-list-view__item--selected:hover { background-color: var(--color-btn-primary-hover); } @@ -78,9 +77,11 @@ padding-bottom: 0 !important; } +.utk-listview .unity-collection-view__item--selected .unity-label, +.utk-listview .unity-collection-view__item--selected .unity-text-element, .utk-listview .unity-list-view__item--selected .unity-label, .utk-listview .unity-list-view__item--selected .unity-text-element { - color: var(--color-base-01) !important; + color: var(--color-text-on-primary) !important; } /* =================================== @@ -90,31 +91,6 @@ /* Note: Alternating rows must be handled in C# code Unity USS does not support :nth-child() pseudo-class */ -/* =================================== - Scrollbar - =================================== */ - -.utk-listview .unity-scroller--vertical > .unity-scroller__low-button, -.utk-listview .unity-scroller--vertical > .unity-scroller__high-button { - display: none; -} - -.utk-listview .unity-scroller--vertical .unity-base-slider__tracker { - background-color: transparent; - border-width: 0; -} - -.utk-listview .unity-scroller--vertical .unity-base-slider__dragger { - width: 6px; - border-radius: 3px; - background-color: var(--color-text-disabled); - border-width: 0; -} - -.utk-listview .unity-scroller--vertical .unity-base-slider__dragger:hover { - background-color: var(--color-text-secondary); -} - /* =================================== Empty State =================================== */ diff --git a/Assets/Resources/UIToolkit/List/UTKMultiColumnListView.uss b/Assets/Resources/UIToolkit/List/UTKMultiColumnListView.uss index d58a68a3..ecf58849 100644 --- a/Assets/Resources/UIToolkit/List/UTKMultiColumnListView.uss +++ b/Assets/Resources/UIToolkit/List/UTKMultiColumnListView.uss @@ -46,7 +46,11 @@ .utk-multicolumn-listview .unity-multi-column-header__column-title { font-size: var(--font-size-body2); -unity-font-style: bold; - color: var(--color-text-primary); + color: var(--color-text-primary) !important; +} + +.utk-multicolumn-listview .unity-multi-column-header__column .unity-text-element { + color: var(--color-text-primary) !important; } /* =================================== @@ -92,7 +96,7 @@ =================================== */ .utk-multicolumn-listview .unity-multi-column-view__cell { - padding: var(--space-s) var(--space-m); + padding: 0 var(--space-m); border-right-width: 1px; border-right-color: var(--color-border-light); } @@ -105,6 +109,8 @@ .utk-multicolumn-listview .unity-multi-column-view__cell > .unity-label { font-size: var(--font-size-body2); color: var(--color-text-primary); + padding-top: 0; + padding-bottom: 0; } .utk-multicolumn-listview .unity-list-view__item--selected .unity-multi-column-view__cell > .unity-label { @@ -118,35 +124,6 @@ /* Note: Alternating rows must be handled in C# code Unity USS does not support :nth-child() pseudo-class */ -/* =================================== - Scrollbar - =================================== */ - -.utk-multicolumn-listview .unity-scroller--vertical { - width: 8px; -} - -.utk-multicolumn-listview .unity-scroller--vertical > .unity-scroller__low-button, -.utk-multicolumn-listview .unity-scroller--vertical > .unity-scroller__high-button { - display: none; -} - -.utk-multicolumn-listview .unity-scroller--vertical .unity-base-slider__tracker { - background-color: transparent; - border-width: 0; -} - -.utk-multicolumn-listview .unity-scroller--vertical .unity-base-slider__dragger { - width: 6px; - border-radius: 3px; - background-color: var(--color-text-disabled); - border-width: 0; -} - -.utk-multicolumn-listview .unity-scroller--vertical .unity-base-slider__dragger:hover { - background-color: var(--color-text-secondary); -} - /* =================================== Sort Indicator =================================== */ diff --git a/Assets/Resources/UIToolkit/List/UTKMultiColumnTreeView.uss b/Assets/Resources/UIToolkit/List/UTKMultiColumnTreeView.uss index 5ee9813c..10edb086 100644 --- a/Assets/Resources/UIToolkit/List/UTKMultiColumnTreeView.uss +++ b/Assets/Resources/UIToolkit/List/UTKMultiColumnTreeView.uss @@ -46,7 +46,11 @@ .utk-multicolumn-treeview .unity-multi-column-header__column-title { font-size: var(--font-size-body2); -unity-font-style: bold; - color: var(--color-text-primary); + color: var(--color-text-primary) !important; +} + +.utk-multicolumn-treeview .unity-multi-column-header__column .unity-text-element { + color: var(--color-text-primary) !important; } /* =================================== @@ -100,10 +104,6 @@ transition-property: rotate; } -.utk-multicolumn-treeview .unity-tree-view__item-toggle:checked { - rotate: 90deg; -} - .utk-multicolumn-treeview .unity-tree-view__item--selected .unity-tree-view__item-toggle { -unity-background-image-tint-color: var(--color-text-on-primary); } @@ -125,9 +125,10 @@ =================================== */ .utk-multicolumn-treeview .unity-multi-column-view__cell { - padding: var(--space-s) var(--space-m); + padding: 0 var(--space-m); border-right-width: 1px; border-right-color: var(--color-border-light); + align-self: center; } /* Unity USS does not support :last-child pseudo-class */ @@ -144,35 +145,6 @@ color: var(--color-text-on-primary); } -/* =================================== - Scrollbar - =================================== */ - -.utk-multicolumn-treeview .unity-scroller--vertical { - width: 8px; -} - -.utk-multicolumn-treeview .unity-scroller--vertical > .unity-scroller__low-button, -.utk-multicolumn-treeview .unity-scroller--vertical > .unity-scroller__high-button { - display: none; -} - -.utk-multicolumn-treeview .unity-scroller--vertical .unity-base-slider__tracker { - background-color: transparent; - border-width: 0; -} - -.utk-multicolumn-treeview .unity-scroller--vertical .unity-base-slider__dragger { - width: 6px; - border-radius: 3px; - background-color: var(--color-text-disabled); - border-width: 0; -} - -.utk-multicolumn-treeview .unity-scroller--vertical .unity-base-slider__dragger:hover { - background-color: var(--color-text-secondary); -} - /* =================================== Sort Indicator =================================== */ diff --git a/Assets/Resources/UIToolkit/List/UTKTreeView.uss b/Assets/Resources/UIToolkit/List/UTKTreeView.uss index 55e48cbe..3a9177c9 100644 --- a/Assets/Resources/UIToolkit/List/UTKTreeView.uss +++ b/Assets/Resources/UIToolkit/List/UTKTreeView.uss @@ -22,7 +22,7 @@ =================================== */ .utk-treeview .unity-tree-view__item { - padding: var(--space-xs) 0; + padding: 0; background-color: transparent; transition-duration: var(--anim-fast); transition-property: background-color; @@ -32,10 +32,13 @@ background-color: var(--color-btn-hover); } +/* Unity TreeView uses unity-collection-view__item--selected class internally */ +.utk-treeview .unity-collection-view__item--selected, .utk-treeview .unity-tree-view__item--selected { background-color: var(--color-btn-primary); } +.utk-treeview .unity-collection-view__item--selected:hover, .utk-treeview .unity-tree-view__item--selected:hover { background-color: var(--color-btn-primary-hover); } @@ -47,20 +50,30 @@ .utk-treeview .unity-tree-view__item-toggle { width: 16px; height: 16px; - margin-right: var(--space-xs); -unity-background-image-tint-color: var(--color-text-secondary); transition-duration: var(--anim-fast); transition-property: rotate; } -.utk-treeview .unity-tree-view__item-toggle:checked { - rotate: 0deg; +/* Override #unity-checkmark white color from UTKDefaultStyle.uss */ +.utk-treeview .unity-tree-view__item-toggle #unity-checkmark { + -unity-background-image-tint-color: var(--color-text-secondary); } +.utk-treeview .unity-tree-view__item-toggle:hover #unity-checkmark { + -unity-background-image-tint-color: var(--color-text-primary); +} + +.utk-treeview .unity-collection-view__item--selected .unity-tree-view__item-toggle, .utk-treeview .unity-tree-view__item--selected .unity-tree-view__item-toggle { -unity-background-image-tint-color: var(--color-text-on-primary); } +.utk-treeview .unity-collection-view__item--selected .unity-tree-view__item-toggle #unity-checkmark, +.utk-treeview .unity-tree-view__item--selected .unity-tree-view__item-toggle #unity-checkmark { + -unity-background-image-tint-color: var(--color-text-on-primary); +} + /* =================================== Indentation =================================== */ @@ -88,35 +101,7 @@ color: var(--color-text-primary); } +.utk-treeview .unity-collection-view__item--selected .unity-tree-view__item-content > .unity-label, .utk-treeview .unity-tree-view__item--selected .unity-tree-view__item-content > .unity-label { color: var(--color-text-on-primary); } - -/* =================================== - Scrollbar - =================================== */ - -.utk-treeview .unity-scroller--vertical { - width: 8px; -} - -.utk-treeview .unity-scroller--vertical > .unity-scroller__low-button, -.utk-treeview .unity-scroller--vertical > .unity-scroller__high-button { - display: none; -} - -.utk-treeview .unity-scroller--vertical .unity-base-slider__tracker { - background-color: transparent; - border-width: 0; -} - -.utk-treeview .unity-scroller--vertical .unity-base-slider__dragger { - width: 6px; - border-radius: 3px; - background-color: var(--color-text-disabled); - border-width: 0; -} - -.utk-treeview .unity-scroller--vertical .unity-base-slider__dragger:hover { - background-color: var(--color-text-secondary); -} diff --git a/Assets/Resources/UIToolkit/Modal/UTKAlert.uss b/Assets/Resources/UIToolkit/Modal/UTKAlert.uss index 07374337..b283e093 100644 --- a/Assets/Resources/UIToolkit/Modal/UTKAlert.uss +++ b/Assets/Resources/UIToolkit/Modal/UTKAlert.uss @@ -11,10 +11,12 @@ .utk-alert { flex-direction: column; - align-items: center; min-width: 280px; max-width: 400px; - padding: var(--space-xl); + padding-top: var(--space-m); + padding-left: var(--space-xl); + padding-right: var(--space-xl); + padding-bottom: var(--space-xl); background-color: var(--color-bg-modal); border-radius: var(--radius-l); border-width: var(--border-width); @@ -22,91 +24,33 @@ } /* =================================== - Icon Container - =================================== */ - -.utk-alert__icon-container { - width: 48px; - height: 48px; - border-radius: var(--radius-full); - align-items: center; - justify-content: center; - margin-bottom: var(--space-l); -} - -.utk-alert__icon { - font-size: 24px; - -unity-text-align: middle-center; -} - -/* =================================== - Type Variants - Icons - =================================== */ - -.utk-alert--info .utk-alert__icon-container { - background-color: var(--color-state-info); -} - -.utk-alert--info .utk-alert__icon { - color: var(--color-text-on-primary); -} - -.utk-alert--success .utk-alert__icon-container { - background-color: var(--color-state-success); -} - -.utk-alert--success .utk-alert__icon { - color: var(--color-text-on-primary); -} - -.utk-alert--warning .utk-alert__icon-container { - background-color: var(--color-state-warning); -} - -.utk-alert--warning .utk-alert__icon { - color: var(--color-text-on-primary); -} - -.utk-alert--error .utk-alert__icon-container { - background-color: var(--color-state-error); -} - -.utk-alert--error .utk-alert__icon { - color: var(--color-text-on-primary); -} - -.utk-alert--confirm .utk-alert__icon-container { - background-color: var(--color-btn-primary); -} - -.utk-alert--confirm .utk-alert__icon { - color: var(--color-text-on-primary); -} - -/* =================================== - Content - =================================== */ - -.utk-alert__content { - flex-direction: column; - align-items: center; - margin-bottom: var(--space-l); -} - -/* =================================== - Title + Title (상단 왼쪽) =================================== */ .utk-alert__title { font-size: var(--font-size-h3); color: var(--color-text-primary); -unity-font-style: bold; - -unity-text-align: middle-center; - margin-bottom: var(--space-s); + -unity-text-align: middle-left; + margin: 0; + padding: 0; + align-self: flex-start; } /* =================================== - Message + Content (가운데) + =================================== */ + +.utk-alert__content { + flex-direction: column; + align-items: center; + flex-grow: 1; + margin-top: var(--space-l); + margin-bottom: var(--space-l); +} + +/* =================================== + Message (가운데) =================================== */ .utk-alert__message { @@ -130,3 +74,27 @@ margin-left: var(--space-s); margin-right: var(--space-s); } + +/* =================================== + Type Variants - Title Color + =================================== */ + +.utk-alert--info .utk-alert__title { + color: var(--color-text-primary); +} + +.utk-alert--success .utk-alert__title { + color: var(--color-state-success); +} + +.utk-alert--warning .utk-alert__title { + color: var(--color-state-warning); +} + +.utk-alert--error .utk-alert__title { + color: var(--color-state-error); +} + +.utk-alert--confirm .utk-alert__title { + color: var(--color-text-primary); +} diff --git a/Assets/Resources/UIToolkit/Modal/UTKColorPicker.uss b/Assets/Resources/UIToolkit/Modal/UTKColorPicker.uss index 4d4f3e36..54350392 100644 --- a/Assets/Resources/UIToolkit/Modal/UTKColorPicker.uss +++ b/Assets/Resources/UIToolkit/Modal/UTKColorPicker.uss @@ -21,6 +21,7 @@ justify-content: space-between; align-items: center; margin-bottom: var(--space-l); + cursor: move; } .utk-color-picker__title { diff --git a/Assets/Resources/UIToolkit/Modal/UTKDatePicker.uss b/Assets/Resources/UIToolkit/Modal/UTKDatePicker.uss index 0f05ebb6..2d6a931b 100644 --- a/Assets/Resources/UIToolkit/Modal/UTKDatePicker.uss +++ b/Assets/Resources/UIToolkit/Modal/UTKDatePicker.uss @@ -187,6 +187,67 @@ color: var(--color-text-disabled); } +/* =================================== + Date Range Selection Styles + =================================== */ + +.utk-date-picker__day-btn--range-start { + background-color: var(--color-calendar-selected); + color: var(--color-text-on-primary); + -unity-font-style: bold; + border-top-left-radius: var(--radius-s); + border-bottom-left-radius: var(--radius-s); + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.utk-date-picker__day-btn--range-start:hover { + background-color: var(--color-btn-primary-hover); +} + +.utk-date-picker__day-btn--range-end { + background-color: var(--color-calendar-selected); + color: var(--color-text-on-primary); + -unity-font-style: bold; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: var(--radius-s); + border-bottom-right-radius: var(--radius-s); +} + +.utk-date-picker__day-btn--range-end:hover { + background-color: var(--color-btn-primary-hover); +} + +/* 시작일과 종료일이 같은 경우 */ +.utk-date-picker__day-btn--range-start.utk-date-picker__day-btn--range-end { + border-radius: var(--radius-s); +} + +.utk-date-picker__day-btn--in-range { + background-color: var(--color-calendar-range); + color: var(--color-text-primary); + border-radius: 0; +} + +.utk-date-picker__day-btn--in-range:hover { + background-color: var(--color-calendar-range-hover); +} + +/* =================================== + Range Info Label + =================================== */ + +.utk-date-picker__range-info { + margin-top: var(--space-m); + padding: var(--space-s) var(--space-m); + background-color: var(--color-bg-secondary); + border-radius: var(--radius-s); + font-size: var(--font-size-body2); + color: var(--color-text-secondary); + -unity-text-align: middle-center; +} + /* =================================== Time Picker Row =================================== */ diff --git a/Assets/Resources/UIToolkit/Modal/UTKPanel.uss b/Assets/Resources/UIToolkit/Modal/UTKPanel.uss index 97bc6305..692cffee 100644 --- a/Assets/Resources/UIToolkit/Modal/UTKPanel.uss +++ b/Assets/Resources/UIToolkit/Modal/UTKPanel.uss @@ -95,6 +95,11 @@ padding: var(--space-l); } +.utk-panel__content .unity-label { + font-size: var(--font-size-body2); + color: var(--color-text-primary); +} + .utk-panel--collapsed .utk-panel__content { display: none; } diff --git a/Assets/Resources/UIToolkit/Modal/UTKToast.uss b/Assets/Resources/UIToolkit/Modal/UTKToast.uss index 5ce0b838..62efd084 100644 --- a/Assets/Resources/UIToolkit/Modal/UTKToast.uss +++ b/Assets/Resources/UIToolkit/Modal/UTKToast.uss @@ -20,56 +20,24 @@ } /* =================================== - Icon + Type Variants - Message Color + Toast는 항상 어두운 배경을 사용하므로 밝은 색상 직접 지정 =================================== */ -.utk-toast__icon { - width: 20px; - height: 20px; - margin-right: var(--space-m); - font-size: 14px; - -unity-text-align: middle-center; - border-radius: var(--radius-full); +.utk-toast--info .utk-toast__message { + color: #FFFFFF; } -/* =================================== - Type Variants - =================================== */ - -.utk-toast--info { - border-left-width: 3px; - border-left-color: var(--color-state-info); +.utk-toast--success .utk-toast__message { + color: #4CAF50; } -.utk-toast--info .utk-toast__icon { - color: var(--color-state-info); +.utk-toast--warning .utk-toast__message { + color: #FFB74D; } -.utk-toast--success { - border-left-width: 3px; - border-left-color: var(--color-state-success); -} - -.utk-toast--success .utk-toast__icon { - color: var(--color-state-success); -} - -.utk-toast--warning { - border-left-width: 3px; - border-left-color: var(--color-state-warning); -} - -.utk-toast--warning .utk-toast__icon { - color: var(--color-state-warning); -} - -.utk-toast--error { - border-left-width: 3px; - border-left-color: var(--color-state-error); -} - -.utk-toast--error .utk-toast__icon { - color: var(--color-state-error); +.utk-toast--error .utk-toast__message { + color: #EF5350; } /* =================================== @@ -79,7 +47,7 @@ .utk-toast__message { flex-grow: 1; font-size: var(--font-size-body2); - color: var(--color-text-primary); + color: #FFFFFF; white-space: normal; } diff --git a/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss b/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss index 39ec3dfe..add9a42d 100644 --- a/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss +++ b/Assets/Resources/UIToolkit/Style/UTKDefaultStyle.uss @@ -354,13 +354,17 @@ ListView/TreeView 항목 텍스트 스타일 color: var(--color-base-01) !important; } +.unity-tree-view__item-content { + align-self: center; +} + /* TreeView 항목 텍스트 색상 */ .unity-tree-view__item .unity-label, .unity-tree-view__item .unity-text-element { color: var(--color-text-primary) !important; -unity-text-align: middle-left; - padding-top: 0; - padding-bottom: 0; + padding: 0; + margin: 0; } .unity-tree-view__item--selected .unity-label, @@ -376,12 +380,16 @@ TreeView 항목 스타일 /* TreeView 펼치기/접기 화살표 토글 - 크기 및 정렬 조정 */ .unity-tree-view__item-toggle { - margin-right: 4px; + margin-top: 0; + margin-bottom: 0; + margin-right: 0; + margin-left: 0; width: 16px; height: 16px; min-width: 16px; min-height: 16px; padding: 0; + align-self: center; -unity-background-image-tint-color: var(--color-text-secondary); } @@ -557,6 +565,10 @@ SetupDraggerEvents() 메서드처럼 코드로 MouseEnterEvent/MouseLeaveEvent transition-property: background-color; } +.unity-scroller--vertical .unity-base-slider__dragger:hover { + background-color: var(--color-scroller-dragger-hover); +} + /* 상하 화살표 버튼 - 완전히 숨김 처리 */ .unity-scroller--vertical .unity-repeat-button { display: none; @@ -566,6 +578,11 @@ SetupDraggerEvents() 메서드처럼 코드로 MouseEnterEvent/MouseLeaveEvent min-height: 0; } +.unity-scroller--vertical > .unity-scroller__low-button, +.unity-scroller--vertical > .unity-scroller__high-button { + display: none; +} + /* 슬라이더 - 여백 제거 */ .unity-scroller--vertical .unity-slider { margin: 0; @@ -614,6 +631,10 @@ SetupDraggerEvents() 메서드처럼 코드로 MouseEnterEvent/MouseLeaveEvent transition-property: background-color; } +.unity-scroller--horizontal .unity-base-slider__dragger:hover { + background-color: var(--color-scroller-dragger-hover); +} + /* 좌우 화살표 버튼 - 완전히 숨김 처리 */ .unity-scroller--horizontal .unity-repeat-button { display: none; @@ -623,6 +644,11 @@ SetupDraggerEvents() 메서드처럼 코드로 MouseEnterEvent/MouseLeaveEvent min-height: 0; } +.unity-scroller--horizontal > .unity-scroller__low-button, +.unity-scroller--horizontal > .unity-scroller__high-button { + display: none; +} + /* 슬라이더 - 여백 제거 */ .unity-scroller--horizontal .unity-slider { margin: 0; diff --git a/Assets/Resources/UIToolkit/Style/UTKThemeDark.uss b/Assets/Resources/UIToolkit/Style/UTKThemeDark.uss index 1fbdd9fe..06d11cb4 100644 --- a/Assets/Resources/UIToolkit/Style/UTKThemeDark.uss +++ b/Assets/Resources/UIToolkit/Style/UTKThemeDark.uss @@ -161,6 +161,8 @@ --color-calendar-saturday: var(--color-blue-02); --color-calendar-today: var(--color-seti-orange); --color-calendar-selected: var(--color-blue-06); + --color-calendar-range: rgba(66, 133, 244, 0.25); + --color-calendar-range-hover: rgba(66, 133, 244, 0.35); /* =================================== Semantic Colors - State (color-state-*) diff --git a/Assets/Resources/UIToolkit/Style/UTKThemeLight.uss b/Assets/Resources/UIToolkit/Style/UTKThemeLight.uss index d367f2fe..af96555c 100644 --- a/Assets/Resources/UIToolkit/Style/UTKThemeLight.uss +++ b/Assets/Resources/UIToolkit/Style/UTKThemeLight.uss @@ -168,6 +168,8 @@ --color-calendar-saturday: var(--color-blue-02); --color-calendar-today: var(--color-orange-02); --color-calendar-selected: var(--color-blue-02); + --color-calendar-range: rgba(66, 133, 244, 0.15); + --color-calendar-range-hover: rgba(66, 133, 244, 0.25); /* =================================== Semantic Colors - State (color-state-*) diff --git a/Assets/Resources/UIToolkit/Tab/UTKTab.uss b/Assets/Resources/UIToolkit/Tab/UTKTab.uss deleted file mode 100644 index f36396e2..00000000 --- a/Assets/Resources/UIToolkit/Tab/UTKTab.uss +++ /dev/null @@ -1,95 +0,0 @@ -/* - * =================================== - * UTKTab.uss - * Unity Tab 래핑 스타일 - * =================================== - */ - -/* =================================== - Base Tab - =================================== */ - -.utk-tab { - flex-direction: row; - align-items: center; - justify-content: center; - padding: var(--space-m) var(--space-l); - background-color: transparent; - border-width: 0; - border-bottom-width: 2px; - border-bottom-color: transparent; - cursor: resource('UIToolkit/Images/cursor_point_white_32') 14 5; - transition-duration: var(--anim-fast); - transition-property: background-color, border-color, color; -} - -.utk-tab:hover { - background-color: rgba(255, 255, 255, 0.05); -} - -.utk-tab--selected { - border-bottom-color: var(--color-btn-primary); -} - -/* =================================== - Tab Header (Label Container) - =================================== */ - -.utk-tab > .unity-tab__header { - flex-direction: row; - align-items: center; - justify-content: center; - background-color: transparent; - border-width: 0; - padding: 0; - margin: 0; -} - -/* =================================== - Tab Header Label - =================================== */ - -.utk-tab > .unity-tab__header > .unity-tab__header-label { - font-size: var(--font-size-body2); - color: var(--color-text-secondary); - margin: 0; - padding: 0; -} - -.utk-tab--selected > .unity-tab__header > .unity-tab__header-label { - color: var(--color-btn-primary); - -unity-font-style: bold; -} - -/* =================================== - Tab Content Container - =================================== */ - -.utk-tab > .unity-tab__content-container { - flex-grow: 1; - padding: var(--space-m); -} - -/* =================================== - Hide Tab Underline (Use Custom) - =================================== */ - -.utk-tab > .unity-tab__header > .unity-tab__header-underline { - display: none; -} - -/* =================================== - Disabled State - =================================== */ - -.utk-tab--disabled { - cursor: arrow; -} - -.utk-tab--disabled:hover { - background-color: transparent; -} - -.utk-tab--disabled > .unity-tab__header > .unity-tab__header-label { - color: var(--color-text-disabled); -} diff --git a/Assets/Resources/UIToolkit/Tab/UTKTab.uss.meta b/Assets/Resources/UIToolkit/Tab/UTKTab.uss.meta deleted file mode 100644 index b0300413..00000000 --- a/Assets/Resources/UIToolkit/Tab/UTKTab.uss.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 26349aa08d395b241b70ebcbf9b99671 -ScriptedImporter: - internalIDToNameTable: [] - externalObjects: {} - serializedVersion: 2 - userData: - assetBundleName: - assetBundleVariant: - script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} - disableValidation: 0 diff --git a/Assets/Resources/UIToolkit/Tab/UTKTabView.uss b/Assets/Resources/UIToolkit/Tab/UTKTabView.uss index 3f5b06ce..b47739e1 100644 --- a/Assets/Resources/UIToolkit/Tab/UTKTabView.uss +++ b/Assets/Resources/UIToolkit/Tab/UTKTabView.uss @@ -12,6 +12,7 @@ .utk-tabview { flex-direction: column; flex-grow: 1; + color: var(--color-text-secondary); } /* =================================== @@ -42,47 +43,57 @@ padding: var(--space-l); } +/* Tab Content inside unity-tab__content-container */ +.utk-tabview .unity-tab__content-container { + background-color: transparent; +} + +.utk-tabview .unity-tab__content-container .unity-label { + font-size: var(--font-size-body2); + color: var(--color-text-primary); +} + /* =================================== - Tab styling inside TabView + Tab Header inside TabView =================================== */ -.utk-tabview > .unity-tab-view__header-container > .unity-tab { - padding: var(--space-m) var(--space-l); - background-color: transparent; +.utk-tabview .unity-tab__header { + background-color: transparent !important; border-width: 0; - border-bottom-width: 2px; - border-bottom-color: transparent; + padding: var(--space-s) var(--space-l); + margin-right: var(--space-s); transition-duration: var(--anim-fast); - transition-property: background-color, border-color; + transition-property: background-color; } -.utk-tabview > .unity-tab-view__header-container > .unity-tab:hover { - background-color: rgba(255, 255, 255, 0.05); +.utk-tabview .unity-tab__header:hover { + background-color: var(--color-btn-hover) !important; } -.utk-tabview > .unity-tab-view__header-container > .unity-tab:checked { - border-bottom-color: var(--color-btn-primary); +.utk-tabview .unity-tab__header:checked { + background-color: transparent !important; } -/* Tab Header inside TabView */ -.utk-tabview > .unity-tab-view__header-container > .unity-tab > .unity-tab__header { - background-color: transparent; - border-width: 0; - padding: 0; - margin: 0; -} - -.utk-tabview > .unity-tab-view__header-container > .unity-tab > .unity-tab__header > .unity-tab__header-label { +.utk-tabview .unity-tab__header-label { font-size: var(--font-size-body2); - color: var(--color-text-secondary); + color: var(--color-text-secondary) !important; } -.utk-tabview > .unity-tab-view__header-container > .unity-tab:checked > .unity-tab__header > .unity-tab__header-label { - color: var(--color-btn-primary); +.utk-tabview .unity-tab__header:checked .unity-tab__header-label { + color: var(--color-btn-primary) !important; -unity-font-style: bold; } -/* Hide underline (use custom border) */ -.utk-tabview > .unity-tab-view__header-container > .unity-tab > .unity-tab__header > .unity-tab__header-underline { - display: none; +/* Tab Underline */ +.utk-tabview .unity-tab__header-underline { + height: 2px; + background-color: transparent !important; + position: absolute; + bottom: 0; + left: 0; + right: 0; +} + +.utk-tabview .unity-tab__header:checked > .unity-tab__header-underline { + background-color: var(--color-btn-primary) !important; } diff --git a/Assets/Sample/UIToolkit/UTKColorPickerSample.cs b/Assets/Sample/UIToolkit/UTKColorPickerSample.cs index 1f050455..4ea9d206 100644 --- a/Assets/Sample/UIToolkit/UTKColorPickerSample.cs +++ b/Assets/Sample/UIToolkit/UTKColorPickerSample.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UIElements; using UVC.UIToolkit.Modal; @@ -105,7 +106,7 @@ namespace UVC.Sample.UIToolkit alphaRow.style.marginBottom = 15; _useAlphaToggle = new Toggle("Use Alpha Channel"); - _useAlphaToggle.value = false; + _useAlphaToggle.value = true; _useAlphaToggle.style.color = Color.white; alphaRow.Add(_useAlphaToggle); @@ -117,6 +118,12 @@ namespace UVC.Sample.UIToolkit openPickerBtn.style.marginBottom = 10; container.Add(openPickerBtn); + // Async 버튼 + var openPickerAsyncBtn = new Button(() => OpenColorPickerAsync().Forget()) { text = "Open Color Picker (Async)" }; + openPickerAsyncBtn.style.height = 32; + openPickerAsyncBtn.style.marginBottom = 10; + container.Add(openPickerAsyncBtn); + // 프리셋 색상 버튼들 var presetLabel = new Label("Preset Colors:"); presetLabel.style.color = Color.white; @@ -162,6 +169,30 @@ namespace UVC.Sample.UIToolkit _currentPicker.OnColorSelected += OnColorSelected; } + private async UniTaskVoid OpenColorPickerAsync() + { + if (_root == null) return; + + bool useAlpha = _useAlphaToggle?.value ?? true; + + // ShowAsync를 사용하여 색상 선택 대기 + // OK 클릭 시 선택된 색상 반환, 취소/닫기 시 _currentColor 반환 + Color selectedColor = await UTKColorPicker.ShowAsync(_root, _currentColor, "Select Color (Async)", useAlpha); + + // 결과 처리 + _currentColor = selectedColor; + if (_colorPreview != null) + { + _colorPreview.style.backgroundColor = selectedColor; + } + if (_colorLabel != null) + { + _colorLabel.text = ColorToHex(selectedColor); + } + + Debug.Log($"[Async] Color Result: {ColorToHex(selectedColor)}"); + } + private void OnColorChanged(Color color) { // 실시간 미리보기 업데이트 diff --git a/Assets/Sample/UIToolkit/UTKDatePickerSample.cs b/Assets/Sample/UIToolkit/UTKDatePickerSample.cs index 242a2fbf..e8e64b86 100644 --- a/Assets/Sample/UIToolkit/UTKDatePickerSample.cs +++ b/Assets/Sample/UIToolkit/UTKDatePickerSample.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UIElements; using UVC.Locale; @@ -18,9 +19,12 @@ namespace UVC.Sample.UIToolkit private VisualElement? _root; private Label? _dateLabel; private Label? _dateTimeLabel; + private Label? _dateRangeLabel; private DateTime _selectedDate = DateTime.Today; private DateTime _selectedDateTime = DateTime.Now; + private DateTime _rangeStartDate = DateTime.Today; + private DateTime _rangeEndDate = DateTime.Today.AddDays(7); private UTKDatePicker? _currentPicker; private void Start() @@ -78,6 +82,44 @@ namespace UVC.Sample.UIToolkit _dateTimeLabel.text = FormatDateTime(_selectedDateTime); container.Add(dateTimeSection.container); + // Async 섹션 + var asyncSection = new VisualElement(); + asyncSection.style.marginBottom = 15; + + var asyncLabel = new Label("Async/Await Mode"); + asyncLabel.style.color = new Color(0.8f, 0.8f, 0.8f); + asyncLabel.style.fontSize = 12; + asyncLabel.style.marginBottom = 5; + asyncSection.Add(asyncLabel); + + var asyncBtn = new Button(() => OpenDatePickerAsync().Forget()) { text = "Open Date Picker (Async)" }; + asyncBtn.style.height = 32; + asyncSection.Add(asyncBtn); + + container.Add(asyncSection); + + // Date Range 섹션 + var rangeSection = CreateSection("Date Range Mode", "Open Range Picker", OpenDateRangePicker); + _dateRangeLabel = rangeSection.label; + _dateRangeLabel.text = FormatDateRange(_rangeStartDate, _rangeEndDate); + container.Add(rangeSection.container); + + // Date Range Async 섹션 + var rangeAsyncSection = new VisualElement(); + rangeAsyncSection.style.marginBottom = 15; + + var rangeAsyncLabel = new Label("Date Range Async Mode"); + rangeAsyncLabel.style.color = new Color(0.8f, 0.8f, 0.8f); + rangeAsyncLabel.style.fontSize = 12; + rangeAsyncLabel.style.marginBottom = 5; + rangeAsyncSection.Add(rangeAsyncLabel); + + var rangeAsyncBtn = new Button(() => OpenDateRangePickerAsync().Forget()) { text = "Open Range Picker (Async)" }; + rangeAsyncBtn.style.height = 32; + rangeAsyncSection.Add(rangeAsyncBtn); + + container.Add(rangeAsyncSection); + // 구분선 var separator = new VisualElement(); separator.style.height = 1; @@ -185,6 +227,34 @@ namespace UVC.Sample.UIToolkit _currentPicker.OnClosed += OnPickerClosed; } + private async UniTaskVoid OpenDatePickerAsync() + { + if (_root == null) return; + + // ShowAsync를 사용하여 날짜 선택 대기 + // OK 클릭 시 선택된 날짜 반환, 취소/닫기 시 null 반환 + DateTime? result = await UTKDatePicker.ShowAsync( + _root, + _selectedDate, + UTKDatePicker.PickerMode.DateOnly, + "Select Date (Async)" + ); + + if (result.HasValue) + { + _selectedDate = result.Value; + if (_dateLabel != null) + { + _dateLabel.text = FormatDate(result.Value); + } + Debug.Log($"[Async] Date Result: {FormatDate(result.Value)}"); + } + else + { + Debug.Log("[Async] Date selection cancelled"); + } + } + private void OnDateSelected(DateTime date) { _selectedDate = date; @@ -245,12 +315,75 @@ namespace UVC.Sample.UIToolkit return dateTime.ToString("yyyy-MM-dd HH:mm"); } + private string FormatDateRange(DateTime start, DateTime end) + { + return $"{start:yyyy-MM-dd} ~ {end:yyyy-MM-dd}"; + } + + private void OpenDateRangePicker() + { + if (_root == null || _currentPicker != null) return; + + _currentPicker = UTKDatePicker.ShowRange( + _root, + _rangeStartDate, + _rangeEndDate, + false, + "Select Date Range" + ); + + _currentPicker.OnDateRangeSelected += OnDateRangeSelected; + _currentPicker.OnClosed += OnPickerClosed; + } + + private async UniTaskVoid OpenDateRangePickerAsync() + { + if (_root == null) return; + + // ShowRangeAsync를 사용하여 날짜 범위 선택 대기 + // OK 클릭 시 선택된 범위 반환, 취소/닫기 시 null 반환 + var result = await UTKDatePicker.ShowRangeAsync( + _root, + _rangeStartDate, + _rangeEndDate, + false, + "Select Date Range (Async)" + ); + + if (result.HasValue) + { + _rangeStartDate = result.Value.Start; + _rangeEndDate = result.Value.End; + if (_dateRangeLabel != null) + { + _dateRangeLabel.text = FormatDateRange(result.Value.Start, result.Value.End); + } + Debug.Log($"[Async] Range Result: {FormatDateRange(result.Value.Start, result.Value.End)}"); + } + else + { + Debug.Log("[Async] Date range selection cancelled"); + } + } + + private void OnDateRangeSelected(DateTime start, DateTime end) + { + _rangeStartDate = start; + _rangeEndDate = end; + if (_dateRangeLabel != null) + { + _dateRangeLabel.text = FormatDateRange(start, end); + } + Debug.Log($"Date Range Selected: {FormatDateRange(start, end)}"); + } + private void OnDestroy() { if (_currentPicker != null) { _currentPicker.OnDateSelected -= OnDateSelected; _currentPicker.OnDateSelected -= OnDateTimeSelected; + _currentPicker.OnDateRangeSelected -= OnDateRangeSelected; _currentPicker.OnClosed -= OnPickerClosed; _currentPicker.Dispose(); _currentPicker = null; diff --git a/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs b/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs index d9fb0f02..16926d71 100644 --- a/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs +++ b/Assets/Sample/UIToolkit/UTKStyleGuideSample.cs @@ -1,6 +1,7 @@ #nullable enable using System; using System.Collections.Generic; +using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UIElements; using UVC.UIToolkit; @@ -787,6 +788,21 @@ public class UTKStyleGuideSample : MonoBehaviour }), }), new TreeViewItemData(9, "Parent 3"), + new TreeViewItemData(10, "Parent 4", new List> + { + new TreeViewItemData(11, "Child 4-1"), + new TreeViewItemData(12, "Child 4-2"), + }), + new TreeViewItemData(13, "Parent 5", new List> + { + new TreeViewItemData(14, "Child 5-1"), + new TreeViewItemData(15, "Child 5-2", new List> + { + new TreeViewItemData(16, "Grandchild 5-2-1"), + new TreeViewItemData(17, "Grandchild 5-2-2"), + }), + }), + new TreeViewItemData(18, "Parent 6"), }; treeView.SetRootItems(treeItems); @@ -794,31 +810,138 @@ public class UTKStyleGuideSample : MonoBehaviour container.Add(treeView); } + /// + /// MultiColumnListView 샘플 데이터 클래스 + /// + private class SampleListItem + { + public int Id { get; set; } + public string Name { get; set; } = ""; + public string Status { get; set; } = ""; + public int Progress { get; set; } + } + private void CreateMultiColumnListViewPreview(VisualElement container) { AddDescription(container, "멀티 컬럼 리스트 뷰 (정렬 가능한 테이블)"); - var placeholder = new Label("MultiColumnListView requires Columns and data binding setup."); - placeholder.style.color = new Color(0.6f, 0.6f, 0.6f); - container.Add(placeholder); - var listView = new UTKMultiColumnListView(); listView.style.height = 200; - listView.style.width = 400; + listView.style.width = 450; + + // 컬럼 정의 + listView.columns.Add(new Column { name = "id", title = "ID", width = 50 }); + listView.columns.Add(new Column { name = "name", title = "Name", width = 150, stretchable = true }); + listView.columns.Add(new Column { name = "status", title = "Status", width = 100 }); + listView.columns.Add(new Column { name = "progress", title = "Progress", width = 80 }); + + // 샘플 데이터 + var items = new List + { + new() { Id = 1, Name = "Task Alpha", Status = "Active", Progress = 75 }, + new() { Id = 2, Name = "Task Beta", Status = "Pending", Progress = 30 }, + new() { Id = 3, Name = "Task Gamma", Status = "Completed", Progress = 100 }, + new() { Id = 4, Name = "Task Delta", Status = "Active", Progress = 50 }, + new() { Id = 5, Name = "Task Epsilon", Status = "Cancelled", Progress = 0 }, + new() { Id = 6, Name = "Task Zeta", Status = "Active", Progress = 90 }, + new() { Id = 7, Name = "Task Eta", Status = "Pending", Progress = 15 }, + new() { Id = 8, Name = "Task Theta", Status = "Completed", Progress = 100 }, + }; + + listView.itemsSource = items; + listView.fixedItemHeight = 24; + + // 셀 생성 및 바인딩 + listView.columns["id"].makeCell = () => new Label(); + listView.columns["id"].bindCell = (e, i) => ((Label)e).text = items[i].Id.ToString(); + + listView.columns["name"].makeCell = () => new Label(); + listView.columns["name"].bindCell = (e, i) => ((Label)e).text = items[i].Name; + + listView.columns["status"].makeCell = () => new Label(); + listView.columns["status"].bindCell = (e, i) => ((Label)e).text = items[i].Status; + + listView.columns["progress"].makeCell = () => new Label(); + listView.columns["progress"].bindCell = (e, i) => ((Label)e).text = $"{items[i].Progress}%"; + container.Add(listView); } + /// + /// MultiColumnTreeView 샘플 데이터 클래스 + /// + private class SampleTreeItem + { + public int Id { get; set; } + public string Name { get; set; } = ""; + public string Type { get; set; } = ""; + public string Size { get; set; } = ""; + } + private void CreateMultiColumnTreeViewPreview(VisualElement container) { AddDescription(container, "멀티 컬럼 트리 뷰 (계층적 테이블)"); - var placeholder = new Label("MultiColumnTreeView requires Columns and TreeViewItemData setup."); - placeholder.style.color = new Color(0.6f, 0.6f, 0.6f); - container.Add(placeholder); - var treeView = new UTKMultiColumnTreeView(); - treeView.style.height = 200; - treeView.style.width = 400; + treeView.style.height = 220; + treeView.style.width = 450; + + // 컬럼 정의 + treeView.columns.Add(new Column { name = "name", title = "Name", width = 200, stretchable = true }); + treeView.columns.Add(new Column { name = "type", title = "Type", width = 100 }); + treeView.columns.Add(new Column { name = "size", title = "Size", width = 80 }); + + // 샘플 데이터 (계층 구조) + var rootItems = new List> + { + new(1, new SampleTreeItem { Id = 1, Name = "Documents", Type = "Folder", Size = "2.5 GB" }, + new List> + { + new(11, new SampleTreeItem { Id = 11, Name = "Reports", Type = "Folder", Size = "500 MB" }, + new List> + { + new(111, new SampleTreeItem { Id = 111, Name = "Q1_Report.pdf", Type = "PDF", Size = "2.3 MB" }), + new(112, new SampleTreeItem { Id = 112, Name = "Q2_Report.pdf", Type = "PDF", Size = "1.8 MB" }), + }), + new(12, new SampleTreeItem { Id = 12, Name = "Images", Type = "Folder", Size = "1.2 GB" }), + }), + new(2, new SampleTreeItem { Id = 2, Name = "Projects", Type = "Folder", Size = "5.0 GB" }, + new List> + { + new(21, new SampleTreeItem { Id = 21, Name = "ProjectA", Type = "Folder", Size = "2.0 GB" }), + new(22, new SampleTreeItem { Id = 22, Name = "ProjectB", Type = "Folder", Size = "3.0 GB" }), + }), + new(3, new SampleTreeItem { Id = 3, Name = "Config.json", Type = "JSON", Size = "4 KB" }), + }; + + treeView.SetRootItems(rootItems); + treeView.fixedItemHeight = 22; + + // 셀 생성 및 바인딩 + treeView.columns["name"].makeCell = () => new Label(); + treeView.columns["name"].bindCell = (e, i) => + { + var item = treeView.GetItemDataForIndex(i); + ((Label)e).text = item.Name; + }; + + treeView.columns["type"].makeCell = () => new Label(); + treeView.columns["type"].bindCell = (e, i) => + { + var item = treeView.GetItemDataForIndex(i); + ((Label)e).text = item.Type; + }; + + treeView.columns["size"].makeCell = () => new Label(); + treeView.columns["size"].bindCell = (e, i) => + { + var item = treeView.GetItemDataForIndex(i); + ((Label)e).text = item.Size; + }; + + // 기본 확장 + treeView.ExpandAll(); + container.Add(treeView); } @@ -840,16 +963,50 @@ public class UTKStyleGuideSample : MonoBehaviour { AddDescription(container, "스크롤 뷰 컴포넌트"); - var scrollView = new UTKScrollView(); - scrollView.style.height = 150; - scrollView.style.width = 300; + var row = CreateRow(container, "ScrollViews"); + + // 세로 스크롤 + var verticalScroll = new UTKScrollView(); + verticalScroll.style.height = 150; + verticalScroll.style.width = 200; for (int i = 1; i <= 15; i++) { - scrollView.Add(new Label($"Scrollable Item {i}")); + verticalScroll.Add(new Label($"Vertical Item {i}")); } + row.Add(verticalScroll); - container.Add(scrollView); + // 가로 스크롤 + var horizontalScroll = new UTKScrollView(); + horizontalScroll.mode = ScrollViewMode.Horizontal; + horizontalScroll.style.height = 150; + horizontalScroll.style.width = 200; + + var horizontalContent = new VisualElement(); + horizontalContent.style.flexDirection = FlexDirection.Row; + for (int i = 1; i <= 15; i++) + { + var item = new Label($"H-Item {i}"); + item.style.minWidth = 80; + item.style.marginRight = 8; + horizontalContent.Add(item); + } + horizontalScroll.Add(horizontalContent); + row.Add(horizontalScroll); + + // 양방향 스크롤 + var bothScroll = new UTKScrollView(); + bothScroll.mode = ScrollViewMode.VerticalAndHorizontal; + bothScroll.style.height = 150; + bothScroll.style.width = 200; + + for (int i = 1; i <= 10; i++) + { + var item = new Label($"This is a very long scrollable item number {i} that requires horizontal scrolling"); + item.style.whiteSpace = WhiteSpace.NoWrap; + bothScroll.Add(item); + } + row.Add(bothScroll); } #endregion @@ -909,23 +1066,102 @@ public class UTKStyleGuideSample : MonoBehaviour { if (_root == null) return; - AddDescription(container, "알림 다이얼로그"); + AddDescription(container, "알림 다이얼로그 (async/await 지원)"); - var row = CreateRow(container, "Alerts"); + // Alert Types (Async) + var row1 = CreateRow(container, "Alert Types (Async)"); - var infoBtn = new UTKButton("Show Info", "", UTKButton.ButtonVariant.Primary); - infoBtn.OnClicked += () => UTKAlert.ShowInfo(_root, "Information", "This is an info message."); - row.Add(infoBtn); + UTKAlert.SetRoot(_root); - var warnBtn = new UTKButton("Show Warning", "", UTKButton.ButtonVariant.Normal); - warnBtn.OnClicked += () => UTKAlert.ShowWarning(_root, "Warning", "This is a warning message."); - row.Add(warnBtn); + var infoBtn = new UTKButton("Info", "", UTKButton.ButtonVariant.Primary); + infoBtn.OnClicked += () => ShowInfoAlertAsync().Forget(); + row1.Add(infoBtn); - var confirmBtn = new UTKButton("Show Confirm", "", UTKButton.ButtonVariant.Normal); - confirmBtn.OnClicked += () => UTKAlert.ShowConfirm(_root, "Confirm", "Are you sure?", - () => Debug.Log("Confirmed!"), - () => Debug.Log("Cancelled!")); - row.Add(confirmBtn); + var successBtn = new UTKButton("Success", "", UTKButton.ButtonVariant.Normal); + successBtn.OnClicked += () => ShowSuccessAlertAsync().Forget(); + row1.Add(successBtn); + + var warnBtn = new UTKButton("Warning", "", UTKButton.ButtonVariant.Normal); + warnBtn.OnClicked += () => ShowWarningAlertAsync().Forget(); + row1.Add(warnBtn); + + var errorBtn = new UTKButton("Error", "", UTKButton.ButtonVariant.Danger); + errorBtn.OnClicked += () => ShowErrorAlertAsync().Forget(); + row1.Add(errorBtn); + + // Confirm Dialog (Async) + var row2 = CreateRow(container, "Confirm Dialog (Async)"); + + var confirmBtn = new UTKButton("Confirm", "", UTKButton.ButtonVariant.Normal); + confirmBtn.OnClicked += () => ShowConfirmAlertAsync().Forget(); + row2.Add(confirmBtn); + + var confirmCustomBtn = new UTKButton("Custom Labels", "", UTKButton.ButtonVariant.Normal); + confirmCustomBtn.OnClicked += () => ShowConfirmCustomLabelsAsync().Forget(); + row2.Add(confirmCustomBtn); + + // Callback Style (Non-Async) + var row3 = CreateRow(container, "Callback Style"); + + var callbackBtn = new UTKButton("With Callback", "", UTKButton.ButtonVariant.OutlineNormal); + callbackBtn.OnClicked += () => + { + UTKAlert.ShowInfo(_root, "Callback Style", "This uses callback instead of async.", + onClose: () => Debug.Log("Alert closed via callback")); + }; + row3.Add(callbackBtn); + + var confirmCallbackBtn = new UTKButton("Confirm Callback", "", UTKButton.ButtonVariant.OutlineNormal); + confirmCallbackBtn.OnClicked += () => + { + UTKAlert.ShowConfirm(_root, "Confirm", "Do you want to proceed?", + onConfirm: () => Debug.Log("Confirmed via callback!"), + onCancel: () => Debug.Log("Cancelled via callback!")); + }; + row3.Add(confirmCallbackBtn); + } + + private async UniTaskVoid ShowInfoAlertAsync() + { + if (_root == null) return; + await UTKAlert.ShowInfoAsync("Information", "This is an info message."); + Debug.Log("Info alert closed"); + } + + private async UniTaskVoid ShowSuccessAlertAsync() + { + if (_root == null) return; + await UTKAlert.ShowSuccessAsync("Success", "Operation completed successfully!", closeOnBlockerClick: true); + Debug.Log("Success alert closed"); + } + + private async UniTaskVoid ShowWarningAlertAsync() + { + if (_root == null) return; + await UTKAlert.ShowWarningAsync("Warning", "This is a warning message."); + Debug.Log("Warning alert closed"); + } + + private async UniTaskVoid ShowErrorAlertAsync() + { + if (_root == null) return; + await UTKAlert.ShowErrorAsync("Error", "An error has occurred!"); + Debug.Log("Error alert closed"); + } + + private async UniTaskVoid ShowConfirmAlertAsync() + { + if (_root == null) return; + bool result = await UTKAlert.ShowConfirmAsync("Confirm", "Are you sure?"); + Debug.Log(result ? "Confirmed!" : "Cancelled!"); + } + + private async UniTaskVoid ShowConfirmCustomLabelsAsync() + { + if (_root == null) return; + bool result = await UTKAlert.ShowConfirmAsync("Delete Item", "Are you sure you want to delete this item?", + confirmLabel: "Delete", cancelLabel: "Keep"); + Debug.Log(result ? "Item deleted!" : "Item kept!"); } private void CreateToastPreview(VisualElement container) @@ -936,16 +1172,22 @@ public class UTKStyleGuideSample : MonoBehaviour var row = CreateRow(container, "Toasts"); + UTKToast.SetRoot(_root); + var infoBtn = new UTKButton("Info Toast", "", UTKButton.ButtonVariant.Primary); - infoBtn.OnClicked += () => UTKToast.ShowInfo(_root, "This is an info toast!", 3000); + infoBtn.OnClicked += () => UTKToast.Show("This is an info toast!This is an info toast!This is an info toast!This is an info toast!This is an info toast!"); row.Add(infoBtn); var successBtn = new UTKButton("Success Toast", "", UTKButton.ButtonVariant.Normal); - successBtn.OnClicked += () => UTKToast.ShowSuccess(_root, "Operation successful!", 3000); + successBtn.OnClicked += () => UTKToast.ShowSuccess("Operation successful!"); row.Add(successBtn); + var warningBtn = new UTKButton("Warning Toast", "", UTKButton.ButtonVariant.Normal); + warningBtn.OnClicked += () => UTKToast.ShowWarning("Warning: Check your input!"); + row.Add(warningBtn); + var errorBtn = new UTKButton("Error Toast", "", UTKButton.ButtonVariant.Danger); - errorBtn.OnClicked += () => UTKToast.ShowError(_root, "An error occurred!", 3000); + errorBtn.OnClicked += () => UTKToast.ShowError("An error occurred!"); row.Add(errorBtn); } @@ -968,37 +1210,227 @@ public class UTKStyleGuideSample : MonoBehaviour #region Picker Previews + private Color _selectedColor = Color.white; + private VisualElement? _colorPreviewBox; + private Label? _colorHexLabel; + private void CreateColorPickerPreview(VisualElement container) { - AddDescription(container, "색상 선택 컴포넌트"); + if (_root == null) return; - var colorLabel = new Label("Selected: #FFFFFFFF"); - container.Add(colorLabel); + AddDescription(container, "색상 선택 컴포넌트 (버튼 클릭으로 모달 표시)"); - var colorPicker = new UTKColorPicker(); - colorPicker.style.position = Position.Relative; - colorPicker.SetColor(Color.white); - colorPicker.OnColorChanged += (color) => - { - colorLabel.text = $"Selected: #{ColorUtility.ToHtmlStringRGBA(color)}"; - }; - container.Add(colorPicker); + // 현재 색상 미리보기 + var previewRow = CreateRow(container, "Current Color"); + + _colorPreviewBox = new VisualElement(); + _colorPreviewBox.style.width = 60; + _colorPreviewBox.style.height = 30; + _colorPreviewBox.style.backgroundColor = _selectedColor; + _colorPreviewBox.style.borderTopLeftRadius = 4; + _colorPreviewBox.style.borderTopRightRadius = 4; + _colorPreviewBox.style.borderBottomLeftRadius = 4; + _colorPreviewBox.style.borderBottomRightRadius = 4; + _colorPreviewBox.style.borderTopWidth = 1; + _colorPreviewBox.style.borderBottomWidth = 1; + _colorPreviewBox.style.borderLeftWidth = 1; + _colorPreviewBox.style.borderRightWidth = 1; + _colorPreviewBox.style.borderTopColor = new Color(0.4f, 0.4f, 0.4f); + _colorPreviewBox.style.borderBottomColor = new Color(0.4f, 0.4f, 0.4f); + _colorPreviewBox.style.borderLeftColor = new Color(0.4f, 0.4f, 0.4f); + _colorPreviewBox.style.borderRightColor = new Color(0.4f, 0.4f, 0.4f); + previewRow.Add(_colorPreviewBox); + + _colorHexLabel = new Label($"#{ColorUtility.ToHtmlStringRGBA(_selectedColor)}"); + _colorHexLabel.style.marginLeft = 10; + previewRow.Add(_colorHexLabel); + + // 버튼들 + var row1 = CreateRow(container, "With Alpha"); + var withAlphaBtn = new UTKButton("Open Color Picker (Alpha)", "", UTKButton.ButtonVariant.Primary); + withAlphaBtn.OnClicked += () => OpenColorPicker(true); + row1.Add(withAlphaBtn); + + var row2 = CreateRow(container, "Without Alpha"); + var withoutAlphaBtn = new UTKButton("Open Color Picker (No Alpha)", "", UTKButton.ButtonVariant.Normal); + withoutAlphaBtn.OnClicked += () => OpenColorPicker(false); + row2.Add(withoutAlphaBtn); + + // Async 버튼 + var row3 = CreateRow(container, "Async/Await"); + var asyncBtn = new UTKButton("Open Color Picker (Async)", "", UTKButton.ButtonVariant.Normal); + asyncBtn.OnClicked += () => OpenColorPickerAsync().Forget(); + row3.Add(asyncBtn); } + private void OpenColorPicker(bool useAlpha) + { + if (_root == null) return; + + var picker = UTKColorPicker.Show(_root, _selectedColor, "Select Color", useAlpha); + picker.OnColorChanged += (color) => + { + // 실시간 미리보기 업데이트 + if (_colorPreviewBox != null) + _colorPreviewBox.style.backgroundColor = color; + if (_colorHexLabel != null) + _colorHexLabel.text = useAlpha + ? $"#{ColorUtility.ToHtmlStringRGBA(color)}" + : $"#{ColorUtility.ToHtmlStringRGB(color)}"; + }; + picker.OnColorSelected += (color) => + { + _selectedColor = color; + Debug.Log($"Color Selected: #{(useAlpha ? ColorUtility.ToHtmlStringRGBA(color) : ColorUtility.ToHtmlStringRGB(color))}"); + }; + } + + private async UniTaskVoid OpenColorPickerAsync() + { + if (_root == null) return; + + // ShowAsync를 사용하여 색상 선택 대기 + // OK 클릭 시 선택된 색상 반환, 취소/닫기 시 _selectedColor 반환 + Color result = await UTKColorPicker.ShowAsync(_root, _selectedColor, "Select Color (Async)", useAlpha: true); + + // 결과 처리 + _selectedColor = result; + if (_colorPreviewBox != null) + _colorPreviewBox.style.backgroundColor = result; + if (_colorHexLabel != null) + _colorHexLabel.text = $"#{ColorUtility.ToHtmlStringRGBA(result)}"; + + Debug.Log($"[Async] Color Result: #{ColorUtility.ToHtmlStringRGBA(result)}"); + } + + private DateTime _selectedDate = DateTime.Today; + private DateTime _rangeStartDate = DateTime.Today; + private DateTime _rangeEndDate = DateTime.Today.AddDays(7); + private Label? _dateLabel; + private Label? _rangeDateLabel; + private void CreateDatePickerPreview(VisualElement container) { - AddDescription(container, "날짜 선택 컴포넌트"); + if (_root == null) return; - var dateLabel = new Label($"Selected: {DateTime.Now:yyyy-MM-dd}"); - container.Add(dateLabel); + AddDescription(container, "날짜 선택 컴포넌트 (버튼 클릭으로 모달 표시)"); - var datePicker = new UTKDatePicker(); - datePicker.style.position = Position.Relative; - datePicker.OnDateSelected += (date) => + // 현재 선택된 날짜 표시 + var previewRow = CreateRow(container, "Current Date"); + _dateLabel = new Label($"Selected: {_selectedDate:yyyy-MM-dd}"); + previewRow.Add(_dateLabel); + + // 날짜만 선택 버튼 + var row1 = CreateRow(container, "Date Only"); + var dateOnlyBtn = new UTKButton("Open Date Picker", "", UTKButton.ButtonVariant.Primary); + dateOnlyBtn.OnClicked += () => OpenDatePicker(UTKDatePicker.PickerMode.DateOnly); + row1.Add(dateOnlyBtn); + + // 날짜 + 시간 선택 버튼 + var row2 = CreateRow(container, "Date & Time"); + var dateTimeBtn = new UTKButton("Open DateTime Picker", "", UTKButton.ButtonVariant.Normal); + dateTimeBtn.OnClicked += () => OpenDatePicker(UTKDatePicker.PickerMode.DateAndTime); + row2.Add(dateTimeBtn); + + // Async 버튼 + var row3 = CreateRow(container, "Async/Await"); + var asyncBtn = new UTKButton("Open Date Picker (Async)", "", UTKButton.ButtonVariant.Normal); + asyncBtn.OnClicked += () => OpenDatePickerAsync().Forget(); + row3.Add(asyncBtn); + + // 날짜 범위 표시 + var rangePreviewRow = CreateRow(container, "Date Range"); + _rangeDateLabel = new Label($"Range: {_rangeStartDate:yyyy-MM-dd} ~ {_rangeEndDate:yyyy-MM-dd}"); + rangePreviewRow.Add(_rangeDateLabel); + + // 날짜 범위 선택 버튼 + var row4 = CreateRow(container, "Date Range"); + var rangeBtn = new UTKButton("Open Range Picker", "", UTKButton.ButtonVariant.Normal); + rangeBtn.OnClicked += OpenDateRangePicker; + row4.Add(rangeBtn); + + // 날짜 범위 Async 버튼 + var row5 = CreateRow(container, "Range Async"); + var rangeAsyncBtn = new UTKButton("Open Range Picker (Async)", "", UTKButton.ButtonVariant.Normal); + rangeAsyncBtn.OnClicked += () => OpenDateRangePickerAsync().Forget(); + row5.Add(rangeAsyncBtn); + } + + private void OpenDatePicker(UTKDatePicker.PickerMode mode) + { + if (_root == null) return; + + string title = mode == UTKDatePicker.PickerMode.DateOnly ? "Select Date" : "Select Date & Time"; + var picker = UTKDatePicker.Show(_root, _selectedDate, mode, title); + picker.OnDateSelected += (date) => { - dateLabel.text = $"Selected: {date:yyyy-MM-dd HH:mm}"; + _selectedDate = date; + if (_dateLabel != null) + { + _dateLabel.text = mode == UTKDatePicker.PickerMode.DateOnly + ? $"Selected: {date:yyyy-MM-dd}" + : $"Selected: {date:yyyy-MM-dd HH:mm}"; + } + Debug.Log($"Date Selected: {date:yyyy-MM-dd HH:mm}"); }; - container.Add(datePicker); + } + + private async UniTaskVoid OpenDatePickerAsync() + { + if (_root == null) return; + + // ShowAsync를 사용하여 날짜 선택 대기 + // OK 클릭 시 선택된 날짜 반환, 취소/닫기 시 null 반환 + DateTime? result = await UTKDatePicker.ShowAsync(_root, _selectedDate, UTKDatePicker.PickerMode.DateOnly, "Select Date (Async)"); + + if (result.HasValue) + { + _selectedDate = result.Value; + if (_dateLabel != null) + _dateLabel.text = $"Selected: {result.Value:yyyy-MM-dd}"; + Debug.Log($"[Async] Date Result: {result.Value:yyyy-MM-dd}"); + } + else + { + Debug.Log("[Async] Date selection cancelled"); + } + } + + private void OpenDateRangePicker() + { + if (_root == null) return; + + var picker = UTKDatePicker.ShowRange(_root, _rangeStartDate, _rangeEndDate, false, "Select Date Range"); + picker.OnDateRangeSelected += (start, end) => + { + _rangeStartDate = start; + _rangeEndDate = end; + if (_rangeDateLabel != null) + _rangeDateLabel.text = $"Range: {start:yyyy-MM-dd} ~ {end:yyyy-MM-dd}"; + Debug.Log($"Date Range Selected: {start:yyyy-MM-dd} ~ {end:yyyy-MM-dd}"); + }; + } + + private async UniTaskVoid OpenDateRangePickerAsync() + { + if (_root == null) return; + + // ShowRangeAsync를 사용하여 날짜 범위 선택 대기 + // OK 클릭 시 선택된 범위 반환, 취소/닫기 시 null 반환 + var result = await UTKDatePicker.ShowRangeAsync(_root, _rangeStartDate, _rangeEndDate, false, "Select Date Range (Async)"); + + if (result.HasValue) + { + _rangeStartDate = result.Value.Start; + _rangeEndDate = result.Value.End; + if (_rangeDateLabel != null) + _rangeDateLabel.text = $"Range: {result.Value.Start:yyyy-MM-dd} ~ {result.Value.End:yyyy-MM-dd}"; + Debug.Log($"[Async] Range Result: {result.Value.Start:yyyy-MM-dd} ~ {result.Value.End:yyyy-MM-dd}"); + } + else + { + Debug.Log("[Async] Date range selection cancelled"); + } } #endregion diff --git a/Assets/Scripts/UVC/UIToolkit/Card/UTKCard.cs b/Assets/Scripts/UVC/UIToolkit/Card/UTKCard.cs index dcd08715..43beaaec 100644 --- a/Assets/Scripts/UVC/UIToolkit/Card/UTKCard.cs +++ b/Assets/Scripts/UVC/UIToolkit/Card/UTKCard.cs @@ -181,6 +181,7 @@ namespace UVC.UIToolkit // Actions _actions = new VisualElement { name = "actions" }; _actions.AddToClassList("utk-card__actions"); + _actions.style.display = DisplayStyle.None; // 기본적으로 숨김 hierarchy.Add(_actions); UpdateVariant(); @@ -281,6 +282,13 @@ namespace UVC.UIToolkit public void AddAction(VisualElement element) { _actions?.Add(element); + UpdateActionsVisibility(); + } + + private void UpdateActionsVisibility() + { + if (_actions == null) return; + _actions.style.display = _actions.childCount > 0 ? DisplayStyle.Flex : DisplayStyle.None; } /// diff --git a/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs b/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs index 87840478..8cb103f9 100644 --- a/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs +++ b/Assets/Scripts/UVC/UIToolkit/Common/UTKTooltipManager.cs @@ -159,6 +159,7 @@ namespace UVC.UIToolkit.Common _tooltipLabel.text = displayText; _tooltipContainer.style.display = DisplayStyle.Flex; + _tooltipContainer.BringToFront(); _isVisible = true; // 다음 프레임에 위치 조정 (레이아웃 계산 후) @@ -217,11 +218,18 @@ namespace UVC.UIToolkit.Common _tooltipRegistry[element] = tooltip; // 이벤트 콜백 생성 및 등록 + // 참고: evt.position은 로컬 좌표이므로, 패널 기준 좌표로 변환 필요 EventCallback enterCallback = evt => { if (_tooltipRegistry.TryGetValue(element, out var text)) { - ShowDelayed(text, evt.position).Forget(); + // 로컬 좌표를 root 좌표로 변환 + var rootPosition = element.LocalToWorld(evt.localPosition); + if (_root != null) + { + rootPosition = _root.WorldToLocal(rootPosition); + } + ShowDelayed(text, rootPosition).Forget(); } }; @@ -231,7 +239,13 @@ namespace UVC.UIToolkit.Common { if (_isVisible) { - AdjustPosition(evt.position); + // 로컬 좌표를 root 좌표로 변환 + var rootPosition = element.LocalToWorld(evt.localPosition); + if (_root != null) + { + rootPosition = _root.WorldToLocal(rootPosition); + } + AdjustPosition(rootPosition); } }; diff --git a/Assets/Scripts/UVC/UIToolkit/Modal/UTKAlert.cs b/Assets/Scripts/UVC/UIToolkit/Modal/UTKAlert.cs index 3c459e66..7aed68fd 100644 --- a/Assets/Scripts/UVC/UIToolkit/Modal/UTKAlert.cs +++ b/Assets/Scripts/UVC/UIToolkit/Modal/UTKAlert.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using Cysharp.Threading.Tasks; using UnityEngine; using UnityEngine.UIElements; using UVC.UIToolkit.Modal; @@ -18,10 +19,10 @@ namespace UVC.UIToolkit #endregion #region Fields + private static VisualElement? _root; + private bool _disposed; private UTKModalBlocker? _blocker; - private VisualElement? _iconContainer; - private Label? _iconLabel; private Label? _titleLabel; private Label? _messageLabel; private VisualElement? _buttonContainer; @@ -29,6 +30,11 @@ namespace UVC.UIToolkit private string _title = ""; private string _message = ""; private AlertType _alertType = AlertType.Info; + private string _confirmLabel = "OK"; + private string _cancelLabel = "Cancel"; + + private EventCallback? _keyDownCallback; + private VisualElement? _keyEventTarget; #endregion #region Events @@ -116,45 +122,234 @@ namespace UVC.UIToolkit } #endregion - #region Static Factory + #region Static Methods + /// + /// 기본 루트 요소 설정 + /// + /// Alert를 표시할 기본 루트 요소 + public static void SetRoot(VisualElement root) + { + _root = root; + } + + /// + /// 기본 루트 요소 반환 + /// + public static VisualElement? GetRoot() => _root; + #endregion + + #region Static Factory (without parent) + /// + /// Info 알림 표시 (SetRoot로 설정된 루트 사용) + /// + public static UTKAlert ShowInfo(string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return Show(_root!, title, message, AlertType.Info, onClose, closeOnBlockerClick, confirmLabel); + } + + /// + /// Success 알림 표시 (SetRoot로 설정된 루트 사용) + /// + public static UTKAlert ShowSuccess(string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return Show(_root!, title, message, AlertType.Success, onClose, closeOnBlockerClick, confirmLabel); + } + + /// + /// Warning 알림 표시 (SetRoot로 설정된 루트 사용) + /// + public static UTKAlert ShowWarning(string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return Show(_root!, title, message, AlertType.Warning, onClose, closeOnBlockerClick, confirmLabel); + } + + /// + /// Error 알림 표시 (SetRoot로 설정된 루트 사용) + /// + public static UTKAlert ShowError(string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return Show(_root!, title, message, AlertType.Error, onClose, closeOnBlockerClick, confirmLabel); + } + + /// + /// Confirm 대화상자 표시 (SetRoot로 설정된 루트 사용) + /// + public static UTKAlert ShowConfirm(string title, string message, Action? onConfirm, Action? onCancel = null, bool closeOnBlockerClick = false, string confirmLabel = "OK", string cancelLabel = "Cancel") + { + ValidateRoot(); + var alert = Show(_root!, title, message, AlertType.Confirm, null, closeOnBlockerClick, confirmLabel, cancelLabel); + alert.OnConfirm = onConfirm; + alert.OnCancel = onCancel; + return alert; + } + + /// + /// 알림 표시 (SetRoot로 설정된 루트 사용) + /// + public static UTKAlert Show(string title, string message, AlertType type = AlertType.Info, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK", string cancelLabel = "Cancel") + { + ValidateRoot(); + return Show(_root!, title, message, type, onClose, closeOnBlockerClick, confirmLabel, cancelLabel); + } + + private static void ValidateRoot() + { + if (_root == null) + { + throw new InvalidOperationException("UTKAlert.SetRoot()를 먼저 호출하여 기본 루트 요소를 설정해야 합니다."); + } + } + #endregion + + #region Static Factory Async (without parent) + /// + /// Info 알림 표시 (비동기, SetRoot로 설정된 루트 사용) + /// + public static UniTask ShowInfoAsync(string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return ShowAsync(_root!, title, message, AlertType.Info, closeOnBlockerClick, confirmLabel); + } + + /// + /// Success 알림 표시 (비동기, SetRoot로 설정된 루트 사용) + /// + public static UniTask ShowSuccessAsync(string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return ShowAsync(_root!, title, message, AlertType.Success, closeOnBlockerClick, confirmLabel); + } + + /// + /// Warning 알림 표시 (비동기, SetRoot로 설정된 루트 사용) + /// + public static UniTask ShowWarningAsync(string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return ShowAsync(_root!, title, message, AlertType.Warning, closeOnBlockerClick, confirmLabel); + } + + /// + /// Error 알림 표시 (비동기, SetRoot로 설정된 루트 사용) + /// + public static UniTask ShowErrorAsync(string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + ValidateRoot(); + return ShowAsync(_root!, title, message, AlertType.Error, closeOnBlockerClick, confirmLabel); + } + + /// + /// Confirm 대화상자 표시 (비동기, SetRoot로 설정된 루트 사용) + /// + /// 확인 버튼 클릭 시 true, 취소 버튼 클릭 시 false + public static UniTask ShowConfirmAsync(string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK", string cancelLabel = "Cancel") + { + ValidateRoot(); + return ShowConfirmAsync(_root!, title, message, closeOnBlockerClick, confirmLabel, cancelLabel); + } + #endregion + + #region Static Factory Async (with parent) + /// + /// Info 알림 표시 (비동기) + /// + public static UniTask ShowInfoAsync(VisualElement parent, string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + return ShowAsync(parent, title, message, AlertType.Info, closeOnBlockerClick, confirmLabel); + } + + /// + /// Success 알림 표시 (비동기) + /// + public static UniTask ShowSuccessAsync(VisualElement parent, string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + return ShowAsync(parent, title, message, AlertType.Success, closeOnBlockerClick, confirmLabel); + } + + /// + /// Warning 알림 표시 (비동기) + /// + public static UniTask ShowWarningAsync(VisualElement parent, string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + return ShowAsync(parent, title, message, AlertType.Warning, closeOnBlockerClick, confirmLabel); + } + + /// + /// Error 알림 표시 (비동기) + /// + public static UniTask ShowErrorAsync(VisualElement parent, string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK") + { + return ShowAsync(parent, title, message, AlertType.Error, closeOnBlockerClick, confirmLabel); + } + + /// + /// Confirm 대화상자 표시 (비동기) + /// + /// 확인 버튼 클릭 시 true, 취소 버튼 클릭 시 false + public static UniTask ShowConfirmAsync(VisualElement parent, string title, string message, bool closeOnBlockerClick = false, string confirmLabel = "OK", string cancelLabel = "Cancel") + { + var tcs = new UniTaskCompletionSource(); + var alert = Show(parent, title, message, AlertType.Confirm, null, closeOnBlockerClick, confirmLabel, cancelLabel); + alert.OnConfirm = () => tcs.TrySetResult(true); + alert.OnCancel = () => tcs.TrySetResult(false); + alert.OnClosed = () => tcs.TrySetResult(false); + return tcs.Task; + } + + /// + /// 알림 표시 (비동기) + /// + public static UniTask ShowAsync(VisualElement parent, string title, string message, AlertType type, bool closeOnBlockerClick = false, string confirmLabel = "OK", string cancelLabel = "Cancel") + { + var tcs = new UniTaskCompletionSource(); + var alert = Show(parent, title, message, type, () => tcs.TrySetResult(), closeOnBlockerClick, confirmLabel, cancelLabel); + return tcs.Task; + } + #endregion + + #region Static Factory (with parent) /// /// Info 알림 표시 /// - public static UTKAlert ShowInfo(VisualElement parent, string title, string message, Action? onClose = null) + public static UTKAlert ShowInfo(VisualElement parent, string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") { - return Show(parent, title, message, AlertType.Info, onClose); + return Show(parent, title, message, AlertType.Info, onClose, closeOnBlockerClick, confirmLabel); } /// /// Success 알림 표시 /// - public static UTKAlert ShowSuccess(VisualElement parent, string title, string message, Action? onClose = null) + public static UTKAlert ShowSuccess(VisualElement parent, string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") { - return Show(parent, title, message, AlertType.Success, onClose); + return Show(parent, title, message, AlertType.Success, onClose, closeOnBlockerClick, confirmLabel); } /// /// Warning 알림 표시 /// - public static UTKAlert ShowWarning(VisualElement parent, string title, string message, Action? onClose = null) + public static UTKAlert ShowWarning(VisualElement parent, string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") { - return Show(parent, title, message, AlertType.Warning, onClose); + return Show(parent, title, message, AlertType.Warning, onClose, closeOnBlockerClick, confirmLabel); } /// /// Error 알림 표시 /// - public static UTKAlert ShowError(VisualElement parent, string title, string message, Action? onClose = null) + public static UTKAlert ShowError(VisualElement parent, string title, string message, Action? onClose = null, bool closeOnBlockerClick = false, string confirmLabel = "OK") { - return Show(parent, title, message, AlertType.Error, onClose); + return Show(parent, title, message, AlertType.Error, onClose, closeOnBlockerClick, confirmLabel); } /// /// Confirm 대화상자 표시 /// - public static UTKAlert ShowConfirm(VisualElement parent, string title, string message, Action? onConfirm, Action? onCancel = null) + public static UTKAlert ShowConfirm(VisualElement parent, string title, string message, Action? onConfirm, Action? onCancel = null, bool closeOnBlockerClick = false, string confirmLabel = "OK", string cancelLabel = "Cancel") { - var alert = Show(parent, title, message, AlertType.Confirm, null); + var alert = Show(parent, title, message, AlertType.Confirm, null, closeOnBlockerClick, confirmLabel, cancelLabel); alert.OnConfirm = onConfirm; alert.OnCancel = onCancel; return alert; @@ -163,13 +358,32 @@ namespace UVC.UIToolkit /// /// 알림 표시 /// - public static UTKAlert Show(VisualElement parent, string title, string message, AlertType type, Action? onClose) + /// 부모 요소 + /// 제목 + /// 메시지 + /// 알림 유형 + /// 닫힘 콜백 + /// 배경 클릭 시 닫힘 여부 + /// 확인 버튼 레이블 + /// 취소 버튼 레이블 (Confirm 타입에서만 사용) + public static UTKAlert Show(VisualElement parent, string title, string message, AlertType type, Action? onClose, bool closeOnBlockerClick = false, string confirmLabel = "OK", string cancelLabel = "Cancel") { var alert = new UTKAlert(title, message, type); alert.OnClosed = onClose; + alert._confirmLabel = confirmLabel; + alert._cancelLabel = cancelLabel; + alert.UpdateButtons(); - alert._blocker = UTKModalBlocker.Show(parent, 0.5f, true); - alert._blocker.OnBlockerClicked += alert.Close; + alert._blocker = UTKModalBlocker.Show(parent, 0.5f, closeOnBlockerClick); + if (closeOnBlockerClick) + { + alert._blocker.OnBlockerClicked += alert.Close; + } + else + { + // closeOnBlockerClick이 false일 때 blocker 클릭 시 Alert로 포커스 유지 + alert._blocker.OnBlockerClicked += () => alert.Focus(); + } alert._blocker.Add(alert); // 중앙 정렬 @@ -178,6 +392,13 @@ namespace UVC.UIToolkit alert.style.top = Length.Percent(50); alert.style.translate = new Translate(Length.Percent(-50), Length.Percent(-50)); + // ESC 키 이벤트 등록 + alert.RegisterEscapeKey(parent); + + // Alert에 포커스 설정 (키보드 이벤트 수신을 위해) + alert.focusable = true; + alert.schedule.Execute(() => alert.Focus()); + return alert; } #endregion @@ -187,26 +408,21 @@ namespace UVC.UIToolkit { AddToClassList("utk-alert"); - _iconContainer = new VisualElement { name = "icon-container" }; - _iconContainer.AddToClassList("utk-alert__icon-container"); - Add(_iconContainer); - - _iconLabel = new Label { name = "icon" }; - _iconLabel.AddToClassList("utk-alert__icon"); - _iconContainer.Add(_iconLabel); + // Title (상단 왼쪽) + _titleLabel = new Label { name = "title" }; + _titleLabel.AddToClassList("utk-alert__title"); + Add(_titleLabel); + // Content (가운데 메시지) var contentContainer = new VisualElement { name = "content" }; contentContainer.AddToClassList("utk-alert__content"); Add(contentContainer); - _titleLabel = new Label { name = "title" }; - _titleLabel.AddToClassList("utk-alert__title"); - contentContainer.Add(_titleLabel); - _messageLabel = new Label { name = "message" }; _messageLabel.AddToClassList("utk-alert__message"); contentContainer.Add(_messageLabel); + // Buttons _buttonContainer = new VisualElement { name = "buttons" }; _buttonContainer.AddToClassList("utk-alert__buttons"); Add(_buttonContainer); @@ -248,19 +464,6 @@ namespace UVC.UIToolkit }; AddToClassList(typeClass); - // 아이콘 업데이트 - if (_iconLabel != null) - { - _iconLabel.text = _alertType switch - { - AlertType.Success => "✓", - AlertType.Warning => "⚠", - AlertType.Error => "✕", - AlertType.Confirm => "?", - _ => "ℹ" - }; - } - // 버튼 업데이트 UpdateButtons(); } @@ -273,7 +476,7 @@ namespace UVC.UIToolkit if (_alertType == AlertType.Confirm) { - var cancelBtn = new UTKButton("Cancel", "", UTKButton.ButtonVariant.Normal); + var cancelBtn = new UTKButton(_cancelLabel, "", UTKButton.ButtonVariant.Normal); cancelBtn.AddToClassList("utk-alert__btn"); cancelBtn.OnClicked += () => { @@ -282,7 +485,7 @@ namespace UVC.UIToolkit }; _buttonContainer.Add(cancelBtn); - var confirmBtn = new UTKButton("OK", "", UTKButton.ButtonVariant.Primary); + var confirmBtn = new UTKButton(_confirmLabel, "", UTKButton.ButtonVariant.Primary); confirmBtn.AddToClassList("utk-alert__btn"); confirmBtn.OnClicked += () => { @@ -293,18 +496,64 @@ namespace UVC.UIToolkit } else { - var okBtn = new UTKButton("OK", "", UTKButton.ButtonVariant.Primary); + var okBtn = new UTKButton(_confirmLabel, "", UTKButton.ButtonVariant.Primary); okBtn.AddToClassList("utk-alert__btn"); okBtn.OnClicked += Close; _buttonContainer.Add(okBtn); } } + /// + /// ESC 키 이벤트 등록 + /// + private void RegisterEscapeKey(VisualElement parent) + { + _keyEventTarget = parent; + _keyDownCallback = evt => + { + if (evt.keyCode == KeyCode.Escape) + { + evt.StopPropagation(); + HandleEscapeKey(); + } + }; + + // 패널에 키 이벤트 등록 (포커스와 관계없이 캡처) + _keyEventTarget.RegisterCallback(_keyDownCallback, TrickleDown.TrickleDown); + } + + /// + /// ESC 키 해제 + /// + private void UnregisterEscapeKey() + { + if (_keyDownCallback != null && _keyEventTarget != null) + { + _keyEventTarget.UnregisterCallback(_keyDownCallback, TrickleDown.TrickleDown); + _keyDownCallback = null; + _keyEventTarget = null; + } + } + + /// + /// ESC 키 처리 + /// + private void HandleEscapeKey() + { + if (_alertType == AlertType.Confirm) + { + // Confirm 타입: 취소 처리 + OnCancel?.Invoke(); + } + Close(); + } + /// /// 알림 닫기 /// public void Close() { + UnregisterEscapeKey(); OnClosed?.Invoke(); if (_blocker != null) { diff --git a/Assets/Scripts/UVC/UIToolkit/Modal/UTKColorPicker.cs b/Assets/Scripts/UVC/UIToolkit/Modal/UTKColorPicker.cs index 79258688..a2c30152 100644 --- a/Assets/Scripts/UVC/UIToolkit/Modal/UTKColorPicker.cs +++ b/Assets/Scripts/UVC/UIToolkit/Modal/UTKColorPicker.cs @@ -11,6 +11,36 @@ namespace UVC.UIToolkit.Modal /// UIToolkit 기반 컬러 피커 모달 /// HSV 색공간 + RGB 슬라이더 + Hex 입력 지원 /// + /// + /// + /// // 기본 사용법 (Alpha 포함) + /// var picker = UTKColorPicker.Show(rootVisualElement, Color.red, "Select Color"); + /// picker.OnColorChanged += (color) => + /// { + /// // 실시간 색상 변경 시 호출 + /// Debug.Log($"Color changing: {color}"); + /// }; + /// picker.OnColorSelected += (color) => + /// { + /// // OK 버튼 클릭 시 호출 + /// Debug.Log($"Color selected: #{ColorUtility.ToHtmlStringRGBA(color)}"); + /// }; + /// + /// // Alpha 없이 사용 + /// var pickerNoAlpha = UTKColorPicker.Show(rootVisualElement, Color.blue, "Select Color", useAlpha: false); + /// + /// // async/await 사용법 (UniTask) + /// Color selectedColor = await UTKColorPicker.ShowAsync(rootVisualElement, Color.red, "Select Color"); + /// // OK 클릭 시 선택된 색상 반환, 취소/닫기 시 initialColor(Color.red) 반환 + /// Debug.Log($"Result: #{ColorUtility.ToHtmlStringRGBA(selectedColor)}"); + /// + /// // 인스턴스 직접 생성 + /// var colorPicker = new UTKColorPicker(); + /// colorPicker.UseAlpha = true; // Alpha 슬라이더 표시 여부 (기본값: true) + /// colorPicker.SetColor(Color.green); + /// container.Add(colorPicker); + /// + /// [UxmlElement] public partial class UTKColorPicker : VisualElement, IDisposable { @@ -22,7 +52,7 @@ namespace UVC.UIToolkit.Modal #region Fields private bool _disposed; - private bool _useAlpha; + private bool _useAlpha = true; private bool _isUpdating; // 재귀 업데이트 방지 private Color _originalColor = Color.white; @@ -61,9 +91,15 @@ namespace UVC.UIToolkit.Modal private Label? _titleLabel; private Button? _closeButton; - private Button? _cancelButton; - private Button? _confirmButton; + private UTKButton? _cancelButton; + private UTKButton? _confirmButton; private VisualElement? _alphaRow; + private VisualElement? _header; + + // 드래그 관련 필드 + private bool _isDragging; + private Vector2 _dragStartPosition; + private Vector2 _dragStartMousePosition; #endregion #region Events @@ -77,6 +113,23 @@ namespace UVC.UIToolkit.Modal public event Action? OnClosed; #endregion + #region Properties + /// + /// 알파(투명도) 값을 다룰지 여부 (기본값: true) + /// + [UxmlAttribute] + public bool UseAlpha + { + get => _useAlpha; + set + { + _useAlpha = value; + SetAlphaVisible(value); + UpdateHexField(); + } + } + #endregion + #region Constructor public UTKColorPicker() { @@ -152,7 +205,7 @@ namespace UVC.UIToolkit.Modal VisualElement parent, Color initialColor, string title = "Color Picker", - bool useAlpha = false) + bool useAlpha = true) { var picker = new UTKColorPicker(); picker._useAlpha = useAlpha; @@ -175,6 +228,41 @@ namespace UVC.UIToolkit.Modal return picker; } + + /// + /// 컬러 피커를 표시하고 색상 선택을 기다립니다. + /// OK 버튼 클릭 시 선택된 색상을 반환하고, 취소/닫기 시 initialColor를 반환합니다. + /// + /// 부모 VisualElement + /// 초기 색상 + /// 피커 제목 + /// 알파 채널 사용 여부 + /// 선택된 색상 또는 초기 색상 + public static async UniTask ShowAsync( + VisualElement parent, + Color initialColor, + string title = "Color Picker", + bool useAlpha = true) + { + var tcs = new UniTaskCompletionSource(); + Color resultColor = initialColor; + + var picker = Show(parent, initialColor, title, useAlpha); + + picker.OnColorSelected += (color) => + { + resultColor = color; + tcs.TrySetResult(resultColor); + }; + + picker.OnClosed += () => + { + // OnColorSelected가 먼저 호출된 경우 이미 완료됨 + tcs.TrySetResult(resultColor); + }; + + return await tcs.Task; + } #endregion #region Public Methods @@ -222,8 +310,8 @@ namespace UVC.UIToolkit.Modal AddToClassList("utk-color-picker"); // Header - var header = new VisualElement { name = "header" }; - header.AddToClassList("utk-color-picker__header"); + _header = new VisualElement { name = "header" }; + _header.AddToClassList("utk-color-picker__header"); _titleLabel = new Label("Color Picker") { name = "title" }; _titleLabel.AddToClassList("utk-color-picker__title"); @@ -231,9 +319,9 @@ namespace UVC.UIToolkit.Modal _closeButton = new Button { name = "close-btn", text = "\u2715" }; // ✕ _closeButton.AddToClassList("utk-color-picker__close-btn"); - header.Add(_titleLabel); - header.Add(_closeButton); - Add(header); + _header.Add(_titleLabel); + _header.Add(_closeButton); + Add(_header); // SV Box + Hue Bar row var colorArea = new VisualElement { name = "color-area" }; @@ -309,10 +397,10 @@ namespace UVC.UIToolkit.Modal var buttonRow = new VisualElement { name = "button-row" }; buttonRow.AddToClassList("utk-color-picker__button-row"); - _cancelButton = new Button { name = "cancel-btn", text = "Cancel" }; + _cancelButton = new UTKButton("Cancel", "", UTKButton.ButtonVariant.Normal) { name = "cancel-btn" }; _cancelButton.AddToClassList("utk-color-picker__cancel-btn"); - _confirmButton = new Button { name = "confirm-btn", text = "OK" }; + _confirmButton = new UTKButton("OK", "", UTKButton.ButtonVariant.Primary) { name = "confirm-btn" }; _confirmButton.AddToClassList("utk-color-picker__confirm-btn"); buttonRow.Add(_cancelButton); @@ -362,17 +450,35 @@ namespace UVC.UIToolkit.Modal _titleLabel ??= this.Q