From 1794e5d418a1f09b5fdd945cfd343cdf2ef071d1 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Wed, 27 May 2026 19:29:43 -0300 Subject: [PATCH] feat(ui/progressbar): UProgressBar -> UImage com material custom Primary/Secondary UUIProgressBar_Base envolve UImage + MID em vez do UProgressBar nativo. Material UI_M_ProgressBar expoe PrimaryLevel/SecondaryLevel (modelo assimetrico: Secondary so aparece se > Primary), 5 cores (Primary + ColourBottom, Secondary + ColourBottom, Empty) e gradient toggle com bilinear remap controlado por GradientSplit. Cores e gradient ficam expostos como UPROPERTY com InlineEditConditionToggle: quando o toggle esta off usa o valor da MI atribuida no Brush, quando on sobrescreve via MID em PreConstruct. Setters runtime tambem marcam o flag pra persistir o override no proximo re-construct. Wrappers semanticos: ShowHealPreview, ShowDamageLag, ShowBuffer, ClearSecondary. NativeTick com ease-out cubica e duracoes separadas pra Primary e Secondary. Inclui MI exemplo UI_M_ProgressBar_HP e WBP UI_ProgressBar_Master refatorado (UProgressBar nativo substituido por UImage FillImage com brush apontando para o material pai). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../UI/Shared/UI_ProgressBar_Master.uasset | Bin 0 -> 31625 bytes .../Progressbar/UI_M_ProgressBar.uasset | Bin 0 -> 29766 bytes .../Progressbar/UI_M_ProgressBar_HP.uasset | Bin 0 -> 10066 bytes .../Game/UI/Widgets/UIProgressBar_Base.cpp | 278 ++++++++++++++++++ .../ZMMO/Game/UI/Widgets/UIProgressBar_Base.h | 196 ++++++++++++ 5 files changed, 474 insertions(+) create mode 100644 Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset create mode 100644 Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar.uasset create mode 100644 Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar_HP.uasset create mode 100644 Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp create mode 100644 Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h diff --git a/Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset b/Content/ZMMO/UI/Shared/UI_ProgressBar_Master.uasset new file mode 100644 index 0000000000000000000000000000000000000000..40cc97a0b401de7106cae9bf6930cbeae77146e2 GIT binary patch literal 31625 zcmeHQ2Ygh;)}MvYK?sH_;7tf60n!5m6iC?=AUy#EC2X=c$--uL*^&@IrCP8dfT4YoH^&rnQ|wW z?MtH9{&3>NiJ7e#>)491W0WJ^`{MKt&(2-@^-Fh5s;oNxe4?%VzFU&dW2`;l&e=X= z)bZ?3+kUr{J$&(;=)oZbyR>)znM?D{cYm<6=Us2TxWCI@f-Ty8k!4G2{=TmZC*C#m zACEj$Pq2vHC2O9_AG|5EUHGck7jHEUC)lS6@7&)d|BAC8x%joa*H8RqWJiLHcmMp_ z!i@XVKMcF>dY96_*C2wW&A8>A%-kmN{W5LU;ZEA7FU~`s+sP}3Y_*Tr|NR0nVe<0CYM`v5^)H7kbe77 z^E*&}X?MoLDUXXwOG!;mOBt1%l$aQol#rf0IxRUVIcenR^t4eKaU+RDOJ`EHD}u4J zC|@%`gGE9JO2{`60dfO9@1lGUT^}cMH2+z!{i}o99$Jxo`L%8Dy-B+zs!$!%(ES!fA21W7F+UwKTtcmRjLvKjw@W*Hwoz+Nvxz zm0dbvSq|iuMT)H^w~BHs$uw_%XC0^FR+GzB zq|WhJ$Vu`{ZcD9NVyT$TUhZd^(}NS(Yid-RJIzyGZdD5`Hk)c@b7BTf?%4!6#p1FM z4J(X1=Nzb|+&I@^vYAzLjx9rVS*mR0daGY7%ZJyLXPaD_)^chbRuvpnbAicXbFl{2siHnlRm~;#0*|vwEuO7fi_}UN zyY`+7#zH}9R*&j%600na#mru7*SEBn9$H*aoup=p>U2R-i`*TCH7Hb4?@(Fi(3Kyy zfdqEDmBn;2@9%;d=)tS^ZXx$C=L*iRRh>?YSuM*}EmhTS`N(!3zWmMJs6s-@$dK-8 z`NS+!H$B1)AR)#Yhr2%AZnb;J{6{he!ZEysGd;EnZrE-|S4@X#(h8(%NcY@5(V9zo z4DJl3K5VY)Hf5OHCZo;mtY>fD;8@rec&Eoz&0>!9H1melyH#()_T`P(hMiKRuK(XEFw%DpzSm=O%BY3834#ZyW^Te$Pbm{qd8D)8P>I_?dNQmMPyUpcx zdZ^db+1IYo%8);hFRaTQ`7xd$;Uu-5b$Qw|9VPHQ#YA>w#xEA!8VZP7Y4TXzSx%Fq zI?ryV%0J#$3*iMqqwe;?;Y0{;rhZUG4PZYUzxi5NKZ86%tsn^RkW;7!+nos+Og*|} z5_*s3zr~h$YA))2((>%jK5&VpMg@v}zv1;S;FlQ|m&0nR&!d3M=B?hB)($YIsV>#U z&gkEL4EPzf8Vj6itwpW#R_tKI!j*vNF=J4Q;yOzoe&}onU^MdvQ@7-9`C;7(BjPB6 zglf*PS9s8|a%`1$_IO#hgNwb&9=CBjqHIfzwXjFcxt>JS@`zfCm`M!x$U^?Lfc#; zh2d$375}&~V~Xq^gr_w7T$ZtJ`ElN4UmZC$rYewNSjKyuuI#YSAtf0!7?VfSYg34FM>JO5)Z`d7r#;q+4w_x!Le$nGMhVI8AN2MS9X2@ zOd18!g?CJy5A*wT){Wc0=aVb1xmalD8Bd^Mj7e6v32J2-i{ndBLynF7*hDehQe$$~ zPgZMHE9;!Pb2Z|rz!j?%=)hTYZ0IuB^|I%=Ivozdjfd@C&7l?hm`$>B9c@~TCP&~+l%gxn^Zq*y3MvQ9fA3!q!6lT53r%EjKZ4|q@y zg(6(+vAQ-dp{Y*QWMxx!Y>q)BDpDO*3Jj3a7h&yBUv?GB6Ee^4wp7+*+-L1>*|iew zE>c}IIjB&@6mT-c5NK}w1i>QLF^m$|*ALL*duWu;4Xvjs1Go#BN~v)gM@Ybb`gSy|7kcR)+U71e5uiJf@-$~sOadyAO1 zM6d4qF*=jC7isV`yW4HAVNn+xco}voo@AU{=6B^%md+w$ejNm1eQ}yOmB$xBeZ^M0 zn-zxNa6Oy|4=$F!XWB>5OtHPvtsC<$S@+&LXrb8cl=fj`w>u-z1Yy5b;WwPeOY(_{ zmnx2}`5Cq@QRn)4{BvX6k3ax-wMrAl2i{zW2Oe*)>F3EqEqKsH?$`ypYhdGs;38V% z&?x%Bxi2rkoTr4QlO+}hdw74lr%*>J_3e5q zQCA|jsfk!^Wk!6tMPqH+7?({~QupMv#s!*y~A`K#)R6_dnRsmXGa z6Km((2CL`n0(NvzP8pOgmv6qM1PsHzd~ZPpyhEa~8fWGq>%V|UYQe%?H}^V>?t)`k zKu3S|j6(;|QXQd`4omqoS`V|4*WY?378za|O)Xxj*yR3Kra<{(p2$M%TQAgRfqdN6 zx@*~_1or6mzjcAC`P4<5xm>uxa3Kuk4fXw}rp-q~w7EjpamE|b)=3G7*JZpQb1j>8 z76i(r@JS0ZYYv4&6U}#cTr^Rj2pQY`9`rI1qSQ&>7$6_kI0#RZtu^nd)5%%_yu?R8<^& z#-&)~riKaQcMR(1JSGs{RHG=l-U*aK5lG=v5UPvDESftjm3F6Ijs_)l^HxQn5rf#$ z6qfjsOlHMpq0Xj|fNla;%tpyrHiI`+(?@})oMsiV-Ut!v^Xiytc7>qaLHU1E9UXt0 z9%QML(gAk`I86}l(36}^x9p5yCe;>NeK{+t>$%4%Ih9Ji-J{rOFHA8h6ewlgBkbfR zBkE|nGNOVeo$xs>!ElJBR}3Vja;&vyu|LV<`%D_nU4ZzK=7fR0ot}A4@|{gFZVGgMKrtO^w=nZ z@x`q|(|C_Vgc#N06kg29%CYisDh_Q^N_ZzlCtiX~4j!~L<2$*WsTOXk_@duxttSGL zTX8`otxN^Y4B+feZGV}(&<$A-v4$%a_Yf^`lJ`-~PL(q>QS?=aq=SE9zVa z?H)qw)Rm!lYO`h--jOxV=;;+slS?zAup1b0W;vRwwV;3=?4o5D*FK`Wn@mR5FPFR6 z)N0Z)m!ZN${g+xG>Zb6c*3vGiHV7%o`BW9!?DjZqioLRuGt&?KI&zG7u(vjqp_nGj zPW-XE3;lCE_AW^CREyZ!a*2p>@}c2Ua&-|JelHhC3x7BF_PTaQy-%0??*9C{_;SbJ z&52LH12e30Xq;k$#i-|O-fiq72%Y(_;S!|yiMGzB1h(r z?v5bnzNPx^41(?(qPsN+y03`t_8{pt21&OmNV+XS(%l^--F-pQJrE?_>LBR$lm6}x zg6>PAyVnmLmOw}ww2Xa6boYpRfBv#r;E>_nH&RPRu%oA|%J1FkIJq`T6^}`R#eWQTu z`F(W00Je}d*FMSbS6%zy*NCmi!S7~alsBi*LlCJsegi~j@W&4*@Ow>k1Z!Ck%!s5p zeq)>A2R7h$H3mOQV=J`0rQcxq9n{|rk^S%8)4)&mKRrL_&>z3+XNv~3kTusn$?r#9`(Qths1v;3 z5%-g=;--bHIexoDSv^14&mX_Ynid!dbTIsI9#GbX9S+PHr2Vo4;^))AFJ087=hry^ zzjT`*ZXs)~eUcxx{Pk&#c)*VAUNn@_^3%ZYeNopTojy_bRrsL<-S&plP@3(~a{Qv0 zDCz~u=jPfMCh9suLY3ozenCzD~{(Evs#-ec*kQ~UNE+qdh~u|vl$ojY|2?bkiDYuC_-K7GUbMMVya z9vV4h$cTgqqejG?8#iRg=*cPPrW>G zB*u8s>BKe`+8#2sHbk&ip@!C>h7-@Rvq{c22A*Wo46Ry+v}xO}eTR;nh~VLFtd*g4 z>sBGH+q4NGjTkN>JPQeJ(>**swe6XOrgjl?!V(r;x32xbvr-)_2j{q1*bxO3yC&38Tc(AJ0l@yMh9eC(NLpL_m=7hl@( z@|!z%?SAX+cmDJ4-j6@|^s{}RfAQu1?++e2eB|g4KmK$K?Ls?c(puK2U7^&jRv{s+ zL)xKThE{cSwGItw6CU5Tdum}j)0{IS5*D=&OS^8}1KT?cOf34YN4fKjjy)rj_73_U zP2(*Ku3?LRQ_H*!^R??;*15HTjMqApjc0qS*Dq&7|GNG#9Yn31_`o&!<5o>luIO;! zohkF<%X3l&Kkj(6Z1#ak*Un$Q^1F=PnW#aS6wh=*pG)uyq$r?C%{_bC^(%zxI@f_*8z~Jwic)N>@xqkV?n|F_TW$i2fV$*L9 zAFk%lc;TU&CdNcy~4suMf}fzWddeqw7bQ^OiV{Zpw~!Y+G_* z#+q4~Wt}3PAw$%mRgbm_BZWmxy=PFLjlDK?-*?rn`J3LKemu<@^_5ZCaNnAH?m2(n zL+brRzT%nXLvL%<|am2~29cm*m!z#@C5IPGZCRDl22mZpff8!4VOsDb{1 zN#6AqcUf!}v(rZ3MEXr zWFXmKUo*)%izI}-9VC%K_*h`P)Rj+;PC~M#o@SshBsJ^MwY8LZIY=wuI)^CQ3#us6 z5u~VO(FAEHpgvB&t;Mn))UEPX`|8%iq!h9@wbJ!0iJQz4xs0gCpT%Z+t!$tWsMX}N zXEnL!cBKPT3e<$^;x2C>V`gojbJET0)&$3CI(6)iL1hiH(#mIQvd>bM} z`E-`R(kRN0qH7FAmSmPfzi9+D(tRSqk|Q#i=29XGZ}2^^VL&rlZ+kNA=H>4t97kaFq!m5g6L zpVGI_$In1hEzSE+wN=uqd=eva)o^|K;j!?~OV6(PyXvn0RFn*v$jDF#CVa9M+ z+yf@@9}5^ForIUuJ<86ai?+7-oC$abU8qZ6=chV2>I4pRRavi@?k#koPMgSOU9cF& zh%~xT1{dh0Y?2T70tVi5=z?5mPh&XB$~r&`-gpMR!~tGKS1nzr12n*+EY$Z>QGIFEWfjsrKHI~nM65Sk@V zwaslsJ%zlL@X@A_eu74>ifFG`KxE4b$QwQ6+4v3T^L@nnYVH;219H7`s?thdz>1=Z zYOf_v_pqsSr>#5szTnKEik7lrSyAErx`*E)zm{>Gz$7gtU!20 z$RV}s-)}k!Dy$D0YCHTiwL|J*B6(mM$&R%q(gH# z{6HZ`^ZJv1UP*HwtYZu;Qj}}z=iLMb95w%T^T=SQr&7Ap*7kz}mX~uo_-qaD&jvd1 zsQF!RQdW@nV76gkLqs|0wSU&Nqnt7Li+^J&9%J?1aMkErtu@6B4NBY6gA-`>HJa8f znUrHKlT7@KbcdBsGOeq#Xst7f;8{eILNrFAPNY@ND1yfmg@Hu{#jiPuqyL}SS^B?! zJEM1*nU!=Q`>PK!EGQlOY??wgN+O$$B#Whym6FI_8T31nY?wy2PNWMq&Ys@ z*HHwAr44L&Q2d(8IYN|c8dqeb%cHm&PNRJ#c{O4Q_CgS8V}jx+cZ?eL207Q0(fYT} z@uZCU9UuK{s3m+{P;HQNjK;H|5{h}4TWb467zJYe=qWy+hPw(3_NV2{#6vae<90&8 z(rpALp0C9WMn=f1b{3k+h=(ZnU6H5zq00 z4>*8_#!Hrp_wtA*twI9ONj~w^ZzW9DA5VQ)!X%FbFAv0bjR#`BMwj5_fxRpVlROdx zkA|(m9*V3-@=Kt&p}`Wp{1UzV5(U3XW}^7!qBR;?n#g&NA^MBxS+84PZ5W%RRQ@(>5R0+@FX>w13Ln(Q65q%I$VK#u8Xpp9 zbSA(?Al=*i#_#bPQV(hP?UClb(HRrqpurG+vOxFEMDI5ZTqKG14xg~}2|m^AeTDDh zhb)Ii@z?J3St@$7o^P|(^P`%XCM$hN1jh-qk3Z@^Y&4djvT?d^TgLD=_d_HGA2}g$ z8-F)r2)+8EN_x1UQ<2FCAp!Pn7A)Z1wycYpTa>=hg;_{=QItaSi7byx&s8L5qfX@c|#4 zT-B7xRS>|E^p4l3T{;NTyzB5&5zQS4In`4aIFX@!@e6L#%=s0Z67lnCFoOP3UUZth zuJiLczi_*x}gP!k2RHJ~n((*8|a8R6)!$d_o9GUmIc>5^BQnYng7u z%?R<-U*sS{|3hkAfVbfs4M%U^wWKe2u7LS1I8Gq7$kk9~T(>xKE*o>PAlB=Z6QNH^ zB2M$CZ!u2OH)!z$-tLArzCggBc?4UTGI29qJT+0~QPQ~lySABv2pKnYjQ~jAeZ(^Y zp+0pRDW1lN9Q;R%`^NG}ZvmQfNfCr-B~p{x5vh=xAWRyZEfP7ht@znQtuoST#!YHd zXfNXs{i^(VFWs5>y@{|ETEUv=+0OBx{Dj|Q9pt2M1u3+?ttN^ZK@~%%7*Yx2q-R;X zo$eG`p<1Z4yf<+Qly(vvPYT~pu@S^VR5pTH>CVjSRcVKXzU&LZDe3!dvr%B}>@5$Z z_1~X;Rb1-zuO?4CLs|)y$WD$96WOiE{n@(Ly~H1^8ihpSe;7py^JvV3TCz%_+Ub25im;Ia3hQ_c`9T#sKFs;gQi7#>}__}CE ze0f?m+2~CxrU*y@n-CD8mE*to6P`*wFZA6>^hvdoO%TQOUi@E(Oc8>OY+S&|R3QjU zM2-xKXRp)sSJ?Uk!dCmeuF(A1uTNfdsQRZ6%b>TV31JkB8%S;Fda>W;XS3JM$XYh9 zOW&tEJzL`sb_!ujdJyLql^lvUy6Ik^W%Ibj-b79x%fn zAGUt4?tH1xfhzKo@6NZ;2=Z5V{sX#m-y;KiFHg3nFSCC1@wMfnZjdH~C3&Ew9D_>t z;xpGinATHh3S1`Ep14FP}TwH+ zn?oxAh2EU0)Ot5>Z3(pz8tq7Lf2~4mCZf_-FhfaWPQqgK;1X+;V}!ME%|&tDmJl9q z%CMFJEi{XD305jD`qfrB+S&)d3g>%_O90-`IewkI&d)g)(zZjiAye0u2Diaz8-XA$ zCXNRLe?F0PTIreptnye_XvxL+H(V&$<3xXwWKtZs9 zcZ!1KLsm+1O^wDUafqe}xeNq|YbRGASBYF-Fjc@%Nt1%t3gm2&H`z#lQ3eJ=VqVuk zga1Q(X_&fLK40joj42v`kVt3fqI1VC!yASpYa7+0*}nj$^demYKm>tl%^ zLMIh}eab@~o@MGrH|;A1eKG#YlKaQ6SkieLV++U67ucXH_^~*B{KU?!p0ameLfd)^ z$72@)sfn8o)8Gv@uC5m_h}A?i4Oc%w%g0TK)^6w&g_aO4up@?q!g3mkXBn_?&m&qa z?kLUDGPYFY$ap233RU3WSA)6m0qk_`Bgp(4BzRUN04|5rxRspJFQS2gZ{UUL-+Srdd`%?$1W3ay^hla3_5Nj?vXXoak@ZWF7ibp zM}~gH--C!3V5{*=sY=WEFE=3GI9dg;2@>D(0Rd8ztr{qCIY$M4v94iDuUuz(H^6uE!ek=V{5GP=b#ez-SR|=$bH~km0 zI7%V}o!=~5Ko{w-5Rdl@{A)1sUsD($2=u)PvnrnWtj#7Kt`RvhuA+Xd!$N4$DG)P7 zj*JsXLFWjBbT>3RS-^0QOD0}0Ts&PU@^&Hzq23T@>EL?v&IBC9k+`8WlmBWzoP%uk zAc0tjpM*6`#_kVd21mNCDc@G_u-vWuGm#NtZpC0%4(nISO8kg=X-2S)vkj0WcuO;B9h{F{!ur`*V zo1K?Ewb{gIs)Oqb=91y1akTIVMvX@)eH-NG_E3*hr7UM)3b!l_l_=_JLplBh3OWR; zrOuT38)E{;RM;M}%OS-VKpBQVDTQp+NlDQ-68$DAngq#icd%bbvQb)#z21dKy)L@~ zP>UZ*vKhGN&WkA(wv&+BX&Kk9o{;-3)D2<~!7O?ffvbldg03FKFacQUtKUxlsP>IV gH!O@9``jB~Eze-kEl;%{j4lj$`RS-Q@eKO^18yeD(f|Me literal 0 HcmV?d00001 diff --git a/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar.uasset b/Content/ZMMO/UI/Themes/Default/Progressbar/UI_M_ProgressBar.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a24dffe736c3f51366dc108a6eedda448b0f387d GIT binary patch literal 29766 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®+-JaT_fEjsPcC=#f7GLeACS}Wiso2}r9!T^ViC==YYYa`N@doT;T zv#8_bboN77+jiPvsld%H%$rx4%(>IEKe4X$o@q#vlU;XVSM#BccfQ8zd%d6a zWPo~OCJ^wZw69)-Lukq<_Cr8GNL9_Ow@Oxi_S%mX2eiw}H&_eYc&tG+^V(&{=Kqq= zu;f6|h_@Pk3E8*{T=g%90mH<@Ve((h2oZ#fl{@)zkLU5@50`eGMbl#PI*x6PbJ=)0|v+i+j}FP-#0+Qd-gZt5|Z{b$L7y{_p}4H zHt`}>$e!)~@T=u3n(l0BsQ6NcC}T1yV_LFuu{Va{+(I}jc&u8;b0bQMv$C_!8RJ-t zVp<7i)HR%Q!cs(YU2uxj=UI7ZmoAv$beCTf|8`Gu{mIshqQ8SN+*1%5R@GA^P(tl^ zB1udJ))O&HVzXwxr_BD=vRd!Cme<3uDpMdc_Sh&IE9lUqiR9p1V zxyzg4xZN_GYu8_g86ac8;5cK8uUGU9j%8){#v4&G^Ygsj=hl^$^>MZd5`M^oCP!{gpqay_gu1xFs6Da!Lb zTeMly`r!C(yHC0nbr)5~Nqi;ucVWlvqawo{Ffw?kNW!GdsK9$nh?S>#tk;%FmLPu* zqM`e5^;fz(W|Xx*n)KeCNBKeXZYnRwov7Mgf&H}0;qjz>JdX!sciwhj4taRT zejk-{ST@VLDnM8jka!W#nE2ZsX3Oe?Ww)&I3+#%P_@>YJ7#x0Rz%f#!I}Q7s0S_Zj zI}jYiTTQuPhY6iLjSub>=@XLvcFTu82|aP}fkP806GNzzNtKbDug2$X>SPQIQG{Gg zA~-re&Xz(br1)6g%K~Rk%t9T9^!FpZG`M5{8onctWnx*m`S7Mxq>yTSC4?^-dRf6n z%$km`;K%}eEkmmElyU-}Ss6Txiv%fVe~P{C&wXI^>3bw|r|ypFlLi)v&?xGOP>`X9 zpupW80K78+cJ&r5Y^UvMHRBStU)@z9UnHCgkph5Y9u0VBh)_4)2>Ti^Y(ot_G9~#i z@Jwkwuw#ciAt(UMM&5c9r5>}<>wpKJ0>CBIV*s~a0D=07dQy7A8~xHR(b>?#%}_IS zfs6nEJ(#)_0TOSgDR{+DLj{H*xE%#BT=XM2e*r)ba1{d3w@0d>ot}nXHq?;zG*C9c zDaqM@p$|GR#%M4YS{w?OF#t?s28{cy*oS8lRK;QC_Z?gJ9xT~TOZjs~h&}}WZV^Jy z9w9Gu{*7oKC*j&WargK4i z)3zGp4RjW+q0lf2_H3|-y}0F_zS{i%Sv;64`2QHJVj2i2YUgI4QB~C%hi4xgCL?>e zsd21`^~>3N&9Z0jqFw*uJm+c9@ek%X$KmrP+c`N+Tp3}FuhpMp^K^UD%M*@&pBA@* z^}m+eKEJu;#d~h_AJcQ!rgu!L`S4ZySDVM&e7>9;f8=V+{mP}`du$>*?0E(37wA^L$7D4#mQBC^S&#JL-Wk z0R}9yq2V{RCnQmA;2$mkuKJ3)S24Wm54Z7kTPM?Yz!e9;6GwJfJI7I^euyw94>J&% z9vJY{xFY8Z7mJWNiL1n`kM3N5X*#Wfp&xzq1d@r^kN+LF>et$0pNU_#fqt~Z=^kcZ P2o`Y;Hha>;82JAUhl<+H literal 0 HcmV?d00001 diff --git a/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp new file mode 100644 index 0000000..4120258 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.cpp @@ -0,0 +1,278 @@ +#include "UIProgressBar_Base.h" + +#include "Components/Image.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Materials/MaterialInterface.h" +#include "Styling/SlateBrush.h" + +namespace +{ + constexpr float MinAnimSeconds = 0.001f; + + static const FName P_PrimaryLevel("PrimaryLevel"); + static const FName P_SecondaryLevel("SecondaryLevel"); + static const FName P_PrimaryColour("PrimaryColour"); + static const FName P_PrimaryColourBottom("PrimaryColourBottom"); + static const FName P_SecondaryColour("SecondaryColour"); + static const FName P_SecondaryColourBottom("SecondaryColourBottom"); + static const FName P_EmptyColour("EmptyColour"); + static const FName P_EnableGradient("EnableGradient"); + static const FName P_GradientSplit("GradientSplit"); + + FORCEINLINE float EaseOutCubic(float Alpha) + { + return 1.f - FMath::Pow(1.f - Alpha, 3.f); + } +} + +void UUIProgressBar_Base::NativePreConstruct() +{ + Super::NativePreConstruct(); + + CurrentPrimaryLevel = InitialPrimaryLevel; + TargetPrimaryLevel = InitialPrimaryLevel; + PrimaryStart = InitialPrimaryLevel; + PrimaryElapsed = 0.f; + bAnimatingPrimary = false; + + CurrentSecondaryLevel = InitialSecondaryLevel; + TargetSecondaryLevel = InitialSecondaryLevel; + SecondaryStart = InitialSecondaryLevel; + SecondaryElapsed = 0.f; + bAnimatingSecondary = false; + + EnsureMID(); + ApplyOverridesToMID(); + ApplyPrimaryToMID(CurrentPrimaryLevel); + ApplySecondaryToMID(CurrentSecondaryLevel); +} + +void UUIProgressBar_Base::NativeConstruct() +{ + Super::NativeConstruct(); + + EnsureMID(); + ApplyOverridesToMID(); + ApplyPrimaryToMID(CurrentPrimaryLevel); + ApplySecondaryToMID(CurrentSecondaryLevel); +} + +void UUIProgressBar_Base::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) +{ + Super::NativeTick(MyGeometry, InDeltaTime); + + if (!bAnimatingPrimary && !bAnimatingSecondary) + { + return; + } + + if (bAnimatingPrimary) + { + PrimaryElapsed += InDeltaTime; + const float Duration = FMath::Max(PrimaryAnimationSeconds, MinAnimSeconds); + const float Alpha = FMath::Clamp(PrimaryElapsed / Duration, 0.f, 1.f); + CurrentPrimaryLevel = FMath::Lerp(PrimaryStart, TargetPrimaryLevel, EaseOutCubic(Alpha)); + ApplyPrimaryToMID(CurrentPrimaryLevel); + if (Alpha >= 1.f) + { + CurrentPrimaryLevel = TargetPrimaryLevel; + ApplyPrimaryToMID(CurrentPrimaryLevel); + bAnimatingPrimary = false; + } + } + + if (bAnimatingSecondary) + { + SecondaryElapsed += InDeltaTime; + const float Duration = FMath::Max(SecondaryAnimationSeconds, MinAnimSeconds); + const float Alpha = FMath::Clamp(SecondaryElapsed / Duration, 0.f, 1.f); + CurrentSecondaryLevel = FMath::Lerp(SecondaryStart, TargetSecondaryLevel, EaseOutCubic(Alpha)); + ApplySecondaryToMID(CurrentSecondaryLevel); + if (Alpha >= 1.f) + { + CurrentSecondaryLevel = TargetSecondaryLevel; + ApplySecondaryToMID(CurrentSecondaryLevel); + bAnimatingSecondary = false; + } + } +} + +void UUIProgressBar_Base::SetTargetPrimaryLevel(float Primary01) +{ + const float Clamped = FMath::Clamp(Primary01, 0.f, 1.f); + if (PrimaryAnimationSeconds <= MinAnimSeconds) + { + SetPrimaryLevelImmediate(Clamped); + return; + } + if (FMath::IsNearlyEqual(Clamped, CurrentPrimaryLevel, KINDA_SMALL_NUMBER) && + FMath::IsNearlyEqual(Clamped, TargetPrimaryLevel, KINDA_SMALL_NUMBER)) + { + return; + } + TargetPrimaryLevel = Clamped; + PrimaryStart = CurrentPrimaryLevel; + PrimaryElapsed = 0.f; + bAnimatingPrimary = true; +} + +void UUIProgressBar_Base::SetPrimaryLevelImmediate(float Primary01) +{ + const float Clamped = FMath::Clamp(Primary01, 0.f, 1.f); + CurrentPrimaryLevel = Clamped; + TargetPrimaryLevel = Clamped; + PrimaryStart = Clamped; + PrimaryElapsed = 0.f; + bAnimatingPrimary = false; + ApplyPrimaryToMID(CurrentPrimaryLevel); +} + +void UUIProgressBar_Base::SetTargetSecondaryLevel(float Sec01) +{ + const float Clamped = FMath::Clamp(Sec01, 0.f, 1.f); + if (SecondaryAnimationSeconds <= MinAnimSeconds) + { + SetSecondaryLevelImmediate(Clamped); + return; + } + if (FMath::IsNearlyEqual(Clamped, CurrentSecondaryLevel, KINDA_SMALL_NUMBER) && + FMath::IsNearlyEqual(Clamped, TargetSecondaryLevel, KINDA_SMALL_NUMBER)) + { + return; + } + TargetSecondaryLevel = Clamped; + SecondaryStart = CurrentSecondaryLevel; + SecondaryElapsed = 0.f; + bAnimatingSecondary = true; +} + +void UUIProgressBar_Base::SetSecondaryLevelImmediate(float Sec01) +{ + const float Clamped = FMath::Clamp(Sec01, 0.f, 1.f); + CurrentSecondaryLevel = Clamped; + TargetSecondaryLevel = Clamped; + SecondaryStart = Clamped; + SecondaryElapsed = 0.f; + bAnimatingSecondary = false; + ApplySecondaryToMID(CurrentSecondaryLevel); +} + +void UUIProgressBar_Base::SetPrimaryColour(FLinearColor Color) +{ + PrimaryColour = Color; + bOverridePrimaryColour = true; + if (FillMID) FillMID->SetVectorParameterValue(P_PrimaryColour, Color); +} + +void UUIProgressBar_Base::SetPrimaryColourBottom(FLinearColor Color) +{ + PrimaryColourBottom = Color; + bOverridePrimaryColourBottom = true; + if (FillMID) FillMID->SetVectorParameterValue(P_PrimaryColourBottom, Color); +} + +void UUIProgressBar_Base::SetSecondaryColour(FLinearColor Color) +{ + SecondaryColour = Color; + bOverrideSecondaryColour = true; + if (FillMID) FillMID->SetVectorParameterValue(P_SecondaryColour, Color); +} + +void UUIProgressBar_Base::SetSecondaryColourBottom(FLinearColor Color) +{ + SecondaryColourBottom = Color; + bOverrideSecondaryColourBottom = true; + if (FillMID) FillMID->SetVectorParameterValue(P_SecondaryColourBottom, Color); +} + +void UUIProgressBar_Base::SetEmptyColour(FLinearColor Color) +{ + EmptyColour = Color; + bOverrideEmptyColour = true; + if (FillMID) FillMID->SetVectorParameterValue(P_EmptyColour, Color); +} + +void UUIProgressBar_Base::SetGradientEnabled(bool bEnabled) +{ + bEnableGradient = bEnabled; + bOverrideEnableGradient = true; + if (FillMID) FillMID->SetScalarParameterValue(P_EnableGradient, bEnabled ? 1.f : 0.f); +} + +void UUIProgressBar_Base::SetGradientSplit(float Split01) +{ + GradientSplit = FMath::Clamp(Split01, 0.f, 1.f); + bOverrideGradientSplit = true; + if (FillMID) FillMID->SetScalarParameterValue(P_GradientSplit, GradientSplit); +} + +void UUIProgressBar_Base::ApplyOverridesToMID() +{ + if (!FillMID) + { + return; + } + if (bOverridePrimaryColour) FillMID->SetVectorParameterValue(P_PrimaryColour, PrimaryColour); + if (bOverridePrimaryColourBottom) FillMID->SetVectorParameterValue(P_PrimaryColourBottom, PrimaryColourBottom); + if (bOverrideSecondaryColour) FillMID->SetVectorParameterValue(P_SecondaryColour, SecondaryColour); + if (bOverrideSecondaryColourBottom) FillMID->SetVectorParameterValue(P_SecondaryColourBottom, SecondaryColourBottom); + if (bOverrideEmptyColour) FillMID->SetVectorParameterValue(P_EmptyColour, EmptyColour); + if (bOverrideEnableGradient) FillMID->SetScalarParameterValue(P_EnableGradient, bEnableGradient ? 1.f : 0.f); + if (bOverrideGradientSplit) FillMID->SetScalarParameterValue(P_GradientSplit, GradientSplit); +} + +void UUIProgressBar_Base::ShowHealPreview(float TargetPct, FLinearColor PreviewColor) +{ + SetSecondaryColour(PreviewColor); + SetTargetSecondaryLevel(TargetPct); +} + +void UUIProgressBar_Base::ShowDamageLag(float OldPct, float NewPct, FLinearColor LagColor) +{ + SetSecondaryColour(LagColor); + SetSecondaryLevelImmediate(OldPct); + SetPrimaryLevelImmediate(NewPct); + SetTargetSecondaryLevel(NewPct); +} + +void UUIProgressBar_Base::ShowBuffer(float BufferPct, FLinearColor BufferColor) +{ + SetSecondaryColour(BufferColor); + SetTargetSecondaryLevel(FMath::Clamp(CurrentPrimaryLevel + BufferPct, 0.f, 1.f)); +} + +void UUIProgressBar_Base::ClearSecondary() +{ + SetTargetSecondaryLevel(0.f); +} + +void UUIProgressBar_Base::EnsureMID() +{ + if (FillMID || !FillImage) + { + return; + } + UMaterialInterface* Mat = Cast(FillImage->GetBrush().GetResourceObject()); + if (!Mat) + { + return; + } + FillMID = UMaterialInstanceDynamic::Create(Mat, this); + FillImage->SetBrushFromMaterial(FillMID); +} + +void UUIProgressBar_Base::ApplyPrimaryToMID(float Value) +{ + if (FillMID) + { + FillMID->SetScalarParameterValue(P_PrimaryLevel, Value); + } +} + +void UUIProgressBar_Base::ApplySecondaryToMID(float Value) +{ + if (FillMID) + { + FillMID->SetScalarParameterValue(P_SecondaryLevel, Value); + } +} diff --git a/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h new file mode 100644 index 0000000..aebd7b1 --- /dev/null +++ b/Source/ZMMO/Game/UI/Widgets/UIProgressBar_Base.h @@ -0,0 +1,196 @@ +#pragma once + +#include "CoreMinimal.h" +#include "CommonUserWidget.h" +#include "UIProgressBar_Base.generated.h" + +class UImage; +class UMaterialInstanceDynamic; + +/** + * ProgressBar do ZMMO. Wrappa um UImage com material custom + * (UI_M_ProgressBar) e expõe API com dois níveis: + * Primary (preenchimento atual) + Secondary (preview / damage lag / buffer). + * + * Modelo assimétrico: SecondaryLevel só aparece visualmente se for MAIOR + * que PrimaryLevel — representa heal preview, damage trail, buffer + * empilhado em cima da barra principal. + * + * Cores: por padrão vêm da Material Instance atribuída ao FillImage. + * Cada cor tem um toggle de override (InlineEditConditionToggle) que, + * quando ligado, sobrescreve o valor da MI via MID em PreConstruct. + */ +UCLASS(Abstract, Blueprintable) +class ZMMO_API UUIProgressBar_Base : public UCommonUserWidget +{ + GENERATED_BODY() + +public: + // === Levels === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar", + meta = (ClampMin = "0", ClampMax = "1")) + float InitialPrimaryLevel = 1.f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar", + meta = (ClampMin = "0", ClampMax = "1")) + float InitialSecondaryLevel = 0.f; + + // === Animation === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Animation", + meta = (ClampMin = "0", ClampMax = "5")) + float PrimaryAnimationSeconds = 0.25f; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Animation", + meta = (ClampMin = "0", ClampMax = "5")) + float SecondaryAnimationSeconds = 0.8f; + + // === Color overrides (sobrescrevem a MI) === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverridePrimaryColour = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverridePrimaryColour")) + FLinearColor PrimaryColour = FLinearColor(0.957f, 0.769f, 0.353f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverridePrimaryColourBottom = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverridePrimaryColourBottom")) + FLinearColor PrimaryColourBottom = FLinearColor(0.478f, 0.282f, 0.094f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverrideSecondaryColour = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverrideSecondaryColour")) + FLinearColor SecondaryColour = FLinearColor(0.246201f, 0.002428f, 0.002125f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverrideSecondaryColourBottom = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverrideSecondaryColourBottom")) + FLinearColor SecondaryColourBottom = FLinearColor(0.123f, 0.001f, 0.001f, 1.f); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (InlineEditConditionToggle)) + bool bOverrideEmptyColour = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Colors", + meta = (EditCondition = "bOverrideEmptyColour")) + FLinearColor EmptyColour = FLinearColor(0.f, 0.f, 0.f, 0.6f); + + // === Gradient overrides === + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (InlineEditConditionToggle)) + bool bOverrideEnableGradient = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (EditCondition = "bOverrideEnableGradient")) + bool bEnableGradient = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (InlineEditConditionToggle)) + bool bOverrideGradientSplit = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ProgressBar|Gradient", + meta = (EditCondition = "bOverrideGradientSplit", ClampMin = "0", ClampMax = "1")) + float GradientSplit = 0.5f; + + // === Primary API === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetTargetPrimaryLevel(float Primary01); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetPrimaryLevelImmediate(float Primary01); + + UFUNCTION(BlueprintPure, Category = "ProgressBar") + float GetCurrentPrimaryLevel() const { return CurrentPrimaryLevel; } + + // === Secondary API === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetTargetSecondaryLevel(float Sec01); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar") + void SetSecondaryLevelImmediate(float Sec01); + + UFUNCTION(BlueprintPure, Category = "ProgressBar") + float GetCurrentSecondaryLevel() const { return CurrentSecondaryLevel; } + + // === Runtime color/gradient setters (também marcam o override pra evitar reset no próximo PreConstruct) === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetPrimaryColour(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetPrimaryColourBottom(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetSecondaryColour(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetSecondaryColourBottom(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Colors") + void SetEmptyColour(FLinearColor Color); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Gradient") + void SetGradientEnabled(bool bEnabled); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Gradient") + void SetGradientSplit(float Split01); + + // === Wrappers semânticos === + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ShowHealPreview(float TargetPct, FLinearColor PreviewColor); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ShowDamageLag(float OldPct, float NewPct, FLinearColor LagColor); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ShowBuffer(float BufferPct, FLinearColor BufferColor); + + UFUNCTION(BlueprintCallable, Category = "ProgressBar|Effects") + void ClearSecondary(); + +protected: + virtual void NativePreConstruct() override; + virtual void NativeConstruct() override; + virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; + + UPROPERTY(meta = (BindWidget)) + TObjectPtr FillImage; + +private: + UPROPERTY(Transient) + TObjectPtr FillMID; + + void EnsureMID(); + void ApplyPrimaryToMID(float Value); + void ApplySecondaryToMID(float Value); + void ApplyOverridesToMID(); + + float CurrentPrimaryLevel = 1.f; + float TargetPrimaryLevel = 1.f; + float PrimaryStart = 1.f; + float PrimaryElapsed = 0.f; + bool bAnimatingPrimary = false; + + float CurrentSecondaryLevel = 0.f; + float TargetSecondaryLevel = 0.f; + float SecondaryStart = 0.f; + float SecondaryElapsed = 0.f; + bool bAnimatingSecondary = false; +};