Image* active_image;
Font* active_font;
+ Font* default_font;
bh_arr(mat3) world_transforms;
b32 world_transform_dirty;
void gfx_image_unbind(ImmediateRenderer *ir);
Font* gfx_font_load(ImmediateRenderer *ir, char* font_path, i32 font_size);
+void gfx_font_destroy(ImmediateRenderer *ir, Font *font);
+void gfx_font_use(ImmediateRenderer *ir, Font *font);
f32 gfx_font_get_text_width(ImmediateRenderer *ir, char* msgptr, i32 msglen);
#endif
void mat3_identity(mat3 *out);
void mat3_mul(mat3 *out, mat3 a, mat3 b);
-#endif
\ No newline at end of file
+// Logging
+typedef enum LogLevel {
+ DEBUG, INFO, WARNING, ERROR,
+ Log_Level_Count
+} LogLevel;
+extern LogLevel log_level;
+extern char *log_level_strings[Log_Level_Count];
+
+void log_level_set(LogLevel level);
+void logprint(LogLevel level, char *format, ...);
+
+#endif
// State
setClearColor :: (r, g, b: f32, a: f32 = 1) -> void #foreign "heartbreak" "graphics_set_clear_color" ---
-setColor :: (r, g, b: f32, a: f32 = 1) -> void #foreign "heartbreak" "graphics_set_color" ---
+setColor :: (r, g, b: f32, a: f32 = 1) -> void #foreign "heartbreak" "graphics_set_color" ---
+setLineWidth :: (width: i32) -> void #foreign "heartbreak" "graphics_set_line_width" ---
//
// Drawing
pop :: () -> void #foreign "heartbreak" "graphics_pop" ---
//
-// Objects
+// Images
//
Image :: #type i64
newImage :: (path: str) -> Image #foreign "heartbreak" "graphics_image_load" ---
drawImage :: (img: Image, x, y: f32, w: f32 = -1, h: f32 = -1) -> void #foreign "heartbreak" "graphics_image_draw" ---
-#private_file imageParam :: (img: Image, param: i32, value: i32) -> bool #foreign "heartbreak" "graphics_image_param" ---
+#local imageParam :: (img: Image, param: i32, value: i32) -> bool #foreign "heartbreak" "graphics_image_param" ---
ImageFilter :: enum {
Nearest :: 0x01;
getImageProperty :: (img: Image, prop: ImageProperty) -> i32 #foreign "heartbreak" "graphics_image_property" ---
getImageDimensions :: (img: Image) => getImageProperty(img, .Width), getImageProperty(img, .Height);
+//
+// Fonts
+//
+Font :: #type i64
+newFont :: (path: str, size: i32) -> Font #foreign "heartbreak" "graphics_font_load" ---
+setFont :: (font: Font = 0) -> void #foreign "heartbreak" "graphics_font_set" ---
getTextWidth :: (text: str) -> f32 #foreign "heartbreak" "graphics_get_text_width" ---
+
+//
+// Meshes
+//
+
+
+//
+// Shaders
+//
Right_Alt :: 346;
Right_Super :: 347;
Menu :: 348;
- Last :: 348;
+ Last :: Menu;
}
ButtonConstant :: enum {
Button_7 :: 6;
Button_8 :: 7;
- Left :: 0;
- Right :: 1;
- Middle :: 2;
+ Left :: Button_1;
+ Right :: Button_2;
+ Middle :: Button_3;
}
keyIsDown :: (key: KeyConstant) -> bool #foreign "heartbreak" "input_key_is_down" ---
#load "core/std"
#load "./heartbreak"
-#private hb :: package heartbreak
+#package hb :: package heartbreak
-#private_file symbol_source :: package main
-
-#if !#defined(symbol_source.load) {
- load :: () {}
-}
-
-#if !#defined(symbol_source.update) {
- update :: (dt: f32) {}
-}
-
-#if !#defined(symbol_source.draw) {
- draw :: () {}
-}
+#local symbol_source :: package main
// This is one place where top-level macros would be nice.
#if #defined(symbol_source.keydown) { #export "__heartbreak_keydown" symbol_source.keydown }
ir->current_color = (ImmediateColor) { 1, 1, 1, 1 };
ir->world_transform_dirty = 1;
- ir->active_font = gfx_font_load(ir, "/usr/share/fonts/nerd-fonts-complete/TTF/mononoki-Regular Nerd Font Complete Mono.ttf", 32.0f);
+ // @TODO Use a font that is within this project
+ ir->default_font = gfx_font_load(ir, "/usr/share/fonts/nerd-fonts-complete/TTF/mononoki-Regular Nerd Font Complete Mono.ttf", 32.0f);
+ ir->active_font = ir->default_font;
}
void gfx_immediate_renderer_flush(ImmediateRenderer *ir) {
return font;
}
+void gfx_font_destroy(ImmediateRenderer *ir, Font *font) {
+ bh_free(ir->allocator, font->name);
+ glDeleteTextures(1, &font->texture);
+ bh_free(ir->allocator, font->char_data);
+ bh_free(ir->allocator, font);
+}
+
+void gfx_font_use(ImmediateRenderer *ir, Font *font) {
+ // If the desired image is already bound, then just keep it bound.
+ if (ir->active_font == font) return;
+
+ if (font == NULL) {
+ ir->active_font = ir->default_font;
+ } else {
+ ir->active_font = font;
+ }
+}
+
f32 gfx_font_get_text_width(ImmediateRenderer *ir, char* msgptr, i32 msglen) {
stbtt_aligned_quad quad;
wasm_val_vec_t results;
wasm_val_vec_new_uninitialized(&args, 0);
+ logprint(INFO, "Calling _start in WASM module");
wasm_trap_t* result = wasm_func_call(start_func, &args, &results);
if (result != NULL) {
goto cleanup;
error_handling:
- bh_printf("An error occured trying to run the WASM module...\n");
+ logprint(ERROR, "An error occured trying to run the WASM module...\n");
cleanup:
if (wasm_instance) wasm_instance_delete(wasm_instance);
}
int main(int argc, char* argv[]) {
+ log_level_set(WARNING);
+ logprint(INFO, "Heartbreak version " VERSION);
+
if (argc < 2) {
bh_printf("Expected a .wasm file to run.\n");
return 1;
return 1;
}
+ logprint(DEBUG, "Loading file %s", argv[1]);
bh_allocator heap_allocator = bh_heap_allocator();
bh_file_contents wasm_file = bh_file_read_contents_direct(heap_allocator, argv[1]);
+ logprint(INFO, "Loaded file %s", argv[1]);
run_wasm_file((bh_buffer) {
.data = wasm_file.data,
return NULL;
}
+HEARTBREAK_DEF(set_line_width, (WASM_I32), ()) {
+ glLineWidth(params->data[0].of.i32);
+ return NULL;
+}
+
HEARTBREAK_DEF(rectangle, (WASM_I32,WASM_F32,WASM_F32,WASM_F32,WASM_F32), ()) {
gfx_image_unbind(&renderer);
bh_printf("Loading image: %b\n", str_data, str_size);
- u8 filename[512];
+ char* filename = alloca(str_size + 1);
strncpy(filename, str_data, str_size);
filename[str_size] = '\0';
i32 param = params->data[1].of.i32;
i32 value = params->data[2].of.i32;
- i32 filter;
+
+ static const GLint filters[] = { -1, GL_NEAREST, GL_LINEAR };
+ static const GLint wraps[] = { -1, GL_CLAMP_TO_EDGE, GL_MIRRORED_REPEAT, GL_REPEAT };
switch (param) {
- case 1:
- filter = -1;
- if (value == 1) filter = GL_NEAREST;
- if (value == 2) filter = GL_LINEAR;
- if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
- break;
- case 2:
- filter = -1;
- if (value == 1) filter = GL_NEAREST;
- if (value == 2) filter = GL_LINEAR;
- if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
- break;
- case 3:
- filter = -1;
- if (value == 1) filter = GL_CLAMP_TO_EDGE;
- if (value == 2) filter = GL_MIRRORED_REPEAT;
- if (value == 3) filter = GL_REPEAT;
- if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, filter);
- break;
- case 4:
- filter = -1;
- if (value == 1) filter = GL_CLAMP_TO_EDGE;
- if (value == 2) filter = GL_MIRRORED_REPEAT;
- if (value == 3) filter = GL_REPEAT;
- if (filter > 0) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, filter);
- break;
+ case 1: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filters[value]); break;
+ case 2: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filters[value]); break;
+ case 3: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wraps[value]); break;
+ case 4: glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wraps[value]); break;
}
glBindTexture(GL_TEXTURE_2D, -1);
return NULL;
}
+HEARTBREAK_DEF(font_load, (WASM_I32, WASM_I32, WASM_I32), (WASM_I64)) {
+ char* str_data = wasm_memory_data(wasm_memory) + params->data[0].of.i32;
+ u32 str_size = params->data[1].of.i32;
+
+ char *font_name = alloca(str_size + 1);
+ strncpy(font_name, str_data, str_size);
+ font_name[str_size] = '\0';
+
+ Font *new_font = gfx_font_load(&renderer, font_name, params->data[2].of.i32);
+ wasm_val_init_ptr(&results->data[0], new_font);
+ return NULL;
+}
+
+HEARTBREAK_DEF(font_destroy, (WASM_I64), ()) {
+ Font *font = (Font *) params->data[0].of.i64;
+ assert(font->magic_number == 0xbeefbeefbeefbeef);
+
+ gfx_font_destroy(&renderer, font);
+
+ return NULL;
+}
+
+HEARTBREAK_DEF(font_set, (WASM_I64), ()) {
+ Font *font = (Font *) params->data[0].of.i64;
+ if (font) assert(font->magic_number == 0xbeefbeefbeefbeef);
+
+ gfx_font_use(&renderer, font);
+
+ return NULL;
+}
+
HEARTBREAK_DEF(get_text_width, (WASM_I32, WASM_I32), (WASM_F32)) {
char* str_data = wasm_memory_data(wasm_memory) + params->data[0].of.i32;
u32 str_size = params->data[1].of.i32;
HEARTBREAK_MODULE {
HEARTBREAK_FUNC(set_clear_color)
- HEARTBREAK_FUNC(clear)
HEARTBREAK_FUNC(set_color)
+ HEARTBREAK_FUNC(set_line_width)
+
+ HEARTBREAK_FUNC(clear)
HEARTBREAK_FUNC(rectangle)
HEARTBREAK_FUNC(circle)
HEARTBREAK_FUNC(ellipse)
HEARTBREAK_FUNC(image_param)
HEARTBREAK_FUNC(image_property)
+ HEARTBREAK_FUNC(font_load)
+ HEARTBREAK_FUNC(font_destroy)
+ HEARTBREAK_FUNC(font_set)
HEARTBREAK_FUNC(get_text_width)
{ NULL }
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwMakeContextCurrent(glfw_window);
+
glfwSetWindowSizeCallback(glfw_window, __glfw_window_size_callback);
glfwSetKeyCallback(glfw_window, __glfw_key_callback);
glfwSetMouseButtonCallback(glfw_window, __glfw_mouse_button_callback);
glfwSwapInterval(1);
- // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
}
}
+
+LogLevel log_level = INFO;
+char *log_level_strings[Log_Level_Count] = {
+" DEBUG ",
+" INFO ",
+"WARNING",
+" ERROR ",
+};
+
+void log_level_set(LogLevel level) {
+ log_level = level;
+}
+
+void logprint(LogLevel level, char *format, ...) {
+ if (level < log_level) return;
+
+ va_list args;
+ va_start(args, format);
+
+ char buf[2048];
+ vsnprintf(buf, 2048, format, args);
+
+ va_end(args);
+
+ printf("[%s] %s\n", log_level_strings[level], buf);
+}
--- /dev/null
+#load "./../misc/onyx/qhb"
+
+use package core
+use package core.intrinsics.onyx
+
+Pos :: struct { x, y: i32; }
+
+Board :: struct {
+ Cell :: enum #flags {
+ Empty :: 0;
+ Covered :: 1;
+
+ Mine :: 2;
+ Flag :: 3;
+
+ Is_Number :: 0x10;
+ Number1 :: Is_Number | 1;
+ Number2 :: Is_Number | 2;
+ Number3 :: Is_Number | 3;
+ Number4 :: Is_Number | 4;
+ Number5 :: Is_Number | 5;
+ Number6 :: Is_Number | 6;
+ Number7 :: Is_Number | 7;
+ Number8 :: Is_Number | 8;
+ }
+
+ width, height : i32;
+ visible_cells : [] Cell;
+ true_cells : [] Cell;
+}
+
+init_board :: (use this: ^Board, w, h: i32) {
+ width = w;
+ height = h;
+ memory.alloc_slice(^visible_cells, w * h);
+ memory.alloc_slice(^true_cells, w * h);
+}
+
+generate_board :: (use this: ^Board, mine_count: i32) {
+ // Assumes an initialized board
+
+ for ^cell: visible_cells do *cell = .Covered;
+ for ^cell: true_cells do *cell = .Empty;
+
+ mine_positions := array.make(Pos);
+ for i: mine_count {
+ while true {
+ mx, my := random.between(0, width - 1), random.between(0, height - 1);
+ if get_true_cell(this, mx, my) != .Mine {
+ set_true_cell(this, mx, my, .Mine);
+ mine_positions << .{mx, my};
+ break;
+ }
+ }
+ }
+
+ for ^mine_pos: mine_positions {
+ for neighbor: neighbors(this, mine_pos.x, mine_pos.y) {
+ cell := get_true_cell(this, neighbor.x, neighbor.y);
+ if cell == .Empty do cell = .Number1;
+ elseif cell&.Is_Number do cell += 1;
+
+ set_true_cell(this, neighbor.x, neighbor.y, cell);
+ }
+ }
+}
+
+neighbors :: (board: ^Board, x, y: i32, include_corners := true) -> Iterator(Pos) {
+ Context :: struct {
+ board: ^Board;
+ cx, cy: i32;
+ dx, dy: i32;
+
+ include_corners: bool;
+ }
+
+ context := new(Context);
+ *context = .{ board, x, y, -1, -1, include_corners };
+
+ next :: (use _: ^Context) -> (Pos, bool) {
+ step :: macro () {
+ @Gross @Cleanup
+ dx += 1;
+ if dx > 1 { dx = -1; dy += 1; }
+ if math.abs(dx) + math.abs(dy) == 2 && !include_corners{
+ dx += 1;
+ if dx > 1 { dx = -1; dy += 1; }
+ }
+ }
+
+ while true {
+ if dy > 1 do break;
+ if dx == 0 && dy == 0 { step(); continue; }
+
+ rx, ry := cx + dx, cy + dy;
+ step();
+
+ if rx < 0 || rx >= board.width || ry < 0 || ry >= board.height do continue;
+ return .{rx, ry}, true;
+ }
+
+ return __zero_value(Pos), false;
+ }
+
+ return Iterator(Pos).{ context, next, cfree };
+}
+
+get_visible_cell :: (use this: ^Board, x, y: i32) => {
+ if x >= 0 && x < width && y >= 0 && y < height do return visible_cells[x + y * width];
+ return .Empty;
+}
+
+set_visible_cell :: (use this: ^Board, x, y: i32, cell: Board.Cell) => {
+ if x >= 0 && x < width && y >= 0 && y < height do visible_cells[x + y * width] = cell;
+}
+
+get_true_cell :: (use this: ^Board, x, y: i32) => {
+ if x >= 0 && x < width && y >= 0 && y < height do return true_cells[x + y * width];
+ return .Empty;
+}
+
+set_true_cell :: (use this: ^Board, x, y: i32, cell: Board.Cell) => {
+ if x >= 0 && x < width && y >= 0 && y < height do true_cells[x + y * width] = cell;
+}
+
+reveal_cell :: (use this: ^Board, x, y: i32) => {
+ set_visible_cell(this, x, y, get_true_cell(this, x, y));
+}
+
+flood_reveal :: (use this: ^Board, x, y: i32) => {
+ positions := array.make(Pos);
+ positions << .{x, y};
+
+ while positions.count != 0 {
+ p := positions[0];
+ array.delete(^positions, 0);
+
+ reveal_cell(this, p.x, p.y);
+ if get_true_cell(this, p.x, p.y) & .Is_Number do continue;
+
+ for n: neighbors(this, p.x, p.y, include_corners=false) {
+ true_cell := get_true_cell(this, n.x, n.y);
+ visible_cell := get_visible_cell(this, n.x, n.y);
+ if true_cell != .Mine && visible_cell == .Covered {
+ positions << n;
+ }
+ }
+ }
+}
+
+cell_size :: 32.0f
+border :: 2.0f
+
+board: Board;
+
+load :: () {
+ random.set_seed(clock.time());
+ init_board(^board, 20, 20);
+ generate_board(^board, 5);
+}
+
+update :: (dt: f32) {
+ if hb.input.keyIsDown(.Escape) {
+ hb.window.setShouldClose(true);
+ }
+
+ #persist last_mouse_down := false;
+ mouse_down := hb.input.mouseIsDown(.Left);
+ if last_mouse_down && !mouse_down {
+ mx, my := hb.input.mouseGetPos();
+ cx, cy := cast(i32) math.floor(mx / cell_size), cast(i32) math.floor(my / cell_size);
+ if cx < board.width && cy < board.height {
+ flood_reveal(^board, cx, cy);
+ }
+ }
+ last_mouse_down = mouse_down;
+}
+
+draw :: () {
+ size :: cell_size
+
+ hb.graphics.setClearColor(0.05, 0.05, 0.05, 1);
+ hb.graphics.clear();
+
+ for y: board.height {
+ for x: board.width {
+ cell := get_visible_cell(^board, x, y);
+ if cell == .Empty {
+ hb.graphics.setColor(0.1, 0.1, 0.1);
+ hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
+ }
+
+ if cell == .Covered {
+ hb.graphics.setColor(0.6, 0.6, 0.6);
+ hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
+ }
+
+ if cell == .Mine {
+ hb.graphics.setColor(0.1, 0.1, 0.1);
+ hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
+ hb.graphics.setColor(1, 0, 0);
+ hb.graphics.circle(.Fill, ~~x * size + 0.5 * size, ~~y * size + 0.5 * size, (size - 2 * border) / 2);
+ }
+
+ if cell & .Is_Number {
+ buf: [2] u8;
+ @Bug // Unary bitwise-not does not work on enum values
+ s := conv.str_format(buf, "{}", cast(i32) (cell - .Is_Number));
+ w := hb.graphics.getTextWidth(s);
+
+ hb.graphics.setColor(0.3, 0.3, 0.3);
+ hb.graphics.rectangle(.Fill, ~~x * size, ~~y * size, size - border, size - border);
+ hb.graphics.setColor(0.7, 0.5, 0.5);
+ hb.graphics.print(s, ~~x * size + (size - w) / 2, ~~(y + 1) * size - 3 * border);
+ }
+ }
+ }
+
+ mx, my := hb.input.mouseGetPos();
+ mx, my = size * math.floor(mx / size), size * math.floor(my / size);
+ if mx < ~~board.width * size && my < ~~board.height * size {
+ hb.graphics.setColor(1, 1, 1, 0.3);
+ hb.graphics.rectangle(.Fill, ~~mx, ~~my, size - border, size - border);
+ }
+}
printf("Width is: {}\n", hb.graphics.getTextWidth("Hello, World!"));
+ better_font := hb.graphics.newFont("/usr/share/fonts/TTF/InputSerif-Bold.ttf", 32);
+ assert(better_font != 0, "Failed to load font!");
+ hb.graphics.setFont(better_font);
+ // hb.graphics.setFont();
+
// hb.window.setDimensions(1200, 900);
}
-keydown :: (key, mods: i32) {
+keydown :: (key: hb.input.KeyConstant, mods: i32) {
printf("Key down! {} {}\n", key, mods);
}
-keyup :: (key: i32) {
+keyup :: (key: hb.input.KeyConstant) {
printf("Key up! {}\n", key);
}
hb.graphics.arc(.Line, 300, 300, 100, math.PI / 4, math.PI * 7 / 4);
hb.graphics.setColor(0, 1, 1);
+ hb.graphics.rectangle(.Fill, 400, 90 - 32, 200 + math.sin(t) * 100, 10);
hb.graphics.print("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+`~[{}]\|<,>./?'\";:", 400, 100, 200 + math.sin(t) * 100);
+ hb.graphics.setLineWidth(3);
hb.graphics.setColor(1, 1, 1);
- hb.graphics.circle(.Line, 200, 500, 100, 100);
+ hb.graphics.circle(.Line, 200, 500, 50, 100);
}