2026-10-18 15:09:05 +00:00
// ─────────────────────────────────────────────────────────────────────────────
// WLEquipGui.RefLayout.cs — PD 레퍼런스 레이아웃(#881 · 2026-10-16)
/ /
// PD 원문: 「장비 아이템에 텍스트가 너무 많아. 첨부한 레퍼런스 게임과 같이 장비 UI 팝업의 레이아웃을 수정해줘」
// + 「인벤토리 화면도 예시 이미지와 같이 레이아웃을 개선하도록 해.」
// - 장비 상세 팝업 V2: 등급 리본 · 제목 띠 · 아이콘 + 「레벨: n/max」 · 능력치(+다음 레벨 증가분) + 합성 · 강등 작은 버튼 · 짧은 설명 ·
// 「등급 스킬」 목록(등급 색 점 · 잠긴 등급 = 자물쇠 + 흐림 · 마지막 줄 = 시너지) · 비용 줄 · 버튼 3(장비 · 레벨업 · 일괄 레벨업).
// 예전 긴 글(FR 옵션 · 계산식 · 설명 문단)은 넣지 않는다.
// - 인벤토리 머리줄: 부위 탭 대신 「정렬 · 나의 장비 · 합성」(부위 거르기 = 빈 슬롯 누르기 · 제목 누르면 전체).
// 치수 · 색 · 글자 · 아이콘 = Resources/WL/WLEquipGui.json itemPopup · hub(C45).
// ─────────────────────────────────────────────────────────────────────────────
using System.Collections.Generic ;
using TMPro ;
using UnityEngine ;
using UnityEngine.UI ;
using WL.PartSkill ;
namespace WL.Equip
{
public partial class WLEquipGui
{
WLGuiItemPopup IP ;
bool IpOn { get { return IP ! = null & & IP . enabled ! = 0 ; } }
TextMeshProUGUI _ipLevel , _ipStat , _ipDesc , _ipSection , _ipGold , _ipStone ;
Image _ipStatIcon , _ipGoldIcon , _ipStoneIcon ;
RectTransform _ipRowsRoot , _ipStoneGroup ;
sealed class IpRow { public RectTransform root ; public Image dot , lockImg ; public TextMeshProUGUI text ; }
readonly List < IpRow > _ipRows = new List < IpRow > ( ) ;
public static int ItemPopupV2Refreshes ;
// ── 인벤토리 머리줄 · 정렬 ──────────────────────────────────────────
int _sortMode ;
TextMeshProUGUI _sortLabel , _gridTitle ;
void BuildGridHeader ( )
{
var hdr = NewRT ( _hub , "GridHeader" ) ;
hdr . anchorMin = new Vector2 ( 0f , 1f ) ; hdr . anchorMax = new Vector2 ( 1f , 1f ) ; hdr . pivot = new Vector2 ( 0.5f , 1f ) ;
hdr . anchoredPosition = new Vector2 ( 0f , - H . tabTop ) ; hdr . sizeDelta = new Vector2 ( 0f , H . headerH ) ;
var sortBtn = NewButton ( hdr , SortLabel ( ) , Spr ( "btnPrimary" ) , Color . white , H . headerBtnW , H . headerH , ( ) = > { _sortMode = ( _sortMode + 1 ) % Mathf . Max ( 1 , H . sortLabels ! = null ? H . sortLabels . Length : 1 ) ; Refresh ( ) ; } , H . headerBtnFontPx , DarkCol ( ) ) ;
Place ( ( RectTransform ) sortBtn . transform , new Vector2 ( 0f , 0.5f ) , new Vector2 ( H . edge , 0f ) , new Vector2 ( H . headerBtnW , H . headerH ) , new Vector2 ( 0f , 0.5f ) ) ;
_sortLabel = sortBtn . GetComponentInChildren < TextMeshProUGUI > ( ) ;
_composeOpenBtn = NewButton ( hdr , "합성" , Spr ( "btnPrimary" ) , Color . white , H . headerBtnW , H . headerH , ( ) = > OpenCompose ( - 1 ) , H . headerBtnFontPx , DarkCol ( ) ) ;
Place ( ( RectTransform ) _composeOpenBtn . transform , new Vector2 ( 1f , 0.5f ) , new Vector2 ( - H . edge , 0f ) , new Vector2 ( H . headerBtnW , H . headerH ) , new Vector2 ( 1f , 0.5f ) ) ;
_gridTitle = NewText ( hdr , "Title" , H . headerFontPx , TextAlignmentOptions . Midline , TextCol ( ) ) ;
_gridTitle . fontStyle = FontStyles . Bold ; ApplyOutline ( _gridTitle ) ; _gridTitle . raycastTarget = true ;
var trt = _gridTitle . rectTransform ; trt . anchorMin = new Vector2 ( 0f , 0f ) ; trt . anchorMax = new Vector2 ( 1f , 1f ) ;
trt . offsetMin = new Vector2 ( H . edge + H . headerBtnW + 16f , 0f ) ; trt . offsetMax = new Vector2 ( - ( H . edge + H . headerBtnW + 16f ) , 0f ) ;
var tb = _gridTitle . gameObject . AddComponent < Button > ( ) ; tb . targetGraphic = _gridTitle ; tb . onClick . AddListener ( ( ) = > { _tab = 0 ; Refresh ( ) ; } ) ; // 제목 = 부위 거르기 해제
}
string SortLabel ( )
{
var l = H . sortLabels ;
return l ! = null & & l . Length > 0 ? l [ Mathf . Clamp ( _sortMode , 0 , l . Length - 1 ) ] : "정렬" ;
}
void RefreshGridHeader ( )
{
if ( _sortLabel ! = null ) _sortLabel . text = SortLabel ( ) ;
if ( _gridTitle ! = null ) _gridTitle . text = H . gridTitle + ( _tab ! = 0 ? " · " + WLEquipTables . SlotName ( ( eFREquipmentType ) _tab ) : "" ) ;
}
static int TierOfItem ( Item it )
{
WLGradeTier t ; int plus ;
return it ! = null & & it . itemJson ! = null & & it . itemJson . IsEquipment & & WLEquipGrades . TryMap ( it . itemJson , out t , out plus ) ? t . tier * 100 + plus : 0 ;
}
/// <summary>그리드 목록 — 장착분 빼기(hub.gridHideEquipped) + 정렬(0 = 등급순 · 1 = 레벨순 · 장비 먼저 · 소모품 뒤).</summary>
List < Item > GridItems ( List < Item > src )
{
if ( H . headerMode = = 0 & & H . gridHideEquipped = = 0 ) return src ;
var list = new List < Item > ( src . Count ) ;
for ( int i = 0 ; i < src . Count ; i + + )
{
var it = src [ i ] ;
if ( H . gridHideEquipped ! = 0 & & it . itemJson . IsEquipment & & WLInventory . IsEquipped ( it . rawItem . _id ) ) continue ;
list . Add ( it ) ;
}
if ( H . headerMode = = 0 ) return list ;
int mode = _sortMode ;
list . Sort ( ( a , b ) = >
{
bool qa = a . itemJson . IsEquipment , qb = b . itemJson . IsEquipment ;
if ( qa ! = qb ) return qa ? - 1 : 1 ;
int ta = TierOfItem ( a ) , tb = TierOfItem ( b ) , la = a . rawItem . level , lb = b . rawItem . level ;
int c = mode = = 1 ? lb . CompareTo ( la ) : tb . CompareTo ( ta ) ;
if ( c ! = 0 ) return c ;
c = mode = = 1 ? tb . CompareTo ( ta ) : lb . CompareTo ( la ) ;
if ( c ! = 0 ) return c ;
c = a . itemJson . EquipmentType . CompareTo ( b . itemJson . EquipmentType ) ;
if ( c ! = 0 ) return c ;
return a . rawItem . _id . CompareTo ( b . rawItem . _id ) ;
} ) ;
return list ;
}
// ── 장비 상세 팝업 V2 ────────────────────────────────────────────────
void BuildItemPopupV2 ( )
{
_popup = new GameObject ( "DetailPopup" , typeof ( RectTransform ) ) ; _popup . layer = UILayer ; _popup . transform . SetParent ( _root , false ) ;
var prt = ( RectTransform ) _popup . transform ; Stretch ( prt ) ;
var dim = NewImage ( prt , "Dim" , null , Col ( "dim" , new Color ( 0f , 0f , 0f , 0.7f ) ) , Image . Type . Simple ) ; Stretch ( dim . rectTransform ) ; dim . raycastTarget = true ;
var dimBtn = dim . gameObject . AddComponent < Button > ( ) ; dimBtn . targetGraphic = dim ; dimBtn . onClick . AddListener ( ( ) = > _popup . SetActive ( false ) ) ;
var box = NewPopupBox ( prt , "Box" , new Vector2 ( IP . w , IP . h ) ) ; var b = box . rectTransform ;
const float rim = 14f ;
// 제목 띠 + 등급 리본 + 닫기
var titleBar = NewImage ( b , "TitleBar" , Spr ( "titleBar" ) , Col ( "popupTitleBar" , new Color ( 1f , 0.78f , 0.2f , 1f ) ) ) ;
var tb = titleBar . rectTransform ; tb . anchorMin = new Vector2 ( 0f , 1f ) ; tb . anchorMax = new Vector2 ( 1f , 1f ) ; tb . pivot = new Vector2 ( 0.5f , 1f ) ;
tb . offsetMin = new Vector2 ( rim , - IP . titleH - rim ) ; tb . offsetMax = new Vector2 ( - rim , - rim ) ;
_popupName = NewText ( tb , "Name" , IP . titleFontPx , TextAlignmentOptions . Midline , Col ( "popupTitleText" , Color . white ) ) ; _popupName . fontStyle = FontStyles . Bold ; ApplyOutline ( _popupName ) ;
Stretch ( _popupName . rectTransform , 100f , 4f , 100f , 4f ) ;
_popupRarityImg = NewImage ( b , "Ribbon" , Spr ( "gradeLabel" ) , Color . white ) ;
Place ( _popupRarityImg . rectTransform , new Vector2 ( 0.5f , 1f ) , new Vector2 ( 0f , IP . ribbonH * 0.35f ) , new Vector2 ( IP . ribbonW , IP . ribbonH ) , new Vector2 ( 0.5f , 0.5f ) ) ;
_popupRarity = NewText ( _popupRarityImg . rectTransform , "Rarity" , IP . ribbonFontPx , TextAlignmentOptions . Midline , Color . white ) ; _popupRarity . fontStyle = FontStyles . Bold ; ApplyOutline ( _popupRarity ) ; Stretch ( _popupRarity . rectTransform ) ;
var close = NewIconButton ( b , "Close" , "closeIcon" , L . closeBtnW , ( ) = > _popup . SetActive ( false ) , "closeBtnBg" ) ;
Place ( ( RectTransform ) close . transform , new Vector2 ( 1f , 1f ) , new Vector2 ( 10f , 10f ) , new Vector2 ( L . closeBtnW , L . closeBtnH ) , new Vector2 ( 1f , 1f ) ) ;
// 아이콘 + 레벨
float x0 = rim + IP . pad , y0 = rim + IP . titleH + IP . pad ;
_popupForm = NewCell ( b , "Form" , IP . iconSize , L . cellIconInset , L . cellFontPx , true , false , null ) ;
Place ( _popupForm . root , new Vector2 ( 0f , 1f ) , new Vector2 ( x0 , - y0 ) , new Vector2 ( IP . iconSize , IP . iconSize ) , new Vector2 ( 0f , 1f ) ) ;
_ipLevel = NewText ( b , "Level" , IP . levelFontPx , TextAlignmentOptions . Midline , TextCol ( ) ) ; _ipLevel . fontStyle = FontStyles . Bold ;
Place ( _ipLevel . rectTransform , new Vector2 ( 0f , 1f ) , new Vector2 ( x0 , - ( y0 + IP . iconSize + 4f ) ) , new Vector2 ( IP . iconSize , IP . levelFontPx + 12f ) , new Vector2 ( 0f , 1f ) ) ;
// 능력치 상자 + 작은 버튼(합성 · 강등)
float rx = x0 + IP . iconSize + IP . gap , rw = IP . w - rx - x0 ;
float statW = rw - ( IP . smallBtn + IP . gap ) * 2f ;
var statBox = NewImage ( b , "StatBox" , Spr ( "innerBox" ) , Col ( "statBox" , new Color ( 0.1f , 0.14f , 0.3f , 0.92f ) ) ) ;
Place ( statBox . rectTransform , new Vector2 ( 0f , 1f ) , new Vector2 ( rx , - y0 ) , new Vector2 ( statW , IP . statH ) , new Vector2 ( 0f , 1f ) ) ;
_ipStatIcon = NewImage ( statBox . rectTransform , "Icon" , null , Color . white , Image . Type . Simple ) ; _ipStatIcon . preserveAspect = true ;
Place ( _ipStatIcon . rectTransform , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 10f , 0f ) , new Vector2 ( IP . statIcon , IP . statIcon ) , new Vector2 ( 0f , 0.5f ) ) ;
_ipStat = NewText ( statBox . rectTransform , "Value" , IP . statFontPx , TextAlignmentOptions . MidlineRight , TextCol ( ) ) ; _ipStat . fontStyle = FontStyles . Bold ; ApplyOutline ( _ipStat ) ;
_ipStat . enableWordWrapping = false ; _ipStat . overflowMode = TextOverflowModes . Ellipsis ;
var srt = _ipStat . rectTransform ; srt . anchorMin = Vector2 . zero ; srt . anchorMax = Vector2 . one ; srt . offsetMin = new Vector2 ( IP . statIcon + 18f , 0f ) ; srt . offsetMax = new Vector2 ( - 16f , 0f ) ;
_composeBtn = NewButton ( b , "합성" , Spr ( "btnBlue" ) , Color . white , IP . smallBtn , IP . statH , ( ) = > OpenCompose ( _selectedId ) , IP . smallBtnFontPx , TextCol ( ) ) ;
Place ( ( RectTransform ) _composeBtn . transform , new Vector2 ( 0f , 1f ) , new Vector2 ( rx + statW + IP . gap , - y0 ) , new Vector2 ( IP . smallBtn , IP . statH ) , new Vector2 ( 0f , 1f ) ) ;
_downBtn = NewButton ( b , "강등" , Spr ( "btnDanger" ) , Color . white , IP . smallBtn , IP . statH , ( ) = > Act ( ( ) = > WLInventory . Downgrade ( _selectedId ) ) , IP . smallBtnFontPx , TextCol ( ) ) ;
Place ( ( RectTransform ) _downBtn . transform , new Vector2 ( 0f , 1f ) , new Vector2 ( rx + statW + IP . gap * 2f + IP . smallBtn , - y0 ) , new Vector2 ( IP . smallBtn , IP . statH ) , new Vector2 ( 0f , 1f ) ) ;
// 짧은 설명
float dy = y0 + IP . statH + IP . gap ;
float dh = Mathf . Max ( IP . descH , IP . iconSize + IP . levelFontPx + 16f - IP . statH - IP . gap ) ;
var desc = NewImage ( b , "Desc" , Spr ( "innerBox" ) , Col ( "descBox" , new Color ( 0f , 0f , 0f , 0.28f ) ) ) ;
Place ( desc . rectTransform , new Vector2 ( 0f , 1f ) , new Vector2 ( rx , - dy ) , new Vector2 ( rw , dh ) , new Vector2 ( 0f , 1f ) ) ;
_ipDesc = NewText ( desc . rectTransform , "Text" , IP . descFontPx , TextAlignmentOptions . MidlineLeft , Col ( "descText" , new Color ( 0.92f , 0.94f , 1f , 1f ) ) ) ;
_ipDesc . enableWordWrapping = true ; _ipDesc . overflowMode = TextOverflowModes . Ellipsis ; Stretch ( _ipDesc . rectTransform , 16f , 8f , 16f , 8f ) ;
// 등급 스킬
float bottomBlock = rim + IP . pad + IP . btnH + IP . gap + IP . costH + IP . gap ;
float sy = Mathf . Max ( dy + dh , y0 + IP . iconSize + IP . levelFontPx + 16f ) + IP . gap ;
var sec = NewImage ( b , "Section" , Spr ( "innerBox" ) , Col ( "sectionBox" , new Color ( 0f , 0f , 0f , 0.26f ) ) ) ;
var secRt = sec . rectTransform ; secRt . anchorMin = Vector2 . zero ; secRt . anchorMax = Vector2 . one ; secRt . offsetMin = new Vector2 ( x0 , bottomBlock ) ; secRt . offsetMax = new Vector2 ( - x0 , - sy ) ;
var head = NewImage ( secRt , "Head" , Spr ( "innerBox" ) , Col ( "sectionHead" , new Color ( 0f , 0f , 0f , 0.3f ) ) ) ;
var hrt = head . rectTransform ; hrt . anchorMin = new Vector2 ( 0f , 1f ) ; hrt . anchorMax = new Vector2 ( 1f , 1f ) ; hrt . pivot = new Vector2 ( 0.5f , 1f ) ; hrt . offsetMin = new Vector2 ( 0f , - IP . sectionTitleH ) ; hrt . offsetMax = Vector2 . zero ;
_ipSection = NewText ( hrt , "Title" , IP . sectionTitleFontPx , TextAlignmentOptions . MidlineLeft , TextCol ( ) ) ; _ipSection . fontStyle = FontStyles . Bold ; Stretch ( _ipSection . rectTransform , 20f , 0f , 20f , 0f ) ;
_ipSection . text = IP . sectionTitle ;
_ipRowsRoot = NewRT ( secRt , "Rows" ) ;
_ipRowsRoot . anchorMin = Vector2 . zero ; _ipRowsRoot . anchorMax = Vector2 . one ; _ipRowsRoot . offsetMin = new Vector2 ( 16f , 8f ) ; _ipRowsRoot . offsetMax = new Vector2 ( - 16f , - IP . sectionTitleH - 8f ) ;
var vl = _ipRowsRoot . gameObject . AddComponent < VerticalLayoutGroup > ( ) ; vl . spacing = 2f ;
vl . childControlWidth = true ; vl . childControlHeight = true ; vl . childForceExpandWidth = true ; vl . childForceExpandHeight = false ;
var circle = WLPartSkillHud . CircleSprite ( ) ;
for ( int i = 0 ; i < 8 ; i + + )
{
var r = new IpRow ( ) ;
r . root = NewRT ( _ipRowsRoot , "Row" + i ) ; Pref ( r . root , 0f , IP . rowH ) ;
r . dot = NewImage ( r . root , "Dot" , circle , Color . white , Image . Type . Simple ) ;
Place ( r . dot . rectTransform , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 6f + ( IP . lockSize - IP . dotSize ) * 0.5f , 0f ) , new Vector2 ( IP . dotSize , IP . dotSize ) , new Vector2 ( 0f , 0.5f ) ) ;
r . lockImg = NewImage ( r . root , "Lock" , Spr ( "lockIcon" ) , Col ( "lockTint" , new Color ( 1f , 0.75f , 0.3f , 1f ) ) , Image . Type . Simple ) ; r . lockImg . preserveAspect = true ;
Place ( r . lockImg . rectTransform , new Vector2 ( 0f , 0.5f ) , new Vector2 ( 6f , 0f ) , new Vector2 ( IP . lockSize , IP . lockSize ) , new Vector2 ( 0f , 0.5f ) ) ;
r . text = NewText ( r . root , "Text" , IP . rowFontPx , TextAlignmentOptions . MidlineLeft , TextCol ( ) ) ; r . text . enableWordWrapping = false ; r . text . overflowMode = TextOverflowModes . Ellipsis ;
var t = r . text . rectTransform ; t . anchorMin = Vector2 . zero ; t . anchorMax = Vector2 . one ; t . offsetMin = new Vector2 ( IP . lockSize + 20f , 0f ) ; t . offsetMax = Vector2 . zero ;
_ipRows . Add ( r ) ;
}
// 비용 줄(골드 · 강화석)
var cost = NewRT ( b , "Cost" ) ;
cost . anchorMin = new Vector2 ( 0f , 0f ) ; cost . anchorMax = new Vector2 ( 1f , 0f ) ; cost . pivot = new Vector2 ( 0.5f , 0f ) ;
cost . offsetMin = new Vector2 ( x0 , rim + IP . pad + IP . btnH + IP . gap ) ; cost . offsetMax = new Vector2 ( - x0 , rim + IP . pad + IP . btnH + IP . gap + IP . costH ) ;
var hl = cost . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ; hl . spacing = 10f ; hl . childAlignment = TextAnchor . MiddleCenter ;
hl . childControlWidth = true ; hl . childControlHeight = true ; hl . childForceExpandWidth = false ; hl . childForceExpandHeight = false ;
_ipGoldIcon = NewImage ( cost , "GoldIcon" , Spr ( "goldIcon" ) , Color . white , Image . Type . Simple ) ; _ipGoldIcon . preserveAspect = true ; Pref ( _ipGoldIcon . rectTransform , IP . costIcon , IP . costIcon ) ;
_ipGold = NewText ( cost , "Gold" , IP . costFontPx , TextAlignmentOptions . MidlineLeft , TextCol ( ) ) ; _ipGold . fontStyle = FontStyles . Bold ; ApplyOutline ( _ipGold ) ;
_ipGold . enableWordWrapping = false ; var gle = _ipGold . gameObject . AddComponent < LayoutElement > ( ) ; gle . minHeight = IP . costH ;
_ipStoneGroup = NewRT ( cost , "StoneGroup" ) ;
var shl = _ipStoneGroup . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ; shl . spacing = 10f ; shl . padding = new RectOffset ( 30 , 0 , 0 , 0 ) ; shl . childAlignment = TextAnchor . MiddleCenter ;
shl . childControlWidth = true ; shl . childControlHeight = true ; shl . childForceExpandWidth = false ; shl . childForceExpandHeight = false ;
_ipStoneIcon = NewImage ( _ipStoneGroup , "StoneIcon" , Spr ( "stoneIcon" ) , Color . white , Image . Type . Simple ) ; _ipStoneIcon . preserveAspect = true ; Pref ( _ipStoneIcon . rectTransform , IP . costIcon , IP . costIcon ) ;
_ipStone = NewText ( _ipStoneGroup , "Stone" , IP . costFontPx , TextAlignmentOptions . MidlineLeft , TextCol ( ) ) ; _ipStone . fontStyle = FontStyles . Bold ; ApplyOutline ( _ipStone ) ;
_ipStone . enableWordWrapping = false ; var sle = _ipStone . gameObject . AddComponent < LayoutElement > ( ) ; sle . minHeight = IP . costH ;
// 버튼 3 — 장비 · 레벨업 · 일괄 레벨업
var row = NewRT ( b , "Buttons" ) ;
row . anchorMin = new Vector2 ( 0f , 0f ) ; row . anchorMax = new Vector2 ( 1f , 0f ) ; row . pivot = new Vector2 ( 0.5f , 0f ) ;
row . offsetMin = new Vector2 ( x0 , rim + IP . pad ) ; row . offsetMax = new Vector2 ( - x0 , rim + IP . pad + IP . btnH ) ;
var bl = row . gameObject . AddComponent < HorizontalLayoutGroup > ( ) ; bl . spacing = IP . gap ;
bl . childControlWidth = true ; bl . childControlHeight = true ; bl . childForceExpandWidth = false ; bl . childForceExpandHeight = true ;
_equipBtn = NewButton ( row , "장비" , Spr ( "btnBlue" ) , Color . white , 0f , IP . btnH , OnEquipToggle , IP . btnFontPx , TextCol ( ) ) ; Flex ( ( RectTransform ) _equipBtn . transform , 1f ) ;
_equipLabel = _equipBtn . GetComponentInChildren < TextMeshProUGUI > ( ) ;
_lvBtn = NewButton ( row , "레벨업" , Spr ( "btnPrimary" ) , Color . white , 0f , IP . btnH , ( ) = > Act ( ( ) = > WLInventory . ItemLevelUp ( _selectedId , false ) ) , IP . btnFontPx , DarkCol ( ) ) ; Flex ( ( RectTransform ) _lvBtn . transform , 1f ) ;
_lvAllBtn = NewButton ( row , "일괄 레벨업" , Spr ( "btnSecondary" ) , Color . white , 0f , IP . btnH , ( ) = > Act ( ( ) = > WLInventory . ItemLevelUp ( _selectedId , true ) ) , IP . btnFontPx , DarkCol ( ) ) ; Flex ( ( RectTransform ) _lvAllBtn . transform , 1f ) ;
_popup . SetActive ( false ) ;
}
void RefreshItemPopupV2 ( Item sel )
{
ItemPopupV2Refreshes + + ;
var j = sel . itemJson ;
bool isEq = j . IsEquipment ;
BindCell ( _popupForm , sel , false ) ;
_popupName . text = j . Item_name ;
int tier = 1 , plus = 0 ; WLGradeTier t = null ;
if ( isEq & & WLEquipGrades . TryMap ( j , out t , out plus ) ) tier = t . tier ;
_popupRarityImg . color = isEq ? TierCol ( tier ) : Col ( "frameEmpty" , Color . gray ) ;
_popupRarity . text = isEq ? WLEquipGrades . Label ( j , false ) : "소모품" ;
_ipLevel . text = isEq ? IP . levelPrefix + sel . rawItem . level + "/" + j . ItemMaxLv : "× " + sel . rawItem . count ;
string iconKey = null ;
_ipStat . text = isEq ? StatText ( sel , out iconKey ) : "" ;
string sprName ; _ipStatIcon . sprite = iconKey ! = null & & IP . statIcons ! = null & & IP . statIcons . TryGetValue ( iconKey , out sprName ) ? WLEquipGuiAssets . Get ( sprName ) : null ;
_ipStatIcon . color = _ipStatIcon . sprite ! = null ? Color . white : new Color ( 1f , 1f , 1f , 0f ) ;
_ipDesc . text = DescText ( sel ) ;
FillGradeRows ( sel , isEq ? tier : 0 ) ;
FillCost ( sel , isEq ) ;
bool equipped = isEq & & WLInventory . IsEquipped ( sel . rawItem . _id ) ;
_equipLabel . text = equipped ? "해제" : "장비" ;
SetEnabled ( _equipBtn , isEq , "btnBlue" ) ;
bool canLv = isEq & & WLInventory . NextLevelCost ( sel ) ! = null ;
SetEnabled ( _lvBtn , canLv , "btnPrimary" ) ; SetEnabled ( _lvAllBtn , canLv , "btnSecondary" ) ;
ItemComposeJson cj ; ItemJson rj ;
SetEnabled ( _composeBtn , isEq & & WLInventory . ComposeInfo ( sel , out cj , out rj ) , "btnBlue" ) ;
SetEnabled ( _downBtn , isEq & & WLEquipTables . FindComposeByResult ( j . ItemGrade , j . GradeLv ) ! = null , "btnDanger" ) ;
}
/// <summary>능력치 한 줄 — 평평한 값 = 「7.0<초록>+0.3</초록>」(다음 레벨 증가분) · 반지 = 근접/원거리 · 신발 · 목걸이 = 배율 문구.</summary>
string StatText ( Item sel , out string iconKey )
{
iconKey = null ;
var part = WLEquipGrades . Part ( sel . itemJson . EquipmentType ) ;
if ( part = = null ) return "" ;
if ( WLEquipGrades . IsRateOutput ( part ) ) { iconKey = part . output ; return WLPartStats . ItemStatLine ( sel , true ) ; }
eStat o ; double bv , bonus ; string line ;
if ( ! WLEquipStats . ItemValues ( sel , WLEquipStats . CurrentMainStat ( ) , out o , out bv , out bonus , out line ) ) return "" ;
double cur = bv + bonus ;
iconKey = WLEquipGrades . IsRingOutput ( part ) ? "RING" : o . ToString ( ) ;
string head = "" ;
if ( WLEquipGrades . IsRingOutput ( part ) )
{
string rn ; bool ok = WLPartStats . RingMatches ( sel , out rn ) ;
head = ( rn . Length > 0 ? rn . Split ( ' ' ) [ 0 ] : rn ) + " " ;
if ( ! ok ) return "<color=#FF8A8A>" + head + cur . ToString ( "0.#" ) + "</color>" ;
}
string s = head + cur . ToString ( "0.#" ) ;
if ( WLInventory . NextLevelCost ( sel ) ! = null )
{
eStat o2 ; double bv2 , bonus2 ; string line2 ;
if ( WLEquipStats . ItemValues ( sel , WLEquipStats . CurrentMainStat ( ) , out o2 , out bv2 , out bonus2 , out line2 , sel . rawItem . level + 1 ) )
{
double d = bv2 + bonus2 - cur ;
if ( d > 0.05 ) s + = "<color=#" + ColorUtility . ToHtmlStringRGB ( Col ( "statDelta" , new Color ( 0.49f , 0.92f , 0.38f , 1f ) ) ) + ">+" + d . ToString ( "0.#" ) + "</color>" ;
}
}
return s ;
}
/// <summary>짧은 설명 — 파츠 스킬 설명 · 무기 = 주력/각성 스킬 이름 · 그 밖 = 표 설명.</summary>
string DescText ( Item sel )
{
var j = sel . itemJson ;
if ( ! j . IsEquipment ) return j . Desc ? ? "" ;
var d = WLPartSkillData . Enabled ? WLPartSkillData . DefForGroup ( j . GroupNo ) : null ;
if ( d ! = null & & ( d . kind = = WLPartKind . Active | | d . kind = = WLPartKind . Passive ) ) return d . desc ;
if ( j . EquipmentType = = eFREquipmentType . Weapon )
{
var rows = WL . Combat . Weapon . WLWeaponSkillTable . Rows ( j . GroupNo ) ;
if ( rows ! = null & & rows . Count > 0 )
{
var sb = new System . Text . StringBuilder ( ) ;
2026-10-22 15:24:35 +00:00
foreach ( var r in WL . Combat . Weapon . WLWeaponSkillTable . ByUnlock ( rows ) ) { if ( sb . Length > 0 ) sb . Append ( " · " ) ; sb . Append ( WL . Combat . Weapon . WLWeaponSkillTable . SlotName ( r . slot ) ) . Append ( " [" ) . Append ( r . skillName ) . Append ( ']' ) ; } // #973 기본 → 주력 → 각성
2026-10-18 15:09:05 +00:00
return sb . ToString ( ) ;
}
}
return j . Desc ? ? "" ;
}
/// <summary>등급 스킬 줄 — 파츠 스킬 = 등급별 단계 · 무기 = 무기 스킬 등급표 · 마지막 = 시너지. 지금 등급보다 높은 줄 = 자물쇠 + 흐림.</summary>
void FillGradeRows ( Item sel , int tier )
{
var j = sel . itemJson ;
var texts = new List < string > ( ) ; var tiers = new List < int > ( ) ; var open = new List < bool > ( ) ;
var d = j . IsEquipment & & WLPartSkillData . Enabled ? WLPartSkillData . DefForGroup ( j . GroupNo ) : null ;
int maxParts = Mathf . Max ( 1 , IP . rowMaxParts ) ;
if ( d ! = null & & ( d . kind = = WLPartKind . Active | | d . kind = = WLPartKind . Passive ) )
{
2026-10-22 15:24:35 +00:00
for ( int t = 1 ; t < = WLEquipGrades . MaxTier ; t + + ) { texts . Add ( WLPartSkillText . TierRow ( d , t , maxParts ) ) ; tiers . Add ( t ) ; open . Add ( t < = tier ) ; } // #973 G1~G8
2026-10-18 15:09:05 +00:00
}
else if ( j . IsEquipment & & j . EquipmentType = = eFREquipmentType . Weapon )
{
var rows = WL . Combat . Weapon . WLWeaponSkillTable . Rows ( j . GroupNo ) ;
2026-10-22 15:24:35 +00:00
string main = "" , awaken = "" , basic = "" ;
if ( rows ! = null ) foreach ( var r in rows ) { if ( r . slot = = 1 ) main = r . skillName ; else if ( r . slot = = 2 ) awaken = r . skillName ; else if ( r . slot = = 3 ) basic = r . skillName ; } // #973 3 = 기본
2026-10-18 15:09:05 +00:00
var wt = IP . weaponTierText ;
if ( rows ! = null & & rows . Count > 0 & & wt ! = null & & wt . Length > 1 )
2026-10-22 15:24:35 +00:00
for ( int t = 1 ; t < = WLEquipGrades . MaxTier & & t < wt . Length ; t + + ) { texts . Add ( wt [ t ] . Replace ( "{main}" , main ) . Replace ( "{awaken}" , awaken ) . Replace ( "{basic}" , basic . Length > 0 ? basic : "준비 중" ) ) ; tiers . Add ( t ) ; open . Add ( t < = tier ) ; }
2026-10-18 15:09:05 +00:00
}
// 시너지 줄(파츠 액티브 · 무기 시너지 · 결합)
if ( d ! = null & & d . HasSynergy & & d . kind ! = WLPartKind . Passive )
{
var s = SlotFor ( sel , d ) ;
var p = WLPartSkillData . Def ( ! string . IsNullOrEmpty ( d . synKey ) ? d . synKey : d . combineKey ) ;
int need = WLPartSkillData . S . synergyLevel ;
texts . Add ( "★ 시너지 [" + ( string . IsNullOrEmpty ( d . synName ) ? d . name : d . synName ) + "] · " + ( p ! = null ? p . name : "?" ) + " " + need + "단계" ) ;
tiers . Add ( 0 ) ; open . Add ( s ! = null & & s . synergy ) ;
}
for ( int i = 0 ; i < _ipRows . Count ; i + + )
{
var r = _ipRows [ i ] ;
bool on = i < texts . Count ;
SetOn ( r . root , on | | ( i = = 0 & & texts . Count = = 0 ) ) ;
if ( ! on )
{
if ( i = = 0 & & texts . Count = = 0 ) { SetOn ( r . dot , false ) ; SetOn ( r . lockImg , false ) ; r . text . text = IP . noSkillText ; r . text . color = Col ( "textDim" , Color . gray ) ; }
continue ;
}
bool isOpen = open [ i ] ;
SetOn ( r . dot , isOpen ) ; SetOn ( r . lockImg , ! isOpen ) ;
r . dot . color = tiers [ i ] > 0 ? TierCol ( tiers [ i ] ) : Col ( "synergyOn" , new Color ( 1f , 0.8f , 0.25f , 1f ) ) ;
r . lockImg . color = tiers [ i ] > 0 ? TierCol ( tiers [ i ] ) : Col ( "synergyOn" , new Color ( 1f , 0.8f , 0.25f , 1f ) ) ;
r . text . text = texts [ i ] ;
var c = TextCol ( ) ; if ( ! isOpen ) c . a = 0.45f ;
r . text . color = c ;
}
}
void FillCost ( Item sel , bool isEq )
{
var cost = isEq ? WLInventory . NextLevelCost ( sel ) : null ;
SetOn ( _ipGoldIcon , cost ! = null ) ;
if ( cost = = null ) { _ipGold . text = isEq ? IP . maxLevelText : "" ; SetOn ( _ipStoneGroup , false ) ; return ; }
long g = WLInventory . GetGold ( ) ;
string lack = "#" + ColorUtility . ToHtmlStringRGB ( Col ( "costLack" , new Color ( 1f , 0.45f , 0.45f , 1f ) ) ) ;
_ipGold . text = ( g < cost . MoneyCountRequired ? "<color=" + lack + ">" : "" ) + Short ( g ) + "/" + Short ( cost . MoneyCountRequired ) + ( g < cost . MoneyCountRequired ? "</color>" : "" ) ;
bool needStone = cost . ItemKeyRequired > 0 & & cost . ItemCountRequired > 0 ;
SetOn ( _ipStoneGroup , needStone ) ;
if ( needStone )
{
long have = WLInventory . GetItemCountByIndex ( cost . ItemKeyRequired ) ;
_ipStone . text = ( have < cost . ItemCountRequired ? "<color=" + lack + ">" : "" ) + have + "/" + cost . ItemCountRequired + ( have < cost . ItemCountRequired ? "</color>" : "" ) ;
}
}
public string ItemPopupStateForProbe
{
get
{
if ( ! IpOn | | _popup = = null ) return "v2 off" ;
int rows = 0 ; foreach ( var r in _ipRows ) if ( r . root . gameObject . activeSelf ) rows + + ;
return "v2 open=" + _popup . activeSelf + " name='" + ( _popupName ! = null ? _popupName . text : "" ) + "' stat='" + ( _ipStat ! = null ? _ipStat . text : "" ) + "' rows=" + rows + " gold='" + ( _ipGold ! = null ? _ipGold . text : "" ) + "'" ;
}
}
}
}