// 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" ---
//
//
// Images
//
-Image :: #type i64
+Image :: #distinct i64
+#operator == macro (a, b: Image) => cast(i64) a == cast(i64) b;
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" ---
//
// Fonts
//
-Font :: #type i64
+Font :: #distinct i64
+#operator != macro (a, b: Font) => cast(i64) a != cast(i64) b;
+#operator == macro (a, b: Font) => cast(i64) a == cast(i64) b;
newFont :: (path: str, size: i32) -> Font #foreign "heartbreak" "graphics_font_load" ---
setFont :: (font: Font = 0) -> void #foreign "heartbreak" "graphics_font_set" ---
//
Image* gfx_image_load(ImmediateRenderer *ir, const char* filename) {
Image* img = malloc(sizeof(Image));
+
img->img_data = stbi_load(filename, &img->width, &img->height, &img->channels, 4);
+ if (img->img_data == NULL) {
+ free(img);
+ logprint(ERROR, "Failed to load image '%s'.", filename);
+ return NULL;
+ }
+
img->magic_number = 0xdeadbeefdeadbeef;
glGenTextures(1, &img->texture);
}
void gfx_image_unbind(ImmediateRenderer *ir) {
+ if (ir->active_image == NULL) return;
if (ir->tris_vertex_count > 0) gfx_immediate_renderer_flush(ir);
ir->active_image = NULL;
+ glUseProgram(ir->tris_shader.program);
glUniform1f(ir->tris_shader.texture_enabled_uniform, 0);
}
use package core
+assets : struct {
+ ["./tests/player.png"]
+ player: hb.graphics.Image;
+
+ ["./assets/fonts/mononoki-Regular Nerd Font Complete Mono.ttf", 72]
+ title_font: hb.graphics.Font;
+}
+
+load_assets :: (assets: any) {
+ ptr_asset_info := cast(^type_info.Type_Info_Pointer) type_info.get_type_info(assets.type);
+ assert(ptr_asset_info.kind == .Pointer, "Loading assets only works for pointers to strucutres.");
+ asset_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(ptr_asset_info.to);
+ assert(asset_info.kind == .Struct, "Loading assets only works for pointers to strucutres.");
+
+ asset_store := cast(^u8) *cast(^rawptr) assets.data;
+
+ for ^asset: asset_info.members {
+ switch asset.type {
+ case hb.graphics.Image {
+ path := *(cast(^str) array.first(asset.tags, (t) => t.type == str).data);
+ img := hb.graphics.newImage(path);
+
+ if img == 0 {
+ println("Failed to load image");
+ continue;
+ }
+
+ @TODO // Add settings like scaling, filters, etc.
+
+ *cast(^hb.graphics.Image) (asset_store + asset.offset) = img;
+ }
+
+ case hb.graphics.Font {
+ path := *(cast(^str) array.first(asset.tags, (t) => t.type == str).data);
+ size := *(cast(^i32) array.first(asset.tags, (t) => t.type == i32).data);
+
+ font := hb.graphics.newFont(path, ~~size);
+
+ if font == 0 {
+ println("Failed to load font");
+ continue;
+ }
+
+ *cast(^hb.graphics.Font) (asset_store + asset.offset) = font;
+ }
+ }
+ }
+}
+
Vec2 :: struct (T: type_expr) {
x, y: T;
GameState :: enum { MainMenu; Playing; Paused; Lost; }
-state := GameState.Playing;
+state := GameState.MainMenu;
the_snake: Snake;
the_food: Vec2i;
load :: () {
hb.window.setTitle("Snake 🐍");
+ load_assets(^assets);
+
the_snake = snake_make(.{ 0, 0 });
the_food = .{ 10, 10 };
}
hb.window.setShouldClose(true);
}
+ if state == .MainMenu {
+ if hb.input.keyIsDown(.Space) {
+ state = .Playing;
+ return;
+ }
+ }
+
if state != .Playing do return;
#persist last_left_down := false;
ww, wh := hb.window.getDimensions();
+ centered_text :: macro (text: str, y: f32) {
+ width := hb.graphics.getTextWidth(text);
+ hb.graphics.print(text, (~~ww - width) / 2, y);
+ }
+
switch state {
case .MainMenu {
- title := "Snake!";
- title_width := hb.graphics.getTextWidth(title);
- hb.graphics.print(title, (~~ww - title_width) / 2, 100);
+ hb.graphics.setFont(assets.title_font);
+ hb.graphics.setColor(0.2, 0.2, 0.2);
+ centered_text("Snake!", 100);
+
+ hb.graphics.setFont();
+ centered_text("Option 1", 200);
+ centered_text("Option 2", 240);
}
case .Playing {
draw_game();
- #if false {
+ #if true {
hb.graphics.setColor(1, 0, 0);
fps_buffer: [20] u8;
fps_str := conv.str_format(fps_buffer, "FPS: {}", cast(i32) hb.timer.getFPS());