diff --git a/Makefile b/Makefile index 5c9fe4c..25d8dae 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,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 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 +LDFLAGS=`pkg-config --libs x11 xcursor 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 %.o: %.c $(DEPS) diff --git a/game.c b/game.c index 9b9f8bb..a3477f2 100644 --- a/game.c +++ b/game.c @@ -122,7 +122,7 @@ u8 get_tile_font_size(Tile tile) { /////////////////////////////////// -void draw_bg() { +void draw_bg(void) { Size window_size = zephr_get_window_size(); UIConstraints board_con = {0}; @@ -153,20 +153,58 @@ void draw_bg() { draw_text(score, 40, text_con, COLOR_BLACK, ALIGN_TOP_LEFT); } -void draw_ui() { +void draw_quit_dialog(void) { + UIConstraints dialog_con = {0}; + set_x_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&dialog_con, 0, UI_CONSTRAINT_FIXED); + set_width_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&dialog_con, 1.f, UI_CONSTRAINT_RELATIVE); + draw_quad(&dialog_con, ColorRGBA(0, 0, 0, 200), 0, ALIGN_CENTER); + + set_width_constraint(&dialog_con, 0.4f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&dialog_con, 0.2f, UI_CONSTRAINT_RELATIVE); + draw_quad(&dialog_con, ColorRGBA(135, 124, 124, 255), 8, ALIGN_CENTER); + + UIConstraints text_con = {0}; + set_parent_constraint(&text_con, &dialog_con); + set_x_constraint(&text_con, 0, UI_CONSTRAINT_FIXED); + set_y_constraint(&text_con, -0.04f, UI_CONSTRAINT_RELATIVE); + set_width_constraint(&text_con, 1, UI_CONSTRAINT_RELATIVE_PIXELS); + draw_text("Are you sure you want to quit?", 36.f, text_con, COLOR_WHITE, ALIGN_CENTER); + + UIConstraints btn_con = {0}; + set_parent_constraint(&btn_con, &dialog_con); + set_width_constraint(&btn_con, 0.1f, UI_CONSTRAINT_RELATIVE); + set_height_constraint(&btn_con, 0.05f, UI_CONSTRAINT_RELATIVE); + set_y_constraint(&btn_con, -0.02f, UI_CONSTRAINT_RELATIVE); + set_x_constraint(&btn_con, -0.07f, UI_CONSTRAINT_RELATIVE); + if (draw_button(&btn_con, ColorRGBA(201, 146, 126, 255), "Yes", btn_con.height * 0.20f, ALIGN_BOTTOM_CENTER, BUTTON_STATE_ACTIVE)) { + game.quit_dialog = false; + zephr_quit(); + } + + set_y_constraint(&btn_con, -0.02f, UI_CONSTRAINT_RELATIVE); + set_x_constraint(&btn_con, 0.07f, UI_CONSTRAINT_RELATIVE); + if (draw_button(&btn_con, ColorRGBA(201, 146, 126, 255), "No", btn_con.height * 0.20f, ALIGN_BOTTOM_CENTER, BUTTON_STATE_ACTIVE)) { + game.quit_dialog = false; + } +} + +void draw_ui(void) { + ButtonState bg_btns_state = game.quit_dialog ? BUTTON_STATE_INACTIVE : BUTTON_STATE_ACTIVE; 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_width_constraint(&btn_con, 80, 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)) { + if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[HELP_ICON], btn_con.height * 0.15f, ALIGN_TOP_RIGHT, bg_btns_state)) { 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)) { + if (draw_icon_button(&btn_con, ColorRGBA(201, 146, 126, 255), game.icon_textures[SETTINGS_ICON], btn_con.height * 0.15f, ALIGN_TOP_RIGHT, bg_btns_state)) { printf("settings\n"); } @@ -174,12 +212,16 @@ void draw_ui() { 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)) { + if (draw_button(&btn_con, ColorRGBA(201, 172, 126, 255), "New Game", btn_con.height * 0.25f, ALIGN_BOTTOM_CENTER, bg_btns_state)) { printf("new game\n"); } + + if (game.quit_dialog) { + draw_quit_dialog(); + } } -void draw_board() { +void draw_board(void) { // board UIConstraints board_con = {0}; set_y_constraint(&board_con, 0.05f, UI_CONSTRAINT_RELATIVE); @@ -237,6 +279,7 @@ void draw_board() { } } + /////////////////////////////////// // // @@ -245,7 +288,7 @@ void draw_board() { // /////////////////////////////////// -void move_right() { +void move_right(void) { if (game.animating) return; game.last_move_dir = MOVE_DIR_RIGHT; @@ -296,7 +339,7 @@ void move_right() { } } -void move_left() { +void move_left(void) { if (game.animating) return; game.last_move_dir = MOVE_DIR_LEFT; @@ -348,7 +391,7 @@ void move_left() { } -void move_down() { +void move_down(void) { if (game.animating) return; game.last_move_dir = MOVE_DIR_DOWN; @@ -396,7 +439,7 @@ void move_down() { } } -void move_up() { +void move_up(void) { if (game.animating) return; game.last_move_dir = MOVE_DIR_UP; @@ -455,7 +498,7 @@ void spawn_new_tile(u8 x, u8 y) { } } -void spawn_random_tile() { +void spawn_random_tile(void) { u8 available_tiles_count = 0; u8 available_tiles[16]; @@ -608,7 +651,7 @@ void update_positions(f64 delta_t) { } } -bool gameover() { +bool gameover(void) { for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { if (game.board[y][x].value == 0) { @@ -650,22 +693,26 @@ void game_init(void) { void handle_keyboard_input(ZephrEvent e) { - if ( - (e.key.mods & ZEPHR_KEY_MOD_CTRL && e.key.code == ZEPHR_KEYCODE_Q) || - e.key.code == ZEPHR_KEYCODE_ESCAPE - ) { - // TODO: show confirmation dialog - zephr_quit(); + if (e.key.mods & ZEPHR_KEY_MOD_CTRL && e.key.code == ZEPHR_KEYCODE_Q) { + game.quit_dialog = true; + } else if (e.key.code == ZEPHR_KEYCODE_ESCAPE) { + if (game.quit_dialog) { + game.quit_dialog = false; + } } else if (e.key.code == ZEPHR_KEYCODE_F11) { zephr_toggle_fullscreen(); } else if (e.key.code == ZEPHR_KEYCODE_UP) { - move_up(); + if (!game.quit_dialog) + move_up(); } else if (e.key.code == ZEPHR_KEYCODE_DOWN) { - move_down(); + if (!game.quit_dialog) + move_down(); } else if (e.key.code == ZEPHR_KEYCODE_LEFT) { - move_left(); + if (!game.quit_dialog) + move_left(); } else if (e.key.code == ZEPHR_KEYCODE_RIGHT) { - move_right(); + if (!game.quit_dialog) + move_right(); } } @@ -680,7 +727,11 @@ void game_loop(void) { while (zephr_iter_events(&e)) { switch (e.type) { case ZEPHR_EVENT_WINDOW_CLOSED: - zephr_quit(); + if (game.quit_dialog) { + zephr_quit(); + } else { + game.quit_dialog = true; + } break; case ZEPHR_EVENT_KEY_PRESSED: handle_keyboard_input(e); @@ -696,8 +747,8 @@ void game_loop(void) { update_positions(delta_t); draw_bg(); - draw_ui(); draw_board(); + draw_ui(); zephr_swap_buffers(); } diff --git a/game.h b/game.h index 9f23bc2..e9bbe0d 100644 --- a/game.h +++ b/game.h @@ -29,13 +29,14 @@ typedef struct Tile { typedef struct Game { Tile board[4][4]; + u32 score; MoveDir last_move_dir; bool animating; bool spawning_new_tile; bool game_over; - u32 score; TextureId icon_textures[ICON_TEXTURE_COUNT]; + bool quit_dialog; } Game; void draw_board(void); diff --git a/ui.c b/ui.c index d1ee4bf..9847cb6 100644 --- a/ui.c +++ b/ui.c @@ -341,7 +341,8 @@ void draw_texture(UIConstraints *constraints, const TextureId texture_id, const set_bool(ui_shader, "hasTexture", false); } -bool draw_button(UIConstraints *constraints, const Color color, const char *text, f32 radius, Alignment align) { +bool draw_button(UIConstraints *constraints, const Color color, const char *text, f32 radius, Alignment align, ButtonState state) { + Color text_color = COLOR_BLACK; Rect rect = {0}; apply_constraints(constraints, &rect.pos, &rect.size); @@ -350,7 +351,7 @@ bool draw_button(UIConstraints *constraints, const Color color, const char *text 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) { + if (is_hovered && state == BUTTON_STATE_ACTIVE) { zephr_set_cursor(ZEPHR_CURSOR_HAND); if (left_mouse_pressed) { @@ -360,6 +361,15 @@ bool draw_button(UIConstraints *constraints, const Color color, const char *text Color new_color = mult_color(color, 0.9f); draw_quad(constraints, new_color, radius, align); } + } else if (state == BUTTON_STATE_DISABLED) { + if (is_hovered) { + zephr_set_cursor(ZEPHR_CURSOR_DISABLED); + } + + Color new_color = color; + new_color.a = 100; + text_color.a = 100; + draw_quad(constraints, new_color, radius, align); } else { draw_quad(constraints, color, radius, align); } @@ -376,17 +386,17 @@ bool draw_button(UIConstraints *constraints, const Color color, const char *text // TODO: need to adjust the font size based on the text height - draw_text(text, font_size, text_constraints, COLOR_BLACK, ALIGN_CENTER); + draw_text(text, font_size, text_constraints, text_color, ALIGN_CENTER); } - if (is_hovered && zephr_ctx->mouse.released && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT) { + if (is_hovered && state == BUTTON_STATE_ACTIVE && 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) { +bool draw_icon_button(UIConstraints *constraints, Color btn_color, const TextureId icon_tex_id, f32 radius, Alignment align, ButtonState state) { CORE_DEBUG_ASSERT(icon_tex_id, "draw_icon_button() requires that you provide an icon texture"); Rect rect = {0}; @@ -398,7 +408,7 @@ bool draw_icon_button(UIConstraints *constraints, Color btn_color, const Texture 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) { + if (is_hovered && state == BUTTON_STATE_ACTIVE) { zephr_set_cursor(ZEPHR_CURSOR_HAND); if (left_mouse_pressed) { @@ -408,6 +418,13 @@ bool draw_icon_button(UIConstraints *constraints, Color btn_color, const Texture btn_color = mult_color(btn_color, 0.9f); icon_color = mult_color(icon_color, 0.9f); } + } else if (state == BUTTON_STATE_DISABLED) { + if (is_hovered) { + zephr_set_cursor(ZEPHR_CURSOR_DISABLED); + } + + icon_color.a = 100; + btn_color.a = 100; } draw_quad(constraints, btn_color, radius, align); @@ -421,7 +438,7 @@ bool draw_icon_button(UIConstraints *constraints, Color btn_color, const Texture 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) { + if (is_hovered && state == BUTTON_STATE_ACTIVE && zephr_ctx->mouse.released && zephr_ctx->mouse.button == ZEPHR_MOUSE_BUTTON_LEFT) { return true; } diff --git a/ui.h b/ui.h index 3dc1ba0..31a550f 100644 --- a/ui.h +++ b/ui.h @@ -36,6 +36,12 @@ typedef struct UIConstraints { struct UIConstraints *parent; } UIConstraints; +typedef enum ButtonState { + BUTTON_STATE_ACTIVE, + BUTTON_STATE_INACTIVE, + BUTTON_STATE_DISABLED, +} ButtonState; + int init_ui(const char* font_path); void set_parent_constraint(UIConstraints *constraints, UIConstraints *parent_constraints); void set_x_constraint(UIConstraints *constraints, float value, UIConstraint type); @@ -48,5 +54,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, 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); +bool draw_button(UIConstraints *constraints, Color color, const char *text, f32 radius, Alignment align, ButtonState state); +bool draw_icon_button(UIConstraints *constraints, Color color, const TextureId icon_tex_id, f32 radius, Alignment align, ButtonState state); diff --git a/zephr.c b/zephr.c index 2b4c661..d375153 100644 --- a/zephr.c +++ b/zephr.c @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -770,6 +771,7 @@ void zephr_init(const char* font_path, const char* icon_path, const char* window zephr_ctx->window.size = window_size; zephr_ctx->projection = orthographic_projection_2d(0.f, window_size.width, window_size.height, 0.f); + zephr_ctx->cursors[ZEPHR_CURSOR_ARROW] = XCreateFontCursor(x11_display, XC_left_ptr); zephr_ctx->cursors[ZEPHR_CURSOR_IBEAM] = XCreateFontCursor(x11_display, XC_xterm); zephr_ctx->cursors[ZEPHR_CURSOR_CROSSHAIR] = XCreateFontCursor(x11_display, XC_crosshair); @@ -777,6 +779,8 @@ void zephr_init(const char* font_path, const char* icon_path, const char* window zephr_ctx->cursors[ZEPHR_CURSOR_HRESIZE] = XCreateFontCursor(x11_display, XC_sb_h_double_arrow); zephr_ctx->cursors[ZEPHR_CURSOR_VRESIZE] = XCreateFontCursor(x11_display, XC_sb_v_double_arrow); + zephr_ctx->cursors[ZEPHR_CURSOR_DISABLED] = XcursorLibraryLoadCursor(x11_display, "crossed_circle"); + x11_get_screen_size(&zephr_ctx->screen_size.width, &zephr_ctx->screen_size.height); start_internal_timer(); } diff --git a/zephr.h b/zephr.h index 0a85537..b0de73a 100644 --- a/zephr.h +++ b/zephr.h @@ -61,6 +61,7 @@ typedef enum ZephrCursor { ZEPHR_CURSOR_HAND, ZEPHR_CURSOR_HRESIZE, ZEPHR_CURSOR_VRESIZE, + ZEPHR_CURSOR_DISABLED, ZEPHR_CURSOR_COUNT, } ZephrCursor;