From 2f88c53a07d61418d13d930f7e68bc4e13d9e17c Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Wed, 27 Dec 2023 22:42:35 +0200 Subject: [PATCH] zephr/game: textures, icons, and buttons - zephr: correctly set cursor when hovering button - zephr: added texture loading and displaying - zephr: added icon button - game: added help and settings icon buttons --- Makefile | 2 +- assets/icons/help.png | Bin 0 -> 2781 bytes assets/icons/settings.png | Bin 0 -> 1867 bytes game.c | 66 ++++++++++++++----- game.h | 9 +++ shader.c | 9 +++ shader.h | 4 ++ shaders/ui.frag | 9 ++- texture.c | 45 +++++++++++++ texture.h | 7 ++ ui.c | 131 ++++++++++++++++++++++++++++++++++---- ui.h | 4 +- zephr.c | 34 ++-------- 13 files changed, 263 insertions(+), 57 deletions(-) create mode 100644 assets/icons/help.png create mode 100644 assets/icons/settings.png create mode 100644 texture.c create mode 100644 texture.h diff --git a/Makefile b/Makefile index 5c30556..5c9fe4c 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ BIN=c2048 CC=gcc CFLAGS=-Wall -Wextra -Werror -Wfloat-conversion -Wimplicit-fallthrough -pedantic -g `pkg-config --cflags freetype2` -I3rdparty/glad/include -I3rdparty/fmod/include -I3rdparty/stb/ -OBJ=main.o game.o core.o shader.o text.o audio.o timer.o ui.o zephr.o zephr_math.o 3rdparty/glad/src/gl.o 3rdparty/glad/src/glx.o 3rdparty/stb/stb.o +OBJ=main.o game.o core.o shader.o text.o audio.o texture.o timer.o ui.o zephr.o zephr_math.o 3rdparty/glad/src/gl.o 3rdparty/glad/src/glx.o 3rdparty/stb/stb.o LDFLAGS=`pkg-config --libs x11 freetype2` -lm -L3rdparty/fmod/lib -Wl,-rpath=3rdparty/fmod/lib -lfmod DEPS=3rdparty/glad/include/glad/gl.h 3rdparty/glad/include/glad/glx.h 3rdparty/fmod/include/fmod.h 3rdparty/stb/stb_image.h diff --git a/assets/icons/help.png b/assets/icons/help.png new file mode 100644 index 0000000000000000000000000000000000000000..d69be42c0cedcad7191ae5d76b5ad7a278d2d91d GIT binary patch literal 2781 zcmV<33L^E1P)z@Q6z(1gSO!iIf!W8L0CNKDU1FySId;~zPk?*^#5#5?s$wTQ>=PhPfV{VZIRVZR z&`jrb&y0|SH2uBq9*qEL<0?;2|Ge-0_v;=h7Qadzv*?&Ecpv(;bjh~y1dab@g#9ATe zc$rIZsJ$XEggEUd{TEmNbd0oA{i_rJ&FQsIwa27Rc1t7rGzt?Kq_*1ejrgoe$*2T? zOvUUF0d`4E+^&)fjjB?zO)Bbn6+p}!fcxv)Pv?Q~0FCwE zmun($>!zBpPf+s4fj(D088szTIDo-CajeBkKXSpDY}W0T`m`D>5HUVkvguR zKu${WgWr5q=knxm)2EL~?*<3Jm-s~ZGofg)e;=NPT=y^V3M&%Co1 zsmdL$M1%w(2mo%xSLb~dHMzQ-ODtP^9iNcOBWHnUxI=;94ZyvP?cGR-XSRh7Ki6DO z25pz~PT6gViP~Ig2;m)h0iZpNx>Rpa;uu}8R5P@JMaQ?bTl!^dRY=+4*oMB64}7H9 z8GuZOzCnX5P5fBM71zn;nNyvvHe)JrdtmT&npui#okpO)U7rkR8a%2l3W6S!Y}f-= z8|DlEiL8!r!Sn*@`#4^8@o-WdEuLy$-p0XW&$|T1zDrIg-J7V98;scnO60w`JU(=a zdz=8si1G>{h~fOCzsx1R2jR!3`F&kqg*P01)@TMO2Tg+R-U%T2KoC z7;d9Pi<@fZs3Wb4$g#K!&nIih27rjuBI0P3VEs}0d5vpud8*Aig^>n2!%0M17E|!a{=@w5wq!~1F@!wGwRd4E$18|pPkXZoW+^^0r zjt^NI>*ykR)CQ_b0I?=R1lKZm|p;_Y6x-3tpE^_bT+ZjwM&H9`}g>OT>~@&0IWp<2$xQhE@&r^ zTxNAx6XC$Muj5GX)(lwgu~oKcQ3^z1up7=Q-$qnyK-it_=uRt67X3h z*dG8u&)HO1w(-%TJVTa2l|9-&ZlU|fS+Uhaim013R-w)vnTw-(8@RcEeY7S03IRY8 za4zWw8z8plS=F`P6k0}h<|g&0qifonY1qapdXA(eY>ByI2`Jf3bXh!PxRH)Mx!>4; z0QC8)0O0IN6k^~JTMYO6-2o7S`$MYC?+yTH!3C@zlK3-GLGxyWy#NTLc82jqvZ8)Q zIIT)6F2D-lh9Wi-=D6|BXnrE1nxGO+JtAouFE+qO9BzI{t7wWXBEJ zC$T2c5IyIbj&@mikt^zwJ93$WerIOsXkt%X!sp!4An((ak!Y7;Xs68$^MLFp zfvggM1V2dLn@i+*Z%=uk_qG~YL?W#KpoSP3Y~G2A&JT%?bYDR>Y*FaQ!UF6zjS~uqJo9%@@#la6<;AE&ZY2i{fKfxp7MyNaLV^y} zz3K4o=XR%of)ps5cbRE#762S62(5~cz$YhO=)e&50=eAJvH|(xU@6ed;7~C=l>>pK z;PAUX1y{rUAen$cdnWfg0gxJGpdcw#1=q2P9-;L;MLHR&Mjl9jW3~1qyHk_ND_hFJ zXdcHh7nTYdNCC1_mNhmY#B`a3=UvUSwgD#L2NK^b)zR^A0w9$F;FQQ`rQD+nv)6n; zn57qF#uDlXy?)In4dV;|dJb#YF#4*aS;PTc&gY(A8&`AA0O*c5T$y@k>RKdX(e*V4}0J;-sXXs1o+9oe$ugK`yo*C~%N|OsB z;`e+Ault~_SAFdo>l(EXJAvK+7<&LFs78pYZ@G%Z-Yj>qCV^@q+=TADV}3LuRInEW z0A~Eq0^6cscA zLV6Tc-$v_vZ9Hc|q1tL=OLreN%l6Jn2OuX1=N_L>M0Dv#b3Jk|kxSo`-kBKy10ZNu zjSTCq?V^iN3qV}jDu3|*VFWK zLLYP%QDgWyU1dq1Ak04KT<`bhk=S!*16+Xk*4~**74)qUxoABL04hd%0l)%)iYZX> j@Cyl808lXnDjxoS-Dax0URdw^00000NkvXXu0mjfb!r_4 literal 0 HcmV?d00001 diff --git a/assets/icons/settings.png b/assets/icons/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..13b645e45e99b7dd3074c5603b64f38f640d9864 GIT binary patch literal 1867 zcmV-R2ekN!P)S+6wu2RLi_YH3#b?PxSJ8Z9OM=ac|-D$NTc9;jm_@IXCK#{?XU^BU@b zIws&)oYznf)G-0a;;c1v{n!0X7PCD{X#>VKnnu%;$-n)Ht8qSScMbwdx4FESvEjQx z@5Ght#?N7+UaKE~ycT0@LIe8x`tQ?A8~Ij1pY84+286tt9RMTj7U1sK4y+M|PwJoQ z7vL$k?o5!uOIw|8P5S*?n~5iiz}Pt@^vSH8n2;Cm&Q80gvW4vH7M*H_xow6%Ye;X||1_~2Cf(gGAPKuH(n`OVgyU%36uFp=*#}3P# zFmfT3&&V;nup^TfWE@})mM>phkAt0d6oFP&}3Kc34b4m`d*^Afb}N8tt| z(K1xK8F>TQqD_(Pr35NV^2(Ci2}BQcUYBGEYSef2K<9zVvgX)n!Ln+oG~+Lde3F=- z#urj|3-LuOX+zOv`@hn5NlX*Bi3E%vC!>dR*m>5%0p*)QEvUT$*CjbGFa0rcgr(Nt zWOIV~l)8>R>_~5~&w6){o(n=2256qIRMC=jvt_poz>10!Y_m*du%YNe>eTZ-3jtKg zc8S&L5;E*NWl0OnAJx)OWOvVHJvvZJnCUzO6(4EjQ-`HbzSDwNXe^4X%49w&&}71& zYd4OP7?*IKzEzxLmaQL}+f(*-%7RcO7y9T+9yShsEFs=}G}TJvUuK%x2TlD+MmQq7T-YdDb@ZFIS>95rE{GG2uR{FwW0R$Qu%nBa`YBbY2QMRngR? zg$|If?88#u!?7k-&Wo=Y4 zS#rnTvWQDGaFtsYE`WjzUPr%a3b4$k%A!2!lFx;$?{?rO2mQg>pcJpf*aFo!5zv|% zc0&ZMv3EHztrVN34OI!7`FI$m-GGu2i{l0*L5SrLusc{NQ2zQ{7VZbWV-z0zK%^b z$yeF}#m$u5ZNs!wW=FMv<|?0>01C&s;?0VKqyg!gtY!t-nMv0N_8PG70|DtGvZQVu8|DxQUXPHe}&y#1l0pABjbUpTDY{G*U%DP*txz8 z@j?Q{3c|3fEtpQp4+qtpY|%5Y1T=OKcQ2a~@-e~pP-)S?pvnG!fR70Zuk>K?`n>?< zLQ6k)3F$4!)Nh++>hr;-l`FN&`BMS)s0Ca0EG9g~cjvN%jdt?q8SnD>V6*RVhVTug z22_rWpkl47Td^jac=FO1Nk7O+$67uXJ=vZTc*e2|sWzO5oy1GQP!RqX45 zI)1Ajs0ZqpfMao9Lp@N(1RRU=8tQ>MCg50{*HFhmzW@T;_@EgmLInT-002ovPDHLk FV1hk`h-m-- literal 0 HcmV?d00001 diff --git a/game.c b/game.c index 01776bb..9b9f8bb 100644 --- a/game.c +++ b/game.c @@ -122,7 +122,7 @@ u8 get_tile_font_size(Tile tile) { /////////////////////////////////// -void draw_board() { +void draw_bg() { Size window_size = zephr_get_window_size(); UIConstraints board_con = {0}; @@ -132,7 +132,7 @@ void draw_board() { // bg draw_quad(&board_con, ColorRGBA(248, 250, 230, 255), 0, ALIGN_TOP_LEFT); - // text + // 2048 text UIConstraints text_con = {0}; set_y_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE); @@ -140,7 +140,48 @@ void draw_board() { draw_text("2048", 100, text_con, ColorRGBA(34, 234, 82, 155), ALIGN_TOP_CENTER); + // score + char score[16]; + sprintf(score, "%d", game.score); + + set_parent_constraint(&text_con, NULL); + set_x_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE); + set_y_constraint(&text_con, 0.8f, UI_CONSTRAINT_RELATIVE); + draw_text("Score", 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); + + set_x_constraint(&text_con, 0.15f, UI_CONSTRAINT_RELATIVE); + draw_text(score, 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); +} + +void draw_ui() { + UIConstraints btn_con = {0}; + + set_x_constraint(&btn_con, -8, UI_CONSTRAINT_RELATIVE_PIXELS); + set_y_constraint(&btn_con, 8, UI_CONSTRAINT_RELATIVE_PIXELS); + set_width_constraint(&btn_con, 100, UI_CONSTRAINT_RELATIVE_PIXELS); + set_height_constraint(&btn_con, 1, UI_CONSTRAINT_ASPECT_RATIO); + + if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[HELP_ICON], btn_con.height * 0.25f, ALIGN_TOP_RIGHT)) { + printf("help\n"); + } + + set_x_constraint(&btn_con, -8 - btn_con.width - 10, UI_CONSTRAINT_RELATIVE_PIXELS); + if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[SETTINGS_ICON], btn_con.height * 0.25f, ALIGN_TOP_RIGHT)) { + printf("settings\n"); + } + + set_x_constraint(&btn_con, 0, UI_CONSTRAINT_RELATIVE_PIXELS); + set_y_constraint(&btn_con, -0.05f, UI_CONSTRAINT_RELATIVE); + set_width_constraint(&btn_con, 200, UI_CONSTRAINT_RELATIVE_PIXELS); + set_height_constraint(&btn_con, 65, UI_CONSTRAINT_RELATIVE_PIXELS); + if (draw_button(&btn_con, ColorRGBA(201, 172, 126, 255), "New Game", btn_con.height * 0.25f, ALIGN_BOTTOM_CENTER)) { + printf("new game\n"); + } +} + +void draw_board() { // board + UIConstraints board_con = {0}; set_y_constraint(&board_con, 0.05f, UI_CONSTRAINT_RELATIVE); set_height_constraint(&board_con, 0.6f, UI_CONSTRAINT_RELATIVE); set_width_constraint(&board_con, 1, UI_CONSTRAINT_ASPECT_RATIO); @@ -166,6 +207,8 @@ void draw_board() { } // filled tiles + UIConstraints text_con = {0}; + set_width_constraint(&text_con, 1.5, UI_CONSTRAINT_RELATIVE_PIXELS); UIConstraints tile_con = {0}; set_parent_constraint(&tile_con, &board_con); set_height_constraint(&tile_con, tile_height, UI_CONSTRAINT_FIXED); @@ -188,22 +231,10 @@ void draw_board() { set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); set_y_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); - draw_text(value, get_tile_font_size(*tile), text_con, mul_color(tile_color, 0.5), ALIGN_CENTER); + draw_text(value, get_tile_font_size(*tile), text_con, mult_color(tile_color, 0.5), ALIGN_CENTER); } } } - - // score - char score[16]; - sprintf(score, "%d", game.score); - - set_parent_constraint(&text_con, NULL); - set_x_constraint(&text_con, 0.05f, UI_CONSTRAINT_RELATIVE); - set_y_constraint(&text_con, 0.8f, UI_CONSTRAINT_RELATIVE); - draw_text("Score", 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); - - set_x_constraint(&text_con, 0.15f, UI_CONSTRAINT_RELATIVE); - draw_text(score, 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); } /////////////////////////////////// @@ -601,6 +632,9 @@ bool gameover() { void game_init(void) { srand(time(NULL)); + game.icon_textures[HELP_ICON] = load_texture("assets/icons/help.png"); + game.icon_textures[SETTINGS_ICON] = load_texture("assets/icons/settings.png"); + for (int i = 0; i < 2; i++) { spawn_random_tile(); } @@ -661,6 +695,8 @@ void game_loop(void) { last_frame = now; update_positions(delta_t); + draw_bg(); + draw_ui(); draw_board(); zephr_swap_buffers(); diff --git a/game.h b/game.h index 24347ca..9f23bc2 100644 --- a/game.h +++ b/game.h @@ -10,6 +10,13 @@ typedef enum MoveDir { MOVE_DIR_RIGHT, } MoveDir; +typedef enum IconTexture { + HELP_ICON, + SETTINGS_ICON, + + ICON_TEXTURE_COUNT, +} IconTexture; + typedef struct Tile { u16 value; u16 new_value; @@ -27,6 +34,8 @@ typedef struct Game { bool spawning_new_tile; bool game_over; u32 score; + + TextureId icon_textures[ICON_TEXTURE_COUNT]; } Game; void draw_board(void); diff --git a/shader.c b/shader.c index 3a4cac3..2c36a95 100644 --- a/shader.c +++ b/shader.c @@ -90,6 +90,15 @@ void set_float(Shader shader, const char *name, float val) { glUniform1f(loc, val); } +void set_int(Shader shader, const char *name, int val) { + int loc = glGetUniformLocation(shader.program, name); + glUniform1i(loc, val); +} + +void set_bool(Shader shader, const char *name, bool val) { + set_int(shader, name, val); +} + void set_vec2f(Shader shader, const char *name, float val1, float val2) { int loc = glGetUniformLocation(shader.program, name); glUniform2f(loc, val1, val2); diff --git a/shader.h b/shader.h index c05d03b..dc4a89a 100644 --- a/shader.h +++ b/shader.h @@ -1,5 +1,7 @@ #pragma once +#include + typedef struct Shader { int program; } Shader; @@ -9,6 +11,8 @@ Shader create_shader(const char *vertex_path, const char *fragment_path); void use_shader(Shader shader); void set_mat4f(Shader shader, const char *name, float *mat); void set_float(Shader shader, const char *name, float val); +void set_int(Shader shader, const char *name, int val); +void set_bool(Shader shader, const char *name, bool val); void set_vec2f(Shader shader, const char *name, float val1, float val2); void set_vec3f(Shader shader, const char *name, float val1, float val2, float val3); void set_vec4f(Shader shader, const char *name, float val1, float val2, float val3, float val4); diff --git a/shaders/ui.frag b/shaders/ui.frag index a869a5e..7ffd499 100644 --- a/shaders/ui.frag +++ b/shaders/ui.frag @@ -7,6 +7,9 @@ uniform vec4 aColor; uniform float borderRadius; uniform float uiWidth; uniform float uiHeight; +uniform bool hasTexture; + +uniform sampler2D tex; const float smoothness = 1.0; @@ -29,5 +32,9 @@ void main() { } } - FragColor = vec4(aColor.rgb, alpha); + if (hasTexture) { + FragColor = texture(tex, v_TexCoords) * vec4(aColor.rgb, alpha); + } else { + FragColor = vec4(aColor.rgb, alpha); + } } diff --git a/texture.c b/texture.c new file mode 100644 index 0000000..d6f7b52 --- /dev/null +++ b/texture.c @@ -0,0 +1,45 @@ +#include "glad/gl.h" +#include "stb_image.h" + +#include "core.h" +#include "texture.h" + +TextureId load_texture(const char *path) { + int width, height, nrChannels; + unsigned char *data = stbi_load(path, &width, &height, &nrChannels, STBI_rgb_alpha); + GLuint texture_id; + + u32 format = GL_RGBA; + + switch (nrChannels) { + case 1: + format = GL_RED; + break; + case 2: + format = GL_RG; + break; + case 3: + format = GL_RGB; + break; + case 4: + format = GL_RGBA; + break; + } + + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + + glGenerateMipmap(GL_TEXTURE_2D); + + stbi_image_free(data); + + return texture_id; +} diff --git a/texture.h b/texture.h new file mode 100644 index 0000000..467dcb8 --- /dev/null +++ b/texture.h @@ -0,0 +1,7 @@ +#pragma once + +#include "glad/gl.h" + +typedef GLuint TextureId; + +TextureId load_texture(const char *path); diff --git a/ui.c b/ui.c index be1e61c..d1ee4bf 100644 --- a/ui.c +++ b/ui.c @@ -1,6 +1,7 @@ #include #include +#include #include "shader.h" #include "zephr.h" @@ -171,6 +172,14 @@ void apply_alignment(Alignment align, UIConstraints *constraints, Vec2f *pos, Si } } +bool inside_rect(Rect *rect, Vec2 *point) { + if (point->x >= rect->pos.x && point->x <= rect->pos.x + rect->size.width && + point->y >= rect->pos.y && point->y <= rect->pos.y + rect->size.height) { + return true; + } + return false; +} + void draw_quad(UIConstraints *constraints, const Color color, float border_radius, Alignment align) { use_shader(ui_shader); @@ -245,6 +254,10 @@ void draw_triangle(UIConstraints *constraints, const Color color, Alignment alig apply_constraints(constraints, &pos, &size); apply_alignment(align, constraints, &pos, size); + // set the positions after applying alignment so children can use them + constraints->x = pos.x; + constraints->y = pos.y; + Matrix4x4 model = identity(); apply_translation(&model, (Vec2f){-size.width / 2.f, -size.height / 2.f}); apply_rotation(&model, to_radians(constraints->rotation)); @@ -271,12 +284,61 @@ void draw_triangle(UIConstraints *constraints, const Color color, Alignment alig glBindVertexArray(0); } -bool inside_rect(Rect *rect, Vec2 *point) { - if (point->x >= rect->pos.x && point->x <= rect->pos.x + rect->size.width && - point->y >= rect->pos.y && point->y <= rect->pos.y + rect->size.height) { - return true; - } - return false; +void draw_texture(UIConstraints *constraints, const TextureId texture_id, const Color color, f32 radius, Alignment align) { + use_shader(ui_shader); + + set_vec4f(ui_shader, "aColor", color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f); + set_float(ui_shader, "uiWidth", constraints->width); + set_float(ui_shader, "uiHeight", constraints->height); + set_float(ui_shader, "borderRadius", radius); + set_bool(ui_shader, "hasTexture", true); + + set_mat4f(ui_shader, "projection", (float *)zephr_ctx->projection.m); + + Vec2f pos = { 0.f, 0.f }; + Sizef size = { 0.f, 0.f }; + + apply_constraints(constraints, &pos, &size); + apply_alignment(align, constraints, &pos, size); + + // set the positions after applying alignment so children can use them + constraints->x = pos.x; + constraints->y = pos.y; + + Matrix4x4 model = identity(); + apply_translation(&model, (Vec2f){-size.width / 2.f, -size.height / 2.f}); + apply_rotation(&model, to_radians(constraints->rotation)); + apply_translation(&model, (Vec2f){size.width / 2.f, size.height / 2.f}); + + apply_translation(&model, pos); + + set_mat4f(ui_shader, "model", (float *)model.m); + + glBindVertexArray(ui_vao); + + float vertices[6][4] = { + // bottom left tri + {0, 0 + size.height, 0.0, 1.0}, + {0, 0, 0.0, 0.0}, + {0 + size.width, 0, 1.0, 0.0}, + + // top right tri + {0, 0 + size.height, 0.0, 1.0}, + {0 + size.width, 0, 1.0, 0.0}, + {0 + size.width, 0 + size.height, 1.0, 1.0}, + }; + + glBindBuffer(GL_ARRAY_BUFFER, ui_vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_id); + + glDrawArrays(GL_TRIANGLES, 0, 6); + + glBindVertexArray(0); + set_bool(ui_shader, "hasTexture", false); } bool draw_button(UIConstraints *constraints, const Color color, const char *text, f32 radius, Alignment align) { @@ -285,10 +347,13 @@ bool draw_button(UIConstraints *constraints, const Color color, const char *text apply_constraints(constraints, &rect.pos, &rect.size); apply_alignment(align, constraints, &rect.pos, rect.size); - if (inside_rect(&rect, &zephr_ctx->mouse.pos)) { + bool is_hovered = inside_rect(&rect, &zephr_ctx->mouse.pos); + bool left_mouse_pressed = zephr_ctx->mouse.pressed && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT; + + if (is_hovered) { zephr_set_cursor(ZEPHR_CURSOR_HAND); - if (zephr_ctx->mouse.pressed) { + if (left_mouse_pressed) { Color new_color = mult_color(color, 0.8f); draw_quad(constraints, new_color, radius, align); } else { @@ -296,7 +361,6 @@ bool draw_button(UIConstraints *constraints, const Color color, const char *text draw_quad(constraints, new_color, radius, align); } } else { - zephr_set_cursor(ZEPHR_CURSOR_ARROW); draw_quad(constraints, color, radius, align); } @@ -306,22 +370,65 @@ bool draw_button(UIConstraints *constraints, const Color color, const char *text set_parent_constraint(&text_constraints, constraints); set_x_constraint(&text_constraints, 0, UI_CONSTRAINT_RELATIVE_PIXELS); set_y_constraint(&text_constraints, 0, UI_CONSTRAINT_RELATIVE_PIXELS); - set_width_constraint(&text_constraints, 1.5, UI_CONSTRAINT_RELATIVE_PIXELS); + set_width_constraint(&text_constraints, 1.0, UI_CONSTRAINT_RELATIVE_PIXELS); - u8 font_size = (u8)(constraints->width * 0.2 / text_constraints.width * 0.2 * 100.f); + u8 font_size = (u8)((constraints->width) / (text_constraints.width * 0.1 * 65.f)); // TODO: need to adjust the font size based on the text height draw_text(text, font_size, text_constraints, COLOR_BLACK, ALIGN_CENTER); } - if (inside_rect(&rect, &zephr_ctx->mouse.pos) && zephr_ctx->mouse.released && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT) { + if (is_hovered && zephr_ctx->mouse.released && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT) { return true; } return false; } +bool draw_icon_button(UIConstraints *constraints, Color btn_color, const TextureId icon_tex_id, f32 radius, Alignment align) { + CORE_DEBUG_ASSERT(icon_tex_id, "draw_icon_button() requires that you provide an icon texture"); + + Rect rect = {0}; + Color icon_color = COLOR_WHITE; + + apply_constraints(constraints, &rect.pos, &rect.size); + apply_alignment(align, constraints, &rect.pos, rect.size); + + bool is_hovered = inside_rect(&rect, &zephr_ctx->mouse.pos); + bool left_mouse_pressed = zephr_ctx->mouse.pressed && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT; + + if (is_hovered) { + zephr_set_cursor(ZEPHR_CURSOR_HAND); + + if (left_mouse_pressed) { + btn_color = mult_color(btn_color, 0.8f); + icon_color = mult_color(icon_color, 0.8f); + } else { + btn_color = mult_color(btn_color, 0.9f); + icon_color = mult_color(icon_color, 0.9f); + } + } + + draw_quad(constraints, btn_color, radius, align); + + UIConstraints icon_constraints = {0}; + + set_parent_constraint(&icon_constraints, constraints); + set_x_constraint(&icon_constraints, 0, UI_CONSTRAINT_RELATIVE_PIXELS); + set_y_constraint(&icon_constraints, 0, UI_CONSTRAINT_RELATIVE_PIXELS); + set_width_constraint(&icon_constraints, constraints->width * 0.8f, UI_CONSTRAINT_FIXED); + set_height_constraint(&icon_constraints, constraints->height * 0.8f, UI_CONSTRAINT_FIXED); + draw_texture(&icon_constraints, icon_tex_id, icon_color, 0, ALIGN_CENTER); + + if (is_hovered && zephr_ctx->mouse.released && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT) { + return true; + } + + return false; +} + + /* enum Element { */ /* Rect, */ /* Circle, */ diff --git a/ui.h b/ui.h index 3ec0af8..3dc1ba0 100644 --- a/ui.h +++ b/ui.h @@ -1,5 +1,6 @@ #pragma once +#include "texture.h" #include "zephr_math.h" typedef struct Rect { @@ -47,4 +48,5 @@ void apply_alignment(Alignment align, UIConstraints *constraints, Vec2f *pos, Si void draw_quad(UIConstraints *constraints, const Color color, float border_radius, Alignment align); void draw_circle(UIConstraints *constraints, const Color color, Alignment align); void draw_triangle(UIConstraints *constraints, const Color color, Alignment align); -bool draw_button(UIConstraints *constraints, const Color color, const char *text, f32 radius, Alignment align); +bool draw_button(UIConstraints *constraints, Color color, const char *text, f32 radius, Alignment align); +bool draw_icon_button(UIConstraints *constraints, Color color, const TextureId icon_tex_id, f32 radius, Alignment align); diff --git a/zephr.c b/zephr.c index bcd6e32..2b4c661 100644 --- a/zephr.c +++ b/zephr.c @@ -529,6 +529,8 @@ void x11_toggle_fullscreen(bool fullscreen) { } } +// TODO: currently the icon shows on gnome's sidebar correctly but not in gnome-system-monitor. +// investigate. void x11_assign_window_icon(const char *icon_path) { int icon_width, icon_height; unsigned char *icon_data = stbi_load(icon_path, &icon_width, &icon_height, NULL, STBI_rgb_alpha); @@ -754,7 +756,7 @@ void x11_make_window_non_resizable(int width, int height) { void zephr_init(const char* font_path, const char* icon_path, const char* window_title, Size window_size) { - zephr_ctx = malloc(sizeof(ZephrContext)); + zephr_ctx = calloc(1, sizeof(ZephrContext)); // TODO: should I initalize the audio here or let the game handle that?? int res = audio_init(); @@ -790,6 +792,9 @@ bool zephr_should_quit(void) { glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); + zephr_ctx->cursor = ZEPHR_CURSOR_ARROW; + zephr_ctx->mouse.released = false; + audio_update(); return zephr_ctx->should_quit; @@ -799,9 +804,8 @@ bool zephr_should_quit(void) { void zephr_swap_buffers(void) { CORE_DEBUG_ASSERT(zephr_ctx, "Zephr context not initialized"); - zephr_ctx->mouse.released = false; - glXSwapBuffers(x11_display, x11_window); + XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[zephr_ctx->cursor]); } Size zephr_get_window_size(void) { @@ -1042,30 +1046,6 @@ void zephr_set_cursor(ZephrCursor cursor) { } zephr_ctx->cursor = cursor; - - switch (cursor) { - case ZEPHR_CURSOR_ARROW: - XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[ZEPHR_CURSOR_ARROW]); - break; - case ZEPHR_CURSOR_IBEAM: - XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[ZEPHR_CURSOR_IBEAM]); - break; - case ZEPHR_CURSOR_CROSSHAIR: - XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[ZEPHR_CURSOR_CROSSHAIR]); - break; - case ZEPHR_CURSOR_HAND: - XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[ZEPHR_CURSOR_HAND]); - break; - case ZEPHR_CURSOR_HRESIZE: - XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[ZEPHR_CURSOR_HRESIZE]); - break; - case ZEPHR_CURSOR_VRESIZE: - XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[ZEPHR_CURSOR_VRESIZE]); - break; - default : - XDefineCursor(x11_display, x11_window, zephr_ctx->cursors[ZEPHR_CURSOR_ARROW]); - break; - } } /* bool zephr_keyboard_scancode_is_pressed(ZephrScancode scancode) { */