From 66016160a4278a4695af56fb920b02734bcd8a55 Mon Sep 17 00:00:00 2001 From: Jeffrey Han Date: Sun, 15 Feb 2015 21:38:54 -0500 Subject: [PATCH] Song menu graphical updates. - Added "selection-*" images from Xiaounlimited's "Nexus Ivory" skin. These do the same thing as the F1-F3 keyboard buttons in the song menu. - Removed the old wrench icon and replaced it with an "Other Options" button. F1 no longer opens the options menu. - Moved the search bar to under the tabs and better simulate osu! behavior. Removed the old search icon. - Added solid black bars at the top and bottom of the song menu. Moved the top divider closer to the end of the information text. Cropped song button images to fit between the bars. Signed-off-by: Jeffrey Han --- README.md | 3 +- res/options.png | Bin 2095 -> 0 bytes res/search.png | Bin 2523 -> 0 bytes res/selection-mods-over.png | Bin 0 -> 5092 bytes res/selection-mods.png | Bin 0 -> 3650 bytes res/selection-options-over.png | Bin 0 -> 5116 bytes res/selection-options.png | Bin 0 -> 3669 bytes res/selection-random-over.png | Bin 0 -> 4690 bytes res/selection-random.png | Bin 0 -> 3522 bytes res/selection-selectoptions-over.png | Bin 0 -> 5248 bytes res/selection-selectoptions.png | Bin 0 -> 3665 bytes src/itdelatrisu/opsu/GameImage.java | 63 +++- src/itdelatrisu/opsu/OsuGroupNode.java | 32 ++- src/itdelatrisu/opsu/ScoreData.java | 12 +- src/itdelatrisu/opsu/SongSort.java | 14 +- src/itdelatrisu/opsu/Utils.java | 10 +- src/itdelatrisu/opsu/states/SongMenu.java | 334 ++++++++++++++-------- 17 files changed, 308 insertions(+), 160 deletions(-) delete mode 100644 res/options.png delete mode 100644 res/search.png create mode 100644 res/selection-mods-over.png create mode 100644 res/selection-mods.png create mode 100644 res/selection-options-over.png create mode 100644 res/selection-options.png create mode 100644 res/selection-random-over.png create mode 100644 res/selection-random.png create mode 100644 res/selection-selectoptions-over.png create mode 100644 res/selection-selectoptions.png diff --git a/README.md b/README.md index 2827413d..a6960d73 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ folder if placed in the `SongPacks` directory. ### First Run The `Music Offset` value will likely need to be adjusted when playing for the first time, or whenever hit objects are out of sync with the music. This and -other game options can be accessed by clicking the wrench icon in the song menu. +other game options can be accessed by clicking the "Other Options" button in +the song menu. ## Building opsu! is distributed as a Maven project. diff --git a/res/options.png b/res/options.png deleted file mode 100644 index 74c38e455feb7782731c91eac0ab049af80ac10c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2095 zcma)-`!^GeAIE3I+%^=smO4shN)+ZcYKvGi%%wOkUC;>o=6>5u%3YzBE_h1V8qE{B zlJ%sgq)2TpF`LWsw6eLax$N@Q^A|kl^Lc;X=e*AQoX?N%B3~a*h=#ES004k^9YgtV zc~m$B=bV8>G_s(D-X;`5CTT+Zuj?EirLwxH#|ImidL1Xs=VQY8&j+gqQ z_gvM%1Zb(M_-P%%>0Q?s%z<4gQV6;0krfSS?;f`Fa5mqyP1E#FzeL z;ed7w*49YmB6O83PaJF`_O5>ZR+|+g8y?MKbPR`s^R}z~x20}6Ssj_p+8=3gz&xxi z*U=rjCi(83_GoqoZ_uhQHgIL^IdbB89v0LO><5QyTY>ijD1egOiBo^2HNeorP+kx( z67DX}uY)!N?Hl9t3E`Pv-^$?g@K*D_M;A|hj8*5hgx@2E$(k}lT1E}C*f4f=5X?eu zD~5`1?#w>uaxVcI7lf7C3A9;=+q0)qa3|U_rx<~x@g83{6ecGihx9M-Ck60m3>wMr zNxmI@w>(=MDsWckM7b3f-Ab+f8l) zZ(b_TGE#oi{zCu_Tpj*t^x)#@t9a)3EydlcPaz|fL~dj$fd7dLoZgQqXL&Cqnpq&2 z?r?WUsR2B2ep_D%AAld}hK#rzxhN;(UVy7fU+`Qh?WfCqV6qk_AMb7C6S0vt-OU(= z{RLbE4&I~7oO88Vx@Np{8+=*mg=YVsOSzEPw5lnFs!$mLFXn*p$VYf@xH}~Z=uGa` zZ)aQZ{8PwI^-2rDN_C)zhWuVIT-~(%K&wlw(qj-zL(2J)n?=rHz7}j|CNWF!HG|r$ zcj%tBoxd~|lYpUruTD(wJJEDVpX+19N?}UKnxV^2Bvp0FPFhlM`VPk)zr{A?KC*+7 z0Vc9N2Rd>mY7~+8*>pS3t$tgFn8kV(5Q0dC*tg0$(P8P0@t^Ikn{IDWfi|o@E*)%# zLDrGM!65UGIJ|M8ox2xSQL0De95J~doR)RAKZF}Wealy3Dn#uZ6!B@>7r53>yyq(~ ziJlG#I*;c?dyjwK_YM)6==}0&R%J=I&dN@L`D(3P2YEMSH)}-<((W(^AE0gB&L2+% zBDC(fPIcF*5FwpqQnhsF*ON_T@EF_oUR-4{Nta+i2-~`_BT+c7LjcXY7i*n=jh#bh z6cx%s_sE~C*RHP@Jx95Qc6rEfpSnea&x6o{_nj=ZV3>6= z=ABY1O$23!m}fU81f@0o97ByV!X1AiTm|}qR57XuD^Ezl#Uk|eF!R*lrh}voi-JkM zx$s$l?7Wl8_>q5HJq3Uk1Go?wv+!sJe+f;$-e5Hwxb1T1)&Tq5_5P@}IE@oeLbmw8VUC z7^K;1b_z9$x>ZL!!h?f7Rt^DNZbyyg$1dUG51YmB;nx;p8_sJu(zHfN{kaUw`R^3y1|MT zlY4L5Fz#zi9_vlLy7I+8Rm87+<3^ik=+~mBm9H$}#(tUaDj_-XB?kFyXHRLAfU_&% z6;xZY&DUD(cLK&1QK~s6@)^YeOM(+x%Ep%@x1L=9vm>ZKUJGi><&J_Up1O+U;sLW@ z#Y7(MRMT4jzrT^3pqLS}OwG6U;*Ye9DHDI6$gHZe+BCXIot{PkHDeOTsbxAO#@a;Y z$!Dyet67kpmt*%xe$mk6lkbY94{d7mYr zlrN2r3UW3Ra&NGHnD;p#=BQwJL1$(3&UGV23SCypot@Rm;BpcJ__D6K`&*OFSZ9bh z6(6c|k76(_1O<$Y_KVHX_S188)3Rdm<_JJ?f*y&uSos=jjc;UTedyC!GD1b(;JGv(+q)L zRT>7SEh#?Uz-V^Wfx34k)(mMj?@ny)JlB&*>6CDXtomxT9O(x&tbA^EeKq`_w20L| zH)1%uL~KYo=+Re^&HUNl63ru zghjafjkRCNRzEKJJubL%e+MZvKU4n-@NW%mvK>=(ak%XRrnknxi}Q?3dULq%zZRle cqpbp@+mi=t(wh(8sCIzYQ6JR9BawOk2l~wKI{*Lx diff --git a/res/search.png b/res/search.png deleted file mode 100644 index 2382d41919c742093f1403384237bbf82bb66a12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2523 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 literal 0 HcmV?d00001 diff --git a/res/selection-mods.png b/res/selection-mods.png new file mode 100644 index 0000000000000000000000000000000000000000..53078a2e7af8f3193b4b4030ace0b0d106384402 GIT binary patch literal 3650 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 literal 0 HcmV?d00001 diff --git a/res/selection-random.png b/res/selection-random.png new file mode 100644 index 0000000000000000000000000000000000000000..8afd55d3ef4bed13c224fabeb4b8e458cd4a83cd GIT binary patch literal 3522 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 literal 0 HcmV?d00001 diff --git a/res/selection-selectoptions.png b/res/selection-selectoptions.png new file mode 100644 index 0000000000000000000000000000000000000000..ab60811ff57c36faced34a3fd62343c651e3ba2f GIT binary patch literal 3665 zcma);Ra6vwx5obn(j^^&ASo#=4KoNs3k;G)gUoY1uPh9{AoGCCv zz|A&TRFCElAo_90bbT*VW^x`=NCJ{6MNov7f?h9frdIuj;iB8Q}|fNv$!Im5kblC4d}@moO%7u`G~-AxQdGCZ9G{!APW3SMGo+WdHB0GV=w78Avv~0 z$Y>(Gf66_5awHSb2u_5Q0S#>;WI?u|87L+J_JdB2%iw`Huorr{_7xxtj&nkYfK56t zGf{C0;9+!$(*kbFpnCM-tNXz6E?{!i?^Xm;;^3~Xg)0oyeE|3oda8OrP7Usw#G&p0 zk`Q3u$Hx~4qH_R~_MU~xZ&3{Wy2zDNIrS*+L_K=HxS4k z3|NF+AUf^v7Uxe-!d1?W4u7;S@kdBMiqIiB{?R#fU{HE&6OGg0gwRm-W6DB7{x&jBO$^u!*yID_LAQ;xMcArZZT=z zv=W`>A8A#+>Ha#97j_7@Mo^Gw_K`C>@Xo{$Ili*wOC)E_K^^mUz(^Pp_<8!{m`rZ# zr^vyiOkcry%wb%6s=n70V{NsYQM?*m8}BJSfUW8#5FN6vWXcokF+M%Jmxs zn{S+SvpGsj#ahC|_`WkFF-p>h#6i0i_!;7%(n7f8Yr5~>!N(cKwa1^C@@!3<6jNq9wNS>=2smHEE;AUvt!u~`^K+g!$Mxq{+bW|etm!GkR` zPBTP>a(QeeqTCCv{x00=Jf765%b?2``p%^Iz1?za4-b_SdqbcYuLn(4%<~AjZo&7% z)1fa!TC&f%S`t(%D@qihGI}x=4W|3)uIgmV**?el{zeq*!sx91@K^IpyLXoEaF)p$ z;G|q^Jt{M^GA*lMRVGzBTW#`~ZFE6d+x-@+(+FD4UOgdOAss~0$n}DCvuU$AG$oqF zP*v#=BT?y5%30E)74Yd9dMCTE{V}r8zzaPe)VBWjW|-tT{M_>|`cQ}3lC*)EpOip7 z!lv?B&c{oIOR|^n+XVWkO9NW!kl~QE7@dL}Q+Yd~{f{=?>nA<7%lylpJ)S*p1xW>Y z1@#R#4R>-)4RK|6%9IV14SI)%hTjZ-&ymYhm70`Z&s)!%%v<P3YbUKOEvz~0pL10*n%HUDDlTiTaKA=hCZ&f;^SLAWJL+Md4>C6- z5tgI$qf;`=SjNFS-A@Y4^1kXISP?kCsQtKvc>Tyhi7YjVJkFlEzP1%T|3jZgi^1cE z1$hm5o!jMv+8z0wCp)pTMMJVhcM7d!{JwPcL4TO6j;T`>W!&{xs17%V8b=_S*7+oz zBvUMEChIt7?6Ru;QS&WYR$Eq4n3tZnvKvysf9U%#Xx!yhXop&WzT~@fz6`z` z18AfG5rQ~ALNmhV+Uap{DDS64btQt!pZe)-&DX^^O=iuc$ePI2jv8LKzF)~TyhrQV zBPkS!kR7F z_sBb_WTf)|g=VRcU=UNv)G&xMx|jYRO?r`Xme&G%i~$0PltG zEMgO}=K5l=+67gA@%dT82TAS>vVM<-iGVSg+y}M4((|zAhT`khEIm<14I) z=Y(<9wnY{uyiG9J&?Dk;$3SF96OqRjl{w`qDr{?Za{H07rKHtDlf&Pb0!}sN_*WPv z0&5oY%PHa6z*WvzuYpfFv+0c$Royt}QP*+TjkS64LL1u5%B;04+GjM=GmSJ04;!}+ z|J)}?W(^AyTB5K}+}lqoA6s3}{UgK5>hO2)1NdLK{Lc7Xlf&fkv#}k{kk&u4za|Ja ztw-l-{p|#EB6GtD*9n(X-P496bxeHa#&PDU=c5`UPoM2x;&I}0GCo@DB<|Y;*SIR& zH0itOmJV(2t@ew8-2ibKc^dP7@_Ux}ytRT>HjXG3j<{)*W?sAwsa_=edg}!jf=kjJ zlDn6yooAMNYU60rY&@hk_I-0c_)Ldzi!NVQ#VSZ|AKw3DW+U=z8e(tsbwNf!Sh&Q= ztUcikHe}stWWk-zYqy!+e{uZ&Wx~30tMZ8L-beqvdcrEB#YcHUO+%sdk^W0U!?5)zX9q&iu;T z_LOS080CUzM|!ZnMpL9fS9A}M*80dqDSX{*t*3Cel&uCI*FJ0sQ)t|Jte zg96riV9k<+gh$&RNgyganxA2`{-jkwIVgTMQj|Yflz)I+1KiHUc0|-J zQST4>!2ulF-zJZhT@I;l|5FK{p~q`>Y>Ekqe{)q>o3<*Lf}s{etTC<921f4oJ6eAP zMyV9r;t%E;$rrnqo7$7>v022C6u*g(IiVNR0`Fi!hB-Jykxh&b5WRPuP`)(y-2AGs7IqQysJx{JL%z6*%m;kpMo7Z!5MZUfUH8FJT2{+ zJt%8KXU|f^p>*Go1tt8_>v1V0+3Z|i+IT6om_G4qaO7oX-KoSz$zjJ~=%xG^Bs28h zet&ASw_nDgF7Ci&lig=f3Q{cWgra#AimPbTw(R?Tygu6VdQH{jg#`Kt=_&e+kz zi0=W#2pay3Igg6t?N250g9gv%QS$j!zoR&s?2oJ)#ffs=W!PM{XM_2r7FIl&e9vw1 z+U{XRJ$~F5vnvY3=lGc+0oaGYvds!ixEaNpZPcch`)f5|% zosgzQILgD;)Lsp+Rjss%>(g9~ib;-{KJw_YZR5epZQb*^nE}L62lF#x-G7ySF%Snj zU_`rGE{oh79&9D4a)&V;3!Q{A{HFz*Y#YhBv|z{1Fg>f;j>L#ch(oHYYuj!)hM@`4 zhnZZ1`Ix)nBr}Z}FW#a~%qe*Pf2aO$GRAeVpNIiZk}%3#2X*5rvq1O0kroDGANfB& CH`eU{ literal 0 HcmV?d00001 diff --git a/src/itdelatrisu/opsu/GameImage.java b/src/itdelatrisu/opsu/GameImage.java index ec6a3ff3..cdb4e999 100644 --- a/src/itdelatrisu/opsu/GameImage.java +++ b/src/itdelatrisu/opsu/GameImage.java @@ -368,6 +368,54 @@ public enum GameImage { }, // Non-Game Components + SELECTION_MODS ("selection-mods", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_MODS_OVERLAY ("selection-mods-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_RANDOM ("selection-random", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_RANDOM_OVERLAY ("selection-random-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OPTIONS ("selection-options", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OPTIONS_OVERLAY ("selection-options-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OTHER_OPTIONS ("selection-selectoptions", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, + SELECTION_OTHER_OPTIONS_OVERLAY ("selection-selectoptions-over", "png", false, false) { + @Override + protected Image process_sub(Image img, int w, int h) { + return img.getScaledCopy((h * 0.115f) / img.getHeight()); + } + }, VOLUME ("volume-bg", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { @@ -383,7 +431,8 @@ public enum GameImage { MENU_BUTTON_BG ("menu-button-background", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(w / 2, h / SongMenu.MAX_SONG_BUTTONS); + // TODO: scale these properly (messy due to non-cropped images) + return img.getScaledCopy(w / 2, (int) (h * 0.95f) / SongMenu.MAX_SONG_BUTTONS); } }, MENU_TAB ("selection-tab", "png", false, false) { @@ -392,18 +441,6 @@ public enum GameImage { return img.getScaledCopy((h * 0.033f) / img.getHeight()); } }, - MENU_SEARCH ("search", "png", false, false) { - @Override - protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(Utils.FONT_BOLD.getLineHeight() * 2f / img.getHeight()); - } - }, - MENU_OPTIONS ("options", "png", false, false) { - @Override - protected Image process_sub(Image img, int w, int h) { - return img.getScaledCopy(Utils.FONT_BOLD.getLineHeight() * 2f / img.getHeight()); - } - }, MENU_MUSICNOTE ("music-note", "png", false, false) { @Override protected Image process_sub(Image img, int w, int h) { diff --git a/src/itdelatrisu/opsu/OsuGroupNode.java b/src/itdelatrisu/opsu/OsuGroupNode.java index 69e8eb39..3cefe22e 100644 --- a/src/itdelatrisu/opsu/OsuGroupNode.java +++ b/src/itdelatrisu/opsu/OsuGroupNode.java @@ -54,38 +54,54 @@ public class OsuGroupNode { * Draws the button. * @param x the x coordinate * @param y the y coordinate + * @param headerY the header end y coordinate (for cropping) + * @param footerY the footer start y coordinate (for cropping) * @param grade the highest grade, if any * @param focus true if this is the focused node */ - public void draw(float x, float y, Grade grade, boolean focus) { + public void draw(float x, float y, float headerY, float footerY, Grade grade, boolean focus) { boolean expanded = (osuFileIndex > -1); - float xOffset = 0f; OsuFile osu; - Color textColor = Color.lightGray; Image bg = GameImage.MENU_BUTTON_BG.getImage(); + Color bgColor; + Color textColor = Color.lightGray; + // draw song button background if (expanded) { // expanded - xOffset = bg.getWidth() / 10f; + x -= bg.getWidth() / 10f; if (focus) { - bg.draw(x - xOffset, y, Color.white); + bgColor = Color.white; textColor = Color.white; } else - bg.draw(x - xOffset, y, Utils.COLOR_BLUE_BUTTON); + bgColor = Utils.COLOR_BLUE_BUTTON; osu = osuFiles.get(osuFileIndex); } else { - bg.draw(x, y, Utils.COLOR_ORANGE_BUTTON); + bgColor = Utils.COLOR_ORANGE_BUTTON; osu = osuFiles.get(0); } + // crop image if necessary + if (y < headerY) { + int cropHeight = (int) (headerY - y); + Image bgCropped = bg.getSubImage(0, cropHeight, bg.getWidth(), bg.getHeight() - cropHeight); + bgCropped.draw(x, headerY, bgColor); + } else if (y + bg.getHeight() > footerY) { + int cropHeight = (int) (footerY - y); + Image bgCropped = bg.getSubImage(0, 0, bg.getWidth(), cropHeight); + bgCropped.draw(x, y, bgColor); + } else + bg.draw(x, y, bgColor); - float cx = x + (bg.getWidth() * 0.05f) - xOffset; + float cx = x + (bg.getWidth() * 0.05f); float cy = y + (bg.getHeight() * 0.2f) - 3; + // draw grade if (grade != Grade.NULL) { Image gradeImg = grade.getMenuImage(); gradeImg.drawCentered(cx - bg.getWidth() * 0.01f + gradeImg.getWidth() / 2f, y + bg.getHeight() / 2.2f); cx += gradeImg.getWidth(); } + // draw text Utils.FONT_MEDIUM.drawString(cx, cy, osu.getTitle(), textColor); Utils.FONT_DEFAULT.drawString(cx, cy + Utils.FONT_MEDIUM.getLineHeight() - 4, String.format("%s // %s", osu.getArtist(), osu.creator), textColor); diff --git a/src/itdelatrisu/opsu/ScoreData.java b/src/itdelatrisu/opsu/ScoreData.java index 5ad238a1..a08ea718 100644 --- a/src/itdelatrisu/opsu/ScoreData.java +++ b/src/itdelatrisu/opsu/ScoreData.java @@ -78,13 +78,13 @@ public class ScoreData implements Comparable { /** * Initializes the base coordinates for drawing. - * @param width the container width - * @param height the container height + * @param containerWidth the container width + * @param topY the top y coordinate */ - public static void init(int width, int height) { - baseX = width * 0.01f; - baseY = height * 0.16f; - buttonWidth = width * 0.4f; + public static void init(int containerWidth, float topY) { + baseX = containerWidth * 0.01f; + baseY = topY; + buttonWidth = containerWidth * 0.4f; float gradeHeight = GameImage.MENU_BUTTON_BG.getImage().getHeight() * 0.45f; buttonHeight = Math.max(gradeHeight, Utils.FONT_DEFAULT.getLineHeight() * 3.03f); buttonOffset = buttonHeight + gradeHeight / 10f; diff --git a/src/itdelatrisu/opsu/SongSort.java b/src/itdelatrisu/opsu/SongSort.java index f2a8016f..5b28ea90 100644 --- a/src/itdelatrisu/opsu/SongSort.java +++ b/src/itdelatrisu/opsu/SongSort.java @@ -145,21 +145,21 @@ public enum SongSort { /** * Initializes the sort tab. - * @param width the container width - * @param height the container height + * @param containerWidth the container width + * @param bottomY the bottom y coordinate */ - public void init(int width, int height) { + public void init(int containerWidth, float bottomY) { Image tab = GameImage.MENU_TAB.getImage(); int tabWidth = tab.getWidth(); - float buttonX = width / 2f; - float tabOffset = (width - buttonX - tabWidth) / (SIZE - 1); + float buttonX = containerWidth / 2f; + float tabOffset = (containerWidth - buttonX - tabWidth) / (SIZE - 1); if (tabOffset > tabWidth) { // prevent tabs from being spaced out tabOffset = tabWidth; - buttonX = (width * 0.99f) - (tabWidth * SIZE); + buttonX = (containerWidth * 0.99f) - (tabWidth * SIZE); } this.tab = new MenuButton(tab, (buttonX + (tabWidth / 2f)) + (id * tabOffset), - (height * 0.15f) - (tab.getHeight() / 2f) - 2f + bottomY - (tab.getHeight() / 2f) ); } diff --git a/src/itdelatrisu/opsu/Utils.java b/src/itdelatrisu/opsu/Utils.java index 39270d73..2d126c37 100644 --- a/src/itdelatrisu/opsu/Utils.java +++ b/src/itdelatrisu/opsu/Utils.java @@ -82,7 +82,8 @@ public class Utils { COLOR_GREEN = new Color(137, 201, 79), COLOR_LIGHT_ORANGE = new Color(255,192,128), COLOR_LIGHT_GREEN = new Color(128,255,128), - COLOR_LIGHT_BLUE = new Color(128,128,255); + COLOR_LIGHT_BLUE = new Color(128,128,255), + COLOR_GREEN_SEARCH = new Color(173, 255, 47); /** The default map colors, used when a map does not provide custom colors. */ public static final Color[] DEFAULT_COMBO = { @@ -215,16 +216,9 @@ public class Utils { for (GameMod mod : GameMod.values()) mod.init(width, height); - // initialize sorts - for (SongSort sort : SongSort.values()) - sort.init(width, height); - // initialize hit objects OsuHitObject.init(width, height); - // initialize score data buttons - ScoreData.init(width, height); - // initialize download nodes DownloadNode.init(width, height); diff --git a/src/itdelatrisu/opsu/states/SongMenu.java b/src/itdelatrisu/opsu/states/SongMenu.java index be4d93be..d616125c 100644 --- a/src/itdelatrisu/opsu/states/SongMenu.java +++ b/src/itdelatrisu/opsu/states/SongMenu.java @@ -83,6 +83,12 @@ public class SongMenu extends BasicGameState { /** Maximum x offset of song buttons for mouse hover, in pixels. */ private static final float MAX_HOVER_OFFSET = 30f; + /** Time, in milliseconds, for the search bar to fade in or out. */ + private static final int SEARCH_TRANSITION_TIME = 250; + + /** Line width of the header/footer divider. */ + private static final int DIVIDER_LINE_WIDTH = 4; + /** Song node class representing an OsuGroupNode and file index. */ private static class SongNode { /** Song node. */ @@ -136,8 +142,8 @@ public class SongMenu extends BasicGameState { /** Current index of hovered song button. */ private int hoverIndex = -1; - /** The options button (to enter the "Game Options" menu). */ - private MenuButton optionsButton; + /** The selection buttons. */ + private MenuButton selectModsButton, selectRandomButton, selectMapOptionsButton, selectOptionsButton; /** The search textfield. */ private TextField search; @@ -146,10 +152,10 @@ public class SongMenu extends BasicGameState { * Delay timer, in milliseconds, before running another search. * This is overridden by character entry (reset) and 'esc' (immediate search). */ - private int searchTimer; + private int searchTimer = 0; /** Information text to display based on the search query. */ - private String searchResultString; + private String searchResultString = null; /** Loader animation. */ private Animation loader; @@ -184,6 +190,12 @@ public class SongMenu extends BasicGameState { /** Current start score (topmost score entry). */ private int startScore = 0; + /** Header and footer end and start y coordinates, respectively. */ + private float headerY, footerY; + + /** Time, in milliseconds, for fading the search bar. */ + private int searchTransitionTimer = SEARCH_TRANSITION_TIME; + // game-related variables private GameContainer container; private StateBasedGame game; @@ -204,26 +216,35 @@ public class SongMenu extends BasicGameState { int width = container.getWidth(); int height = container.getHeight(); + // header/footer coordinates + headerY = height * 0.0075f + GameImage.MENU_MUSICNOTE.getImage().getHeight() + + Utils.FONT_BOLD.getLineHeight() + Utils.FONT_DEFAULT.getLineHeight() + + Utils.FONT_SMALL.getLineHeight(); + footerY = height - GameImage.SELECTION_MODS.getImage().getHeight(); + + // initialize sorts + for (SongSort sort : SongSort.values()) + sort.init(width, headerY - SongMenu.DIVIDER_LINE_WIDTH / 2); + + // initialize score data buttons + ScoreData.init(width, headerY + height * 0.01f); + // song button background & graphics context Image menuBackground = GameImage.MENU_BUTTON_BG.getImage(); // song button coordinates buttonX = width * 0.6f; - buttonY = height * 0.16f; + buttonY = headerY; buttonWidth = menuBackground.getWidth(); buttonHeight = menuBackground.getHeight(); - buttonOffset = (height * 0.8f) / MAX_SONG_BUTTONS; + buttonOffset = (footerY - headerY - DIVIDER_LINE_WIDTH) / MAX_SONG_BUTTONS; // search - searchTimer = 0; - searchResultString = "Type to search!"; - Image searchIcon = GameImage.MENU_SEARCH.getImage(); - Image tab = GameImage.MENU_TAB.getImage(); + int textFieldX = (int) (width * 0.7125f + Utils.FONT_BOLD.getWidth("Search: ")); + int textFieldY = (int) (headerY + Utils.FONT_BOLD.getLineHeight() / 2); search = new TextField( - container, Utils.FONT_DEFAULT, - (int) buttonX + (tab.getWidth() / 2) + searchIcon.getWidth(), - (int) ((height * 0.15f) - (tab.getHeight() * 2.5f)), - (int) (buttonWidth / 2), Utils.FONT_DEFAULT.getLineHeight() + container, Utils.FONT_BOLD, textFieldX, textFieldY, + (int) (width * 0.99f) - textFieldX, Utils.FONT_BOLD.getLineHeight() ); search.setBackgroundColor(Color.transparent); search.setBorderColor(Color.transparent); @@ -231,10 +252,22 @@ public class SongMenu extends BasicGameState { search.setConsumeEvents(false); search.setMaxLength(60); - // options button - Image optionsIcon = GameImage.MENU_OPTIONS.getImage(); - optionsButton = new MenuButton(optionsIcon, search.getX() - (optionsIcon.getWidth() * 1.5f), search.getY()); - optionsButton.setHoverExpand(1.75f); + // selection buttons + float selectX = GameImage.MENU_BACK.getImage().getWidth() * 1.75f; + float selectY = height - GameImage.SELECTION_MODS.getImage().getHeight() / 2f; + float selectOffset = GameImage.SELECTION_MODS.getImage().getWidth() * 1.05f; + selectModsButton = new MenuButton(GameImage.SELECTION_MODS_OVERLAY.getImage(), + selectX, selectY); + selectRandomButton = new MenuButton(GameImage.SELECTION_RANDOM_OVERLAY.getImage(), + selectX + selectOffset, selectY); + selectMapOptionsButton = new MenuButton(GameImage.SELECTION_OPTIONS_OVERLAY.getImage(), + selectX + selectOffset * 2f, selectY); + selectOptionsButton = new MenuButton(GameImage.SELECTION_OTHER_OPTIONS_OVERLAY.getImage(), + selectX + selectOffset * 3f, selectY); + selectModsButton.setHoverFade(0f); + selectRandomButton.setHoverFade(0f); + selectMapOptionsButton.setHoverFade(0f); + selectOptionsButton.setHoverFade(0f); // loader int loaderDim = GameImage.MENU_MUSICNOTE.getImage().getWidth(); @@ -258,19 +291,19 @@ public class SongMenu extends BasicGameState { GameImage.PLAYFIELD.getImage().draw(); } - // header setup - float lowerBound = height * 0.15f; - g.setColor(Utils.COLOR_BLACK_ALPHA); - g.fillRect(0, 0, width, lowerBound); + // top/bottom bars + g.setColor(Color.black); + g.fillRect(0, 0, width, headerY); + g.fillRect(0, footerY, width, height - footerY); g.setColor(Utils.COLOR_BLUE_DIVIDER); - g.setLineWidth(2f); - g.drawLine(0, lowerBound, width, lowerBound); + g.setLineWidth(DIVIDER_LINE_WIDTH); + g.drawLine(0, headerY, width, headerY); + g.drawLine(0, footerY - DIVIDER_LINE_WIDTH / 2, width, footerY - DIVIDER_LINE_WIDTH / 2); g.resetLineWidth(); // header if (focusNode != null) { float marginX = width * 0.005f, marginY = height * 0.005f; - Image musicNote = GameImage.MENU_MUSICNOTE.getImage(); if (MusicController.isTrackLoading()) loader.draw(marginX, marginY); @@ -282,14 +315,14 @@ public class SongMenu extends BasicGameState { if (songInfo == null) songInfo = focusNode.getInfo(); marginX += 5; - Utils.FONT_LARGE.drawString(marginX + iconWidth, marginY, songInfo[0], Color.white); - Utils.FONT_DEFAULT.drawString(marginX + iconWidth, marginY + Utils.FONT_LARGE.getLineHeight() * 0.75f, songInfo[1], Color.white); - float headerY = marginY + iconHeight; - Utils.FONT_BOLD.drawString(marginX, headerY, songInfo[2], Color.white); - headerY += Utils.FONT_BOLD.getLineHeight() - 6; - Utils.FONT_DEFAULT.drawString(marginX, headerY, songInfo[3], Color.white); - headerY += Utils.FONT_DEFAULT.getLineHeight() - 4; - Utils.FONT_SMALL.drawString(marginX, headerY, songInfo[4], Color.white); + Utils.FONT_LARGE.drawString(marginX + iconWidth * 1.05f, marginY, songInfo[0], Color.white); + Utils.FONT_DEFAULT.drawString(marginX + iconWidth * 1.05f, marginY + Utils.FONT_LARGE.getLineHeight() * 0.75f, songInfo[1], Color.white); + float headerTextY = marginY + iconHeight; + Utils.FONT_BOLD.drawString(marginX, headerTextY, songInfo[2], Color.white); + headerTextY += Utils.FONT_BOLD.getLineHeight() - 6; + Utils.FONT_DEFAULT.drawString(marginX, headerTextY, songInfo[3], Color.white); + headerTextY += Utils.FONT_DEFAULT.getLineHeight() - 4; + Utils.FONT_SMALL.drawString(marginX, headerTextY, songInfo[4], Color.white); } // song buttons @@ -300,6 +333,7 @@ public class SongMenu extends BasicGameState { ScoreData[] scores = getScoreDataForNode(node, false); node.draw( buttonX - offset, buttonY + (i*buttonOffset), + headerY + DIVIDER_LINE_WIDTH / 2, footerY - DIVIDER_LINE_WIDTH, (scores == null) ? Grade.NULL : scores[0].getGrade(), (node == focusNode) ); @@ -324,8 +358,16 @@ public class SongMenu extends BasicGameState { ScoreData.drawScrollbar(g, startScore, focusScores.length); } - // options button - optionsButton.draw(); + // selection buttons + // TODO +// GameImage.SELECTION_MODS.getImage().drawCentered(selectModsButton.getX(), selectModsButton.getY()); +// selectModsButton.draw(); + GameImage.SELECTION_RANDOM.getImage().drawCentered(selectRandomButton.getX(), selectRandomButton.getY()); + selectRandomButton.draw(); + GameImage.SELECTION_OPTIONS.getImage().drawCentered(selectMapOptionsButton.getX(), selectMapOptionsButton.getY()); + selectMapOptionsButton.draw(); + GameImage.SELECTION_OTHER_OPTIONS.getImage().drawCentered(selectOptionsButton.getX(), selectOptionsButton.getY()); + selectOptionsButton.draw(); // sorting tabs SongSort currentSort = SongSort.getSort(); @@ -343,15 +385,37 @@ public class SongMenu extends BasicGameState { currentSort.draw(true, false); // search - Image searchIcon = GameImage.MENU_SEARCH.getImage(); - Utils.FONT_BOLD.drawString( - search.getX(), search.getY() - Utils.FONT_BOLD.getLineHeight(), - searchResultString, Color.white - ); - searchIcon.draw(search.getX() - searchIcon.getWidth(), - search.getY() - Utils.FONT_DEFAULT.getLineHeight()); - g.setColor(Color.white); - search.render(container, g); + boolean searchEmpty = search.getText().isEmpty(); + int searchX = search.getX(), searchY = search.getY(); + float searchBaseX = width * 0.7f; + float searchTextX = width * 0.7125f; + float searchRectHeight = Utils.FONT_BOLD.getLineHeight() * 2; + float searchExtraHeight = Utils.FONT_DEFAULT.getLineHeight() * 0.7f; + float searchProgress = (searchTransitionTimer < SEARCH_TRANSITION_TIME) ? + ((float) searchTransitionTimer / SEARCH_TRANSITION_TIME) : 1f; + float oldAlpha = Utils.COLOR_BLACK_ALPHA.a; + if (searchEmpty) { + searchRectHeight += (1f - searchProgress) * searchExtraHeight; + Utils.COLOR_BLACK_ALPHA.a = 0.5f - searchProgress * 0.3f; + } else { + searchRectHeight += searchProgress * searchExtraHeight; + Utils.COLOR_BLACK_ALPHA.a = 0.2f + searchProgress * 0.3f; + } + g.setColor(Utils.COLOR_BLACK_ALPHA); + g.fillRect(searchBaseX, headerY + DIVIDER_LINE_WIDTH / 2, width - searchBaseX, searchRectHeight); + Utils.COLOR_BLACK_ALPHA.a = oldAlpha; + Utils.FONT_BOLD.drawString(searchTextX, searchY, "Search:", Utils.COLOR_GREEN_SEARCH); + if (searchEmpty) + Utils.FONT_BOLD.drawString(searchX, searchY, "Type to search!", Color.white); + else { + g.setColor(Color.white); + // TODO: why is this needed to correctly position the TextField? + search.setLocation(searchX - 3, searchY - 1); + search.render(container, g); + search.setLocation(searchX, searchY); + Utils.FONT_DEFAULT.drawString(searchTextX, searchY + Utils.FONT_BOLD.getLineHeight(), + (searchResultString == null) ? "Searching..." : searchResultString, Color.white); + } // scroll bar if (focusNode != null) { @@ -364,7 +428,7 @@ public class SongMenu extends BasicGameState { else if (startNode.index == focusNode.index) startIndex += startNode.osuFileIndex; Utils.drawScrollbar(g, startIndex, totalNodes, MAX_SONG_BUTTONS, - width, height * 0.16f, 0, buttonHeight, buttonOffset, + width, headerY + DIVIDER_LINE_WIDTH / 2, 0, buttonOffset - DIVIDER_LINE_WIDTH * 1.5f, buttonOffset, Utils.COLOR_BLACK_ALPHA, Color.white, true); } } @@ -394,7 +458,10 @@ public class SongMenu extends BasicGameState { Utils.updateVolumeDisplay(delta); int mouseX = input.getMouseX(), mouseY = input.getMouseY(); Utils.getBackButton().hoverUpdate(delta, mouseX, mouseY); - optionsButton.hoverUpdate(delta, mouseX, mouseY); + selectModsButton.hoverUpdate(delta, mouseX, mouseY); + selectRandomButton.hoverUpdate(delta, mouseX, mouseY); + selectMapOptionsButton.hoverUpdate(delta, mouseX, mouseY); + selectOptionsButton.hoverUpdate(delta, mouseX, mouseY); // beatmap menu timer if (beatmapMenuTimer > -1) { @@ -425,7 +492,7 @@ public class SongMenu extends BasicGameState { // empty search if (search.getText().isEmpty()) - searchResultString = "Type to search!"; + searchResultString = null; // search produced new list: re-initialize it startNode = focusNode = null; @@ -450,39 +517,45 @@ public class SongMenu extends BasicGameState { searchResultString = "No matches found. Hit 'esc' to reset."; } } + if (searchTransitionTimer < SEARCH_TRANSITION_TIME) { + searchTransitionTimer += delta; + if (searchTransitionTimer > SEARCH_TRANSITION_TIME) + searchTransitionTimer = SEARCH_TRANSITION_TIME; + } // slide buttons int height = container.getHeight(); - float targetY = height * 0.16f; - if (buttonY > targetY) { + if (buttonY > headerY) { buttonY -= height * delta / 20000f; - if (buttonY < targetY) - buttonY = targetY; - } else if (buttonY < targetY) { + if (buttonY < headerY) + buttonY = headerY; + } else if (buttonY < headerY) { buttonY += height * delta / 20000f; - if (buttonY > targetY) - buttonY = targetY; + if (buttonY > headerY) + buttonY = headerY; } // mouse hover - OsuGroupNode node = startNode; boolean isHover = false; - for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { - float cx = (node.index == OsuGroupList.get().getExpandedIndex()) ? buttonX * 0.9f : buttonX; - if ((mouseX > cx && mouseX < cx + buttonWidth) && - (mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) { - if (i == hoverIndex) { - if (hoverOffset < MAX_HOVER_OFFSET) { - hoverOffset += delta / 3f; - if (hoverOffset > MAX_HOVER_OFFSET) - hoverOffset = MAX_HOVER_OFFSET; + if (mouseY > headerY && mouseY < footerY) { + OsuGroupNode node = startNode; + for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { + float cx = (node.index == OsuGroupList.get().getExpandedIndex()) ? buttonX * 0.9f : buttonX; + if ((mouseX > cx && mouseX < cx + buttonWidth) && + (mouseY > buttonY + (i * buttonOffset) && mouseY < buttonY + (i * buttonOffset) + buttonHeight)) { + if (i == hoverIndex) { + if (hoverOffset < MAX_HOVER_OFFSET) { + hoverOffset += delta / 3f; + if (hoverOffset > MAX_HOVER_OFFSET) + hoverOffset = MAX_HOVER_OFFSET; + } + } else { + hoverIndex = i; + hoverOffset = 0f; } - } else { - hoverIndex = i; - hoverOffset = 0f; + isHover = true; + break; } - isHover = true; - break; } } if (!isHover) { @@ -512,8 +585,17 @@ public class SongMenu extends BasicGameState { return; } - // options - if (optionsButton.contains(x, y)) { + // selection buttons + if (selectModsButton.contains(x, y)) { + this.keyPressed(Input.KEY_F1, '\0'); + return; + } else if (selectRandomButton.contains(x, y)) { + this.keyPressed(Input.KEY_F2, '\0'); + return; + } else if (selectMapOptionsButton.contains(x, y)) { + this.keyPressed(Input.KEY_F3, '\0'); + return; + } else if (selectOptionsButton.contains(x, y)) { SoundController.playSound(SoundEffect.MENUHIT); game.enterState(Opsu.STATE_OPTIONSMENU, new EmptyTransition(), new FadeInTransition(Color.black)); return; @@ -539,46 +621,48 @@ public class SongMenu extends BasicGameState { } // song buttons - int expandedIndex = OsuGroupList.get().getExpandedIndex(); - OsuGroupNode node = startNode; - for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { - // is button at this index clicked? - float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX; - if ((x > cx && x < cx + buttonWidth) && - (y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) { - float oldHoverOffset = hoverOffset; - int oldHoverIndex = hoverIndex; - - // clicked node is already expanded - if (node.index == expandedIndex) { - if (node.osuFileIndex == focusNode.osuFileIndex) { - // if already focused, load the beatmap - if (button != Input.MOUSE_RIGHT_BUTTON) - startGame(); - else + if (y > headerY && y < footerY) { + int expandedIndex = OsuGroupList.get().getExpandedIndex(); + OsuGroupNode node = startNode; + for (int i = 0; i < MAX_SONG_BUTTONS && node != null; i++, node = node.next) { + // is button at this index clicked? + float cx = (node.index == expandedIndex) ? buttonX * 0.9f : buttonX; + if ((x > cx && x < cx + buttonWidth) && + (y > buttonY + (i * buttonOffset) && y < buttonY + (i * buttonOffset) + buttonHeight)) { + float oldHoverOffset = hoverOffset; + int oldHoverIndex = hoverIndex; + + // clicked node is already expanded + if (node.index == expandedIndex) { + if (node.osuFileIndex == focusNode.osuFileIndex) { + // if already focused, load the beatmap + if (button != Input.MOUSE_RIGHT_BUTTON) + startGame(); + else + SoundController.playSound(SoundEffect.MENUCLICK); + } else { + // focus the node SoundController.playSound(SoundEffect.MENUCLICK); - } else { - // focus the node - SoundController.playSound(SoundEffect.MENUCLICK); - setFocus(node, 0, false); + setFocus(node, 0, false); + } } + + // clicked node is a new group + else { + SoundController.playSound(SoundEffect.MENUCLICK); + setFocus(node, -1, false); + } + + // restore hover data + hoverOffset = oldHoverOffset; + hoverIndex = oldHoverIndex; + + // open beatmap menu + if (button == Input.MOUSE_RIGHT_BUTTON) + beatmapMenuTimer = (node.index == expandedIndex) ? BEATMAP_MENU_DELAY * 4 / 5 : 0; + + return; } - - // clicked node is a new group - else { - SoundController.playSound(SoundEffect.MENUCLICK); - setFocus(node, -1, false); - } - - // restore hover data - hoverOffset = oldHoverOffset; - hoverIndex = oldHoverIndex; - - // open beatmap menu - if (button == Input.MOUSE_RIGHT_BUTTON) - beatmapMenuTimer = (node.index == expandedIndex) ? BEATMAP_MENU_DELAY * 4 / 5 : 0; - - return; } } @@ -621,6 +705,7 @@ public class SongMenu extends BasicGameState { // clear search text search.setText(""); searchTimer = SEARCH_DELAY; + searchTransitionTimer = 0; } else { // return to main menu SoundController.playSound(SoundEffect.MENUBACK); @@ -629,12 +714,14 @@ public class SongMenu extends BasicGameState { } break; case Input.KEY_F1: - SoundController.playSound(SoundEffect.MENUHIT); - game.enterState(Opsu.STATE_OPTIONSMENU, new EmptyTransition(), new FadeInTransition(Color.black)); + // TODO: mods menu +// SoundController.playSound(SoundEffect.MENUHIT); +// game.enterState(); break; case Input.KEY_F2: if (focusNode == null) break; + SoundController.playSound(SoundEffect.MENUHIT); if (input.isKeyDown(Input.KEY_RSHIFT) || input.isKeyDown(Input.KEY_LSHIFT)) { // shift key: previous random track SongNode prev; @@ -718,8 +805,16 @@ public class SongMenu extends BasicGameState { break; default: // wait for user to finish typing - if (Character.isLetterOrDigit(c) || key == Input.KEY_BACK) + // TODO: accept all characters (current conditions are from TextField class) + if ((c > 31 && c < 127) || key == Input.KEY_BACK) { searchTimer = 0; + int textLength = search.getText().length(); + if (key == Input.KEY_BACK) { + if (textLength == 0) + searchTransitionTimer = 0; + } else if (textLength == 1) + searchTransitionTimer = 0; + } break; } } @@ -782,11 +877,15 @@ public class SongMenu extends BasicGameState { throws SlickException { Display.setTitle(game.getTitle()); Utils.getBackButton().resetHover(); - optionsButton.resetHover(); + selectModsButton.resetHover(); + selectRandomButton.resetHover(); + selectMapOptionsButton.resetHover(); + selectOptionsButton.resetHover(); hoverOffset = 0f; hoverIndex = -1; startScore = 0; beatmapMenuTimer = -1; + searchTransitionTimer = SEARCH_TRANSITION_TIME; // reset song stack randomStack = new Stack(); @@ -920,7 +1019,8 @@ public class SongMenu extends BasicGameState { hoverIndex = -1; search.setText(""); searchTimer = SEARCH_DELAY; - searchResultString = "Type to search!"; + searchTransitionTimer = SEARCH_TRANSITION_TIME; + searchResultString = null; // reload songs in new thread reloadThread = new Thread() { @@ -977,16 +1077,16 @@ public class SongMenu extends BasicGameState { if (n < 0 && startNode.prev != null) { startNode = startNode.prev; buttonY += buttonOffset / 4; - if (buttonY > height * 0.18f) - buttonY = height * 0.18f; + if (buttonY > headerY + height * 0.02f) + buttonY = headerY + height * 0.02f; n++; shifted = true; } else if (n > 0 && startNode.next != null && OsuGroupList.get().getNode(startNode, MAX_SONG_BUTTONS) != null) { startNode = startNode.next; buttonY -= buttonOffset / 4; - if (buttonY < height * 0.14f) - buttonY = height * 0.14f; + if (buttonY < headerY - height * 0.02f) + buttonY = headerY - height * 0.02f; n--; shifted = true; } else