From 8d08958280c50992ebe67f9144146cf9f36689d4 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Mon, 8 Sep 2025 19:14:46 -0500 Subject: [PATCH] Add login GUI and location/color persistence --- .gitignore | 4 + assets/heightmap.bin | Bin 40004 -> 40004 bytes assets/heightmap.png | Bin 4624 -> 4654 bytes client/Makefile | 4 +- client/includes/raygui.h | 5993 +++++++++++++++++++++++++++++++++ client/main.cpp | 152 +- client/net/NetworkManager.cpp | 64 + client/net/NetworkManager.hpp | 11 +- server/net/packets.go | 62 +- server/net/server.go | 213 +- server/players.json | 21 +- 11 files changed, 6443 insertions(+), 81 deletions(-) create mode 100644 .gitignore create mode 100644 client/includes/raygui.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8840fe9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o + +server/players.json +client/game diff --git a/assets/heightmap.bin b/assets/heightmap.bin index 08ce812e6b1c5687ed4303586f70ad97dbfae48e..9647afc0de86b6cefdae6385ac04150ea9a71fd0 100644 GIT binary patch literal 40004 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

C7| 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 y3}=+f@*DP#Z+b*z44ZN+%YWxvtM>i2&LAwPQn literal 40004 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Ð*PA0ew1jrGNbbMV`%1TJ@Ss#ran0x$+EJA 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%0Dk`CpnLKw>Dre}0-6c&-`ywi_sf>}=P5`Q zco6Z%^ilL_r4POGIFI&jzD-w$^PY1Yqq(1M(3ILHTD<2THD6Xudn`qIJGq>;O=zGo zy3I7P@+J+PpH8Fs9Y~t}dRkyShPI!jV(aT8;=FWSQATCHp|$KCiH74|;wyiRjGpkE zEY}*03(njrV`GKhlbi77EcXH1Ibp@!W3U_=1n2N56sadedr3OFI`W`5rwqC4>+pEs z1H}G(3jeql&`s&Y#&7bH zc@CUk48=VqJnzPkmU0uVD;vc=fkFCaoTV;$c-J)**njkFh7wL*|o`Lk}cN&3uh4 z1LDN)+=bM?DwM`8%caI&^XSg@bgDTai|TzWpxe|+sprT%>gslxnvc$;g%=Cx-T}ol zMCTHH%Y8##%z`e?e<`|KW=Y3LM@URQ&y+}>DogZ(&r5n1EhaUCFB6|@ZREo`ISj8J z2SL#o;e_7_@*L)VoS%602~wxN z!bZ*;8K2b+*9^`a3E(^|lb>i@^b^0P|Aabzp@wtC;^+56ZU1Lj^RBw?y7!2^`vHpc zDKe`mZY;Tu{QMg1tZqh4#xoS(euGhuzaT5+7o0ikPy8h-lp@_E8GLesd*Z z&ksdm?OFaz+GGTi?>}%|`76SXb;G9oC7w67!HDlUH4kbrrRh56^%X-QIR_u|Q?T1T z2G;%I{C+qP=7GoIXUe-->Z{ROZh(`khNId35h=FICHftSq;TsQ67Id2ST3QGw*^TH zKPcCVYiImLH4FXevea{QRSX~cFFa4Ros;Mi?{qr8EuG$RPo-ry5^0n|0>3W~rvnVU z=)v@*)OUsg^?9Z#hJJLBME-g!$>m+e=#X4VQN0YQeBwcz$8%pmhR{3C7_ zizpAEu|%ueZ|HzGQzkY^1?qO6$P&?3c`AKd7=4F&XhT$Bn&TB z5p1ri2$uJigorW);b(`OP{aO1iTzhB7CD#HfHP(M?jt{^4!bv1VAWkIzrWAt?|d2P z7!Z%t@u4u@b^`AT4kOag9;>GC8Jg29wEjB;_8rejy+su{-yxDal1!p>)t8))nodqw z#z~%<8cP?4G>BKP>e2rC4K!9`5A}TSOGU>pYP>a>rmxMUuUaqDX2S$}WNi>t&vK^0 zXXnr_-{ffch9q(1?_;Fn6VfEchQE=NX}^cyF(d!gsKjh~k+p*?2?oHnUp+U71YN4b%7Z>S+T4JAaDbADnK zHWOtvHR5aKE$QhVER`xni}h9tbk;OOnzPJ_I?WECdzBMtPiY2K8<$0Q-HE4~pM9y0 zr!l>2AWIWhWs9+04q~B^ymV?ylq9wBPjYb5GP16J8qto>AfrxgARA-y$fz?HZ+XEqLP_|?N=3-EQx^J56@(*~Y}bW&omOP5S~=Rn*%&BW}dEx<<%C%M?S=iprqeCR|Vm5u&mGxS)r0MmuB-dQoxy@2O{K! Ng_~rB!UZzI{{VWGXy5<< diff --git a/assets/heightmap.png b/assets/heightmap.png index 70537e6b2e9feaa28403dffc54fb01e3416b7650..38acdbb9de5c5cbb8ec0dd263aec1446ef10ca63 100644 GIT binary patch literal 4654 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 literal 4624 zcmV+r67TJaP)}B4q}us-d#mGpe6vKWsv1Uq0xx((s#{d* zWaT;gxu1=1VyVJOs_JC6Hf*T5cEVn3;lbWg&-&P9T3VLUe6m$d;!iYosytlInm7ks zbE?ic`+f?a<$m_b#%2VM_9^3D+g6xLo~!JGeKi(4${nbQ##3;ls#qtMfH(5&{oMDd z#LIL4DJ=1v{XE$?u$o~NbC}GsunLtD&+Vn^A_f<#wraOVH+E@KhOakHF1erQc}`0o zM1Suz&HLv$nMeWHFl`aP%&k(~d&P3e%BMX7{*am&z*x9RS8Amxf(18X>3bhIH5q;B zl=D3I!F~>mxM{V9dYJXLe&X|WY2}dw^&D(nKtUlSDS!bW~9I1^wz~Sor{A|IZ ztP>GE`>u$~wKfAKrGBt(Pie!FO8n`vz`Jfje!O7^y>Y^*umq@l< zV%T9=Xm^>qZSA_?RF=1@#4)jwN_Y1%N$egu_WbN*#F4t4NH6-Xa$GK2$YOB~lX4q$ zYIEz#Ewa3{b03sK9HV`8kLDTjyfwdzm4Af)+y{X_XMk1UYGqZ|#@ZTfag{0pP2^++ zvH&`6^>A0`qeS{bZaqjdxQf|)SRQqldniVhZk=M%tF_8<;IuU>ngu-dfNF^!jf(U} zRkCHUgwRZ~ae%?(y-OWkCW&qd?COLKqP7UI67WQjYCvxT3={P#@whr7uU1(~JbO(w z0Tbnd17HV*)3emN#gT^UC`5Wmx{j%L?Q(OvaC(t~_N>s_8b%xZj=&1Hn=KZN_RL@thzS^ommuPj>TWb; z=oIWL+y_>zQk=?uE1r9pBd~WD9iK%!M+QxMhktR3=2JM14sL^VB&u;-oa*BYVsHq?#wLee z)Jy|*RN)I)6m}6o@*^JPDJ|UYG8*Q`^?rIK>y^TREGkc;*{H}(Q{jU+c4Hjnz6M2K1LhG!BJp*$ zW1+P=T-%00<5xNKU|| z>{<&MVDJl^g7a`O_OAHwLwq7rDp`r=@YQ^&*jnrJ37hmZ zS6czZltO4NzT>(Wtf}xWJ*Cdju5TnkwSd$G8mXK}?jF8qjacwaxX1cj!TE!E znnuT$g)<-w6_|Rtrl2z{Gjdn)ItNHv$9!3fBN;`+7u>AAR;g%Dt+7;}*4&IsTf*L6k2b;b4hUU1<=jPO()ga$XU zaJC>>YKi!fpsKGV?T95=YSE4t9|*zWS>o~eTnl!t_4zD%UQj47Ql~<3wlHA@0rb@{ zYXOX(RQ0QPYV=&BqYv@q3?zP`PH5=5zP>*$abYp_$>O)iWL*Ba=)o67)M>sp_y9XQ zD0pG{Jnn3bAe}x1k3gU%14dMw;kTI`F~n4%(*Hlw&#}0_^8{DfAOl_MdO#1GX}lv2 zO(sD%u7zttEVwClD3kg1blx~hbX%#Ty zaa~BY>+AdL^Z5{kaW*Cck{lf!7+CQw;ut0jkUcHv0Mjmzv@5(p&*Bnx)er(U2NXjB zV<5afpU>9^tFu6m&rXz{Swgf}A%P$+oV2jvR`G%-^5Lv?5`CrXH#MC3MVPQbN%#s! za31dhMA>vTxLSe;YWOnhgjt0C$**X7T%Y?vB_7luKoSWyr3*Xo&3gt{=C;4x%aMOw z0Kf>Kg0V_WIl2(R%nI0sREbMAh5#^ZIJ$;zkNAM@OKh?CXS4;R9DDxpa*b=gPV>wx zX;&!{XRO)nANSsP6qRc)$)HZ+V}%_TcHa&wZx&g2}I_Dvj%g zp0>Q_dRfo9LhC5L$)i)37)~ouu5Q0nSjkSwhx0r~Bjs?!S`~mM0a#S z6FmSYB18Xf8UxnM%oF{_be){zH;t73&J4*LQ>m}J#^v!3Gaf^;O(>ECqR)Ll`#e8C4~Dz5pXa8KTRoK0#jtHK z=QLyQuWT#9flvRAhqMy^8($YF77~JE(Agsl{QTS}VWg09_n+rXW}|$(i(DjqsWxUl zjf$wEQL7=@*dg zmbx(5**PXelOh*EXWY6Lrl+P-;3nt1W%BIAKn*Vx{#|bjPM4+%T4|uU9oN-;qz*WN z_}NlkHPntp7>miuYL&_(QKEX8??ppvcQaG>$q`C6%tQpxUZ$j zO(O7h9_Gl3nQ zoblf!wI7Q2`1Ry_(Lu#QeY?0SMSe_723SdCR}e+>oSjjP#|phn>7n-^)qVE8cT&6E z4}wD@2fM+1?PyMQo-*aViPH$t_L!#46ttg{(L8yAoeiomBsg9_KfKg9a1~(e0EKg( z5fWTf^B5&k^bx>;OM&}HZqAsm*e?Mv+maEHKGG9!7LZPqwWSZ><-XZafLtIKz<7$} zECuWx-UpFF_m|p-Y))$LVs`Z_C~>5qT*DISqLPZN-Fc{UA__=8C4L|PMd{2+_u32a zjI03B;ixEm_7=0aK|BfkiCSbM%%+!~`_wCM!c|lYDX5xwVcM`jOF3nY(e<2-##LvO zP&SZ6Gm^N?1G!RKqSHExk>5Wz?`dYTqdZKH9aa$NDh>{kjMl@%bVvotiK>+kEkcyq+;}KPP_}ftAj(9{3=eO z*3qLo>c~^$?GYt4FmBC=M%*SJv;I|7F$K)E>l_$ej9BY28YcsdUxV~#RHAVr{bgQh zV6;NHrcCn)H9QcVR|9wUamE+)gyJZlomUy^MM@-d#im2Uf8o2!SyL1n!2%Xeg1yV{ z#oh>5IvU&Pp-#3xlv(swHkLRTfM2@Lq#?$)0xwfyouj@wD=ubMVoXY|0!+1;Q7HQg z8nSa#q?#)IMxaCg%CYXqpLXOe!+*5b+GupL*)r)c zGjDL3b$xwN@uZrAOijf=StEH+L=j#hf-=n|k`>9eJVzqZ9JoucPH|?d!v!CP6!ZJ_ z`GOIfn}O)ma@1X5%O3)ohn6+}JfY|k;FS4A!*Nk*D_+N`Nvy!;H9lY8m&aK7eEsX| zb5Yoeb$K6~KW~V&*5?cT0ABSq92v{?L@gwbd3;1q_JxtW(@JA4Bjx`3x<*jLI{*Iu zFiV>X6__ihzxbDbK401mR<-8_C21i2d;R29IB}p=5IETGjOyuHRO3dxqJpHXYJgJcuc|k)4?$ZhZTUfx zDCj5Iy?%%-IWp^(zJ`ov{*PRnDB8#{6wn&*i5A0H&!>I1I+_60{-SOV{GAH{5Mfi->X?h%>G+(IuwMSX z)~ StatusBar, Panel +* - GroupBox --> Line +* - Line +* - Panel --> StatusBar +* - ScrollPanel --> StatusBar +* - TabBar --> Button +* +* # Basic Controls +* - Label +* - LabelButton --> Label +* - Button +* - Toggle +* - ToggleGroup --> Toggle +* - ToggleSlider +* - CheckBox +* - ComboBox +* - DropdownBox +* - TextBox +* - ValueBox --> TextBox +* - Spinner --> Button, ValueBox +* - Slider +* - SliderBar --> Slider +* - ProgressBar +* - StatusBar +* - DummyRec +* - Grid +* +* # Advance Controls +* - ListView +* - ColorPicker --> ColorPanel, ColorBarHue +* - MessageBox --> Window, Label, Button +* - TextInputBox --> Window, Label, TextBox, Button +* +* It also provides a set of functions for styling the controls based on its properties (size, color) +* +* +* RAYGUI STYLE (guiStyle): +* raygui uses a global data array for all gui style properties (allocated on data segment by default), +* when a new style is loaded, it is loaded over the global style... but a default gui style could always be +* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one +* +* The global style array size is fixed and depends on the number of controls and properties: +* +* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; +* +* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* +* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style +* used for all controls, when any of those base values is set, it is automatically populated to all +* controls, so, specific control values overwriting generic style should be set after base values +* +* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those +* properties are actually common to all controls and can not be overwritten individually (like BASE ones) +* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR +* +* Custom control properties can be defined using the EXTENDED properties for each independent control. +* +* TOOL: rGuiStyler is a visual tool to customize raygui style: github.com/raysan5/rguistyler +* +* +* RAYGUI ICONS (guiIcons): +* raygui could use a global array containing icons data (allocated on data segment by default), +* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set +* must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS will be loaded +* +* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon +* requires 8 integers (16*16/32) to be stored in memory. +* +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set +* +* The global icons array size is fixed and depends on the number of icons and size: +* +* static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS]; +* +* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +* +* TOOL: rGuiIcons is a visual tool to customize/create raygui icons: github.com/raysan5/rguiicons +* +* RAYGUI LAYOUT: +* raygui currently does not provide an auto-layout mechanism like other libraries, +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it +* +* TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout +* +* CONFIGURATION: +* #define RAYGUI_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #define RAYGUI_STANDALONE +* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined +* internally in the library and input management and drawing functions must be provided by +* the user (check library implementation for further details) +* +* #define RAYGUI_NO_ICONS +* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) +* +* #define RAYGUI_CUSTOM_ICONS +* Includes custom ricons.h header defining a set of custom icons, +* this file can be generated using rGuiIcons tool +* +* #define RAYGUI_DEBUG_RECS_BOUNDS +* Draw control bounds rectangles for debug +* +* #define RAYGUI_DEBUG_TEXT_BOUNDS +* Draw text bounds rectangles for debug +* +* VERSIONS HISTORY: +* 5.0-dev (2025) Current dev version... +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: GuiLoadIconsFromMemory() +* ADDED: Multiple new icons +* REMOVED: GuiSpinner() from controls list, using BUTTON + VALUEBOX properties +* REMOVED: GuiSliderPro(), functionality was redundant +* REVIEWED: Controls using text labels to use LABEL properties +* REVIEWED: Replaced sprintf() by snprintf() for more safety +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: GuiProgressBar(), improved borders computing +* REVIEWED: GuiTextBox(), multiple improvements: autocursor and more +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* +* 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() +* ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() +* ADDED: Multiple new icons, mostly compiler related +* ADDED: New DEFAULT properties: TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE +* ADDED: New enum values: GuiTextAlignment, GuiTextAlignmentVertical, GuiTextWrapMode +* ADDED: Support loading styles with custom font charset from external file +* REDESIGNED: GuiTextBox(), support mouse cursor positioning +* REDESIGNED: GuiDrawText(), support multiline and word-wrap modes (read only) +* REDESIGNED: GuiProgressBar() to be more visual, progress affects border color +* REDESIGNED: Global alpha consideration moved to GuiDrawRectangle() and GuiDrawText() +* REDESIGNED: GuiScrollPanel(), get parameters by reference and return result value +* REDESIGNED: GuiToggleGroup(), get parameters by reference and return result value +* REDESIGNED: GuiComboBox(), get parameters by reference and return result value +* REDESIGNED: GuiCheckBox(), get parameters by reference and return result value +* REDESIGNED: GuiSlider(), get parameters by reference and return result value +* REDESIGNED: GuiSliderBar(), get parameters by reference and return result value +* REDESIGNED: GuiProgressBar(), get parameters by reference and return result value +* REDESIGNED: GuiListView(), get parameters by reference and return result value +* REDESIGNED: GuiColorPicker(), get parameters by reference and return result value +* REDESIGNED: GuiColorPanel(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarAlpha(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarHue(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), added extra parameter +* REDESIGNED: GuiListViewEx(), change parameters order +* REDESIGNED: All controls return result as int value +* REVIEWED: GuiScrollPanel() to avoid smallish scroll-bars +* REVIEWED: All examples and specially controls_test_suite +* RENAMED: gui_file_dialog module to gui_window_file_dialog +* UPDATED: All styles to include ISO-8859-15 charset (as much as possible) +* +* 3.6 (10-May-2023) ADDED: New icon: SAND_TIMER +* ADDED: GuiLoadStyleFromMemory() (binary only) +* REVIEWED: GuiScrollBar() horizontal movement key +* REVIEWED: GuiTextBox() crash on cursor movement +* REVIEWED: GuiTextBox(), additional inputs support +* REVIEWED: GuiLabelButton(), avoid text cut +* REVIEWED: GuiTextInputBox(), password input +* REVIEWED: Local GetCodepointNext(), aligned with raylib +* REDESIGNED: GuiSlider*()/GuiScrollBar() to support out-of-bounds +* +* 3.5 (20-Apr-2023) ADDED: GuiTabBar(), based on GuiToggle() +* ADDED: Helper functions to split text in separate lines +* ADDED: Multiple new icons, useful for code editing tools +* REMOVED: Unneeded icon editing functions +* REMOVED: GuiTextBoxMulti(), very limited and broken +* REMOVED: MeasureTextEx() dependency, logic directly implemented +* REMOVED: DrawTextEx() dependency, logic directly implemented +* REVIEWED: GuiScrollBar(), improve mouse-click behaviour +* REVIEWED: Library header info, more info, better organized +* REDESIGNED: GuiTextBox() to support cursor movement +* REDESIGNED: GuiDrawText() to divide drawing by lines +* +* 3.2 (22-May-2022) RENAMED: Some enum values, for unification, avoiding prefixes +* REMOVED: GuiScrollBar(), only internal +* REDESIGNED: GuiPanel() to support text parameter +* REDESIGNED: GuiScrollPanel() to support text parameter +* REDESIGNED: GuiColorPicker() to support text parameter +* REDESIGNED: GuiColorPanel() to support text parameter +* REDESIGNED: GuiColorBarAlpha() to support text parameter +* REDESIGNED: GuiColorBarHue() to support text parameter +* REDESIGNED: GuiTextInputBox() to support password +* +* 3.1 (12-Jan-2022) REVIEWED: Default style for consistency (aligned with rGuiLayout v2.5 tool) +* REVIEWED: GuiLoadStyle() to support compressed font atlas image data and unload previous textures +* REVIEWED: External icons usage logic +* REVIEWED: GuiLine() for centered alignment when including text +* RENAMED: Multiple controls properties definitions to prepend RAYGUI_ +* RENAMED: RICON_ references to RAYGUI_ICON_ for library consistency +* Projects updated and multiple tweaks +* +* 3.0 (04-Nov-2021) Integrated ricons data to avoid external file +* REDESIGNED: GuiTextBoxMulti() +* REMOVED: GuiImageButton*() +* Multiple minor tweaks and bugs corrected +* +* 2.9 (17-Mar-2021) REMOVED: Tooltip API +* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() +* 2.7 (20-Feb-2020) ADDED: Possible tooltips API +* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() +* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() +* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() +* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties +* ADDED: 8 new custom styles ready to use +* Multiple minor tweaks and bugs corrected +* +* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() +* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed +* Refactor all controls drawing mechanism to use control state +* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls +* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string +* REDESIGNED: Style system (breaking change) +* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts +* REVIEWED: GuiComboBox(), GuiListView()... +* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... +* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout +* 1.5 (21-Jun-2017) Working in an improved styles system +* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) +* 1.3 (12-Jun-2017) Complete redesign of style system +* 1.1 (01-Jun-2017) Complete review of the library +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria +* +* DEPENDENCIES: +* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* +* STANDALONE MODE: +* By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs +* +* The following functions should be redefined for a custom backend: +* +* - Vector2 GetMousePosition(void); +* - float GetMouseWheelMove(void); +* - bool IsMouseButtonDown(int button); +* - bool IsMouseButtonPressed(int button); +* - bool IsMouseButtonReleased(int button); +* - bool IsKeyDown(int key); +* - bool IsKeyPressed(int key); +* - int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +* +* - void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +* - void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +* +* - Font GetFontDefault(void); // -- GuiLoadStyleDefault() +* - Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle() +* - Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +* - void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) +* - char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +* - void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data +* - const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs +* - int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +* - void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list +* - unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, redesign, update and maintenance +* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) +* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) +* Adria Arranz: Testing and implementation of additional controls (2018) +* Jordi Jorba: Testing and implementation of additional controls (2018) +* Albert Martos: Review and testing of the library (2015) +* Ian Eito: Review and testing of the library (2015) +* Kevin Gato: Initial implementation of basic components (2014) +* Daniel Nicolas: Initial implementation of basic components (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYGUI_H +#define RAYGUI_H + +#define RAYGUI_VERSION_MAJOR 4 +#define RAYGUI_VERSION_MINOR 5 +#define RAYGUI_VERSION_PATCH 0 +#define RAYGUI_VERSION "5.0-dev" + +#if !defined(RAYGUI_STANDALONE) + #include "raylib.h" +#endif + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +// Function specifiers definition +#ifndef RAYGUIAPI + #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Simple log system to avoid printf() calls if required +// NOTE: Avoiding those calls, also avoids const strings memory usage +#define RAYGUI_SUPPORT_LOG_INFO +#if defined(RAYGUI_SUPPORT_LOG_INFO) + #define RAYGUI_LOG(...) printf(__VA_ARGS__) +#else + #define RAYGUI_LOG(...) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Some types are required for RAYGUI_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + #ifndef __cplusplus + // Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Rectangle type + typedef struct Rectangle { + float x; + float y; + float width; + float height; + } Rectangle; + + // TODO: Texture2D type is very coupled to raylib, required by Font type + // It should be redesigned to be provided by user + typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Texture; + + // Texture2D, same as Texture + typedef Texture Texture2D; + + // Image, pixel data stored in CPU memory (RAM) + typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Image; + + // GlyphInfo, font characters glyphs info + typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data + } GlyphInfo; + + // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() + // It should be redesigned to be provided by user + typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data + } Font; +#endif + +// Style property +// NOTE: Used when exporting style as code for convenience +typedef struct GuiStyleProp { + unsigned short controlId; // Control identifier + unsigned short propertyId; // Property identifier + int propertyValue; // Property value +} GuiStyleProp; + +/* +// Controls text style -NOT USED- +// NOTE: Text style is defined by control +typedef struct GuiTextStyle { + unsigned int size; + int charSpacing; + int lineSpacing; + int alignmentH; + int alignmentV; + int padding; +} GuiTextStyle; +*/ + +// Gui control state +typedef enum { + STATE_NORMAL = 0, + STATE_FOCUSED, + STATE_PRESSED, + STATE_DISABLED +} GuiState; + +// Gui control text alignment +typedef enum { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT +} GuiTextAlignment; + +// Gui control text alignment vertical +// NOTE: Text vertical position inside the text bounds +typedef enum { + TEXT_ALIGN_TOP = 0, + TEXT_ALIGN_MIDDLE, + TEXT_ALIGN_BOTTOM +} GuiTextAlignmentVertical; + +// Gui control text wrap mode +// NOTE: Useful for multiline text +typedef enum { + TEXT_WRAP_NONE = 0, + TEXT_WRAP_CHAR, + TEXT_WRAP_WORD +} GuiTextWrapMode; + +// Gui controls +typedef enum { + // Default -> populates to all controls when set + DEFAULT = 0, + + // Basic controls + LABEL, // Used also for: LABELBUTTON + BUTTON, + TOGGLE, // Used also for: TOGGLEGROUP + SLIDER, // Used also for: SLIDERBAR, TOGGLESLIDER + PROGRESSBAR, + CHECKBOX, + COMBOBOX, + DROPDOWNBOX, + TEXTBOX, // Used also for: TEXTBOXMULTI + VALUEBOX, + CONTROL11, + LISTVIEW, + COLORPICKER, + SCROLLBAR, + STATUSBAR +} GuiControl; + +// Gui base properties for every control +// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) +typedef enum { + BORDER_COLOR_NORMAL = 0, // Control border color in STATE_NORMAL + BASE_COLOR_NORMAL, // Control base color in STATE_NORMAL + TEXT_COLOR_NORMAL, // Control text color in STATE_NORMAL + BORDER_COLOR_FOCUSED, // Control border color in STATE_FOCUSED + BASE_COLOR_FOCUSED, // Control base color in STATE_FOCUSED + TEXT_COLOR_FOCUSED, // Control text color in STATE_FOCUSED + BORDER_COLOR_PRESSED, // Control border color in STATE_PRESSED + BASE_COLOR_PRESSED, // Control base color in STATE_PRESSED + TEXT_COLOR_PRESSED, // Control text color in STATE_PRESSED + BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED + BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED + TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED + BORDER_WIDTH = 12, // Control border size, 0 for no border + //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls + //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls + //TEXT_LINE_SPACING, // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING = 13, // Control text padding, not considering border + TEXT_ALIGNMENT = 14, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls +} GuiControlProperty; + +// TODO: Which text styling properties should be global or per-control? +// At this moment TEXT_PADDING and TEXT_ALIGNMENT is configured and saved per control while +// TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and +// should be configured by user as needed while defining the UI layout + +// Gui extended properties depend on control +// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) +//---------------------------------------------------------------------------------- +// DEFAULT extended properties +// NOTE: Those properties are common to all controls or global +// WARNING: We only have 8 slots for those properties by default!!! -> New global control: TEXT? +typedef enum { + TEXT_SIZE = 16, // Text size (glyphs max height) + TEXT_SPACING, // Text spacing between glyphs + LINE_COLOR, // Line control color + BACKGROUND_COLOR, // Background color + TEXT_LINE_SPACING, // Text spacing between lines + TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) + TEXT_WRAP_MODE // Text wrap-mode inside text bounds + //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline + //TEXT_DECORATION_THICK // Text decoration line thickness +} GuiDefaultProperty; + +// Other possible text properties: +// TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... + +// Label +//typedef enum { } GuiLabelProperty; + +// Button/Spinner +//typedef enum { } GuiButtonProperty; + +// Toggle/ToggleGroup +typedef enum { + GROUP_PADDING = 16, // ToggleGroup separation between toggles +} GuiToggleProperty; + +// Slider/SliderBar +typedef enum { + SLIDER_WIDTH = 16, // Slider size of internal bar + SLIDER_PADDING // Slider/SliderBar internal bar padding +} GuiSliderProperty; + +// ProgressBar +typedef enum { + PROGRESS_PADDING = 16, // ProgressBar internal padding +} GuiProgressBarProperty; + +// ScrollBar +typedef enum { + ARROWS_SIZE = 16, // ScrollBar arrows size + ARROWS_VISIBLE, // ScrollBar arrows visible + SCROLL_SLIDER_PADDING, // ScrollBar slider internal padding + SCROLL_SLIDER_SIZE, // ScrollBar slider size + SCROLL_PADDING, // ScrollBar scroll padding from arrows + SCROLL_SPEED, // ScrollBar scrolling speed +} GuiScrollBarProperty; + +// CheckBox +typedef enum { + CHECK_PADDING = 16 // CheckBox internal check padding +} GuiCheckBoxProperty; + +// ComboBox +typedef enum { + COMBO_BUTTON_WIDTH = 16, // ComboBox right button width + COMBO_BUTTON_SPACING // ComboBox button separation +} GuiComboBoxProperty; + +// DropdownBox +typedef enum { + ARROW_PADDING = 16, // DropdownBox arrow separation from border and items + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) +} GuiDropdownBoxProperty; + +// TextBox/TextBoxMulti/ValueBox/Spinner +typedef enum { + TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable +} GuiTextBoxProperty; + +// ValueBox/Spinner +typedef enum { + SPINNER_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPINNER_BUTTON_SPACING, // Spinner buttons separation +} GuiValueBoxProperty; + +// Control11 +//typedef enum { } GuiControl11Property; + +// ListView +typedef enum { + LIST_ITEMS_HEIGHT = 16, // ListView items height + LIST_ITEMS_SPACING, // ListView items separation + SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) + SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_NORMAL, // ListView items border enabled in normal state + LIST_ITEMS_BORDER_WIDTH // ListView items border width +} GuiListViewProperty; + +// ColorPicker +typedef enum { + COLOR_SELECTOR_SIZE = 16, + HUEBAR_WIDTH, // ColorPicker right hue bar width + HUEBAR_PADDING, // ColorPicker right hue bar separation from panel + HUEBAR_SELECTOR_HEIGHT, // ColorPicker right hue bar selector height + HUEBAR_SELECTOR_OVERFLOW // ColorPicker right hue bar selector overflow +} GuiColorPickerProperty; + +#define SCROLLBAR_LEFT_SIDE 0 +#define SCROLLBAR_RIGHT_SIDE 1 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Global gui state control functions +RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) +RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) +RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) +RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) +RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) +RAYGUIAPI void GuiSetAlpha(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f +RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) +RAYGUIAPI int GuiGetState(void); // Get gui state (global state) + +// Font set/get functions +RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) +RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) + +// Style set/get functions +RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property +RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property + +// Styles loading functions +RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) +RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style + +// Tooltips management functions +RAYGUIAPI void GuiEnableTooltip(void); // Enable gui tooltips (global state) +RAYGUIAPI void GuiDisableTooltip(void); // Disable gui tooltips (global state) +RAYGUIAPI void GuiSetTooltip(const char *tooltip); // Set tooltip string + +// Icons functionality +RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) +#if !defined(RAYGUI_NO_ICONS) +RAYGUIAPI void GuiSetIconScale(int scale); // Set default icon drawing size +RAYGUIAPI unsigned int *GuiGetIcons(void); // Get raygui icons data pointer +RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load raygui icons file (.rgi) into internal icons data +RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position +#endif + +// Utility functions +RAYGUIAPI int GuiGetTextWidth(const char *text); // Get text width considering gui style and icon size (if required) + +// Controls +//---------------------------------------------------------------------------------------------------------- +// Container/separator controls, useful for controls organization +RAYGUIAPI int GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed +RAYGUIAPI int GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name +RAYGUIAPI int GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text +RAYGUIAPI int GuiPanel(Rectangle bounds, const char *text); // Panel control, useful to group controls +RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *active); // Tab Bar control, returns TAB to be closed or -1 +RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control + +// Basic controls set +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control +RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control +RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control + +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control +RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values +RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text + +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control +RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text +RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control + +// Advance controls set +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control +RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters +RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message +RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret +RAYGUIAPI int GuiColorPicker(Rectangle bounds, const char *text, Color *color); // Color Picker control (multiple color controls) +RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); // Color Panel control +RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control +RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control +RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() +//---------------------------------------------------------------------------------------------------------- + +#if !defined(RAYGUI_NO_ICONS) + +#if !defined(RAYGUI_CUSTOM_ICONS) +//---------------------------------------------------------------------------------- +// Icons enumeration +//---------------------------------------------------------------------------------- +typedef enum { + ICON_NONE = 0, + ICON_FOLDER_FILE_OPEN = 1, + ICON_FILE_SAVE_CLASSIC = 2, + ICON_FOLDER_OPEN = 3, + ICON_FOLDER_SAVE = 4, + ICON_FILE_OPEN = 5, + ICON_FILE_SAVE = 6, + ICON_FILE_EXPORT = 7, + ICON_FILE_ADD = 8, + ICON_FILE_DELETE = 9, + ICON_FILETYPE_TEXT = 10, + ICON_FILETYPE_AUDIO = 11, + ICON_FILETYPE_IMAGE = 12, + ICON_FILETYPE_PLAY = 13, + ICON_FILETYPE_VIDEO = 14, + ICON_FILETYPE_INFO = 15, + ICON_FILE_COPY = 16, + ICON_FILE_CUT = 17, + ICON_FILE_PASTE = 18, + ICON_CURSOR_HAND = 19, + ICON_CURSOR_POINTER = 20, + ICON_CURSOR_CLASSIC = 21, + ICON_PENCIL = 22, + ICON_PENCIL_BIG = 23, + ICON_BRUSH_CLASSIC = 24, + ICON_BRUSH_PAINTER = 25, + ICON_WATER_DROP = 26, + ICON_COLOR_PICKER = 27, + ICON_RUBBER = 28, + ICON_COLOR_BUCKET = 29, + ICON_TEXT_T = 30, + ICON_TEXT_A = 31, + ICON_SCALE = 32, + ICON_RESIZE = 33, + ICON_FILTER_POINT = 34, + ICON_FILTER_BILINEAR = 35, + ICON_CROP = 36, + ICON_CROP_ALPHA = 37, + ICON_SQUARE_TOGGLE = 38, + ICON_SYMMETRY = 39, + ICON_SYMMETRY_HORIZONTAL = 40, + ICON_SYMMETRY_VERTICAL = 41, + ICON_LENS = 42, + ICON_LENS_BIG = 43, + ICON_EYE_ON = 44, + ICON_EYE_OFF = 45, + ICON_FILTER_TOP = 46, + ICON_FILTER = 47, + ICON_TARGET_POINT = 48, + ICON_TARGET_SMALL = 49, + ICON_TARGET_BIG = 50, + ICON_TARGET_MOVE = 51, + ICON_CURSOR_MOVE = 52, + ICON_CURSOR_SCALE = 53, + ICON_CURSOR_SCALE_RIGHT = 54, + ICON_CURSOR_SCALE_LEFT = 55, + ICON_UNDO = 56, + ICON_REDO = 57, + ICON_REREDO = 58, + ICON_MUTATE = 59, + ICON_ROTATE = 60, + ICON_REPEAT = 61, + ICON_SHUFFLE = 62, + ICON_EMPTYBOX = 63, + ICON_TARGET = 64, + ICON_TARGET_SMALL_FILL = 65, + ICON_TARGET_BIG_FILL = 66, + ICON_TARGET_MOVE_FILL = 67, + ICON_CURSOR_MOVE_FILL = 68, + ICON_CURSOR_SCALE_FILL = 69, + ICON_CURSOR_SCALE_RIGHT_FILL = 70, + ICON_CURSOR_SCALE_LEFT_FILL = 71, + ICON_UNDO_FILL = 72, + ICON_REDO_FILL = 73, + ICON_REREDO_FILL = 74, + ICON_MUTATE_FILL = 75, + ICON_ROTATE_FILL = 76, + ICON_REPEAT_FILL = 77, + ICON_SHUFFLE_FILL = 78, + ICON_EMPTYBOX_SMALL = 79, + ICON_BOX = 80, + ICON_BOX_TOP = 81, + ICON_BOX_TOP_RIGHT = 82, + ICON_BOX_RIGHT = 83, + ICON_BOX_BOTTOM_RIGHT = 84, + ICON_BOX_BOTTOM = 85, + ICON_BOX_BOTTOM_LEFT = 86, + ICON_BOX_LEFT = 87, + ICON_BOX_TOP_LEFT = 88, + ICON_BOX_CENTER = 89, + ICON_BOX_CIRCLE_MASK = 90, + ICON_POT = 91, + ICON_ALPHA_MULTIPLY = 92, + ICON_ALPHA_CLEAR = 93, + ICON_DITHERING = 94, + ICON_MIPMAPS = 95, + ICON_BOX_GRID = 96, + ICON_GRID = 97, + ICON_BOX_CORNERS_SMALL = 98, + ICON_BOX_CORNERS_BIG = 99, + ICON_FOUR_BOXES = 100, + ICON_GRID_FILL = 101, + ICON_BOX_MULTISIZE = 102, + ICON_ZOOM_SMALL = 103, + ICON_ZOOM_MEDIUM = 104, + ICON_ZOOM_BIG = 105, + ICON_ZOOM_ALL = 106, + ICON_ZOOM_CENTER = 107, + ICON_BOX_DOTS_SMALL = 108, + ICON_BOX_DOTS_BIG = 109, + ICON_BOX_CONCENTRIC = 110, + ICON_BOX_GRID_BIG = 111, + ICON_OK_TICK = 112, + ICON_CROSS = 113, + ICON_ARROW_LEFT = 114, + ICON_ARROW_RIGHT = 115, + ICON_ARROW_DOWN = 116, + ICON_ARROW_UP = 117, + ICON_ARROW_LEFT_FILL = 118, + ICON_ARROW_RIGHT_FILL = 119, + ICON_ARROW_DOWN_FILL = 120, + ICON_ARROW_UP_FILL = 121, + ICON_AUDIO = 122, + ICON_FX = 123, + ICON_WAVE = 124, + ICON_WAVE_SINUS = 125, + ICON_WAVE_SQUARE = 126, + ICON_WAVE_TRIANGULAR = 127, + ICON_CROSS_SMALL = 128, + ICON_PLAYER_PREVIOUS = 129, + ICON_PLAYER_PLAY_BACK = 130, + ICON_PLAYER_PLAY = 131, + ICON_PLAYER_PAUSE = 132, + ICON_PLAYER_STOP = 133, + ICON_PLAYER_NEXT = 134, + ICON_PLAYER_RECORD = 135, + ICON_MAGNET = 136, + ICON_LOCK_CLOSE = 137, + ICON_LOCK_OPEN = 138, + ICON_CLOCK = 139, + ICON_TOOLS = 140, + ICON_GEAR = 141, + ICON_GEAR_BIG = 142, + ICON_BIN = 143, + ICON_HAND_POINTER = 144, + ICON_LASER = 145, + ICON_COIN = 146, + ICON_EXPLOSION = 147, + ICON_1UP = 148, + ICON_PLAYER = 149, + ICON_PLAYER_JUMP = 150, + ICON_KEY = 151, + ICON_DEMON = 152, + ICON_TEXT_POPUP = 153, + ICON_GEAR_EX = 154, + ICON_CRACK = 155, + ICON_CRACK_POINTS = 156, + ICON_STAR = 157, + ICON_DOOR = 158, + ICON_EXIT = 159, + ICON_MODE_2D = 160, + ICON_MODE_3D = 161, + ICON_CUBE = 162, + ICON_CUBE_FACE_TOP = 163, + ICON_CUBE_FACE_LEFT = 164, + ICON_CUBE_FACE_FRONT = 165, + ICON_CUBE_FACE_BOTTOM = 166, + ICON_CUBE_FACE_RIGHT = 167, + ICON_CUBE_FACE_BACK = 168, + ICON_CAMERA = 169, + ICON_SPECIAL = 170, + ICON_LINK_NET = 171, + ICON_LINK_BOXES = 172, + ICON_LINK_MULTI = 173, + ICON_LINK = 174, + ICON_LINK_BROKE = 175, + ICON_TEXT_NOTES = 176, + ICON_NOTEBOOK = 177, + ICON_SUITCASE = 178, + ICON_SUITCASE_ZIP = 179, + ICON_MAILBOX = 180, + ICON_MONITOR = 181, + ICON_PRINTER = 182, + ICON_PHOTO_CAMERA = 183, + ICON_PHOTO_CAMERA_FLASH = 184, + ICON_HOUSE = 185, + ICON_HEART = 186, + ICON_CORNER = 187, + ICON_VERTICAL_BARS = 188, + ICON_VERTICAL_BARS_FILL = 189, + ICON_LIFE_BARS = 190, + ICON_INFO = 191, + ICON_CROSSLINE = 192, + ICON_HELP = 193, + ICON_FILETYPE_ALPHA = 194, + ICON_FILETYPE_HOME = 195, + ICON_LAYERS_VISIBLE = 196, + ICON_LAYERS = 197, + ICON_WINDOW = 198, + ICON_HIDPI = 199, + ICON_FILETYPE_BINARY = 200, + ICON_HEX = 201, + ICON_SHIELD = 202, + ICON_FILE_NEW = 203, + ICON_FOLDER_ADD = 204, + ICON_ALARM = 205, + ICON_CPU = 206, + ICON_ROM = 207, + ICON_STEP_OVER = 208, + ICON_STEP_INTO = 209, + ICON_STEP_OUT = 210, + ICON_RESTART = 211, + ICON_BREAKPOINT_ON = 212, + ICON_BREAKPOINT_OFF = 213, + ICON_BURGER_MENU = 214, + ICON_CASE_SENSITIVE = 215, + ICON_REG_EXP = 216, + ICON_FOLDER = 217, + ICON_FILE = 218, + ICON_SAND_TIMER = 219, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, + ICON_HOT = 228, + ICON_LABEL = 229, + ICON_NAME_ID = 230, + ICON_SLICING = 231, + ICON_MANUAL_CONTROL = 232, + ICON_COLLISION = 233, + ICON_234 = 234, + ICON_235 = 235, + ICON_236 = 236, + ICON_237 = 237, + ICON_238 = 238, + ICON_239 = 239, + ICON_240 = 240, + ICON_241 = 241, + ICON_242 = 242, + ICON_243 = 243, + ICON_244 = 244, + ICON_245 = 245, + ICON_246 = 246, + ICON_247 = 247, + ICON_248 = 248, + ICON_249 = 249, + ICON_250 = 250, + ICON_251 = 251, + ICON_252 = 252, + ICON_253 = 253, + ICON_254 = 254, + ICON_255 = 255, +} GuiIconName; +#endif + +#endif + +#if defined(__cplusplus) +} // Prevents name mangling of functions +#endif + +#endif // RAYGUI_H + +/*********************************************************************************** +* +* RAYGUI IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RAYGUI_IMPLEMENTATION) + +#include // required for: isspace() [GuiTextBox()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), snprintf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() +#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] +#include // Required for: roundf() [GuiColorPicker()] + +// Allow custom memory allocators +#if defined(RAYGUI_MALLOC) || defined(RAYGUI_CALLOC) || defined(RAYGUI_FREE) + #if !defined(RAYGUI_MALLOC) || !defined(RAYGUI_CALLOC) || !defined(RAYGUI_FREE) + #error "RAYGUI: if RAYGUI_MALLOC, RAYGUI_CALLOC, or RAYGUI_FREE is customized, all three must be customized" + #endif +#else + #include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] + + #define RAYGUI_MALLOC(sz) malloc(sz) + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) + #define RAYGUI_FREE(p) free(p) +#endif + +#ifdef __cplusplus + #define RAYGUI_CLITERAL(name) name +#else + #define RAYGUI_CLITERAL(name) (name) +#endif + +// Check if two rectangles are equal, used to validate a slider bounds as an id +#ifndef CHECK_BOUNDS_ID + #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) +#endif + +#if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) + +// Embedded icons, no external file provided +#define RAYGUI_ICON_SIZE 16 // Size of icons in pixels (squared) +#define RAYGUI_ICON_MAX_ICONS 256 // Maximum number of icons +#define RAYGUI_ICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id + +// Icons data is defined by bit array (every bit represents one pixel) +// Those arrays are stored as unsigned int data arrays, so, +// every array element defines 32 pixels (bits) of information +// One icon is defined by 8 int, (8 int * 32 bit = 256 bit = 16*16 pixels) +// NOTE: Number of elemens depend on RAYGUI_ICON_SIZE (by default 16x16 pixels) +#define RAYGUI_ICON_DATA_ELEMENTS (RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32) + +//---------------------------------------------------------------------------------- +// Icons data for all gui possible icons (allocated on data segment by default) +// +// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, +// every 16x16 icon requires 8 integers (16*16/32) to be stored +// +// NOTE 2: A different icon set could be loaded over this array using GuiLoadIcons(), +// but loaded icons set must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS +// +// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +//---------------------------------------------------------------------------------- +static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_NONE + 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // ICON_FOLDER_FILE_OPEN + 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // ICON_FILE_SAVE_CLASSIC + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // ICON_FOLDER_OPEN + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // ICON_FOLDER_SAVE + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // ICON_FILE_OPEN + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // ICON_FILE_SAVE + 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // ICON_FILE_EXPORT + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // ICON_FILE_ADD + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // ICON_FILE_DELETE + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_FILETYPE_TEXT + 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // ICON_FILETYPE_AUDIO + 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // ICON_FILETYPE_IMAGE + 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // ICON_FILETYPE_PLAY + 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // ICON_FILETYPE_VIDEO + 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // ICON_FILETYPE_INFO + 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // ICON_FILE_COPY + 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // ICON_FILE_CUT + 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // ICON_FILE_PASTE + 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_CURSOR_HAND + 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // ICON_CURSOR_POINTER + 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // ICON_CURSOR_CLASSIC + 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // ICON_PENCIL + 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // ICON_PENCIL_BIG + 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // ICON_BRUSH_CLASSIC + 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // ICON_BRUSH_PAINTER + 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // ICON_WATER_DROP + 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // ICON_COLOR_PICKER + 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // ICON_RUBBER + 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // ICON_COLOR_BUCKET + 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // ICON_TEXT_T + 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // ICON_TEXT_A + 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // ICON_SCALE + 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // ICON_RESIZE + 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_POINT + 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_BILINEAR + 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // ICON_CROP + 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // ICON_CROP_ALPHA + 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // ICON_SQUARE_TOGGLE + 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // ICON_SYMMETRY + 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // ICON_SYMMETRY_HORIZONTAL + 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // ICON_SYMMETRY_VERTICAL + 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // ICON_LENS + 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // ICON_LENS_BIG + 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // ICON_EYE_ON + 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // ICON_EYE_OFF + 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // ICON_FILTER_TOP + 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // ICON_FILTER + 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_POINT + 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL + 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG + 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // ICON_TARGET_MOVE + 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // ICON_CURSOR_MOVE + 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // ICON_CURSOR_SCALE + 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // ICON_CURSOR_SCALE_RIGHT + 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // ICON_CURSOR_SCALE_LEFT + 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO + 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // ICON_REREDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // ICON_MUTATE + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // ICON_ROTATE + 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // ICON_REPEAT + 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // ICON_SHUFFLE + 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // ICON_EMPTYBOX + 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // ICON_TARGET + 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL_FILL + 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // ICON_TARGET_MOVE_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // ICON_CURSOR_MOVE_FILL + 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // ICON_CURSOR_SCALE_FILL + 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // ICON_CURSOR_SCALE_RIGHT_FILL + 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // ICON_CURSOR_SCALE_LEFT_FILL + 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO_FILL + 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // ICON_REREDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // ICON_MUTATE_FILL + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // ICON_ROTATE_FILL + 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // ICON_REPEAT_FILL + 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // ICON_SHUFFLE_FILL + 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // ICON_EMPTYBOX_SMALL + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX + 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP + 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // ICON_BOX_BOTTOM_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // ICON_BOX_BOTTOM + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // ICON_BOX_BOTTOM_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_LEFT + 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_CENTER + 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // ICON_BOX_CIRCLE_MASK + 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // ICON_POT + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // ICON_ALPHA_MULTIPLY + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // ICON_ALPHA_CLEAR + 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // ICON_DITHERING + 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // ICON_MIPMAPS + 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // ICON_BOX_GRID + 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // ICON_GRID + 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // ICON_BOX_CORNERS_SMALL + 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // ICON_BOX_CORNERS_BIG + 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // ICON_FOUR_BOXES + 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // ICON_GRID_FILL + 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // ICON_BOX_MULTISIZE + 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_SMALL + 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_MEDIUM + 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // ICON_ZOOM_BIG + 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // ICON_ZOOM_ALL + 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // ICON_ZOOM_CENTER + 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // ICON_BOX_DOTS_SMALL + 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // ICON_BOX_DOTS_BIG + 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // ICON_BOX_CONCENTRIC + 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // ICON_BOX_GRID_BIG + 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // ICON_OK_TICK + 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // ICON_CROSS + 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // ICON_ARROW_LEFT + 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // ICON_ARROW_RIGHT + 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // ICON_ARROW_DOWN + 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP + 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // ICON_ARROW_LEFT_FILL + 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // ICON_ARROW_RIGHT_FILL + 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // ICON_ARROW_DOWN_FILL + 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP_FILL + 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // ICON_AUDIO + 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // ICON_FX + 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // ICON_WAVE + 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // ICON_WAVE_SINUS + 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // ICON_WAVE_SQUARE + 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // ICON_WAVE_TRIANGULAR + 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // ICON_CROSS_SMALL + 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // ICON_PLAYER_PREVIOUS + 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // ICON_PLAYER_PLAY_BACK + 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // ICON_PLAYER_PLAY + 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // ICON_PLAYER_PAUSE + 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // ICON_PLAYER_STOP + 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // ICON_PLAYER_NEXT + 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // ICON_PLAYER_RECORD + 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // ICON_MAGNET + 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_CLOSE + 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_OPEN + 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // ICON_CLOCK + 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // ICON_TOOLS + 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // ICON_GEAR + 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // ICON_GEAR_BIG + 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // ICON_BIN + 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // ICON_HAND_POINTER + 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // ICON_LASER + 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // ICON_COIN + 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // ICON_EXPLOSION + 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // ICON_1UP + 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // ICON_PLAYER + 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // ICON_PLAYER_JUMP + 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // ICON_KEY + 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // ICON_DEMON + 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // ICON_TEXT_POPUP + 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // ICON_GEAR_EX + 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK + 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK_POINTS + 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // ICON_STAR + 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // ICON_DOOR + 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // ICON_EXIT + 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // ICON_MODE_2D + 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // ICON_MODE_3D + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE + 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_TOP + 0x7fe00000, 0x50386030, 0x47c2483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // ICON_CUBE_FACE_LEFT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // ICON_CUBE_FACE_FRONT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3bf27be2, 0x0bfe1bfa, 0x000007fe, // ICON_CUBE_FACE_BOTTOM + 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // ICON_CUBE_FACE_RIGHT + 0x7fe00000, 0x6fe85ff0, 0x781e77e4, 0x7be27be2, 0x7be27be2, 0x24127be2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_BACK + 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // ICON_CAMERA + 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // ICON_SPECIAL + 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // ICON_LINK_NET + 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // ICON_LINK_BOXES + 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // ICON_LINK_MULTI + 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // ICON_LINK + 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // ICON_LINK_BROKE + 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_TEXT_NOTES + 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // ICON_NOTEBOOK + 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // ICON_SUITCASE + 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // ICON_SUITCASE_ZIP + 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // ICON_MAILBOX + 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // ICON_MONITOR + 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // ICON_PRINTER + 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA + 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA_FLASH + 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // ICON_HOUSE + 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // ICON_HEART + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // ICON_CORNER + 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // ICON_VERTICAL_BARS + 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // ICON_VERTICAL_BARS_FILL + 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // ICON_LIFE_BARS + 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // ICON_INFO + 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // ICON_CROSSLINE + 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // ICON_HELP + 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // ICON_FILETYPE_ALPHA + 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // ICON_FILETYPE_HOME + 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS_VISIBLE + 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS + 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_WINDOW + 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // ICON_HIDPI + 0x3ff00000, 0x201c2010, 0x2a842e84, 0x2e842a84, 0x2ba42004, 0x2aa42aa4, 0x20042ba4, 0x00003ffc, // ICON_FILETYPE_BINARY + 0x00000000, 0x00000000, 0x00120012, 0x4a5e4bd2, 0x485233d2, 0x00004bd2, 0x00000000, 0x00000000, // ICON_HEX + 0x01800000, 0x381c0660, 0x23c42004, 0x23c42044, 0x13c82204, 0x08101008, 0x02400420, 0x00000180, // ICON_SHIELD + 0x007e0000, 0x20023fc2, 0x40227fe2, 0x400a403a, 0x400a400a, 0x400a400a, 0x4008400e, 0x00007ff8, // ICON_FILE_NEW + 0x00000000, 0x0042007e, 0x40027fc2, 0x44024002, 0x5f024402, 0x44024402, 0x7ffe4002, 0x00000000, // ICON_FOLDER_ADD + 0x44220000, 0x12482244, 0xf3cf0000, 0x14280420, 0x48122424, 0x08100810, 0x1ff81008, 0x03c00420, // ICON_ALARM + 0x0aa00000, 0x1ff80aa0, 0x1068700e, 0x1008706e, 0x1008700e, 0x1008700e, 0x0aa01ff8, 0x00000aa0, // ICON_CPU + 0x07e00000, 0x04201db8, 0x04a01c38, 0x04a01d38, 0x04a01d38, 0x04a01d38, 0x04201d38, 0x000007e0, // ICON_ROM + 0x00000000, 0x03c00000, 0x3c382ff0, 0x3c04380c, 0x01800000, 0x03c003c0, 0x00000180, 0x00000000, // ICON_STEP_OVER + 0x01800000, 0x01800180, 0x01800180, 0x03c007e0, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_INTO + 0x01800000, 0x07e003c0, 0x01800180, 0x01800180, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_OUT + 0x00000000, 0x0ff003c0, 0x181c1c34, 0x303c301c, 0x30003000, 0x1c301800, 0x03c00ff0, 0x00000000, // ICON_RESTART + 0x00000000, 0x00000000, 0x07e003c0, 0x0ff00ff0, 0x0ff00ff0, 0x03c007e0, 0x00000000, 0x00000000, // ICON_BREAKPOINT_ON + 0x00000000, 0x00000000, 0x042003c0, 0x08100810, 0x08100810, 0x03c00420, 0x00000000, 0x00000000, // ICON_BREAKPOINT_OFF + 0x00000000, 0x00000000, 0x1ff81ff8, 0x1ff80000, 0x00001ff8, 0x1ff81ff8, 0x00000000, 0x00000000, // ICON_BURGER_MENU + 0x00000000, 0x00000000, 0x00880070, 0x0c880088, 0x1e8810f8, 0x3e881288, 0x00000000, 0x00000000, // ICON_CASE_SENSITIVE + 0x00000000, 0x02000000, 0x07000a80, 0x07001fc0, 0x02000a80, 0x00300030, 0x00000000, 0x00000000, // ICON_REG_EXP + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE + 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS + 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT + 0x00000000, 0x20803f00, 0x2a202e40, 0x20082e10, 0x08021004, 0x02040402, 0x00900108, 0x00000060, // ICON_LABEL + 0x00000000, 0x042007e0, 0x47e27c3e, 0x4ffa4002, 0x47fa4002, 0x4ffa4002, 0x7ffe4002, 0x00000000, // ICON_NAME_ID + 0x7fe00000, 0x402e4020, 0x43ce5e0a, 0x40504078, 0x438e4078, 0x402e5e0a, 0x7fe04020, 0x00000000, // ICON_SLICING + 0x00000000, 0x40027ffe, 0x47c24002, 0x55425d42, 0x55725542, 0x50125552, 0x10105016, 0x00001ff0, // ICON_MANUAL_CONTROL + 0x7ffe0000, 0x43c24002, 0x48124422, 0x500a500a, 0x500a500a, 0x44224812, 0x400243c2, 0x00007ffe, // ICON_COLLISION + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_253 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_254 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 +}; + +// NOTE: A pointer to current icons array should be defined +static unsigned int *guiIconsPtr = guiIcons; + +#endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS + +#ifndef RAYGUI_ICON_SIZE + #define RAYGUI_ICON_SIZE 0 +#endif + +// WARNING: Those values define the total size of the style data array, +// if changed, previous saved styles could become incompatible +#define RAYGUI_MAX_CONTROLS 16 // Maximum number of controls +#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of base properties +#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties + +//---------------------------------------------------------------------------------- +// Module Types and Structures Definition +//---------------------------------------------------------------------------------- +// Gui control property style color element +typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GuiState guiState = STATE_NORMAL; // Gui global state, if !STATE_NORMAL, forces defined state + +static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) +static bool guiLocked = false; // Gui lock state (no inputs processed) +static float guiAlpha = 1.0f; // Gui controls transparency + +static unsigned int guiIconScale = 1; // Gui icon default scale (if icons enabled) + +static bool guiTooltip = false; // Tooltip enabled/disabled +static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) + +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier + +static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() +//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking +static int autoCursorCounter = 0; // Frame counter for automatic repeated cursor movement on key-down (cooldown and delay) + +//---------------------------------------------------------------------------------- +// Style data array for all gui style properties (allocated on data segment by default) +// +// NOTE 1: First set of BASE properties are generic to all controls but could be individually +// overwritten per control, first set of EXTENDED properties are generic to all controls and +// can not be overwritten individually but custom EXTENDED properties can be used by control +// +// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), +// but default gui style could always be recovered with GuiLoadStyleDefault() +// +// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +//---------------------------------------------------------------------------------- +static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; + +static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization + +//---------------------------------------------------------------------------------- +// Standalone Mode Functions Declaration +// +// NOTE: raygui depend on some raylib input and drawing functions +// To use raygui as standalone library, below functions must be defined by the user +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + +#define KEY_RIGHT 262 +#define KEY_LEFT 263 +#define KEY_DOWN 264 +#define KEY_UP 265 +#define KEY_BACKSPACE 259 +#define KEY_ENTER 257 + +#define MOUSE_LEFT_BUTTON 0 + +// Input required functions +//------------------------------------------------------------------------------- +static Vector2 GetMousePosition(void); +static float GetMouseWheelMove(void); +static bool IsMouseButtonDown(int button); +static bool IsMouseButtonPressed(int button); +static bool IsMouseButtonReleased(int button); + +static bool IsKeyDown(int key); +static bool IsKeyPressed(int key); +static int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +//------------------------------------------------------------------------------- + +// Drawing required functions +//------------------------------------------------------------------------------- +static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +//------------------------------------------------------------------------------- + +// Text required functions +//------------------------------------------------------------------------------- +static Font GetFontDefault(void); // -- GuiLoadStyleDefault() +static Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle(), load font + +static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) + +static char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +static void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data + +static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs + +static int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +static void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list + +static unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +//------------------------------------------------------------------------------- + +// raylib functions already implemented in raygui +//------------------------------------------------------------------------------- +static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value +static int ColorToInt(Color color); // Returns hexadecimal value for a Color +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text + +static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text +static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) + +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient +//------------------------------------------------------------------------------- + +#endif // RAYGUI_STANDALONE + +//---------------------------------------------------------------------------------- +// Module Internal Functions Declaration +//---------------------------------------------------------------------------------- +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) + +static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds +static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor + +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint); // Gui draw text using default font +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style + +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow); // Split controls text into multiple strings +static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB +static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV + +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() +static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position + +static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor + +//---------------------------------------------------------------------------------- +// Gui Setup Functions Definition +//---------------------------------------------------------------------------------- +// Enable gui global state +// NOTE: We check for STATE_DISABLED to avoid messing custom global state setups +void GuiEnable(void) { if (guiState == STATE_DISABLED) guiState = STATE_NORMAL; } + +// Disable gui global state +// NOTE: We check for STATE_NORMAL to avoid messing custom global state setups +void GuiDisable(void) { if (guiState == STATE_NORMAL) guiState = STATE_DISABLED; } + +// Lock gui global state +void GuiLock(void) { guiLocked = true; } + +// Unlock gui global state +void GuiUnlock(void) { guiLocked = false; } + +// Check if gui is locked (global state) +bool GuiIsLocked(void) { return guiLocked; } + +// Set gui controls alpha global state +void GuiSetAlpha(float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + guiAlpha = alpha; +} + +// Set gui state (global state) +void GuiSetState(int state) { guiState = (GuiState)state; } + +// Get gui state (global state) +int GuiGetState(void) { return guiState; } + +// Set custom gui font +// NOTE: Font loading/unloading is external to raygui +void GuiSetFont(Font font) +{ + if (font.texture.id > 0) + { + // NOTE: If we try to setup a font but default style has not been + // lazily loaded before, it will be overwritten, so we need to force + // default style loading first + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + guiFont = font; + } +} + +// Get custom gui font +Font GuiGetFont(void) +{ + return guiFont; +} + +// Set control style property value +void GuiSetStyle(int control, int property, int value) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + + // Default properties are propagated to all controls + if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) + { + for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + } +} + +// Get control style property value +int GuiGetStyle(int control, int property) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; +} + +//---------------------------------------------------------------------------------- +// Gui Controls Functions Definition +//---------------------------------------------------------------------------------- + +// Window Box control +int GuiWindowBox(Rectangle bounds, const char *title) +{ + // Window title bar height (including borders) + // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() + #if !defined(RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT) + #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 + #endif + + #if !defined(RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT) + #define RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT 18 + #endif + + int result = 0; + //GuiState state = guiState; + + int statusBarHeight = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; + + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; + if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + + const float vPadding = statusBarHeight/2.0f - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT/2.0f; + Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT - vPadding, + statusBar.y + vPadding, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT, RAYGUI_WINDOWBOX_CLOSEBUTTON_HEIGHT }; + + // Update control + //-------------------------------------------------------------------- + // NOTE: Logic is directly managed by button + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiStatusBar(statusBar, title); // Draw window header as status bar + GuiPanel(windowPanel, NULL); // Draw window base + + // Draw window close button + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + result = GuiButton(closeButtonRec, "x"); +#else + result = GuiButton(closeButtonRec, GuiIconText(ICON_CROSS_SMALL, NULL)); +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + //-------------------------------------------------------------------- + + return result; // Window close button clicked: result = 1 +} + +// Group Box control with text name +int GuiGroupBox(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_GROUPBOX_LINE_THICK) + #define RAYGUI_GROUPBOX_LINE_THICK 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + + GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); + //-------------------------------------------------------------------- + + return result; +} + +// Line control +int GuiLine(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_LINE_MARGIN_TEXT) + #define RAYGUI_LINE_MARGIN_TEXT 12 + #endif + #if !defined(RAYGUI_LINE_TEXT_PADDING) + #define RAYGUI_LINE_TEXT_PADDING 4 + #endif + + int result = 0; + GuiState state = guiState; + + Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); + + // Draw control + //-------------------------------------------------------------------- + if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); + else + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = bounds.height; + textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; + textBounds.y = bounds.y; + + // Draw line with embedded text label: "--- text --------------" + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + GuiDrawText(text, textBounds, TEXT_ALIGN_LEFT, color); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + 12 + textBounds.width + 4, bounds.y + bounds.height/2, bounds.width - textBounds.width - RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + } + //-------------------------------------------------------------------- + + return result; +} + +// Panel control +int GuiPanel(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_PANEL_BORDER_WIDTH) + #define RAYGUI_PANEL_BORDER_WIDTH 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if ((text != NULL) && (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f)) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + } + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BASE_COLOR_DISABLED : (int)BACKGROUND_COLOR))); + //-------------------------------------------------------------------- + + return result; +} + +// Tab Bar control +// NOTE: Using GuiToggle() for the TABS +int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) +{ + #define RAYGUI_TABBAR_ITEM_WIDTH 160 + + int result = -1; + //GuiState state = guiState; + + Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; + + if (*active < 0) *active = 0; + else if (*active > count - 1) *active = count - 1; + + int offsetX = 0; // Required in case tabs go out of screen + offsetX = (*active + 2)*RAYGUI_TABBAR_ITEM_WIDTH - GetScreenWidth(); + if (offsetX < 0) offsetX = 0; + + bool toggle = false; // Required for individual toggles + + // Draw control + //-------------------------------------------------------------------- + for (int i = 0; i < count; i++) + { + tabBounds.x = bounds.x + (RAYGUI_TABBAR_ITEM_WIDTH + 4)*i - offsetX; + + if (tabBounds.x < GetScreenWidth()) + { + // Draw tabs as toggle controls + int textAlignment = GuiGetStyle(TOGGLE, TEXT_ALIGNMENT); + int textPadding = GuiGetStyle(TOGGLE, TEXT_PADDING); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(TOGGLE, TEXT_PADDING, 8); + + if (i == (*active)) + { + toggle = true; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + } + else + { + toggle = false; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + if (toggle) *active = i; + } + + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); + + // Draw tab close button + // NOTE: Only draw close button for current tab: if (CheckCollisionPointRec(mousePosition, tabBounds)) + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, "x")) result = i; +#else + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, GuiIconText(ICON_CROSS_SMALL, NULL))) result = i; +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + } + } + + // Draw tab-bar bottom line + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, 1 }, 0, BLANK, GetColor(GuiGetStyle(TOGGLE, BORDER_COLOR_NORMAL))); + //-------------------------------------------------------------------- + + return result; // Return as result the current TAB closing requested +} + +// Scroll Panel control +int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view) +{ + #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 + #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 + + int result = 0; + GuiState state = guiState; + + Rectangle temp = { 0 }; + if (view == NULL) view = &temp; + + Vector2 scrollPos = { 0.0f, 0.0f }; + if (scroll != NULL) scrollPos = *scroll; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 1; + } + + bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + + // Recheck to account for the other scrollbar being visible + if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + + int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + Rectangle horizontalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)horizontalScrollBarWidth + }; + Rectangle verticalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), + (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)verticalScrollBarWidth, + (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Make sure scroll bars have a minimum width/height + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; + + // Calculate view area (area without the scrollbars) + *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? + RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : + RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; + + // Clip view area to the actual content size + if (view->width > content.width) view->width = content.width; + if (view->height > content.height) view->height = content.height; + + float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); + float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + float verticalMin = hasVerticalScrollBar? 0.0f : -1.0f; + float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + +#if defined(SUPPORT_SCROLLBAR_KEY_INPUT) + if (hasHorizontalScrollBar) + { + if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } + + if (hasVerticalScrollBar) + { + if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } +#endif + float wheelMove = GetMouseWheelMove(); + + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + + // Horizontal and vertical scrolling with mouse wheel + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll + } + } + + // Normalize scroll values + if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; + if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; + if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; + if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Save size of the scrollbar slider + const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + + // Draw horizontal scrollbar if visible + if (hasHorizontalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content width and the widget width + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); + scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); + } + else scrollPos.x = 0.0f; + + // Draw vertical scrollbar if visible + if (hasVerticalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content height and the widget height + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); + scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); + } + else scrollPos.y = 0.0f; + + // Draw detail corner rectangle if both scroll bars are visible + if (hasHorizontalScrollBar && hasVerticalScrollBar) + { + Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; + GuiDrawRectangle(corner, 0, BLANK, GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3)))); + } + + // Draw scrollbar lines depending on current state + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + + // Set scrollbar slider size back to the way it was before + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); + //-------------------------------------------------------------------- + + if (scroll != NULL) *scroll = scrollPos; + + return result; +} + +// Label control +int GuiLabel(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + //... + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Button control, returns true when clicked +int GuiButton(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), GetColor(GuiGetStyle(BUTTON, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3)))); + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //------------------------------------------------------------------ + + return result; // Button pressed: result = 1 +} + +// Label button control +int GuiLabelButton(Rectangle bounds, const char *text) +{ + GuiState state = guiState; + bool pressed = false; + + // NOTE: We force bounds.width to be all text + float textWidth = (float)GuiGetTextWidth(text); + if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return pressed; +} + +// Toggle Button control +int GuiToggle(Rectangle bounds, const char *text, bool *active) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (active == NULL) active = &temp; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check toggle button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_NORMAL; + *active = !(*active); + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_NORMAL) + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, ((*active)? BORDER_COLOR_PRESSED : (BORDER + state*3)))), GetColor(GuiGetStyle(TOGGLE, ((*active)? BASE_COLOR_PRESSED : (BASE + state*3))))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, ((*active)? TEXT_COLOR_PRESSED : (TEXT + state*3))))); + } + else + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), GetColor(GuiGetStyle(TOGGLE, BASE + state*3))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, TEXT + state*3))); + } + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; +} + +// Toggle Group control +int GuiToggleGroup(Rectangle bounds, const char *text, int *active) +{ + #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) + #define RAYGUI_TOGGLEGROUP_MAX_ITEMS 32 + #endif + + int result = 0; + float initBoundsX = bounds.x; + + int temp = 0; + if (active == NULL) active = &temp; + + bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int rows[RAYGUI_TOGGLEGROUP_MAX_ITEMS] = { 0 }; + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, rows); + + int prevRow = rows[0]; + + for (int i = 0; i < itemCount; i++) + { + if (prevRow != rows[i]) + { + bounds.x = initBoundsX; + bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); + prevRow = rows[i]; + } + + if (i == (*active)) + { + toggle = true; + GuiToggle(bounds, items[i], &toggle); + } + else + { + toggle = false; + GuiToggle(bounds, items[i], &toggle); + if (toggle) *active = i; + } + + bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); + } + + return result; +} + +// Toggle Slider control extended +int GuiToggleSlider(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + //bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle slider = { + 0, // Calculated later depending on the active toggle + bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + (bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - (itemCount + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING))/itemCount, + bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + (*active)++; + result = 1; + } + else state = STATE_FOCUSED; + } + + if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; + } + + if (*active >= itemCount) *active = 0; + slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + (*active + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING) + (*active)*slider.width; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + (state*3))), + GetColor(GuiGetStyle(TOGGLE, BASE_COLOR_NORMAL))); + + // Draw internal slider + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + + // Draw text in slider + if (text != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = slider.x + slider.width/2 - textBounds.width/2; + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(items[*active], textBounds, GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + (state*3))), guiAlpha)); + } + //-------------------------------------------------------------------- + + return result; +} + +// Check Box control, returns 1 when state changed +int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (checked == NULL) checked = &temp; + + Rectangle textBounds = { 0 }; + + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + Rectangle totalBounds = { + (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, + bounds.y, + bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), + bounds.height, + }; + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, totalBounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + *checked = !(*checked); + result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), BLANK); + + if (*checked) + { + Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), + bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; + GuiDrawRectangle(check, 0, BLANK, GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3))); + } + + GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Combo Box control +int GuiComboBox(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING)); + + Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING), + (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + if (*active < 0) *active = 0; + else if (*active > (itemCount - 1)) *active = itemCount - 1; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + *active += 1; + if (*active >= itemCount) *active = 0; // Cyclic combobox + } + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw combo box main + GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3)))); + GuiDrawText(items[*active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3)))); + + // Draw selector using a custom button + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiButton(selector, TextFormat("%i/%i", *active + 1, itemCount)); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + //-------------------------------------------------------------------- + + return result; +} + +// Dropdown Box control +// NOTE: Returns mouse click +int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + int itemSelected = *active; + int itemFocused = -1; + + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle boundsOpen = bounds; + boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); + + Rectangle itemBounds = bounds; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + // Check if mouse has been pressed or released outside limits + if (!CheckCollisionPointRec(mousePoint, boundsOpen)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + + // Check if already selected item has been pressed again + if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + + // Check focused and selected item + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = i; + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + itemSelected = i; + result = 1; // Item selected + } + break; + } + } + + itemBounds = bounds; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + result = 1; + state = STATE_PRESSED; + } + else state = STATE_FOCUSED; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (editMode) GuiPanel(boundsOpen, NULL); + + GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3))); + GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3))); + + if (editMode) + { + // Draw visible items + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (i == itemSelected) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED))); + } + else if (i == itemFocused) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED))); + } + else GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL))); + } + } + + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) +#if defined(RAYGUI_NO_ICONS) + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL +#endif + } + //-------------------------------------------------------------------- + + *active = itemSelected; + + // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... + return result; // Mouse click: result = 1 +} + +// Text Box control +// NOTE: Returns true on ENTER pressed (useful for data validation) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +{ + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 30 // Frames to wait for autocursor movement + #endif + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 2 // Frames delay for autocursor movement + #endif + + int result = 0; + GuiState state = guiState; + + bool multiline = false; // TODO: Consider multiline text input + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); + + Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); + int textLength = (text != NULL)? (int)strlen(text) : 0; // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GuiGetTextWidth(text) - GuiGetTextWidth(text + thisCursorIndex); + int textIndexOffset = 0; // Text index offset to start drawing in the box + + // Cursor rectangle + // NOTE: Position X value should be updated + Rectangle cursor = { + textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), + textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), + 2, + (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 + }; + + if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); + + // Mouse cursor rectangle + // NOTE: Initialized outside of screen + Rectangle mouseCursor = cursor; + mouseCursor.x = -1; + mouseCursor.width = 1; + + // Blink-cursor frame counter + //if (!autoCursorMode) blinkCursorFrameCounter++; + //else blinkCursorFrameCounter = 0; + + // Update control + //-------------------------------------------------------------------- + // WARNING: Text editing is only supported under certain conditions: + if ((state != STATE_DISABLED) && // Control not disabled + !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode + !guiLocked && // Gui not locked + !guiControlExclusiveMode && // No gui slider on dragging + (wrapMode == TEXT_WRAP_NONE)) // No wrap mode + { + Vector2 mousePosition = GetMousePosition(); + + if (editMode) + { + // GLOBAL: Auto-cursor movement logic + // NOTE: Keystrokes are handled repeatedly when button is held down for some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCounter++; + else autoCursorCounter = 0; + + bool autoCursorShouldTrigger = (autoCursorCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) && ((autoCursorCounter % RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0); + + state = STATE_PRESSED; + + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // If text does not fit in the textbox and current cursor position is out of bounds, + // we add an index offset to text for drawing only what requires depending on cursor + while (textWidth >= textBounds.width) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textIndexOffset, &nextCodepointSize); + + textIndexOffset += nextCodepointSize; + + textWidth = GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex); + } + + int codepoint = GetCharPressed(); // Get Unicode codepoint + if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; + + // Encode codepoint as UTF-8 + int codepointSize = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + + // Handle Paste action + if (IsKeyPressed(KEY_V) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + const char *pasteText = GetClipboardText(); + if (pasteText != NULL) + { + int pasteLength = 0; + int pasteCodepoint; + int pasteCodepointSize; + // count how many codepoints to copy, stopping at the first unwanted control character + while (true) + { + pasteCodepoint = GetCodepointNext(pasteText + pasteLength, &pasteCodepointSize); + if (textLength + pasteLength + pasteCodepointSize >= textSize) break; + if (!(multiline && (pasteCodepoint == (int)'\n')) && !(pasteCodepoint >= 32)) break; + pasteLength += pasteCodepointSize; + } + if (pasteLength > 0) + { + // Move forward data from cursor position + for (int i = textLength + pasteLength; i > textBoxCursorIndex; i--) text[i] = text[i - pasteLength]; + + // Paste data in at cursor + for (int i = 0; i < pasteLength; i++) text[textBoxCursorIndex + i] = pasteText[i]; + + textBoxCursorIndex += pasteLength; + textLength += pasteLength; + text[textLength] = '\0'; + } + } + } + // Add codepoint to text, at current cursor position + // NOTE: Make sure we do not overflow buffer size + else if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Move forward data from cursor position + for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; + + // Add new codepoint in current cursor position + for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; + + textBoxCursorIndex += codepointSize; + textLength += codepointSize; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + + // Move cursor to start + if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; + + // Move cursor to end + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; + + // Delete related codepoints from text, after current cursor position + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_DELETE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) + { + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + // Check whitespace to delete (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) + break; + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Move text after cursor forward (including final null terminator) + for (int i = offset; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + } + // Delete single codepoint from text, after current cursor position + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex + nextCodepointSize; i <= textLength; i++) text[i - nextCodepointSize] = text[i]; + + textLength -= nextCodepointSize; + } + + // Delete related codepoints from text, before current cursor position + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; + + // Check whitespace to delete (ASCII only) + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + // Check characters of the same type to delete (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - accCodepointSize] = text[i]; + + textLength -= accCodepointSize; + textBoxCursorIndex -= accCodepointSize; + } + // Delete single codepoint from text, before current cursor position + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && autoCursorShouldTrigger))) + { + int prevCodepointSize = 0; + + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + // Move text after cursor forward (including final null terminator) + for (int i = textBoxCursorIndex; i <= textLength; i++) text[i - prevCodepointSize] = text[i]; + + textLength -= prevCodepointSize; + textBoxCursorIndex -= prevCodepointSize; + } + + // Move cursor position with keys + if ((textBoxCursorIndex > 0) && IsKeyPressed(KEY_LEFT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int prevCodepointSize; + int prevCodepoint; + + // Check whitespace to skip (ASCII only) + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if (!isspace(prevCodepoint & 0xff)) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + bool puctuation = ispunct(prevCodepoint & 0xff); + while (offset > 0) + { + prevCodepoint = GetCodepointPrevious(text + offset, &prevCodepointSize); + if ((puctuation && !ispunct(prevCodepoint & 0xff)) || (!puctuation && (isspace(prevCodepoint & 0xff) || ispunct(prevCodepoint & 0xff)))) break; + + offset -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + textBoxCursorIndex = offset; + } + else if ((textBoxCursorIndex > 0) && (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && autoCursorShouldTrigger))) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + textBoxCursorIndex -= prevCodepointSize; + } + else if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_RIGHT) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int offset = textBoxCursorIndex; + int accCodepointSize = 0; + int nextCodepointSize; + int nextCodepoint; + + // Check characters of the same type to skip (either ASCII punctuation or anything non-whitespace) + // Not using isalnum() since it only works on ASCII characters + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + bool puctuation = ispunct(nextCodepoint & 0xff); + while (offset < textLength) + { + if ((puctuation && !ispunct(nextCodepoint & 0xff)) || (!puctuation && (isspace(nextCodepoint & 0xff) || ispunct(nextCodepoint & 0xff)))) break; + + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + // Check whitespace to skip (ASCII only) + while (offset < textLength) + { + if (!isspace(nextCodepoint & 0xff)) break; + + offset += nextCodepointSize; + accCodepointSize += nextCodepointSize; + nextCodepoint = GetCodepointNext(text + offset, &nextCodepointSize); + } + + textBoxCursorIndex = offset; + } + else if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && autoCursorShouldTrigger))) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + textBoxCursorIndex += nextCodepointSize; + } + + // Move cursor position with mouse + if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text + { + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; + int codepointIndex = 0; + float glyphWidth = 0.0f; + float widthToMouseX = 0; + int mouseCursorIndex = 0; + + for (int i = textIndexOffset; i < textLength; i += codepointSize) + { + codepoint = GetCodepointNext(&text[i], &codepointSize); + codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) + { + mouseCursor.x = textBounds.x + widthToMouseX; + mouseCursorIndex = i; + break; + } + + widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + + // Check if mouse cursor is at the last position + int textEndWidth = GuiGetTextWidth(text + textIndexOffset); + if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) + { + mouseCursor.x = textBounds.x + textEndWidth; + mouseCursorIndex = textLength; + } + + // Place cursor at required index on mouse click + if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + cursor.x = mouseCursor.x; + textBoxCursorIndex = mouseCursorIndex; + } + } + else mouseCursor.x = -1; + + // Recalculate cursor position.y depending on textBoxCursorIndex + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GuiGetTextWidth(text + textIndexOffset) - GuiGetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + //if (multiline) cursor.y = GetTextLines() + + // Finish text editing on ENTER or mouse click outside bounds + if ((!multiline && IsKeyPressed(KEY_ENTER)) || + (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes + result = 1; + } + } + else + { + if (CheckCollisionPointRec(mousePosition, bounds)) + { + state = STATE_FOCUSED; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + autoCursorCounter = 0; // GLOBAL: Reset counter for repeated keystrokes + result = 1; + } + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + } + else if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); + } + else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); + + // Draw text considering index offset if required + // NOTE: Text index offset depends on cursor position + GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) + { + //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + + // Draw mouse position cursor (if required) + if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + } + else if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; // Mouse button pressed: result = 1 +} + +/* +// Text Box control with multiple lines and word-wrap +// NOTE: This text-box is readonly, no editing supported by default +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) +{ + bool pressed = false; + + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); + + // TODO: Implement methods to calculate cursor position properly + pressed = GuiTextBox(bounds, text, textSize, editMode); + + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + + return pressed; +} +*/ + +// Spinner control, returns selected value +int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + int result = 1; + GuiState state = guiState; + + int tempValue = *value; + + Rectangle valueBoxBounds = { + bounds.x + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING), + bounds.y, + bounds.width - 2*(GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH) + GuiGetStyle(VALUEBOX, SPINNER_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.y, + (float)GuiGetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH), (float)bounds.height }; + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(leftButtonBound, "<")) tempValue--; + if (GuiButton(rightButtonBound, ">")) tempValue++; +#else + if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; + if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; +#endif + + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + result = GuiValueBox(valueBoxBounds, NULL, &tempValue, minValue, maxValue, editMode); + + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(VALUEBOX, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + *value = tempValue; + return result; +} + +// Value Box control, updates input text with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + snprintf(textValue, RAYGUI_VALUEBOX_MAX_CHARS + 1, "%i", *value); + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0 ; i < keyCount; i++) + { + textValue[i] = textValue[i + 1]; + } + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS -1) + { + if (keyCount == 0) + { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + + for (int i = keyCount ; i > -1; i--) textValue[i + 1] = textValue[i]; + + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GuiGetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if ((key >= 48) && (key <= 57)) + { + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; + } + } + } + + // Delete text + if (keyCount > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToInteger(textValue); + + // NOTE: We are not clamp values until user input finishes + //if (*value > maxValue) *value = maxValue; + //else if (*value < minValue) *value = minValue; + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } + } + else + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor rectangle + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = { bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH) + 2, + 2, bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2 - 4 }; + if (cursor.height > bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //snprintf(textValue, sizeof(textValue), "%2.2f", *value); + + Rectangle textBounds = {0}; + if (text != NULL) + { + textBounds.width = (float)GuiGetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Add or remove minus symbol + if (IsKeyPressed(KEY_MINUS)) + { + if (textValue[0] == '-') + { + for (int i = 0; i < keyCount; i++) + { + textValue[i] = textValue[i + 1]; + } + keyCount--; + valueHasChanged = true; + } + else if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS - 1) { + if (keyCount == 0) { + textValue[0] = '0'; + textValue[1] = '\0'; + keyCount++; + } + for (int i = keyCount; i > -1; i--) + { + textValue[i + 1] = textValue[i]; + } + textValue[0] = '-'; + keyCount++; + valueHasChanged = true; + } + } + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GuiGetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GuiGetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Slider control with pro parameters +// NOTE: Other GuiSlider*() controls use this one +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + float oldValue = *value; + + int sliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + if (!CheckCollisionPointRec(mousePoint, slider)) + { + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width - sliderWidth)) + minValue; + } + } + else state = STATE_FOCUSED; + } + + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + } + + // Control value change check + if (oldValue == *value) result = 0; + else result = 1; + + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + if (sliderWidth > 0) // Slider + { + slider.x += sliderValue; + slider.width = (float)sliderWidth; + if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderWidth == 0) // SliderBar + { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; + if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + + // Draw slider internal bar (depends on state) + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + else if (state == STATE_DISABLED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_DISABLED))); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Slider Bar control extended, returns selected value +int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + int preSliderWidth = GuiGetStyle(SLIDER, SLIDER_WIDTH); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 0); + result = GuiSlider(bounds, textLeft, textRight, value, minValue, maxValue); + GuiSetStyle(SLIDER, SLIDER_WIDTH, preSliderWidth); + + return result; +} + +// Progress Bar control extended, shows current progress value +int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + + // Progress bar + Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), + bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, + bounds.height - GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) -1 }; + + // Update control + //-------------------------------------------------------------------- + if (*value > maxValue) *value = maxValue; + + // WARNING: Working with floats could lead to rounding issues + if ((state != STATE_DISABLED)) progress.width = ((float)*value/(maxValue - minValue))*(bounds.width - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)); + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), BLANK); + } + else + { + if (*value > minValue) + { + // Draw progress bar with colored border, more visual + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + } + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1}, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + else + { + // Draw borders not yet reached by value + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y + bounds.height - 1, bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height+GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)-1 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + } + + // Draw slider internal progress bar (depends on state) + GuiDrawRectangle(progress, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED))); + } + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GuiGetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Status Bar control +int GuiStatusBar(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(STATUSBAR, BORDER + (state*3))), GetColor(GuiGetStyle(STATUSBAR, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), GetColor(GuiGetStyle(STATUSBAR, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Dummy rectangle control, intended for placeholding +int GuiDummyRec(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + GuiDrawText(text, GetTextBounds(DEFAULT, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(BUTTON, (state != STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED))); + //------------------------------------------------------------------ + + return result; +} + +// List View control +int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active) +{ + int result = 0; + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + result = GuiListViewEx(bounds, items, itemCount, scrollIndex, active, NULL); + + return result; +} + +// List View control with extended parameters +int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus) +{ + int result = 0; + GuiState state = guiState; + + int itemFocused = (focus == NULL)? -1 : *focus; + int itemSelected = (active == NULL)? -1 : *active; + + // Check if we need a scroll bar + bool useScrollBar = false; + if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING))*count > bounds.height) useScrollBar = true; + + // Define base item rectangle [0] + Rectangle itemBounds = { 0 }; + itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING); + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); + + // Get items on the list + int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + if (visibleItems > count) visibleItems = count; + + int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; + if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; + int endIndex = startIndex + visibleItems; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check mouse inside list view + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Check focused and selected item + for (int i = 0; i < visibleItems; i++) + { + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = startIndex + i; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (itemSelected == (startIndex + i)) itemSelected = -1; + else itemSelected = startIndex + i; + } + break; + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + int wheelMove = (int)GetMouseWheelMove(); + startIndex -= wheelMove; + + if (startIndex < 0) startIndex = 0; + else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; + + endIndex = startIndex + visibleItems; + if (endIndex > count) endIndex = count; + } + } + else itemFocused = -1; + + // Reset item rectangle y to [0] + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Draw visible items + for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) + { + if (GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_NORMAL)) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + + if (state == STATE_DISABLED) + { + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); + } + else + { + if (((startIndex + i) == itemSelected) && (active != NULL)) + { + // Draw item selected + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); + } + else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! + { + // Draw item focused + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); + } + else + { + // Draw item normal (no rectangle) + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); + } + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + Rectangle scrollBarBounds = { + bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Calculate percentage of visible items and apply same percentage to scrollbar + float percentVisible = (float)(endIndex - startIndex)/count; + float sliderSize = bounds.height*percentVisible; + + int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size + int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed + + startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); + + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default + } + //-------------------------------------------------------------------- + + if (active != NULL) *active = itemSelected; + if (focus != NULL) *focus = itemFocused; + if (scrollIndex != NULL) *scrollIndex = startIndex; + + return result; +} + +// Color Panel control - Color (RGBA) variant +int GuiColorPanel(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; + Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv + + GuiColorPanelHSV(bounds, text, &hsv); + + // Check if the hsv was changed, only then change the color + // This is required, because the Color->HSV->Color conversion has precision errors + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value + // Otherwise GuiColorPanel would often modify it's color without user input + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) + { + Vector3 rgb = ConvertHSVtoRGB(hsv); + + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; + } + return result; +} + +// Color Bar Alpha control +// NOTE: Returns alpha value normalized [0..1] +int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +{ + #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) + #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 + #endif + + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, + (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), + (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), + (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw alpha bar: checked background + if (state != STATE_DISABLED) + { + int checksX = (int)bounds.width/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + int checksY = (int)bounds.height/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + + for (int x = 0; x < checksX; x++) + { + for (int y = 0; y < checksY; y++) + { + Rectangle check = { bounds.x + x*RAYGUI_COLORBARALPHA_CHECKED_SIZE, bounds.y + y*RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE }; + GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f) : Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f)); + } + } + + DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw alpha bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Bar Hue control +// Returns hue value normalized [0..1] +// NOTE: Other similar bars (for reference): +// Color GuiColorBarSat() [WHITE->color] +// Color GuiColorBarValue() [BLACK->color], HSV/HSL +// float GuiColorBarLuminance() [BLACK->WHITE] +int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) +{ + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + (*hue)/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + + } + else state = STATE_FOCUSED; + + /*if (IsKeyDown(KEY_UP)) + { + hue -= 2.0f; + if (hue <= 0.0f) hue = 0.0f; + } + else if (IsKeyDown(KEY_DOWN)) + { + hue += 2.0f; + if (hue >= 360.0f) hue = 360.0f; + }*/ + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + // Draw hue bar:color bars + // TODO: Use directly DrawRectangleGradientEx(bounds, color1, color2, color2, color1); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw hue bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Picker control +// NOTE: It's divided in multiple controls: +// Color GuiColorPanel(Rectangle bounds, Color color) +// float GuiColorBarAlpha(Rectangle bounds, float alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead +int GuiColorPicker(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Color temp = { 200, 0, 0, 255 }; + if (color == NULL) color = &temp; + + GuiColorPanel(bounds, NULL, color); + + Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used + Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); + + GuiColorBarHue(boundsHue, NULL, &hsv.x); + + //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); + Vector3 rgb = ConvertHSVtoRGB(hsv); + + *color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), (*color).a }; + + return result; +} + +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB +// NOTE: It's divided in multiple controls: +// int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +// int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanelHSV() size +int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + + Vector3 tempHsv = { 0 }; + + if (colorHsv == NULL) + { + const Vector3 tempColor = { 200.0f/255.0f, 0.0f, 0.0f }; + tempHsv = ConvertRGBtoHSV(tempColor); + colorHsv = &tempHsv; + } + + GuiColorPanelHSV(bounds, NULL, colorHsv); + + const Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + + GuiColorBarHue(boundsHue, NULL, &colorHsv->x); + + return result; +} + +// Color Panel control - HSV variant +int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + GuiState state = guiState; + Vector2 pickerSelector = { 0 }; + + const Color colWhite = { 255, 255, 255, 255 }; + const Color colBlack = { 0, 0, 0, 255 }; + + pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation + pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value + + Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; + Vector3 rgbHue = ConvertHSVtoRGB(maxHue); + Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), + (unsigned char)(255.0f*rgbHue.y), + (unsigned char)(255.0f*rgbHue.z), 255 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; + pickerSelector = mousePoint; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); + DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); + + // Draw color picker: selector + Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; + GuiDrawRectangle(selector, 0, BLANK, colWhite); + } + else + { + DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); + } + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + //-------------------------------------------------------------------- + + return result; +} + +// Message Box control +int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) +{ + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_HEIGHT) + #define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_PADDING) + #define RAYGUI_MESSAGEBOX_BUTTON_PADDING 12 + #endif + + int result = -1; // Returns clicked button from buttons list, 0 refers to closed window button + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT - RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + //int textWidth = GuiGetTextWidth(message) + 2; + + Rectangle textBounds = { 0 }; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; + textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + + prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); + //-------------------------------------------------------------------- + + return result; +} + +// Text Input Box control, ask for text +int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive) +{ + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_PADDING) + #define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 12 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_HEIGHT 26 + #endif + + // Used to enable text edit mode + // WARNING: No more than one GuiTextInputBox() should be open at the same time + static bool textEditMode = false; + + int result = -1; + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT; + + int messageInputHeight = (int)bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - 2*RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + + Rectangle textBounds = { 0 }; + if (message != NULL) + { + int textSize = GuiGetTextWidth(message) + 2; + + textBounds.x = bounds.x + bounds.width/2 - textSize/2; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + textBounds.width = (float)textSize; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + } + + Rectangle textBoxBounds = { 0 }; + textBoxBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + textBoxBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - RAYGUI_TEXTINPUTBOX_HEIGHT/2; + if (message == NULL) textBoxBounds.y = bounds.y + 24 + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); + textBoxBounds.width = bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*2; + textBoxBounds.height = RAYGUI_TEXTINPUTBOX_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + // Draw message if available + if (message != NULL) + { + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + } + + if (secretViewActive != NULL) + { + static char stars[] = "****************"; + if (GuiTextBox(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x, textBoxBounds.y, textBoxBounds.width - 4 - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.height }, + ((*secretViewActive == 1) || textEditMode)? text : stars, textMaxSize, textEditMode)) textEditMode = !textEditMode; + + GuiToggle(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x + textBoxBounds.width - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.y, RAYGUI_TEXTINPUTBOX_HEIGHT, RAYGUI_TEXTINPUTBOX_HEIGHT }, (*secretViewActive == 1)? "#44#" : "#45#", secretViewActive); + } + else + { + if (GuiTextBox(textBoxBounds, text, textMaxSize, textEditMode)) textEditMode = !textEditMode; + } + + int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + if (result >= 0) textEditMode = false; + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); + //-------------------------------------------------------------------- + + return result; // Result is the pressed button index +} + +// Grid control +// NOTE: Returns grid mouse-hover selected cell +// About drawing lines at subpixel spacing, simple put, not easy solution: +// https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster +int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell) +{ + // Grid lines alpha amount + #if !defined(RAYGUI_GRID_ALPHA) + #define RAYGUI_GRID_ALPHA 0.15f + #endif + + int result = 0; + GuiState state = guiState; + + Vector2 mousePoint = GetMousePosition(); + Vector2 currentMouseCell = { -1, -1 }; + + float spaceWidth = spacing/(float)subdivs; + int linesV = (int)(bounds.width/spaceWidth) + 1; + int linesH = (int)(bounds.height/spaceWidth) + 1; + + int color = GuiGetStyle(DEFAULT, LINE_COLOR); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + // NOTE: Cell values must be the upper left of the cell the mouse is in + currentMouseCell.x = floorf((mousePoint.x - bounds.x)/spacing); + currentMouseCell.y = floorf((mousePoint.y - bounds.y)/spacing); + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); + + if (subdivs > 0) + { + // Draw vertical grid lines + for (int i = 0; i < linesV; i++) + { + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; + GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + + // Draw horizontal grid lines + for (int i = 0; i < linesH; i++) + { + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; + GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + } + + if (mouseCell != NULL) *mouseCell = currentMouseCell; + return result; +} + +//---------------------------------------------------------------------------------- +// Tooltip management functions +// NOTE: Tooltips requires some global variables: tooltipPtr +//---------------------------------------------------------------------------------- +// Enable gui tooltips (global state) +void GuiEnableTooltip(void) { guiTooltip = true; } + +// Disable gui tooltips (global state) +void GuiDisableTooltip(void) { guiTooltip = false; } + +// Set tooltip string +void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } + +//---------------------------------------------------------------------------------- +// Styles loading functions +//---------------------------------------------------------------------------------- + +// Load raygui style file (.rgs) +// NOTE: By default a binary file is expected, that file could contain a custom font, +// in that case, custom font image atlas is GRAY+ALPHA and pixel data can be compressed (DEFLATE) +void GuiLoadStyle(const char *fileName) +{ + #define MAX_LINE_BUFFER_SIZE 256 + + bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + // Try reading the files as text file first + FILE *rgsFile = fopen(fileName, "rt"); + + if (rgsFile != NULL) + { + char buffer[MAX_LINE_BUFFER_SIZE] = { 0 }; + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + + if (buffer[0] == '#') + { + int controlId = 0; + int propertyId = 0; + unsigned int propertyValue = 0; + + while (!feof(rgsFile)) + { + switch (buffer[0]) + { + case 'p': + { + // Style property: p + + sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); + GuiSetStyle(controlId, propertyId, (int)propertyValue); + + } break; + case 'f': + { + // Style font: f + + int fontSize = 0; + char charmapFileName[256] = { 0 }; + char fontFileName[256] = { 0 }; + sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); + + Font font = { 0 }; + int *codepoints = NULL; + int codepointCount = 0; + + if (charmapFileName[0] != '0') + { + // Load text data from file + // NOTE: Expected an UTF-8 array of codepoints, no separation + char *textData = LoadFileText(TextFormat("%s/%s", GetDirectoryPath(fileName), charmapFileName)); + codepoints = LoadCodepoints(textData, &codepointCount); + UnloadFileText(textData); + } + + if (fontFileName[0] != '\0') + { + // In case a font is already loaded and it is not default internal font, unload it + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + + if (codepointCount > 0) font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, codepoints, codepointCount); + else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); // Default to 95 standard codepoints + } + + // If font texture not properly loaded, revert to default font and size/spacing + if (font.texture.id == 0) + { + font = GetFontDefault(); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); + } + + UnloadCodepoints(codepoints); + + if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); + + } break; + default: break; + } + + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + } + } + else tryBinary = true; + + fclose(rgsFile); + } + + if (tryBinary) + { + rgsFile = fopen(fileName, "rb"); + + if (rgsFile != NULL) + { + fseek(rgsFile, 0, SEEK_END); + int fileDataSize = ftell(rgsFile); + fseek(rgsFile, 0, SEEK_SET); + + if (fileDataSize > 0) + { + unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); + fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); + + GuiLoadStyleFromMemory(fileData, fileDataSize); + + RAYGUI_FREE(fileData); + } + + fclose(rgsFile); + } + } +} + +// Load style default over global style +void GuiLoadStyleDefault(void) +{ + // We set this variable first to avoid cyclic function calls + // when calling GuiSetStyle() and GuiGetStyle() + guiStyleLoaded = true; + + // Initialize default LIGHT style property values + // WARNING: Default value are applied to all controls on set but + // they can be overwritten later on for every custom control + GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); + GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); + GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); + GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); + GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); + GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); + GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); + GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); + GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); + GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); + GuiSetStyle(DEFAULT, TEXT_PADDING, 0); + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + // Initialize default extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, 15); // DEFAULT, 15 pixels between lines + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); // DEFAULT, text aligned vertically to middle of text-bounds + + // Initialize control-specific property values + // NOTE: Those properties are in default list but require specific values by control type + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 2); + GuiSetStyle(SLIDER, TEXT_PADDING, 4); + GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); + GuiSetStyle(DROPDOWNBOX, TEXT_PADDING, 0); + GuiSetStyle(DROPDOWNBOX, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); + GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); + GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + + // Initialize extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(TOGGLE, GROUP_PADDING, 2); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 16); + GuiSetStyle(SLIDER, SLIDER_PADDING, 1); + GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); + GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 32); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); + GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); + GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_WIDTH, 24); + GuiSetStyle(VALUEBOX, SPINNER_BUTTON_SPACING, 2); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); + GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); + GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH, 1); + GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); + GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); + GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); + GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 16); + GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); + + if (guiFont.texture.id != GetFontDefault().texture.id) + { + // Unload previous font texture + UnloadTexture(guiFont.texture); + RAYGUI_FREE(guiFont.recs); + RAYGUI_FREE(guiFont.glyphs); + guiFont.recs = NULL; + guiFont.glyphs = NULL; + + // Setup default raylib font + guiFont = GetFontDefault(); + + // NOTE: Default raylib font character 95 is a white square + Rectangle whiteChar = guiFont.recs[95]; + + // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(guiFont.texture, RAYGUI_CLITERAL(Rectangle){ whiteChar.x + 1, whiteChar.y + 1, whiteChar.width - 2, whiteChar.height - 2 }); + } +} + +// Get text with icon id prepended +// NOTE: Useful to add icons by name id (enum) instead of +// a number that can change between ricon versions +const char *GuiIconText(int iconId, const char *text) +{ +#if defined(RAYGUI_NO_ICONS) + return NULL; +#else + static char buffer[1024] = { 0 }; + static char iconBuffer[16] = { 0 }; + + if (text != NULL) + { + memset(buffer, 0, 1024); + snprintf(buffer, 1024, "#%03i#", iconId); + + for (int i = 5; i < 1024; i++) + { + buffer[i] = text[i - 5]; + if (text[i - 5] == '\0') break; + } + + return buffer; + } + else + { + snprintf(iconBuffer, 16, "#%03i#", iconId); + + return iconBuffer; + } +#endif +} + +#if !defined(RAYGUI_NO_ICONS) +// Get full icons data pointer +unsigned int *GuiGetIcons(void) { return guiIconsPtr; } + +// Load raygui icons file (.rgi) +// NOTE: In case nameIds are required, they can be requested with loadIconsName, +// they are returned as a guiIconsName[iconCount][RAYGUI_ICON_MAX_NAME_LENGTH], +// WARNING: guiIconsName[]][] memory should be manually freed! +char **GuiLoadIcons(const char *fileName, bool loadIconsName) +{ + // Style File Structure (.rgi) + // ------------------------------------------------------ + // Offset | Size | Type | Description + // ------------------------------------------------------ + // 0 | 4 | char | Signature: "rGI " + // 4 | 2 | short | Version: 100 + // 6 | 2 | short | reserved + + // 8 | 2 | short | Num icons (N) + // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) + + // Icons name id (32 bytes per name id) + // foreach (icon) + // { + // 12+32*i | 32 | char | Icon NameId + // } + + // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) + // S*S pixels/32bit per unsigned int = K unsigned int per icon + // foreach (icon) + // { + // ... | K | unsigned int | Icon Data + // } + + FILE *rgiFile = fopen(fileName, "rb"); + + char **guiIconsName = NULL; + + if (rgiFile != NULL) + { + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + fread(signature, 1, 4, rgiFile); + fread(&version, sizeof(short), 1, rgiFile); + fread(&reserved, sizeof(short), 1, rgiFile); + fread(&iconCount, sizeof(short), 1, rgiFile); + fread(&iconSize, sizeof(short), 1, rgiFile); + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); + } + } + else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); + + // Read icons data directly over internal icons array + fread(guiIconsPtr, sizeof(unsigned int), (int)iconCount*((int)iconSize*(int)iconSize/32), rgiFile); + } + + fclose(rgiFile); + } + + return guiIconsName; +} + +// Load icons from memory +// WARNING: Binary files only +char **GuiLoadIconsFromMemory(const unsigned char *fileData, int dataSize, bool loadIconsName) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + char **guiIconsName = NULL; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&iconCount, fileDataPtr + 4 + 2 + 2, sizeof(short)); + memcpy(&iconSize, fileDataPtr + 4 + 2 + 2 + 2, sizeof(short)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char *)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + memcpy(guiIconsName[i], fileDataPtr, RAYGUI_ICON_MAX_NAME_LENGTH); + fileDataPtr += RAYGUI_ICON_MAX_NAME_LENGTH; + } + } + else + { + // Skip icon name data if not required + fileDataPtr += iconCount*RAYGUI_ICON_MAX_NAME_LENGTH; + } + + int iconDataSize = iconCount*((int)iconSize*(int)iconSize/32)*(int)sizeof(unsigned int); + guiIconsPtr = (unsigned int *)RAYGUI_MALLOC(iconDataSize); + + memcpy(guiIconsPtr, fileDataPtr, iconDataSize); + } + + return guiIconsName; +} + +// Draw selected icon using rectangles pixel-by-pixel +void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) +{ + #define BIT_CHECK(a,b) ((a) & (1u<<(b))) + + for (int i = 0, y = 0; i < RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32; i++) + { + for (int k = 0; k < 32; k++) + { + if (BIT_CHECK(guiIconsPtr[iconId*RAYGUI_ICON_DATA_ELEMENTS + i], k)) + { + #if !defined(RAYGUI_STANDALONE) + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ (float)posX + (k%RAYGUI_ICON_SIZE)*pixelSize, (float)posY + y*pixelSize, (float)pixelSize, (float)pixelSize }, 0, BLANK, color); + #endif + } + + if ((k == 15) || (k == 31)) y++; + } + } +} + +// Set icon drawing size +void GuiSetIconScale(int scale) +{ + if (scale >= 1) guiIconScale = scale; +} + +// Get text width considering gui style and icon size (if required) +int GuiGetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + +#endif // !RAYGUI_NO_ICONS + +//---------------------------------------------------------------------------------- +// Module Internal Functions Definition +//---------------------------------------------------------------------------------- +// Load style from memory +// WARNING: Binary files only +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + int propertyCount = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&propertyCount, fileDataPtr + 4 + 2 + 2, sizeof(int)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'S') && + (signature[3] == ' ')) + { + short controlId = 0; + short propertyId = 0; + unsigned int propertyValue = 0; + + for (int i = 0; i < propertyCount; i++) + { + memcpy(&controlId, fileDataPtr, sizeof(short)); + memcpy(&propertyId, fileDataPtr + 2, sizeof(short)); + memcpy(&propertyValue, fileDataPtr + 2 + 2, sizeof(unsigned int)); + fileDataPtr += 8; + + if (controlId == 0) // DEFAULT control + { + // If a DEFAULT property is loaded, it is propagated to all controls + // NOTE: All DEFAULT properties should be defined first in the file + GuiSetStyle(0, (int)propertyId, propertyValue); + + if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); + } + else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); + } + + // Font loading is highly dependant on raylib API to load font data and image + +#if !defined(RAYGUI_STANDALONE) + // Load custom font if available + int fontDataSize = 0; + memcpy(&fontDataSize, fileDataPtr, sizeof(int)); + fileDataPtr += 4; + + if (fontDataSize > 0) + { + Font font = { 0 }; + int fontType = 0; // 0-Normal, 1-SDF + + memcpy(&font.baseSize, fileDataPtr, sizeof(int)); + memcpy(&font.glyphCount, fileDataPtr + 4, sizeof(int)); + memcpy(&fontType, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + // Load font white rectangle + Rectangle fontWhiteRec = { 0 }; + memcpy(&fontWhiteRec, fileDataPtr, sizeof(Rectangle)); + fileDataPtr += 16; + + // Load font image parameters + int fontImageUncompSize = 0; + int fontImageCompSize = 0; + memcpy(&fontImageUncompSize, fileDataPtr, sizeof(int)); + memcpy(&fontImageCompSize, fileDataPtr + 4, sizeof(int)); + fileDataPtr += 8; + + Image imFont = { 0 }; + imFont.mipmaps = 1; + memcpy(&imFont.width, fileDataPtr, sizeof(int)); + memcpy(&imFont.height, fileDataPtr + 4, sizeof(int)); + memcpy(&imFont.format, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + if ((fontImageCompSize > 0) && (fontImageCompSize != fontImageUncompSize)) + { + // Compressed font atlas image data (DEFLATE), it requires DecompressData() + int dataUncompSize = 0; + unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); + memcpy(compData, fileDataPtr, fontImageCompSize); + fileDataPtr += fontImageCompSize; + + imFont.data = DecompressData(compData, fontImageCompSize, &dataUncompSize); + + // Security check, dataUncompSize must match the provided fontImageUncompSize + if (dataUncompSize != fontImageUncompSize) RAYGUI_LOG("WARNING: Uncompressed font atlas image data could be corrupted"); + + RAYGUI_FREE(compData); + } + else + { + // Font atlas image data is not compressed + imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); + memcpy(imFont.data, fileDataPtr, fontImageUncompSize); + fileDataPtr += fontImageUncompSize; + } + + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + font.texture = LoadTextureFromImage(imFont); + + RAYGUI_FREE(imFont.data); + + // Validate font atlas texture was loaded correctly + if (font.texture.id != 0) + { + // Load font recs data + int recsDataSize = font.glyphCount*sizeof(Rectangle); + int recsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed recs data + memcpy(&recsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) + { + // Recs data is compressed, uncompress it + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); + + memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); + fileDataPtr += recsDataCompressedSize; + + int recsDataUncompSize = 0; + font.recs = (Rectangle *)DecompressData(recsDataCompressed, recsDataCompressedSize, &recsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (recsDataUncompSize != recsDataSize) RAYGUI_LOG("WARNING: Uncompressed font recs data could be corrupted"); + + RAYGUI_FREE(recsDataCompressed); + } + else + { + // Recs data is uncompressed + font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.recs[i], fileDataPtr, sizeof(Rectangle)); + fileDataPtr += sizeof(Rectangle); + } + } + + // Load font glyphs info data + int glyphsDataSize = font.glyphCount*16; // 16 bytes data per glyph + int glyphsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed glyphs data + memcpy(&glyphsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + // Allocate required glyphs space to fill with data + font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); + + if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) + { + // Glyphs data is compressed, uncompress it + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); + + memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); + fileDataPtr += glyphsDataCompressedSize; + + int glyphsDataUncompSize = 0; + unsigned char *glyphsDataUncomp = DecompressData(glypsDataCompressed, glyphsDataCompressedSize, &glyphsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (glyphsDataUncompSize != glyphsDataSize) RAYGUI_LOG("WARNING: Uncompressed font glyphs data could be corrupted"); + + unsigned char *glyphsDataUncompPtr = glyphsDataUncomp; + + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, glyphsDataUncompPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, glyphsDataUncompPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, glyphsDataUncompPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, glyphsDataUncompPtr + 12, sizeof(int)); + glyphsDataUncompPtr += 16; + } + + RAYGUI_FREE(glypsDataCompressed); + RAYGUI_FREE(glyphsDataUncomp); + } + else + { + // Glyphs data is uncompressed + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, fileDataPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, fileDataPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, fileDataPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, fileDataPtr + 12, sizeof(int)); + fileDataPtr += 16; + } + } + } + else font = GetFontDefault(); // Fallback in case of errors loading font atlas texture + + GuiSetFont(font); + + // Set font texture source rectangle to be used as white texture to draw shapes + // NOTE: It makes possible to draw shapes and text (full UI) in a single draw call + if ((fontWhiteRec.x > 0) && + (fontWhiteRec.y > 0) && + (fontWhiteRec.width > 0) && + (fontWhiteRec.height > 0)) SetShapesTexture(font.texture, fontWhiteRec); + } +#endif + } +} + +// Get text bounds considering control bounds +static Rectangle GetTextBounds(int control, Rectangle bounds) +{ + Rectangle textBounds = bounds; + + textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); + textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH) + GuiGetStyle(control, TEXT_PADDING); + textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); + textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); // NOTE: Text is processed line per line! + + // Depending on control, TEXT_PADDING and TEXT_ALIGNMENT properties could affect the text-bounds + switch (control) + { + case COMBOBOX: + case DROPDOWNBOX: + case LISTVIEW: + // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW + case SLIDER: + case CHECKBOX: + case VALUEBOX: + case CONTROL11: + // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER + default: + { + // TODO: WARNING: TEXT_ALIGNMENT is already considered in GuiDrawText() + if (GuiGetStyle(control, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); + else textBounds.x += GuiGetStyle(control, TEXT_PADDING); + } + break; + } + + return textBounds; +} + +// Get text icon if provided and move text cursor +// NOTE: We support up to 999 values for iconId +static const char *GetTextIcon(const char *text, int *iconId) +{ +#if !defined(RAYGUI_NO_ICONS) + *iconId = -1; + if (text[0] == '#') // Maybe we have an icon! + { + char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' + + int pos = 1; + while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) + { + iconValue[pos - 1] = text[pos]; + pos++; + } + + if (text[pos] == '#') + { + *iconId = TextToInteger(iconValue); + + // Move text pointer after icon + // WARNING: If only icon provided, it could point to EOL character: '\0' + if (*iconId >= 0) text += (pos + 1); + } + } +#endif + + return text; +} + +// Get text divided into lines (by line-breaks '\n') +// WARNING: It returns pointers to new lines but it does not add NULL ('\0') terminator! +static const char **GetTextLines(const char *text, int *count) +{ + #define RAYGUI_MAX_TEXT_LINES 128 + + static const char *lines[RAYGUI_MAX_TEXT_LINES] = { 0 }; + for (int i = 0; i < RAYGUI_MAX_TEXT_LINES; i++) lines[i] = NULL; // Init NULL pointers to substrings + + int textSize = (int)strlen(text); + + lines[0] = text; + int len = 0; + *count = 1; + //int lineSize = 0; // Stores current line size, not returned + + for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) + { + if (text[i] == '\n') + { + //lineSize = len; + k++; + lines[k] = &text[i + 1]; // WARNING: next value is valid? + len = 0; + *count += 1; + } + else len++; + } + + //lines[*count - 1].size = len; + + return lines; +} + +// Get text width to next space for provided string +static float GetNextSpaceWidth(const char *text, int *nextSpaceIndex) +{ + float width = 0; + int codepointByteCount = 0; + int codepoint = 0; + int index = 0; + float glyphWidth = 0; + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] != ' ') + { + codepoint = GetCodepoint(&text[i], &codepointByteCount); + index = GetGlyphIndex(guiFont, codepoint); + glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; + width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + else + { + *nextSpaceIndex = i; + break; + } + } + + return width; +} + +// Gui draw text using default font +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint) +{ + #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect + + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + if ((text == NULL) || (text[0] == '\0')) return; // Security check + + // PROCEDURE: + // - Text is processed line per line + // - For every line, horizontal alignment is defined + // - For all text, vertical alignment is defined (multiline text only) + // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) + + // Get text lines (using '\n' as delimiter) to be processed individually + // WARNING: We can't use GuiTextSplit() function because it can be already used + // before the GuiDrawText() call and its buffer is static, it would be overriden :( + int lineCount = 0; + const char **lines = GetTextLines(text, &lineCount); + + // Text style variables + //int alignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT); + int alignmentVertical = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing + + // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap + float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); + float posOffsetY = 0.0f; + + for (int i = 0; i < lineCount; i++) + { + int iconId = 0; + lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor + + // Get text position depending on alignment and iconId + //--------------------------------------------------------------------------------- + Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; + + // NOTE: We get text size after icon has been processed + // WARNING: GuiGetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GuiGetTextWidth(lines[i]); + + // If text requires an icon, add size to measure + if (iconId >= 0) + { + textSizeX += RAYGUI_ICON_SIZE*guiIconScale; + + // WARNING: If only icon provided, text could be pointing to EOF character: '\0' +#if !defined(RAYGUI_NO_ICONS) + if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; +#endif + } + + // Check guiTextAlign global variables + switch (alignment) + { + case TEXT_ALIGN_LEFT: textBoundsPosition.x = textBounds.x; break; + case TEXT_ALIGN_CENTER: textBoundsPosition.x = textBounds.x + textBounds.width/2 - textSizeX/2; break; + case TEXT_ALIGN_RIGHT: textBoundsPosition.x = textBounds.x + textBounds.width - textSizeX; break; + default: break; + } + + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + + switch (alignmentVertical) + { + // Only valid in case of wordWrap = 0; + case TEXT_ALIGN_TOP: textBoundsPosition.y = textBounds.y + posOffsetY; break; + case TEXT_ALIGN_MIDDLE: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + case TEXT_ALIGN_BOTTOM: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + default: break; + } + + // NOTE: Make sure we get pixel-perfect coordinates, + // In case of decimals we got weird text positioning + textBoundsPosition.x = (float)((int)textBoundsPosition.x); + textBoundsPosition.y = (float)((int)textBoundsPosition.y); + //--------------------------------------------------------------------------------- + + // Draw text (with icon if available) + //--------------------------------------------------------------------------------- +#if !defined(RAYGUI_NO_ICONS) + if (iconId >= 0) + { + // NOTE: We consider icon height, probably different than text size + GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + } +#endif + // Get size in bytes of text, + // considering end of line and line break + int lineSize = 0; + for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + + int textOffsetY = 0; + float textOffsetX = 0.0f; + float glyphWidth = 0; + + int ellipsisWidth = GuiGetTextWidth("..."); + bool textOverflow = false; + for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) + { + int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); + int index = GetGlyphIndex(guiFont, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required + if (wrapMode == TEXT_WRAP_CHAR) + { + // Jump to next line if current character reach end of the box limits + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } + } + } + else if (wrapMode == TEXT_WRAP_WORD) + { + if (codepoint == 32) lastSpaceIndex = c; + + // Get width to next space in line + int nextSpaceIndex = 0; + float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); + + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + } + } + + if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint + else + { + // TODO: There are multiple types of spaces in Unicode, + // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html + if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph + { + if (wrapMode == TEXT_WRAP_NONE) + { + // Draw only required text glyphs fitting the textBounds.width + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) + { + // Draw only glyphs inside the bounds + if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + + if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (wrapMode == TEXT_WRAP_NONE) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); + //--------------------------------------------------------------------------------- + } + +#if defined(RAYGUI_DEBUG_TEXT_BOUNDS) + GuiDrawRectangle(textBounds, 0, WHITE, Fade(BLUE, 0.4f)); +#endif +} + +// Gui draw rectangle using default raygui plain style with borders +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) +{ + if (color.a > 0) + { + // Draw rectangle filled with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, GuiFade(color, guiAlpha)); + } + + if (borderWidth > 0) + { + // Draw rectangle border lines with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + } + +#if defined(RAYGUI_DEBUG_RECS_BOUNDS) + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); +#endif +} + +// Draw tooltip using control bounds +static void GuiTooltip(Rectangle controlRec) +{ + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) + { + Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + + if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); + + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, NULL); + + int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); + int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_PADDING, 0); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.0f }, guiTooltipPtr); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); + GuiSetStyle(LABEL, TEXT_PADDING, textPadding); + } +} + +// Split controls text into multiple strings +// Also check for multiple columns (required by GuiToggleGroup()) +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + // NOTE: Those definitions could be externally provided if required + + // TODO: HACK: GuiTextSplit() - Review how textRows are returned to user + // textRow is an externally provided array of integers that stores row number for every splitted string + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data) + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added) + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 1; + + if (textRow != NULL) textRow[0] = 0; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if ((buffer[i] == delimiter) || (buffer[i] == '\n')) + { + result[counter] = buffer + i + 1; + + if (textRow != NULL) + { + if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; + else textRow[counter] = textRow[counter - 1]; + } + + buffer[i] = '\0'; // Set an end of string at this point + + counter++; + if (counter >= RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + + *count = counter; + + return result; +} + +// Convert color data from RGB to HSV +// NOTE: Color data should be passed normalized +static Vector3 ConvertRGBtoHSV(Vector3 rgb) +{ + Vector3 hsv = { 0 }; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; + + min = (rgb.x < rgb.y)? rgb.x : rgb.y; + min = (min < rgb.z)? min : rgb.z; + + max = (rgb.x > rgb.y)? rgb.x : rgb.y; + max = (max > rgb.z)? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Convert color data from HSV to RGB +// NOTE: Color data should be passed normalized +static Vector3 ConvertHSVtoRGB(Vector3 hsv) +{ + Vector3 rgb = { 0 }; + float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; + long i = 0; + + // NOTE: Comparing float values could not work properly + if (hsv.y <= 0.0f) + { + rgb.x = hsv.z; + rgb.y = hsv.z; + rgb.z = hsv.z; + return rgb; + } + + hh = hsv.x; + if (hh >= 360.0f) hh = 0.0f; + hh /= 60.0f; + + i = (long)hh; + ff = hh - i; + p = hsv.z*(1.0f - hsv.y); + q = hsv.z*(1.0f - (hsv.y*ff)); + t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); + + switch (i) + { + case 0: + { + rgb.x = hsv.z; + rgb.y = t; + rgb.z = p; + } break; + case 1: + { + rgb.x = q; + rgb.y = hsv.z; + rgb.z = p; + } break; + case 2: + { + rgb.x = p; + rgb.y = hsv.z; + rgb.z = t; + } break; + case 3: + { + rgb.x = p; + rgb.y = q; + rgb.z = hsv.z; + } break; + case 4: + { + rgb.x = t; + rgb.y = p; + rgb.z = hsv.z; + } break; + case 5: + default: + { + rgb.x = hsv.z; + rgb.y = p; + rgb.z = q; + } break; + } + + return rgb; +} + +// Scroll bar control (used by GuiScrollPanel()) +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) +{ + GuiState state = guiState; + + // Is the scrollbar horizontal or vertical? + bool isVertical = (bounds.width > bounds.height)? false : true; + + // The size (width or height depending on scrollbar type) of the spinner buttons + const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? + (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : + (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; + + // Arrow buttons [<] [>] [∧] [∨] + Rectangle arrowUpLeft = { 0 }; + Rectangle arrowDownRight = { 0 }; + + // Actual area of the scrollbar excluding the arrow buttons + Rectangle scrollbar = { 0 }; + + // Slider bar that moves --[///]----- + Rectangle slider = { 0 }; + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size + + // Calculate rectangles for all of the components + arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ + (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)spinnerSize, (float)spinnerSize }; + + if (isVertical) + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), + bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), + (float)sliderSize }; + } + else // horizontal + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), + bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + (float)sliderSize, + bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && + !CheckCollisionPointRec(mousePoint, arrowUpLeft) && + !CheckCollisionPointRec(mousePoint, arrowDownRight)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Handle mouse wheel + int wheel = (int)GetMouseWheelMove(); + if (wheel != 0) value += wheel; + + // Handle mouse button down + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + // Check arrows click + if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (!CheckCollisionPointRec(mousePoint, slider)) + { + // If click on scrollbar position but not on slider, place slider directly on that position + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + + state = STATE_PRESSED; + } + + // Keyboard control on mouse hover scrollbar + /* + if (isVertical) + { + if (IsKeyDown(KEY_DOWN)) value += 5; + else if (IsKeyDown(KEY_UP)) value -= 5; + } + else + { + if (IsKeyDown(KEY_RIGHT)) value += 5; + else if (IsKeyDown(KEY_LEFT)) value -= 5; + } + */ + } + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background + + GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background + GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar + + // Draw arrows (using icon if available) + if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + { +#if defined(RAYGUI_NO_ICONS) + GuiDrawText(isVertical? "^" : "<", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText(isVertical? "v" : ">", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(isVertical? "#121#" : "#118#", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL + GuiDrawText(isVertical? "#120#" : "#119#", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL +#endif + } + //-------------------------------------------------------------------- + + return value; +} + +// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f +// WARNING: It multiplies current alpha by alpha scale factor +static Color GuiFade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + Color result = { color.r, color.g, color.b, (unsigned char)(color.a*alpha) }; + + return result; +} + +#if defined(RAYGUI_STANDALONE) +// Returns a Color struct from hexadecimal value +static Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xff; + color.g = (unsigned char)(hexValue >> 16) & 0xff; + color.b = (unsigned char)(hexValue >> 8) & 0xff; + color.a = (unsigned char)hexValue & 0xff; + + return color; +} + +// Returns hexadecimal value for a Color +static int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Check if point is inside rectangle +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && + (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Formatting of text with variables to 'embed' +static const char *TextFormat(const char *text, ...) +{ + #if !defined(RAYGUI_TEXTFORMAT_MAX_SIZE) + #define RAYGUI_TEXTFORMAT_MAX_SIZE 256 + #endif + + static char buffer[RAYGUI_TEXTFORMAT_MAX_SIZE]; + + va_list args; + va_start(args, text); + vsnprintf(buffer, RAYGUI_TEXTFORMAT_MAX_SIZE, text, args); + va_end(args); + + return buffer; +} + +// Draw rectangle with vertical gradient fill color +// NOTE: This function is only used by GuiColorPicker() +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; + DrawRectangleGradientEx(bounds, color1, color2, color2, color1); +} + +// Split string into multiple strings +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + } + + *count = counter; + return result; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +static int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + +// Encode codepoint into UTF-8 text (char array size returned as parameter) +static const char *CodepointToUTF8(int codepoint, int *byteSize) +{ + static char utf8[6] = { 0 }; + int size = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + size = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + size = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + size = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + size = 4; + } + + *byteSize = size; + + return utf8; +} + +// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found +// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +static int GetCodepointNext(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} +#endif // RAYGUI_STANDALONE + +#endif // RAYGUI_IMPLEMENTATION diff --git a/client/main.cpp b/client/main.cpp index 2baac00..b56cc57 100644 --- a/client/main.cpp +++ b/client/main.cpp @@ -1,10 +1,17 @@ #include #include + +#define RAYGUI_IMPLEMENTATION +#include "includes/raygui.h" + #include #include #include #include #include +#include +#include +#include #include "PlayerController.hpp" #include "net/NetworkManager.hpp" @@ -93,7 +100,11 @@ struct Heightmap { } }; - +enum GameState { + STATE_LOGIN, + STATE_CONNECTING, + STATE_PLAYING +}; class Game { PlayerController playerController; @@ -106,6 +117,13 @@ class Game { std::unordered_map playerTextures; std::unordered_map remotePlayerModels; + // Login UI state + GameState gameState = STATE_LOGIN; + char usernameBuffer[32] = ""; + bool editMode = false; + std::string loginError = ""; + std::string currentUsername = ""; + public: Game() { InitWindow(1280, 720, "Multiplayer Terrain Game"); @@ -134,12 +152,14 @@ public: // Create player cube (texture will be set when we know our color) auto cubeMesh = GenMeshCube(1.0f, 2.0f, 1.0f); playerModel = LoadModelFromMesh(cubeMesh); - - // Connect to server - network.sendLogin(); } ~Game() { + // Send logout if we're connected + if (gameState == STATE_PLAYING && network.isConnected()) { + network.sendLogout(); + } + UnloadTexture(terrainTexture); for (auto& [color, texture] : playerTextures) { UnloadTexture(texture); @@ -157,26 +177,56 @@ public: update(); render(); } + + // Clean logout when window is closing + if (gameState == STATE_PLAYING && network.isConnected()) { + network.sendLogout(); + // Give the network time to send the packet + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } } private: void update() { + switch (gameState) { + case STATE_LOGIN: + // Login screen - no game updates + break; + + case STATE_CONNECTING: + // Check if we've received a response from the server + if (network.isConnected()) { + gameState = STATE_PLAYING; + currentUsername = std::string(usernameBuffer); + } else if (network.hasLoginError()) { + gameState = STATE_LOGIN; + loginError = network.getLoginError(); + } + break; + + case STATE_PLAYING: + updateGame(); + break; + } + } + + void updateGame() { if (!network.isConnected()) { - if (IsKeyPressed(KEY_SPACE)) { - network.sendLogin(); - } + // Player disconnected, go back to login + gameState = STATE_LOGIN; + loginError = "Disconnected from server"; return; } // Get server position and update player controller playerPos = network.getPosition(); playerController.setPlayerPosition(playerPos); - + // Set player texture based on assigned color if (network.isConnected() && playerTextures.count(network.getPlayerColor()) > 0) { playerModel.materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = playerTextures[network.getPlayerColor()]; } - + // Update remote player models auto remotePlayers = network.getRemotePlayers(); for (const auto& [id, player] : remotePlayers) { @@ -190,7 +240,7 @@ private: remotePlayerModels[id].materials[0].maps[MATERIAL_MAP_DIFFUSE].texture = playerTextures[player.color]; } } - + // Remove models for players who left for (auto it = remotePlayerModels.begin(); it != remotePlayerModels.end();) { if (remotePlayers.find(it->first) == remotePlayers.end()) { @@ -212,7 +262,7 @@ private: if (moveInput.x != 0 || moveInput.z != 0) { network.sendMove(moveInput.x, 0, moveInput.z); } - + // Handle color change with arrow keys static int currentColorIndex = -1; if (IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_RIGHT)) { @@ -227,7 +277,7 @@ private: } if (currentColorIndex == -1) currentColorIndex = 0; } - + // Change color index if (IsKeyPressed(KEY_LEFT)) { currentColorIndex--; @@ -240,16 +290,84 @@ private: currentColorIndex = 0; } } - + // Send color change to server network.sendColorChange(NetworkManager::AVAILABLE_COLORS[currentColorIndex]); } + + // Handle logout + if (IsKeyPressed(KEY_ESCAPE)) { + network.sendLogout(); + gameState = STATE_LOGIN; + loginError = ""; + } } void render() { BeginDrawing(); ClearBackground(SKYBLUE); + switch (gameState) { + case STATE_LOGIN: + renderLoginScreen(); + break; + + case STATE_CONNECTING: + renderConnectingScreen(); + break; + + case STATE_PLAYING: + renderGame(); + break; + } + + EndDrawing(); + } + + void renderLoginScreen() { + // Center the login box + int boxWidth = 300; + int boxHeight = 200; + int boxX = (GetScreenWidth() - boxWidth) / 2; + int boxY = (GetScreenHeight() - boxHeight) / 2; + + // Draw login panel + GuiPanel((Rectangle){(float)boxX, (float)boxY, (float)boxWidth, (float)boxHeight}, "Login"); + + // Username label and text box + GuiLabel((Rectangle){(float)(boxX + 20), (float)(boxY + 50), 80, 30}, "Username:"); + if (GuiTextBox((Rectangle){(float)(boxX + 100), (float)(boxY + 50), 180, 30}, + usernameBuffer, 32, editMode)) { + editMode = !editMode; + } + + // Login button + if (GuiButton((Rectangle){(float)(boxX + 100), (float)(boxY + 100), 100, 30}, "Login")) { + if (strlen(usernameBuffer) > 0) { + // Send login with username + network.sendLoginWithUsername(usernameBuffer); + gameState = STATE_CONNECTING; + loginError = ""; + } else { + loginError = "Please enter a username"; + } + } + + // Error message + if (!loginError.empty()) { + DrawText(loginError.c_str(), boxX + 20, boxY + 150, 20, RED); + } + + // Instructions + DrawText("Enter your username to join the game", 10, 10, 20, WHITE); + } + + void renderConnectingScreen() { + DrawText("Connecting to server...", + GetScreenWidth()/2 - 100, GetScreenHeight()/2 - 10, 20, WHITE); + } + + void renderGame() { BeginMode3D(playerController.getCamera()); // Draw terrain @@ -259,7 +377,7 @@ private: if (network.isConnected()) { DrawModel(playerModel, playerPos, 1.0f, WHITE); } - + // Draw remote players auto remotePlayers = network.getRemotePlayers(); for (const auto& [id, player] : remotePlayers) { @@ -271,16 +389,14 @@ private: EndMode3D(); // UI - DrawText(network.isConnected() ? "Connected" : "Press SPACE to connect", 10, 10, 20, WHITE); + DrawText(TextFormat("Logged in as: %s", currentUsername.c_str()), 10, 10, 20, WHITE); DrawText("WASD: Move | Q/E: Strafe | Right-Click: Rotate Camera", 10, 35, 20, WHITE); - DrawText("Left/Right Arrow: Change Color | Mouse Wheel: Zoom", 10, 60, 20, WHITE); + DrawText("Left/Right Arrow: Change Color | Mouse Wheel: Zoom | ESC: Logout", 10, 60, 20, WHITE); if (network.isConnected()) { std::string colorText = "Your color: " + network.getPlayerColor(); DrawText(colorText.c_str(), 10, 85, 20, WHITE); } DrawFPS(10, 110); - - EndDrawing(); } }; diff --git a/client/net/NetworkManager.cpp b/client/net/NetworkManager.cpp index 286bc56..37733f1 100644 --- a/client/net/NetworkManager.cpp +++ b/client/net/NetworkManager.cpp @@ -53,6 +53,9 @@ void NetworkManager::processMessage(const uint8_t* data, std::size_t size) { case MessageType::ColorChanged: handleColorChanged(data, size); break; + case MessageType::LoginResponse: + handleLoginResponse(data, size); + break; default: break; } @@ -179,6 +182,45 @@ void NetworkManager::sendLogin() { socket.send_to(buffer(msg), serverEndpoint); } +void NetworkManager::sendLoginWithUsername(const std::string& username) { + std::cout << "Attempting login with username: " << username << "\n"; + loginErrorMsg = ""; + currentUsername = username; + std::vector msg(2 + username.size()); + msg[0] = static_cast(MessageType::Login); + msg[1] = static_cast(username.size()); + std::memcpy(&msg[2], username.data(), username.size()); + socket.send_to(buffer(msg), serverEndpoint); + std::cout << "Login packet sent for username: " << username << "\n"; +} + +void NetworkManager::sendLogout() { + if (!connected) { + std::cout << "Warning: sendLogout called but not connected\n"; + return; + } + + std::cout << "Sending logout for player ID " << playerID << " (username: " << currentUsername << ")\n"; + + std::array msg{}; + msg[0] = static_cast(MessageType::Logout); + uint32_t id = playerID; + std::memcpy(&msg[1], &id, sizeof(id)); + socket.send_to(buffer(msg), serverEndpoint); + + std::cout << "Logout packet sent, resetting client state\n"; + + // Reset state + connected = false; + playerID = 0; + currentUsername = ""; + loginErrorMsg = ""; // Clear any error messages + { + std::lock_guard lock(remotePlayersMutex); + remotePlayers.clear(); + } +} + void NetworkManager::sendMove(float dx, float dy, float dz) { if (!connected) return; @@ -241,3 +283,25 @@ std::unordered_map NetworkManager::getRemotePlayers() { std::lock_guard lock(remotePlayersMutex); return remotePlayers; } + +void NetworkManager::handleLoginResponse(const uint8_t* data, std::size_t size) { + // Message format: [type(1)][success(1)][messageLen(1)][message(messageLen)] + if (size < 3) return; + + uint8_t success = data[1]; + uint8_t msgLen = data[2]; + + if (size >= 3 + msgLen) { + std::string message(reinterpret_cast(&data[3]), msgLen); + + if (success == 0) { + // Login failed + loginErrorMsg = message; + connected = false; + std::cout << "Login failed: " << message << "\n"; + } else { + // Login succeeded, wait for spawn message + std::cout << "Login accepted, waiting for spawn...\n"; + } + } +} diff --git a/client/net/NetworkManager.hpp b/client/net/NetworkManager.hpp index a4ed64d..db2fd8a 100644 --- a/client/net/NetworkManager.hpp +++ b/client/net/NetworkManager.hpp @@ -22,7 +22,9 @@ enum class MessageType : uint8_t { PlayerLeft = 0x07, PlayerList = 0x08, ChangeColor = 0x09, - ColorChanged = 0x0A + ColorChanged = 0x0A, + LoginResponse = 0x0B, + Logout = 0x0C }; struct RemotePlayer { @@ -38,11 +40,15 @@ public: ~NetworkManager(); void sendLogin(); + void sendLoginWithUsername(const std::string& username); + void sendLogout(); void sendMove(float dx, float dy, float dz); void sendColorChange(const std::string& newColor); Vector3 getPosition(); bool isConnected() const { return connected; } + bool hasLoginError() const { return !loginErrorMsg.empty(); } + std::string getLoginError() const { return loginErrorMsg; } uint32_t getPlayerID() const { return playerID; } std::string getPlayerColor() const { return playerColor; } @@ -60,6 +66,8 @@ private: std::atomic playerID{0}; std::string playerColor{"red"}; + std::string currentUsername{""}; + std::string loginErrorMsg{""}; std::mutex positionMutex; Vector3 serverPosition{0, 0, 0}; std::atomic connected{false}; @@ -75,4 +83,5 @@ private: void handlePlayerLeft(const uint8_t* data, std::size_t size); void handlePlayerList(const uint8_t* data, std::size_t size); void handleColorChanged(const uint8_t* data, std::size_t size); + void handleLoginResponse(const uint8_t* data, std::size_t size); }; diff --git a/server/net/packets.go b/server/net/packets.go index 4268f7e..895a7d8 100644 --- a/server/net/packets.go +++ b/server/net/packets.go @@ -7,16 +7,18 @@ import ( // Message type constants const ( - MSG_LOGIN = 0x01 - MSG_POSITION = 0x02 - MSG_SPAWN = 0x03 - MSG_MOVE = 0x04 - MSG_UPDATE = 0x05 - MSG_PLAYER_JOINED = 0x06 - MSG_PLAYER_LEFT = 0x07 - MSG_PLAYER_LIST = 0x08 - MSG_CHANGE_COLOR = 0x09 - MSG_COLOR_CHANGED = 0x0A + MSG_LOGIN = 0x01 + MSG_POSITION = 0x02 + MSG_SPAWN = 0x03 + MSG_MOVE = 0x04 + MSG_UPDATE = 0x05 + MSG_PLAYER_JOINED = 0x06 + MSG_PLAYER_LEFT = 0x07 + MSG_PLAYER_LIST = 0x08 + MSG_CHANGE_COLOR = 0x09 + MSG_COLOR_CHANGED = 0x0A + MSG_LOGIN_RESPONSE = 0x0B + MSG_LOGOUT = 0x0C ) // Vec3 represents a 3D vector @@ -141,4 +143,44 @@ func DecodeColorChangePacket(data []byte) (playerID uint32, color string, ok boo color = string(data[6 : 6+colorLen]) return playerID, color, true +} + +// EncodeLoginResponsePacket creates a login response packet +func EncodeLoginResponsePacket(success bool, message string) []byte { + msgBytes := []byte(message) + msg := make([]byte, 3+len(msgBytes)) + msg[0] = MSG_LOGIN_RESPONSE + if success { + msg[1] = 1 + } else { + msg[1] = 0 + } + msg[2] = uint8(len(msgBytes)) + copy(msg[3:], msgBytes) + return msg +} + +// DecodeLoginPacket decodes a login packet with username +func DecodeLoginPacket(data []byte) (username string, ok bool) { + if len(data) < 2 { + return "", false + } + + usernameLen := data[1] + if len(data) < 2+int(usernameLen) { + return "", false + } + + username = string(data[2 : 2+usernameLen]) + return username, true +} + +// DecodeLogoutPacket decodes a logout packet +func DecodeLogoutPacket(data []byte) (playerID uint32, ok bool) { + if len(data) < 5 { + return 0, false + } + + playerID = binary.LittleEndian.Uint32(data[1:5]) + return playerID, true } \ No newline at end of file diff --git a/server/net/server.go b/server/net/server.go index 493c08f..730d8b0 100644 --- a/server/net/server.go +++ b/server/net/server.go @@ -16,6 +16,7 @@ import ( // Player represents a connected player type Player struct { ID uint32 + Username string Position Vec3 Velocity Vec3 Color string @@ -23,13 +24,22 @@ type Player struct { LastSeen time.Time } +// UserData represents persistent user data +type UserData struct { + Username string `json:"username"` + Color string `json:"color"` + Position Vec3 `json:"position"` +} + // Server manages the game state and networking type Server struct { - conn *net.UDPConn - players map[uint32]*Player - heightmap [][]float32 - mutex sync.RWMutex - nextID uint32 + conn *net.UDPConn + players map[uint32]*Player + usersByName map[string]*Player // Track by username for preventing duplicates + userData map[string]*UserData // Persistent user data + heightmap [][]float32 + mutex sync.RWMutex + nextID uint32 } // NewServer creates a new game server @@ -45,13 +55,15 @@ func NewServer(port string, heightmap [][]float32) (*Server, error) { } server := &Server{ - conn: conn, - players: make(map[uint32]*Player), - heightmap: heightmap, - nextID: 0, + conn: conn, + players: make(map[uint32]*Player), + usersByName: make(map[string]*Player), + userData: make(map[string]*UserData), + heightmap: heightmap, + nextID: 0, } - server.loadPlayerPositions() + server.loadUserData() return server, nil } @@ -65,7 +77,7 @@ func (s *Server) Run() error { ticker := time.NewTicker(10 * time.Second) defer ticker.Stop() for range ticker.C { - s.savePlayerPositions() + s.saveUserData() } }() @@ -96,34 +108,69 @@ func (s *Server) Run() error { switch msgType { case MSG_LOGIN: - s.handleLogin(addr) + s.handleLogin(buffer[:n], addr) case MSG_MOVE: s.handleMove(buffer[:n], addr) case MSG_CHANGE_COLOR: s.handleColorChange(buffer[:n], addr) + case MSG_LOGOUT: + s.handleLogout(buffer[:n], addr) } } } -func (s *Server) handleLogin(addr *net.UDPAddr) { +func (s *Server) handleLogin(data []byte, addr *net.UDPAddr) { + username, ok := DecodeLoginPacket(data) + if !ok || username == "" { + // Invalid login packet + log.Printf("Received invalid login packet from %s", addr) + responseMsg := EncodeLoginResponsePacket(false, "Invalid username") + s.conn.WriteToUDP(responseMsg, addr) + return + } + + log.Printf("Login attempt for username: %s from %s", username, addr) + s.mutex.Lock() + + // Check if username is already logged in + if existingPlayer, exists := s.usersByName[username]; exists { + s.mutex.Unlock() + responseMsg := EncodeLoginResponsePacket(false, "User already logged in") + s.conn.WriteToUDP(responseMsg, addr) + log.Printf("Login rejected for %s: already logged in (ID: %d)", username, existingPlayer.ID) + return + } + s.nextID++ playerID := s.nextID - // Assign color based on player ID - colors := []string{"red", "green", "orange", "purple", "white"} - colorIndex := (playerID - 1) % uint32(len(colors)) - color := colors[colorIndex] - - // Spawn at random position on heightmap - x := rand.Float32()*100 - 50 - z := rand.Float32()*100 - 50 - y := s.getHeightAt(x, z) + 1.0 + // Load saved user data or create new + var userData *UserData + var exists bool + if userData, exists = s.userData[username]; !exists { + // New user - assign default color and random position + colors := []string{"red", "green", "orange", "purple", "white"} + colorIndex := (playerID - 1) % uint32(len(colors)) + color := colors[colorIndex] + + x := rand.Float32()*100 - 50 + z := rand.Float32()*100 - 50 + y := s.getHeightAt(x, z) + 1.0 + + userData = &UserData{ + Username: username, + Color: color, + Position: Vec3{x, y, z}, + } + s.userData[username] = userData + } player := &Player{ ID: playerID, - Position: Vec3{x, y, z}, - Color: color, + Username: username, + Position: userData.Position, + Color: userData.Color, Address: addr, LastSeen: time.Now(), } @@ -137,10 +184,15 @@ func (s *Server) handleLogin(addr *net.UDPAddr) { } s.players[playerID] = player + s.usersByName[username] = player s.mutex.Unlock() - // Send spawn message with color - spawnMsg := EncodeSpawnPacket(playerID, player.Position, color) + // Send login success response + responseMsg := EncodeLoginResponsePacket(true, "Login successful") + s.conn.WriteToUDP(responseMsg, addr) + + // Send spawn message with saved position and color + spawnMsg := EncodeSpawnPacket(playerID, userData.Position, userData.Color) s.conn.WriteToUDP(spawnMsg, addr) // Send player list to new player @@ -152,10 +204,10 @@ func (s *Server) handleLogin(addr *net.UDPAddr) { // Notify other players about new player s.broadcastPlayerJoined(player) - log.Printf("Player %d logged in at (%.2f, %.2f, %.2f) with color %s", - playerID, x, y, z, color) + log.Printf("Player %s (ID %d) logged in at (%.2f, %.2f, %.2f) with color %s", + username, playerID, userData.Position.X, userData.Position.Y, userData.Position.Z, userData.Color) - s.savePlayerPositions() + s.saveUserData() } func (s *Server) handleMove(data []byte, _ *net.UDPAddr) { @@ -189,6 +241,11 @@ func (s *Server) handleMove(data []byte, _ *net.UDPAddr) { player.Position.Y = newY player.Position.Z = newZ player.LastSeen = time.Now() + + // Update persistent user data + if userData, exists := s.userData[player.Username]; exists { + userData.Position = player.Position + } s.mutex.Unlock() @@ -196,6 +253,42 @@ func (s *Server) handleMove(data []byte, _ *net.UDPAddr) { s.broadcastUpdate(player) } +func (s *Server) handleLogout(data []byte, _ *net.UDPAddr) { + playerID, ok := DecodeLogoutPacket(data) + if !ok { + log.Printf("Failed to decode logout packet") + return + } + + log.Printf("Received logout request for player ID %d", playerID) + + s.mutex.Lock() + player, exists := s.players[playerID] + if !exists { + s.mutex.Unlock() + log.Printf("Player ID %d not found in active players", playerID) + return + } + + // Save final position + if userData, exists := s.userData[player.Username]; exists { + userData.Position = player.Position + userData.Color = player.Color + } + + // Remove from active players + username := player.Username + delete(s.players, playerID) + delete(s.usersByName, username) + s.mutex.Unlock() + + // Notify other players + s.broadcastPlayerLeft(playerID) + + log.Printf("Player %s (ID %d) successfully logged out", username, playerID) + s.saveUserData() +} + func (s *Server) handleColorChange(data []byte, _ *net.UDPAddr) { playerID, newColor, ok := DecodeColorChangePacket(data) if !ok { @@ -218,6 +311,12 @@ func (s *Server) handleColorChange(data []byte, _ *net.UDPAddr) { } player.Color = newColor + + // Update persistent user data + if userData, exists := s.userData[player.Username]; exists { + userData.Color = newColor + } + s.mutex.Unlock() // Broadcast color change to all players @@ -312,42 +411,58 @@ func (s *Server) checkTimeouts() { now := time.Now() for id, player := range s.players { if now.Sub(player.LastSeen) > 30*time.Second { + // Save final position before removing + if userData, exists := s.userData[player.Username]; exists { + userData.Position = player.Position + userData.Color = player.Color + } + delete(s.players, id) + delete(s.usersByName, player.Username) go s.broadcastPlayerLeft(id) - log.Printf("Player %d timed out", id) + log.Printf("Player %s (ID %d) timed out", player.Username, id) + go s.saveUserData() } } } -func (s *Server) loadPlayerPositions() { +func (s *Server) loadUserData() { data, err := os.ReadFile("players.json") if err != nil { + log.Printf("No existing user data found: %v", err) return } - var savedPlayers map[uint32]Vec3 - json.Unmarshal(data, &savedPlayers) - - for id, pos := range savedPlayers { - if id > s.nextID { - s.nextID = id - } - s.players[id] = &Player{ - ID: id, - Position: pos, - LastSeen: time.Now(), - } + var savedUsers map[string]*UserData + if err := json.Unmarshal(data, &savedUsers); err != nil { + log.Printf("Failed to parse user data: %v", err) + return } + + s.userData = savedUsers + log.Printf("Loaded data for %d users", len(savedUsers)) } -func (s *Server) savePlayerPositions() { +func (s *Server) saveUserData() { s.mutex.RLock() - savedPlayers := make(map[uint32]Vec3) - for id, player := range s.players { - savedPlayers[id] = player.Position + // Deep copy userData to avoid holding lock during file I/O + savedUsers := make(map[string]*UserData) + for username, data := range s.userData { + savedUsers[username] = &UserData{ + Username: data.Username, + Color: data.Color, + Position: data.Position, + } } s.mutex.RUnlock() - data, _ := json.Marshal(savedPlayers) - os.WriteFile("players.json", data, 0644) + data, err := json.MarshalIndent(savedUsers, "", " ") + if err != nil { + log.Printf("Failed to marshal user data: %v", err) + return + } + + if err := os.WriteFile("players.json", data, 0644); err != nil { + log.Printf("Failed to save user data: %v", err) + } } diff --git a/server/players.json b/server/players.json index 9159d25..fdb0455 100644 --- a/server/players.json +++ b/server/players.json @@ -1 +1,20 @@ -{"1":{"X":-13.046684,"Y":-0.008753866,"Z":15.9760895}} \ No newline at end of file +{ + "sky": { + "username": "sky", + "color": "purple", + "position": { + "X": 3.9765263, + "Y": -5.033655, + "Z": 19.824585 + } + }, + "test": { + "username": "test", + "color": "red", + "position": { + "X": 14.141681, + "Y": 2.2383373, + "Z": -0.06184849 + } + } +} \ No newline at end of file