main.c (15844B)
1 #define HS_IMPL 2 #define HS_NUKLEAR 3 #define HS_SFD 4 #include "hs/hs_graphics.h" 5 6 #define MAX_VERTEX_BUFFER 512 * 1024 7 #define MAX_ELEMENT_BUFFER 128 * 1024 8 9 struct nk_context *ctx; 10 struct nk_glfw glfw = {0}; 11 12 hs_tilemap tilemap; 13 uint32_t selected_tile = 0; 14 uint32_t tileset_chosen = false; 15 hs_aroom current_room; 16 hs_aroom undo_room; 17 18 hs_key* undo_key = &(hs_key){.key = GLFW_KEY_Z}; 19 20 uint32_t popup_new_room_on = 0; 21 22 hs_game_data gd = { 23 .width = 960, 24 .height = 540 25 }; 26 27 sfd_Options anders_tale_room = { 28 .title = "Johan, you may now choose an Anders Tale Room file", 29 .filter_name = "Anders tale room", 30 .filter = "*.aroom", 31 }; 32 33 uint32_t tilemap_offset_x, tilemap_offset_y, tilemap_screen_width, tilemap_screen_height; 34 35 static void 36 framebuffer_size_callback(GLFWwindow* win, const int width, const int height) 37 { 38 gd.width = width; 39 tilemap_screen_width = width - 200; 40 gd.height = height; 41 if (tilemap.vertices) { 42 const float viewport_height = (float)tilemap_screen_width * ((float)tilemap.height/tilemap.width); 43 if (viewport_height > gd.height) { 44 const float viewport_width = (float)gd.height * ((float)tilemap.width/tilemap.height); 45 tilemap_offset_x = (tilemap_screen_width - viewport_width) / 2; 46 tilemap_offset_y = 0; 47 tilemap_screen_width = viewport_width; 48 tilemap_screen_height = gd.height; 49 } else { 50 tilemap_offset_x = 0; 51 tilemap_offset_y = (gd.height - viewport_height) / 2; 52 tilemap_screen_height = viewport_height; 53 } 54 55 tilemap_offset_x += 200; 56 } 57 } 58 59 static inline void init() 60 { 61 hs_init(&gd, "Anders tale room editor (mfw Johan anderstaler istendefor å game)", framebuffer_size_callback, 0); 62 63 ctx = nk_glfw3_init(&glfw, gd.window, NK_GLFW3_INSTALL_CALLBACKS); 64 /* Load Fonts: if none of these are loaded a default font will be used */ 65 /* Load Cursor: if you uncomment cursor loading please hide the cursor */ 66 { 67 struct nk_font_atlas *atlas; 68 nk_glfw3_font_stash_begin(&glfw, &atlas); 69 nk_glfw3_font_stash_end(&glfw); 70 } 71 } 72 73 static inline void 74 init_new_room(const uint32_t room_width, const uint32_t room_height) 75 { 76 hs_tilemap_init(&tilemap, 0); 77 framebuffer_size_callback(gd.window, gd.width, gd.height); 78 79 if (current_room.data) 80 free(current_room.data); 81 if (undo_room.data) 82 free(undo_room.data); 83 uint8_t* room_data = calloc(1, sizeof(uint8_t) * room_width * room_height); 84 assert(room_data); 85 86 uint8_t* undo_data = calloc(1, sizeof(uint8_t) * room_width * room_height); 87 assert(undo_data); 88 89 current_room = (hs_aroom){ 90 .width = room_width, 91 .height = room_height, 92 .layers = 1, 93 .data = room_data, 94 }; 95 undo_room = (hs_aroom){ 96 .width = room_width, 97 .height = room_height, 98 .layers = 1, 99 .data = undo_data, 100 }; 101 } 102 103 static inline uint32_t 104 popup_create_new_room() 105 { 106 uint32_t new_tilemap = false; 107 if (nk_popup_begin(ctx, NK_POPUP_STATIC, "Create new room", 108 NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_TITLE, 109 nk_rect(200, 10, 320, 140))) { 110 static int room_width = 20; 111 static int room_height = 20; 112 113 nk_layout_row_dynamic(ctx, 30, 1); 114 nk_property_int(ctx, "Room width:", 0, &room_width, 60, 1, 1); 115 nk_property_int(ctx, "Room height:", 0, &room_height, 60, 1, 1); 116 117 nk_layout_row_dynamic(ctx, 30, 2); 118 if (nk_button_label(ctx, "Create room")) { 119 tilemap.width = room_width; 120 tilemap.height = room_height; 121 tilemap.tile_width = 1.0f/tilemap.width; 122 tilemap.tile_height = 1.0f/tilemap.height; 123 124 init_new_room(room_width, room_height); 125 126 popup_new_room_on = false; 127 new_tilemap = true; 128 nk_popup_close(ctx); 129 } 130 131 if (nk_button_label(ctx, "Quit")) { 132 popup_new_room_on = false; 133 nk_popup_close(ctx); 134 } 135 nk_popup_end(ctx); 136 } 137 return new_tilemap; 138 } 139 140 static struct nk_image* 141 create_tiles(const char* filename, const uint32_t width, const uint32_t height) 142 { 143 int tileset_width, tileset_height; 144 tilemap.tex = hs_tex2d_create_size_info_pixel(filename, GL_RGBA, &tileset_width, &tileset_height); 145 struct nk_image tileset = nk_image_id(tilemap.tex); 146 struct nk_image* tiles = (struct nk_image*)malloc(sizeof(struct nk_image) * width * height); 147 assert(tiles); 148 149 const int tile_width = tileset_width / width; 150 const int tile_height = tileset_height / height; 151 152 uint32_t i = 0; 153 for (uint32_t y = 0; y < height; y++) { 154 const uint32_t ypos = y * tile_height; 155 for (uint32_t x = 0; x < width; x++) { 156 const uint32_t xpos = x * tile_width; 157 tiles[i++] = nk_subimage_handle(tileset.handle, tileset_width, tileset_height, 158 nk_rect(xpos, ypos, tile_width, tile_height)); 159 } 160 } 161 162 return tiles; 163 } 164 165 char* set_and_get_filename_short(char* file_path_src, char** file_path_dest) { 166 if (*file_path_dest) { 167 free(*file_path_dest); 168 *file_path_dest = NULL; 169 } 170 *file_path_dest = malloc(strlen(file_path_src)+1); 171 assert(*file_path_dest); 172 strcpy(*file_path_dest, file_path_src); 173 174 char* last_slash_pos = file_path_src; 175 for (char* f = *file_path_dest; f[1] != '\0'; f++) 176 if (f[0] == '/' || f[0] == '\\') 177 last_slash_pos = f + 1; 178 return last_slash_pos; 179 } 180 181 static inline void 182 side_bar() 183 { 184 if (nk_begin(ctx, "Side bar", nk_rect(0, 0, 200, gd.height), 0)) { 185 const char* const tileset_default_name = "No tileset selected"; 186 static char* tileset_filename = NULL; 187 static char* tileset_filename_short = (char*)tileset_default_name; 188 189 const char* const tilemap_default_name = "No tilemap selected"; 190 const char* const tilemap_new = "New tilemap"; 191 static char* tilemap_filename = NULL; 192 static char* tilemap_filename_short = (char*)tilemap_default_name; 193 194 static struct nk_image* tileset_images = NULL; 195 static uint32_t tileset_image_count = 0; 196 static uint32_t tileset_popup_on = 0; 197 198 nk_layout_row_dynamic(ctx, 10, 1); 199 nk_label(ctx, tilemap_filename_short, NK_TEXT_CENTERED); 200 201 nk_layout_row_dynamic(ctx, 30, 1); 202 if (nk_button_label(ctx, "New room")) { 203 popup_new_room_on = true; 204 } 205 206 nk_layout_row_dynamic(ctx, 30, 1); 207 if (nk_button_label(ctx, "Load room")) { 208 const char *filename = sfd_open_dialog(&anders_tale_room); 209 if (filename) { 210 tilemap_filename_short = set_and_get_filename_short((char*)filename, &tilemap_filename); 211 212 undo_room = hs_aroom_from_file(filename); 213 current_room = hs_aroom_from_file(filename); 214 tilemap.tile_width = 1.0f/current_room.width; 215 tilemap.tile_height = 1.0f/current_room.height; 216 217 hs_aroom_to_tilemap(current_room, &tilemap, 1); 218 framebuffer_size_callback(gd.window, gd.width, gd.height); 219 } 220 } 221 if (current_room.data && 222 nk_button_label(ctx, "Save room")) { 223 const char *filename = sfd_save_dialog(&anders_tale_room); 224 if (filename) { 225 hs_aroom_write_to_file(filename, current_room); 226 tilemap_filename_short = set_and_get_filename_short((char*)filename, &tilemap_filename); 227 } 228 } 229 if (popup_new_room_on) { 230 if (popup_create_new_room()) { 231 tilemap_filename_short = (char*)tilemap_new; 232 } 233 } 234 235 nk_layout_row_dynamic(ctx, 10, 1); 236 nk_layout_row_dynamic(ctx, 10, 1); 237 nk_label(ctx, tileset_filename_short, NK_TEXT_CENTERED); 238 239 nk_layout_row_dynamic(ctx, 30, 1); 240 if (nk_button_label(ctx, "Choose new tileset")) { 241 char* filename = (char*)sfd_open_dialog(&(sfd_Options){ 242 .title = "Choose a tileset", 243 .filter_name = "PNG image", 244 .filter = "*.png", 245 }); 246 247 if (filename && filename[0] != '\0') { 248 tileset_popup_on = true; 249 tileset_filename_short = set_and_get_filename_short((char*)filename, &tileset_filename); 250 } else { 251 tileset_filename_short = (char*)tileset_default_name; 252 } 253 } 254 255 if (tileset_popup_on && 256 nk_popup_begin(ctx, NK_POPUP_STATIC, "Select tileset size", 257 NK_WINDOW_NO_SCROLLBAR | NK_WINDOW_TITLE, 258 nk_rect(200, 30, 320, 140))) { 259 static int tileset_width = 5; 260 static int tileset_height = 3; 261 262 nk_layout_row_dynamic(ctx, 30, 1); 263 nk_property_int(ctx, "Tileset width (in tiles):", 1, &tileset_width, 20, 1, 0.4f); 264 nk_property_int(ctx, "Tileset height (in tiles):", 1, &tileset_height, 50, 1, 0.5f); 265 266 nk_layout_row_dynamic(ctx, 30, 2); 267 if (nk_button_label(ctx, "Confirm")) { 268 if (tileset_images) { 269 free(tileset_images); 270 glDeleteTextures(1, &tilemap.tex); 271 tilemap.tex= 0; 272 } 273 274 tileset_images = create_tiles(tileset_filename, tileset_width, tileset_height); 275 tileset_image_count = tileset_width * tileset_height; 276 277 tilemap.tileset_width = tileset_width; 278 tilemap.tileset_height = tileset_height; 279 280 if (tilemap.width) { 281 hs_aroom_set_tilemap(current_room, &tilemap, 1); 282 } 283 284 tileset_popup_on = false; 285 tileset_chosen = true; 286 nk_popup_close(ctx); 287 } 288 if (nk_button_label(ctx, "Quit")) { 289 tileset_popup_on = false; 290 nk_popup_close(ctx); 291 } 292 293 nk_popup_end(ctx); 294 } 295 296 if (tileset_chosen) { 297 nk_layout_row_dynamic(ctx, 20, 1); 298 nk_label(ctx, "Selected tile:", NK_TEXT_CENTERED); 299 300 nk_layout_row_dynamic(ctx, 190, 1); 301 nk_image(ctx, tileset_images[selected_tile]); 302 303 nk_layout_row_dynamic(ctx, 60, 3); 304 for (uint32_t i = 0; i < tileset_image_count; i++) { 305 if (nk_button_image(ctx, tileset_images[i])) 306 selected_tile = i; 307 } 308 } 309 } 310 nk_end(ctx); 311 } 312 313 static inline void loop() 314 { 315 glViewport(0, 0, gd.height, gd.width); 316 hs_clear(0.2, 0.2, 0.2, 1.0, 0); 317 glEnable(GL_BLEND); 318 319 if (tilemap.vertices && gd.width > 200) { 320 glViewport(tilemap_offset_x, tilemap_offset_y, tilemap_screen_width, tilemap_screen_height); 321 322 hs_tex2d_activate(tilemap.tex, GL_TEXTURE0); 323 hs_tilemap_draw(tilemap); 324 325 if (tileset_chosen) { 326 static uint32_t undo_buffer_update = true; 327 328 if (hs_get_key_toggle(gd, undo_key) == HS_KEY_PRESSED) { 329 undo_buffer_update = false; 330 memcpy(current_room.data, undo_room.data, current_room.width * current_room.height); 331 hs_aroom_set_tilemap(current_room, &tilemap, 1); 332 } else if (glfwGetMouseButton(gd.window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS) { 333 double xpos, ypos; 334 glfwGetCursorPos(gd.window, &xpos, &ypos); 335 336 // reverse y axis 337 ypos -= gd.height; 338 ypos *= -1; 339 int32_t tilex = (xpos - tilemap_offset_x) / (tilemap.tile_width * tilemap_screen_width); 340 int32_t tiley = (ypos - tilemap_offset_y) / (tilemap.tile_height * tilemap_screen_height); 341 if (hs_aroom_get_xy(current_room, tilex, tiley) != selected_tile && 342 xpos > tilemap_offset_x && xpos < tilemap_offset_x + tilemap_screen_width && 343 ypos > tilemap_offset_y && ypos < tilemap_offset_y + tilemap_screen_height) { 344 if (undo_buffer_update) { 345 undo_buffer_update = false; 346 memcpy(undo_room.data, current_room.data, undo_room.width * undo_room.height); 347 } 348 hs_aroom_set_xy(¤t_room, tilex, tiley, selected_tile); 349 350 hs_tilemap_set_xy(&tilemap, tilex, tiley, selected_tile); 351 hs_tilemap_update_vbo(tilemap); 352 } 353 } else if (!undo_buffer_update) { 354 undo_buffer_update = true; 355 } 356 } 357 } 358 359 side_bar(); 360 361 /* IMPORTANT: `nk_glfw_render` modifies some global OpenGL state 362 * with blending, scissor, face culling, depth test and viewport and 363 * defaults everything back into a default state. 364 * Make sure to either a.) save and restore or b.) reset your own state after 365 * rendering the UI. */ 366 nk_glfw3_render(&glfw, NK_ANTI_ALIASING_ON, MAX_VERTEX_BUFFER, MAX_ELEMENT_BUFFER); 367 } 368 369 int main() 370 { 371 init(); 372 hs_loop(gd, nk_glfw3_new_frame(&glfw); loop()); 373 374 nk_glfw3_shutdown(&glfw); 375 hs_exit(); 376 return 0; 377 }