began refactoring graphics out of this project
authorBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 26 Oct 2022 16:31:58 +0000 (11:31 -0500)
committerBrendan Hansen <brendan.f.hansen@gmail.com>
Wed, 26 Oct 2022 16:31:58 +0000 (11:31 -0500)
46 files changed:
.vscode/launch.json
run_tree/run.sh
src/build.onyx
src/entity/components/background.onyx
src/entity/components/collision_mask.onyx
src/entity/components/dispenser.onyx
src/entity/components/entryway.onyx
src/entity/components/money.onyx
src/entity/components/movement.onyx
src/entity/components/patron.onyx
src/entity/components/player.onyx
src/entity/editor.onyx
src/entity/entities.onyx
src/entity/items.onyx
src/entity/scene.onyx
src/entity/schematics/background.onyx
src/entity/schematics/entryway.onyx
src/entity/schematics/furniture.onyx
src/entity/schematics/patron.onyx
src/entity/schematics/player.onyx
src/entity/schematics/tap.onyx
src/game.onyx
src/gfx/canvas.onyx [deleted file]
src/gfx/font.onyx [deleted file]
src/gfx/immediate.onyx [deleted file]
src/gfx/mesh.onyx [deleted file]
src/gfx/shader.onyx [deleted file]
src/gfx/texture.onyx [deleted file]
src/gfx/ui.onyx [deleted file]
src/main.onyx
src/ogre/canvas.onyx [new file with mode: 0644]
src/ogre/colors.onyx [new file with mode: 0644]
src/ogre/font.onyx [new file with mode: 0644]
src/ogre/immediate.onyx [new file with mode: 0644]
src/ogre/input.onyx [new file with mode: 0644]
src/ogre/mesh.onyx [new file with mode: 0644]
src/ogre/shader.onyx [new file with mode: 0644]
src/ogre/texture.onyx [new file with mode: 0644]
src/ogre/ui.onyx [new file with mode: 0644]
src/ogre/vecmath.onyx [new file with mode: 0644]
src/ogre/window.onyx [new file with mode: 0644]
src/sfx/audio_manager.onyx
src/utils/asset_loader.onyx
src/utils/colors.onyx [deleted file]
src/utils/input.onyx [deleted file]
src/utils/vecmath.onyx [deleted file]

index ca18c20a03860e3424521354705ce1136b57afe1..66ac8cd207b79301aee6cdac5a894f21a9a96d41 100644 (file)
             "type": "onyx",
             "request": "launch",
             "name": "Launch",
-            "wasmFile": "game.wasm",
+            "onyxFiles": ["../src/build"],
             "workingDir": "${workspaceFolder}/run_tree",
-            "stopOnEntry": true,
-            "preLaunchTask": "Build debug game"
+            "stopOnEntry": true
+            // "preLaunchTask": "Build debug game"
         }
     ]
 }
index e9050d4926598b0e02213f3056374355a9790d5c..27fb7ad2668421bdd23f8f90f6e16d3160676706 100755 (executable)
@@ -6,4 +6,4 @@ case "$1" in
     run)   onyx-run $dest ;;
     debug) onyx-run --debug $dest ;;
     *)     onyx run -V -I ../src build $@ ;;
-esac
\ No newline at end of file
+esac
index a03806a802ab2bb89df44856913d8803df53b2ee..9246fe2ef0f628a3873d92313ab40a7445882747 100644 (file)
@@ -17,7 +17,6 @@ MINOR_VERSION :: 1
 #load_all "./entity"
 #load_all "./entity/components"
 #load_all "./entity/schematics"
-#load_all "./gfx"
 #load_all "./sfx"
 #load_all "./utils"
 
@@ -26,3 +25,4 @@ MINOR_VERSION :: 1
 #load "./../lib/openal/module"
 #load "./../lib/stb_truetype/module"
 #load "./../lib/stb_image/module"
+#load_all "./ogre"
index 3bce989b5b61c7d2d20c1e94bf46b776a2c32c4c..241071c799772ca2056dc5ef6694926565b69308 100644 (file)
@@ -1,5 +1,6 @@
 
 use core
+use ogre
 
 BackgroundComponent :: struct {
     use component: Component;
index c9d52b5cdb624ad9acf5dc70b3c8ac595ac5e026..707e9c7c37f130d1ebde7def990d40729bed56ae 100644 (file)
@@ -1,5 +1,6 @@
 
 use core
+use ogre
 
 CollisionMaskComponent :: struct {
     use component: Component;
@@ -8,12 +9,16 @@ CollisionMaskComponent :: struct {
     width: i32 = 50;
     height: i32 = 40;
 
-    #tag Entity_Store.Skip, Editor_Hidden
+    @Entity_Store.Skip @Editor_Hidden
     mask: [] bool;
 
-    #tag Entity_Store.Skip
+    @Entity_Store.Skip
     should_render := false;
 
+    init :: (use this: ^CollisionMaskComponent) {
+        mask = make([] bool, width * height);
+    }
+
     added :: (use this: ^CollisionMaskComponent, entity: ^Entity) {
         scene->modify_component(entity, RenderComponent) {
             comp.func = render;
@@ -21,21 +26,21 @@ CollisionMaskComponent :: struct {
     }
 
     update :: (use this: ^CollisionMaskComponent, entity: ^Entity, dt: f32) {
-        if mask.data == null {
-            memory.alloc_slice(^mask, width * height);
-
-            for y: height {
-                for x: width {
-                    mask[y * width + x] = false;
-                    area := Rect.{ ~~(x * grid_size), ~~(y * grid_size), ~~grid_size, ~~grid_size };
-
-                    for scene.entities {
-                        if it.flags & .Solid {
-                            if Rect.intersects(Entity.get_rect(it), area) {
-                                mask[y * width + x] = true;
-                                continue continue;
-                            }
-                        }
+        memory.set(mask.data, 0, width * height * sizeof bool);
+
+        for scene.entities {
+            if it.flags & .Solid {
+                r := it->get_rect();
+
+                g := cast(f32) grid_size;
+                x0 := cast(i32) math.floor(r.x / g);
+                y0 := cast(i32) math.floor(r.y / g);
+                x1 := cast(i32) math.ceil((r.x + r.w) / g);
+                y1 := cast(i32) math.ceil((r.y + r.h) / g);
+
+                for y: y0 .. y1 {
+                    for x: x0 .. x1 {
+                        mask[y * width + x] = true;
                     }
                 }
             }
index ecc60b0a8704fc2d72326506e60871e9639c48e0..649c8254dee51ed7ee588c4f2528910b5aec3c5e 100644 (file)
@@ -1,5 +1,6 @@
 
 use core
+use ogre
 
 //
 // Currently, DispenserComponents only dispense Item_Entity's, and no
@@ -9,12 +10,12 @@ use core
 DispenserComponent :: struct {
     use base: Component;
 
-    #tag Editor_Custom_Field.{render_item_picker}
+    @Editor_Custom_Field.{render_item_picker}
     item: str;
     max_timeout := 2.0f;
     draw_item := true;
 
-    #tag Entity_Store.Skip
+    @Entity_Store.Skip
     timeout := 0.0f;
 
     added :: (use this: ^DispenserComponent, entity: ^Entity) {
index c1801897b58021b22edaa47f6c0ecde1ad3756ec..d313e7877ef001148004e0c787396b1a4e33da9d 100644 (file)
@@ -1,5 +1,6 @@
 
 use core
+use ogre
 
 EntrywayComponent :: struct {
     use component: Component;
index a4e168926e72c426164e9dc7567a9b003d48ff0a..78a29218ac7a7923a22b6c06ae4a3ea6f4d23749 100644 (file)
@@ -1,5 +1,6 @@
 
 use core
+use ogre
 
 #local Transaction :: struct {
     amount: i32;
@@ -12,7 +13,7 @@ MoneyComponent :: struct {
     money: i32 = 100;
     money_sprite: Sprite;
 
-    #tag Editor_Hidden, Entity_Store.Skip
+    @Editor_Hidden, Entity_Store.Skip
     transactions: [..] Transaction;
 
     added :: (use this: ^MoneyComponent, entity: ^Entity) {
index ae7f97f609372d2aa89ba98b3f3ca83e3ede20ff..81f381677c4c63cee1f48d5a12b5f57f0f2e6de5 100644 (file)
@@ -1,6 +1,7 @@
 
 use core
 use glfw3
+use ogre
 
 
 Facing :: enum {
index 1879bc60377ba2753a51b18a6c4d5a43bc2245d4..bc8ac42bbc7aca650cc67e09ef2054254df6472e 100644 (file)
@@ -1,5 +1,6 @@
 
 use core
+use ogre
 
 #local Patron_State :: enum {
     Walking_To_Seat;
@@ -21,9 +22,9 @@ PatronComponent :: struct {
     annoy_timeout: f32;
     walk_speed: f32 = 100.0f;
 
-    #tag Editor_Hidden, Entity_Store.Skip
+    @Editor_Hidden, Entity_Store.Skip
     path: [..] Vector2;
-    #tag Editor_Hidden, Entity_Store.Skip
+    @Editor_Hidden, Entity_Store.Skip
     path_pos: i32;
 
     init :: (use this: ^PatronComponent) {
index 450972b77d30d7c4201efbe9a7b16753a9007330..bb99537293d07abf26440a6a9d63731075c6f905 100644 (file)
@@ -1,13 +1,14 @@
 
 use core
 use glfw3
+use ogre
 
 PlayerComponent :: struct {
     use base: Component;
 
     holding : Entity_ID;
 
-    #tag Editor_Hidden, Entity_Store.Skip
+    @Editor_Hidden, Entity_Store.Skip
     nearby_holding, nearby_interact : Entity_ID;
 
     update :: (player: ^PlayerComponent, use this: ^Entity, dt: f32) {
index 69b02b33a4d016f90ba0fb2e7deb94c8f57dd689..dd45a70afa30fb89d158129a086e573b2a829bf1 100644 (file)
@@ -10,7 +10,9 @@
 use core
 use opengles
 use glfw3
-#local type_info :: runtime.info
+use ogre
+use ogre.ui
+use runtime {type_info :: info}
 
 Editor_Range        :: struct {min, max: f32;}
 Editor_Disabled     :: struct {}
@@ -66,7 +68,7 @@ editor_update :: (dt: f32) {
     mouse_raw := mouse_get_position_vector();
     mouse_pos := mouse_raw - scene_render_offset;
 
-    if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) && mouse_raw.x < ~~window_width - sidebar_width && mouse_raw.x >= 0 {
+    if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) && mouse_raw.x < ~~window.width - sidebar_width && mouse_raw.x >= 0 {
         entity: ^Entity;
         if active_index == 0 {
             entity = scene->make();
@@ -87,7 +89,7 @@ editor_update :: (dt: f32) {
     mouse_raw := mouse_get_position_vector();
     mouse_pos := mouse_raw - scene_render_offset;
 
-    if mouse_raw.x < ~~window_width - sidebar_width && mouse_pos.x >= 0 {
+    if mouse_raw.x < ~~window.width - sidebar_width && mouse_pos.x >= 0 {
         if !dragging && !resizing &&
             (is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) || is_button_just_up(GLFW_MOUSE_BUTTON_RIGHT)) {
             if active_tab == .Edit do active_index = -1;
@@ -200,7 +202,7 @@ editor_draw :: () {
 
     { // Draw menu bar
         x := 0.0f;
-        w := cast(f32) window_width;
+        w := cast(f32) window.width;
         h := menubar_height;
         y := h * (editor_openness - 1);
 
@@ -229,9 +231,9 @@ editor_draw :: () {
     { // Draw sidebar, if necessary
         sidebar_width = editor_openness * 400.0f;
         w := sidebar_width;
-        x := ~~ window_width - w;
+        x := ~~ window.width - w;
         y := 40.0f;
-        h := ~~ window_height - y;
+        h := ~~ window.height - y;
         immediate_set_color(background_color);
         immediate_rectangle(x, y, w, h);
 
@@ -471,7 +473,7 @@ editor_draw :: () {
 
             w := sidebar_width / 2;
             h := 200.0f;
-            x := ~~ window_width - sidebar_width;
+            x := ~~ window.width - sidebar_width;
             render := render_field_editor;
 
             if custom_editors[it.type] != null_proc {
index 8ee2960b7fbbe4822246b5987a67d74981565ee5..7891c70d87b403bb2227f6eecdc711bec1ac8504 100644 (file)
@@ -1,9 +1,10 @@
 
 use core
 use glfw3
+use ogre
 
-#tag Entity_Schematic.{
-    (scene) => wall_create(scene, .{0,0}, .{0,0})
+@Entity_Schematic.{
+    (scene: ^Scene) => wall_create(scene, .{0,0}, .{0,0})
 }
 #local Wall :: struct {
     render :: (use this: ^Entity) {
@@ -25,9 +26,9 @@ wall_create :: (scene: ^Scene, pos, size: Vector2) -> ^Entity {
     return this;
 }
 
-#tag Entity_Schematic.{create}
+@Entity_Schematic.{create}
 #local Door :: struct {
-    create :: (scene) => door_create(scene, .Zero, .Zero);
+    create :: (scene: ^Scene) => door_create(scene, .Zero, .Zero);
 
     render :: (use this: ^Entity) {
         immediate_set_color(.{0.7, 0.7, 0.1});
@@ -85,15 +86,3 @@ door_create :: (scene: ^Scene, pos, size: Vector2) -> ^Entity {
     return this;
 }
 
-
-
-// @Relocate
-move_towards :: (v: ^$T, target: T, diff: T) {
-    if math.abs(target - *v) <= diff {
-        *v = target;
-        return;
-    }
-
-    if *v < target do *v += diff;
-    if *v > target do *v -= diff;
-}
index f084e94cff80313cda1e211a880b7904fc64eaee..a9afb9af403b9b6f982acf71d20ecebc0dca16e4 100644 (file)
@@ -1,5 +1,7 @@
 
 use core
+use ogre
+use ogre.ui
 
 
 Item :: struct {
@@ -87,7 +89,7 @@ item_store_get_item :: (use this: ^Item_Store, id: str) -> ^Item {
     return items[id];
 }
 
-#tag Entity_Schematic.{create}
+@Entity_Schematic.{create}
 #local Item_Entity :: struct {
 
     create :: (scene) => {
@@ -118,7 +120,7 @@ item_store_get_item :: (use this: ^Item_Store, id: str) -> ^Item {
 ItemComponent :: struct {
     use base: Component;
 
-    #tag Editor_Custom_Field.{render_item_picker}
+    @Editor_Custom_Field.{render_item_picker}
     item: str;
 
     get_info :: (use this: ^ItemComponent) -> ^Item {
index 5eb877c3e11fb3407f34011f64b0b36a2054faa2..af11f3725c4df44450d0edce900b1a35e22a2da7 100644 (file)
@@ -1,6 +1,7 @@
 
 use core
-use glfw3
+use ogre
+use ogre.ui
 
 Entity_Nothing :: cast(Entity_ID) 0
 Entity_ID :: #distinct u32
@@ -43,10 +44,10 @@ Entity_ID :: #distinct u32
 }
 
 Component :: struct {
-    #tag Editor_Hidden, Entity_Store.Skip
+    @Editor_Hidden, Entity_Store.Skip
     use vtable: ^Component_Vtable;
 
-    #tag Editor_Hidden, Entity_Store.Skip
+    @Editor_Hidden, Entity_Store.Skip
     type: type_expr;
 }
 
@@ -57,7 +58,7 @@ IsComponent :: interface (c: $C) {
 RenderComponent :: struct {
     use base: Component;
 
-    #tag Editor_Hidden, Entity_Store.Skip
+    @Editor_Hidden, Entity_Store.Skip
     func : (e: ^Entity) -> void;
 
     layer := 0;
@@ -82,13 +83,13 @@ SpriteRenderComponent :: struct {
     }
 }
 
-#tag Entity_Store.Skip
+@Entity_Store.Skip
 SizeComponent :: struct {
     use base: Component;
     func : (e: ^Entity) -> Rect;
 }
 
-#tag Entity_Store.Skip
+@Entity_Store.Skip
 InteractableComponent :: struct {
     use base: Component;
     interact: (e: ^Entity, interactor: ^Entity) -> void;
@@ -98,7 +99,7 @@ Entity :: struct {
     id: Entity_ID;
     flags: Entity_Flags;
 
-    #tag Entity_Store.Skip
+    @Entity_Store.Skip
     schematic: type_expr;
 
     nickname: str;
@@ -113,7 +114,7 @@ Entity :: struct {
         return Rect.{ pos.x - size.x / 2, pos.y - size.y / 2, size.x, size.y };
     };
 
-    #tag Entity_Store.Skip, Editor_Hidden
+    @Entity_Store.Skip, Editor_Hidden
     components: Map(type_expr, ^Component);
 
     has :: (use this: ^Entity,  component_type: type_expr) => components->has(component_type);
index 8e87669c0af35a44376a7a0f1b7e7f8ce8ed6c57..735bb54280b62b77ff02072a6417fadabee9d064 100644 (file)
@@ -3,7 +3,7 @@ use core
 
 // #local background_texture: Texture;
 
-#tag Entity_Schematic.{ Background.create }
+@Entity_Schematic.{ Background.create }
 Background :: struct {
     create :: (scene) => {
         this := scene->make();
index 7329eabc49579a977d69e8c0e80f2d12bffcbfe0..9b898d1956d1a4839b99cd710307f598efec4efe 100644 (file)
@@ -1,7 +1,7 @@
 
 use core
 
-#tag Entity_Schematic.{ Entryway.create }
+@Entity_Schematic.{ Entryway.create }
 Entryway :: struct {
     create :: (scene) => {
         this := scene->make();
index 5362952bc5e06a21777fe179297e6417207c5e6e..7fce57a5956183e0eeb7d53dc8091cad6d62e243 100644 (file)
@@ -1,7 +1,8 @@
 
 use core
+use ogre
 
-#tag Entity_Schematic.{
+@Entity_Schematic.{
     (scene) => Furniture.create(scene, .{0, 0})
 }
 Furniture :: struct {
index 17d60bd69ac92cb79b181907148d6b508a9c08f6..b6b1a638f76c899b9d2b7fcfe152863ecf767389 100644 (file)
@@ -1,8 +1,9 @@
 
 use core
+use ogre
 
-#tag Entity_Schematic.{
-    (scene) => Patron.create(scene, .{0,0})
+@Entity_Schematic.{
+    (scene: ^Scene) => Patron.create(scene, .{0,0})
 }
 Patron :: struct {
     create :: (scene: ^Scene, pos: Vector2) -> ^Entity {
index b13432b5024be5084a28cf6a4a1f45a274c0477f..c71e3670099a6a4cd7c62a289df0c55c8efeb523 100644 (file)
@@ -1,6 +1,7 @@
 
 use core
 use glfw3
+use ogre
 
 Player_Controls :: struct {
     up       : i32;
@@ -30,7 +31,7 @@ player_2_controls :: Player_Controls.{
 }
 
 
-#tag Entity_Schematic.{
+@Entity_Schematic.{
     (scene: ^Scene) => Player.create(scene, .{0,0})
 }
 Player :: struct {
@@ -61,8 +62,8 @@ Player :: struct {
     }
 
     #persist assets: struct {
-        #tag "assets/images/player.png"
-        #tag Texture_Wrap.Clamp
+        @"assets/images/player.png"
+        @Texture_Wrap.Clamp
         texture: Texture;
     }
 }
index 60baef6486220e4f740e9aca8bf9496c82a70617..95e619a98e8469b9f114d9e141e0ce0f876eda12 100644 (file)
@@ -1,5 +1,6 @@
+use ogre
 
-#tag Entity_Schematic.{
+@Entity_Schematic.{
     (scene) => Tap.create(scene, .{0,0}, .{0,0})
 }
 Tap :: struct {
index 427df1c3181887204258a9cedd4bc650f6705047..f798106474d0cb563f44b41e3da6f91fd493f629 100644 (file)
@@ -2,6 +2,7 @@
 use core
 use glfw3
 use opengles
+use ogre
 
 //
 // Game Global Variables
@@ -88,23 +89,23 @@ game_draw :: () {
     texture_wrap(^texture, .Clamp);
     view_rect: Rect;
     if !editor_shown() {
-        view_rect = Rect.{0, 0, ~~window_width, ~~window_height};
+        view_rect = Rect.{0, 0, ~~window.width, ~~window.height};
     } else {
         // view_rect.x = math.lerp(editor_open_percent(), cast(f32) ((window_width - scene_canvas.width) / 2), 0);
-        view_rect.x = math.lerp(editor_open_percent(), cast(f32) ((window_width - scene_canvas.width) / 2), cast(f32) ((window_width - scene_canvas.width - 400) / 2));
-        view_rect.y = ~~ ((window_height - scene_canvas.height) / 2);
+        view_rect.x = math.lerp(editor_open_percent(), cast(f32) ((window.width - scene_canvas.width) / 2), cast(f32) ((window.width - scene_canvas.width - 400) / 2));
+        view_rect.y = ~~ ((window.height - scene_canvas.height) / 2);
         view_rect.w = ~~ scene_canvas.width;
         view_rect.h = ~~ scene_canvas.height;
     }
     scene_render_offset = Rect.top_left(view_rect);
 
     glDisable(GL_CULL_FACE);
-    immediate_image(^texture, view_rect.x, ~~window_height - view_rect.y, view_rect.w, -view_rect.h);
+    immediate_image(^texture, view_rect.x, ~~window.height - view_rect.y, view_rect.w, -view_rect.h);
     if distortion_enabled {
         shader_use(distortion_shader);
         shader_set_uniform(distortion_shader, #cstr "u_texture", 0);
         shader_set_uniform(distortion_shader, #cstr "u_tex_size", Vector2.{800, 608});
-        shader_set_uniform(distortion_shader, #cstr "u_output_size", Vector2.{~~window_width, ~~window_height});
+        shader_set_uniform(distortion_shader, #cstr "u_output_size", Vector2.{~~window.width, ~~window.height});
         immediate_flush(false);
     } else {
         immediate_flush();
diff --git a/src/gfx/canvas.onyx b/src/gfx/canvas.onyx
deleted file mode 100644 (file)
index c8743d3..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-
-use core
-use opengles
-
-
-Canvas :: struct {
-    width, height: i32;
-
-    framebuffer: GLint;
-    depth_stencil_buffer: GLint;
-    color_texture: GLint;
-}
-
-canvas_make :: (width, height: i32) -> Canvas {
-    canvas: Canvas;
-    canvas.width = width;
-    canvas.height = height;
-
-    glGenFramebuffers(1, ^canvas.framebuffer);
-    glBindFramebuffer(GL_FRAMEBUFFER, canvas.framebuffer);
-
-    glGenTextures(1, ^canvas.color_texture);
-    glBindTexture(GL_TEXTURE_2D, canvas.color_texture);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, null);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
-    glBindTexture(GL_TEXTURE_2D, 0);
-    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas.color_texture, 0);
-
-    glGenRenderbuffers(1, ^canvas.depth_stencil_buffer);
-    glBindRenderbuffer(GL_RENDERBUFFER, canvas.depth_stencil_buffer);
-    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
-    glBindRenderbuffer(GL_RENDERBUFFER, 0);
-    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, canvas.depth_stencil_buffer);
-
-    if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE {
-        debug_log(.Error, "Framebuffer is not complete!");
-    }
-
-    glBindFramebuffer(GL_FRAMEBUFFER, 0);  
-    return canvas;
-}
-
-canvas_free :: (use canvas: ^Canvas) {
-    glDeleteFramebuffers(1, ^canvas.framebuffer);
-    glDeleteTextures(1, ^canvas.color_texture);
-    glDeleteRenderbuffers(1, ^canvas.depth_stencil_buffer);
-}
-
-canvas_use :: (use canvas: ^Canvas) {
-    if canvas == null {
-        glBindFramebuffer(GL_FRAMEBUFFER, 0);
-        update_view_matrix(window_width, window_height);
-    } else {
-        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
-        update_view_matrix(width, height);
-    }
-}
-
-canvas_to_texture :: (canvas: ^Canvas) => Texture.{ canvas.color_texture, canvas.width, canvas.height, 3, "<canvas>" };
-
diff --git a/src/gfx/font.onyx b/src/gfx/font.onyx
deleted file mode 100644 (file)
index 1ece709..0000000
+++ /dev/null
@@ -1,266 +0,0 @@
-
-use core
-use stb_truetype
-use opengles
-
-#local {
-    font_registry: Map(FontDescriptor, Font);
-    font_vbo:      GLint;
-    font_vao:      GLint;
-
-    font_shader:   Shader;
-    font_color:    Color;
-}
-
-fonts_init :: () {
-    map.init(^font_registry);
-
-    font_shader = shader_make("./assets/shaders/font.glsl");
-    shader_use(font_shader);
-    shader_link_window_matrix_block(font_shader);
-    shader_link_world_matrix_block(font_shader);
-
-    glGenVertexArrays(1, ^font_vao);
-    glBindVertexArray(font_vao);
-
-    font_interp_buffer: GLint;
-    glGenBuffers(1, ^font_interp_buffer);
-    glBindBuffer(GL_ARRAY_BUFFER, font_interp_buffer);
-    font_interp_data := f32.[
-        0.0, 0.0,
-        1.0, 0.0,
-        1.0, 1.0,
-        0.0, 1.0,
-    ];
-    glBufferData(GL_ARRAY_BUFFER, sizeof typeof font_interp_data, ~~^font_interp_data, GL_STATIC_DRAW);
-    glEnableVertexAttribArray(0);
-    glVertexAttribPointer(0, 2, GL_FLOAT, false, 2 * sizeof f32, ~~0);
-
-    font_index_buffer: GLint;
-    glGenBuffers(1, ^font_index_buffer);
-    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_index_buffer);
-    font_index_data := u8.[ 0, 1, 2, 0, 2, 3 ];
-    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof typeof font_index_data, ~~^font_index_data, GL_STATIC_DRAW);
-
-    glGenBuffers(1, ^font_vbo);
-    glBindBuffer(GL_ARRAY_BUFFER, font_vbo);
-    glBufferData(GL_ARRAY_BUFFER, 1024 * sizeof stbtt_aligned_quad, null, GL_STREAM_DRAW);
-
-    for 1..5 {
-        glEnableVertexAttribArray(it);
-        glVertexAttribDivisor(it, 1);
-    }
-    glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~ 0);
-    glVertexAttribPointer(2, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~16);
-    glVertexAttribPointer(3, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~8);
-    glVertexAttribPointer(4, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~24);
-
-    glBindBuffer(GL_ARRAY_BUFFER, -1);
-    glBindVertexArray(-1);
-
-    font_set_color(.{0,0,0});
-}
-
-
-Font :: struct {
-    texture: GLint;
-    texture_width, texture_height: i32;
-    chars: [] stbtt_packedchar;
-    em: f32;
-}
-
-font_make :: (fd: FontDescriptor) -> Font {
-    texture_size :: 256;
-
-    char_data := memory.make_slice(stbtt_packedchar, 96);
-
-    ttf_file := os.get_contents(fd.path);
-    if ttf_file.count == 0 {
-        println("Bad font");
-        return .{};
-    }
-    defer cfree(ttf_file.data);
-
-    pixels := calloc(texture_size * texture_size);
-    defer cfree(pixels);
-
-    ctx: stbtt_pack_context;
-    stbtt_PackBegin(^ctx, pixels, texture_size, texture_size, 0, 1);
-    stbtt_PackFontRange(^ctx, ttf_file.data, 0, ~~fd.size, #char " ", 96, char_data.data);
-    stbtt_PackEnd(^ctx);
-
-    texture: GLint;
-    glGenTextures(1, ^texture);
-    glBindTexture(GL_TEXTURE_2D, texture);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture_size, texture_size, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-    glBindTexture(GL_TEXTURE_2D, 0);
-
-    font := Font.{
-        texture = texture,
-        texture_width = texture_size,
-        texture_height = texture_size,
-        chars = char_data,
-        em = ~~fd.size,
-    };
-
-    font_registry[fd] = font;
-
-    return font;
-}
-
-font_set_color :: (color: Color) {
-    font_color = color;
-}
-
-font_print :: (font: Font, x, y: f32, format: str, va: ..any) {
-    buf: [1024] u8;
-    msg := conv.format_va(buf, format, va);
-    font_draw(font, x, y, msg);
-}
-
-font_draw :: (font: Font, x, y: f32, msg: str) {
-    quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad);
-    quad_num := 0;
-
-    x_, y_ := x, y;
-
-    for msg {
-        if it == #char "\n" {
-            x_ = x;
-            y_ += font.em + 2;
-        }
-
-        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
-            ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false);
-
-        quad_num += 1;
-    }
-
-    font_render(font, quads[0 .. quad_num]);
-}
-
-font_draw_centered :: (font: Font, x, y, max_width: f32, msg: str) {
-    quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad);
-    quad_num := 0;
-
-    width := font_get_width(font, msg);
-    x_, y_ := x, y;
-    x_ = (max_width - width) / 2 + x;
-
-    for msg {
-        if it == #char "\n" {
-            x_ = (max_width - width) / 2 + x;
-            y_ += font.em + 2;
-        }
-
-        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
-            ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false);
-
-        quad_num += 1;
-    }
-
-    font_render(font, quads[0 .. quad_num]);
-}
-
-#local font_render :: (font: Font, quads: [] stbtt_aligned_quad) {
-    // If this is being used in conjunction with the immediate
-    // rendering system, make sure the immediate objects are flushed
-    // before trying to render over them.
-    #if #defined(immediate_flush) {
-        immediate_flush();
-    }
-
-    glBindBuffer(GL_ARRAY_BUFFER, font_vbo);
-    glBufferSubData(GL_ARRAY_BUFFER, 0, quads.count * sizeof stbtt_aligned_quad, quads.data);
-    glBindBuffer(GL_ARRAY_BUFFER, -1);
-
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, font.texture);
-    shader_use(font_shader);
-    shader_set_uniform(font_shader, #cstr "u_texture", 0);
-    shader_set_uniform(font_shader, #cstr "u_color", font_color);
-
-    glDisable(GL_DEPTH_TEST);
-    glBindVertexArray(font_vao);
-    glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, ~~0, quads.count);
-    glBindTexture(GL_TEXTURE_2D, -1);
-    glBindVertexArray(-1);
-}
-
-font_get_width :: (font: Font, msg: str) -> f32 {
-    x_, y_ := 0.0f, 0.0f;
-    width := 0.0f;
-
-    quad: stbtt_aligned_quad;
-    for msg {
-        if it == #char "\n" {
-            width = math.max(width, x_);
-            x_ = 0;
-            y_ += font.em + 2;
-            continue;
-        }
-
-        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
-            ~~(it - #char " "), ^x_, ^y_, ^quad, false);
-    }
-
-    return math.max(x_, width);
-}
-
-font_get_height :: (font: Font, msg: str) -> f32 {
-    x_, y_ := 0.0f, 0.0f;
-    width := 0.0f;
-
-    quad: stbtt_aligned_quad;
-    for msg {
-        if it == #char "\n" {
-            width = math.max(width, x_);
-            x_ = 0;
-            y_ += font.em + 2;
-            continue;
-        }
-
-        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
-            ~~(it - #char " "), ^x_, ^y_, ^quad, false);
-    }
-
-    return y_ + font.em + 2;
-}
-
-font_get_char_width :: (font: Font, ch: u8) -> f32 {
-    x, y := 0.0f, 0.0f;
-    quad: stbtt_aligned_quad;
-
-    stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
-        ~~(ch - #char " "), ^x, ^y, ^quad, false);
-
-    return x;
-}
-
-
-FontDescriptor :: struct {
-    path : str;
-    size : u32;
-}
-
-#match hash.to_u32 (fd: FontDescriptor) => {
-    name_hash := hash.to_u32(fd.path);
-    size_hash := hash.to_u32(fd.size);
-    return name_hash * 13 + size_hash * 17;
-}
-
-#operator == (f1, f2: FontDescriptor) => f1.path == f2.path && f1.size == f2.size;
-
-font_lookup :: (fd := FontDescriptor.{ "assets/calibri.ttf", 12 }) -> Font {
-    if font_registry->has(fd) {
-        return font_registry[fd];
-    }
-
-    font := font_make(fd);
-    font_registry[fd] = font;
-    return font;
-}
diff --git a/src/gfx/immediate.onyx b/src/gfx/immediate.onyx
deleted file mode 100644 (file)
index 8a88eca..0000000
+++ /dev/null
@@ -1,205 +0,0 @@
-
-use core
-use opengles
-use glfw3
-
-immediate_init :: () {
-    memory.alloc_slice(^vertex_data, Maximum_Vertex_Count);
-    vertex_count = 0;
-
-    imgui_shader = shader_make(Shader_Path);
-    shader_use(imgui_shader);
-    shader_link_window_matrix_block(imgui_shader);
-    shader_link_world_matrix_block(imgui_shader);
-    shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 0.0f);
-    shader_set_uniform(imgui_shader, #cstr "u_texture", 0);
-
-    immediate_mesh = mesh_make(vertex_data, .[], GL_DYNAMIC_DRAW);
-    immediate_color = .{0,0,0};
-}
-
-immediate_flush :: (set_shader := true) {
-    if vertex_count == 0 do return;
-
-    if set_shader {
-        shader_use(imgui_shader);
-        shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 1.0f if rendering_type == .Image else 0.0f);
-    }
-
-    immediate_mesh.vertex_count = vertex_count;
-    mesh_update_verticies(immediate_mesh, vertex_data);
-
-    mesh_draw(immediate_mesh);
-
-    vertex_count = 0;
-    rendering_type = .Plain;
-    immediate_mesh.primitive = GL_TRIANGLES;
-}
-
-immediate_clear :: (color: Color) {
-    glClearColor(color.r, color.g, color.b, color.a);
-    glClear(GL_COLOR_BUFFER_BIT);
-}
-
-immediate_set_color :: (color: Color) {
-    immediate_color = color;
-}
-
-immediate_vertex :: (x, y: f32, t_x := 0.0f, t_y := 0.0f) {
-    if vertex_count >= Maximum_Vertex_Count do immediate_flush();
-    set_rendering_type(.Plain);
-
-    vertex_data[vertex_count] = .{ .{x, y}, .{t_x, t_y}, immediate_color };
-}
-
-immediate_line :: (x1, y1, x2, y2: f32) {
-    if vertex_count >= Maximum_Vertex_Count do immediate_flush();
-    set_rendering_type(.Line);
-    immediate_mesh.primitive = GL_LINES;
-
-    vertex_data[vertex_count + 0] = .{ .{x1, y1}, .{0, 0}, immediate_color };
-    vertex_data[vertex_count + 1] = .{ .{x2, y2}, .{0, 0}, immediate_color };
-    vertex_count += 2;
-}
-
-immediate_triangle :: (x1, x2, x3: Vector2) {
-    if vertex_count + 3 > Maximum_Vertex_Count do immediate_flush();
-    set_rendering_type(.Plain);
-
-    vertex_data[vertex_count + 0] = .{ x1, .{0,0}, immediate_color };
-    vertex_data[vertex_count + 1] = .{ x2, .{0,0}, immediate_color };
-    vertex_data[vertex_count + 2] = .{ x3, .{0,0}, immediate_color };
-    vertex_count += 3;
-}
-
-immediate_rectangle :: (x, y, w, h: f32) {
-    if vertex_count + 6 > Maximum_Vertex_Count do immediate_flush();
-    set_rendering_type(.Plain);
-
-    vertex_data[vertex_count + 0] = .{ .{x,   y},   .{0,0}, immediate_color };
-    vertex_data[vertex_count + 1] = .{ .{x+w, y},   .{0,0}, immediate_color };
-    vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{0,0}, immediate_color };
-    vertex_data[vertex_count + 3] = .{ .{x,   y},   .{0,0}, immediate_color };
-    vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{0,0}, immediate_color };
-    vertex_data[vertex_count + 5] = .{ .{x,   y+h}, .{0,0}, immediate_color };
-    vertex_count += 6;
-}
-
-immediate_image :: (image: ^Texture, x, y, w, h: f32) {
-    if vertex_count > 0 do immediate_flush();
-
-    set_rendering_type(.Image);
-    texture_use(image);
-    shader_use(imgui_shader);
-    shader_set_uniform(imgui_shader, #cstr "u_texture", 0);
-
-    vertex_data[vertex_count + 0] = .{ .{x, y},     .{0,0}, immediate_color };
-    vertex_data[vertex_count + 1] = .{ .{x+w, y},   .{1,0}, immediate_color };
-    vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{1,1}, immediate_color };
-    vertex_data[vertex_count + 3] = .{ .{x, y},     .{0,0}, immediate_color };
-    vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{1,1}, immediate_color };
-    vertex_data[vertex_count + 5] = .{ .{x, y+h},   .{0,1}, immediate_color };
-    vertex_count += 6;
-}
-
-immediate_subimage :: (image: ^Texture, x, y, w, h: f32, tx, ty, tw, th: f32) {
-    if vertex_count > 0 do immediate_flush();
-
-    set_rendering_type(.Image);
-    texture_use(image);
-    shader_use(imgui_shader);
-    shader_set_uniform(imgui_shader, #cstr "u_texture", 0);
-
-    sx := tx / ~~ image.width;
-    sy := ty / ~~ image.height;
-    sw := tw / ~~ image.width;
-    sh := th / ~~ image.height;
-
-    vertex_data[vertex_count + 0] = .{ .{x, y},     .{sx,      sy}, immediate_color };
-    vertex_data[vertex_count + 1] = .{ .{x+w, y},   .{sx+sw,   sy}, immediate_color };
-    vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color };
-    vertex_data[vertex_count + 3] = .{ .{x, y},     .{sx,      sy}, immediate_color };
-    vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color };
-    vertex_data[vertex_count + 5] = .{ .{x, y+h},   .{sx,   sy+sh}, immediate_color };
-    vertex_count += 6;
-}
-
-immediate_ellipse :: () {}
-
-immediate_push_scissor :: (x, y, w, h: f32) {
-    if vertex_count > 0 do immediate_flush();
-
-    // Assuming that x, y, w, and h are in screen (window) coordinates.
-    x += offset.x;
-    y += offset.y;
-
-    xi := cast(i32) x;
-    yi := cast(i32) y;
-    wi := cast(i32) w;
-    hi := cast(i32) h;
-    scissors << .{xi, yi, wi, hi};
-
-    glEnable(GL_SCISSOR_TEST);
-    glScissor(xi, window_height - yi - hi, wi, hi);
-}
-
-immediate_pop_scissor :: () {
-    if scissors.count > 0 {
-        array.pop(^scissors);
-    }
-
-    if scissors.count > 0 {
-        glEnable(GL_SCISSOR_TEST);
-        s := scissors[scissors.count - 1];
-        glScissor(s.x, window_height - s.y - s.h, s.w, s.h);
-    } else {
-        glDisable(GL_SCISSOR_TEST);
-    }
-}
-
-immediate_set_scroll :: (scroll_x, scroll_y: f32) {
-    offset = .{ scroll_x, scroll_y };
-    update_model_matrix(offset);
-}
-
-immediate_get_scroll :: () => offset;
-
-Immediate_Vertex :: struct {
-    pos:   Vector2;
-    tex:   Vector2;
-    color: Color;
-}
-
-#local {
-    Shader_Path   :: "./assets/shaders/imgui.glsl"
-    imgui_shader: Shader;
-
-    Maximum_Vertex_Count :: 1023;
-    vertex_count: i32;
-    vertex_data:  [] Immediate_Vertex;
-
-    immediate_color: Color;
-
-    immediate_mesh: ^Mesh(Immediate_Vertex);
-
-    Rendering_Type :: enum {
-        Plain;
-        Line;
-        Image;
-    }
-    rendering_type := Rendering_Type.Plain;
-
-    Scissor :: struct {
-        x, y, w, h: i32;
-    }
-    scissors: [..] Scissor;
-
-    offset: Vector2;
-
-    set_rendering_type :: (new_type: typeof rendering_type) {
-        if rendering_type != new_type {
-            immediate_flush();
-            rendering_type = new_type;
-        }
-    }
-}
diff --git a/src/gfx/mesh.onyx b/src/gfx/mesh.onyx
deleted file mode 100644 (file)
index 981ff11..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-use core
-use opengles
-
-Mesh :: struct (Vertex_Type: type_expr) {
-    handle: GLint;
-    vertex_handle: GLint;
-    index_handle: GLint;
-
-    vertex_count: u32;
-    index_count: u32;
-    primitive: GLuint = GL_TRIANGLES;
-}
-
-mesh_make :: (verticies: [] $T, indicies: [] u32, gl_hint := GL_STATIC_DRAW) -> ^Mesh(T) {
-    mesh := new(Mesh(T));
-    mesh.vertex_count = verticies.count;
-    mesh.index_count  = indicies.count;
-
-    glGenVertexArrays(1, ^mesh.handle);
-    glBindVertexArray(mesh.handle);
-
-    glGenBuffers(1, ^mesh.vertex_handle);
-    glBindBuffer(GL_ARRAY_BUFFER, mesh.vertex_handle);
-    glBufferData(GL_ARRAY_BUFFER, sizeof T * verticies.count, verticies.data, gl_hint);
-
-    type_info :: runtime.info
-    vertex_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(T);
-    vertex_attr := 0;
-    for attr: vertex_info.members {
-        defer vertex_attr += 1;
-        glEnableVertexAttribArray(vertex_attr);
-
-        switch attr.type {
-            case Vector2 do glVertexAttribPointer(vertex_attr, 2, GL_FLOAT, false, sizeof T, ~~attr.offset);
-            case Vector3 do glVertexAttribPointer(vertex_attr, 3, GL_FLOAT, false, sizeof T, ~~attr.offset);
-            case u32 do glVertexAttribIPointer(vertex_attr, 1, GL_UNSIGNED_INT, sizeof T, ~~attr.offset);
-            case i32 do glVertexAttribIPointer(vertex_attr, 1, GL_INT, sizeof T, ~~attr.offset);
-
-            // It would be nice to not have to have all the cases here.
-            // Instead allow for an extensible way of defining them at compile time.
-            case Color do glVertexAttribPointer(vertex_attr, 4, GL_FLOAT, false, sizeof T, ~~attr.offset);
-
-            case #default {
-                buf: [256] u8;
-                msg := conv.str_format(buf, "Unknown type for GL vertex attribute, {}.", attr.type);
-                assert(false, msg);
-            }
-        }
-    }
-
-    if indicies.count > 0 {
-        glGenBuffers(1, ^mesh.index_handle);
-        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.index_handle);
-        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof i32 * indicies.count, indicies.data, gl_hint);
-    }
-
-    glBindVertexArray(-1);
-
-    return mesh;
-}
-
-mesh_update_verticies :: (use mesh: ^Mesh, verticies: [] mesh.Vertex_Type) {
-    // @TODO // Add bounds checking to arrays here.
-
-    glBindBuffer(GL_ARRAY_BUFFER, vertex_handle);
-    glBufferSubData(GL_ARRAY_BUFFER, 0, verticies.count * sizeof mesh.Vertex_Type, verticies.data);
-    glBindBuffer(GL_ARRAY_BUFFER, -1);
-}
-
-mesh_draw :: (use mesh: ^Mesh) {
-    glBindVertexArray(handle);
-    if index_count > 0 {
-        glDrawElements(primitive, index_count, GL_UNSIGNED_INT, ~~0);
-    } else {
-        glDrawArrays(primitive, 0, vertex_count);
-    }
-    glBindVertexArray(-1);
-}
-
-mesh_free :: (mesh: ^Mesh) {
-    glDeleteBuffers(1, ^mesh.vertex_handle);
-    glDeleteBuffers(1, ^mesh.index_handle);
-    glDeleteVertexArrays(1, ^mesh.handle);
-    cfree(mesh);
-}
-
-
-
diff --git a/src/gfx/shader.onyx b/src/gfx/shader.onyx
deleted file mode 100644 (file)
index 711c6a0..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-
-use core
-use opengles
-
-Shader :: struct {
-    vs, fs: GLuint;
-    prog: GLuint;
-}
-
-window_matrix_block_buffer: GLuint;
-world_matrix_block_buffer:  GLuint;
-
-shaders_init :: () {
-    glGenBuffers(1, ^window_matrix_block_buffer);
-    glGenBuffers(1, ^world_matrix_block_buffer);
-
-    glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer);
-    glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * 16, null, GL_DYNAMIC_DRAW);
-
-    glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer);
-    glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * (16 + 16), null, GL_DYNAMIC_DRAW);
-
-    glBindBuffer(GL_UNIFORM_BUFFER, -1);
-
-    glBindBufferBase(GL_UNIFORM_BUFFER, WINDOW_MATRIX_BLOCK, window_matrix_block_buffer);
-    glBindBufferBase(GL_UNIFORM_BUFFER, WORLD_MATRIX_BLOCK, world_matrix_block_buffer);
-}
-
-shader_make :: (shader_path: str) -> Shader {
-    shader_source := os.get_contents(shader_path);
-    vs := compile_shader(shader_source, GL_VERTEX_SHADER);
-    fs := compile_shader(shader_source, GL_FRAGMENT_SHADER);
-
-    prog := link_program(vs, fs);
-
-    return Shader.{vs, fs, prog};
-}
-
-shader_use :: (shader: Shader) {
-    glUseProgram(shader.prog);
-}
-
-#local {
-    WINDOW_MATRIX_BLOCK :: 0;
-    WORLD_MATRIX_BLOCK  :: 1;
-}
-
-shader_link_window_matrix_block :: (use shader: Shader) {
-    matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_window_matrix_block");
-    glUniformBlockBinding(prog, matrix_block_index, WINDOW_MATRIX_BLOCK);
-}
-
-shader_link_world_matrix_block :: (use shader: Shader) {
-    matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_world_matrix_block");
-    glUniformBlockBinding(prog, matrix_block_index, WORLD_MATRIX_BLOCK);
-}
-
-shader_set_uniform :: (shader: Shader, uniform: cstr, value: $T) {
-    glUseProgram(shader.prog);
-    location := glGetUniformLocation(shader.prog, uniform);
-
-    set_uniform_internal(location, value);
-    
-    set_uniform_internal :: #match {
-        macro (location: GLint, value: u32) do glUniform1i(location, value); ,
-        macro (location: GLint, value: f32) do glUniform1f(location, value); ,
-        macro (location: GLint, value: Vector2) do glUniform2f(location, value.x, value.y); ,
-        macro (location: GLint, value: Vector3) do glUniform3f(location, value.x, value.y, value.z); ,
-        macro (location: GLint, value: Color)   do glUniform4f(location, value.r, value.g, value.b, value.a); ,
-
-        macro (location: GLint, value: $T) {
-            buffer: [1024] u8;
-            assert(false, conv.format(buffer, "Bad shader_set_uniform case: {}", T));
-        }
-    }
-}
-
-update_view_matrix :: (width, height: u32) {
-    matrix : [16] f32;
-    top    := 0.0f;
-    left   := 0.0f;
-    right  := cast(f32) width;
-    bottom := cast(f32) height;
-    far    := 10.0f;
-    near   := 0f;
-    
-    matrix[0] = 2 / (right - left);
-    matrix[5] = 2 / (top - bottom);
-    matrix[10] = -2 / (far - near);
-    matrix[12] = -(right + left) / (right - left);
-    matrix[13] = -(top + bottom) / (top - bottom);
-    matrix[14] = -(far + near) / (far - near);
-    matrix[15] = 1;
-
-    glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer);
-    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof matrix, ^matrix);
-    glBindBuffer(GL_UNIFORM_BUFFER, -1);
-
-    glViewport(0, 0, width, height);
-}
-
-update_world_matrix :: () {
-    world_mat: [16] f32;
-    world_mat[0] = 1;
-    world_mat[5] = 1;
-    world_mat[10] = 1;
-    world_mat[15] = 1;
-
-    glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer);
-    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof world_mat, ^world_mat);
-    glBindBuffer(GL_UNIFORM_BUFFER, -1);
-}
-
-update_model_matrix :: (v: Vector2) {
-    model_mat: [16] f32;
-    model_mat[0]  = 1;
-    model_mat[5]  = 1;
-    model_mat[10] = 1;
-    model_mat[12] = v.x;
-    model_mat[13] = v.y;
-    model_mat[14] = 0;
-    model_mat[15] = 1;
-
-    glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer);
-    glBufferSubData(GL_UNIFORM_BUFFER, 16 * sizeof f32, sizeof typeof model_mat, ^model_mat);
-    glBindBuffer(GL_UNIFORM_BUFFER, -1);
-}
-
-
-#local {
-    compile_shader :: (source: str, type: GLenum) -> GLint {
-        shader := glCreateShader(type);
-
-        #persist VERTEX_HEADER := """
-#version 300 es
-#define VERTEX_SHADER 1
-#define COMM out
-        """;
-
-        #persist FRAGMENT_HEADER := """
-#version 300 es
-#define FRAGMENT_SHADER 1
-#define COMM in
-        """;
-
-        header      := VERTEX_HEADER if type == GL_VERTEX_SHADER else FRAGMENT_HEADER;
-        sources     : [] cptr(u8) = .[ cptr.make(header.data), cptr.make(source.data) ];
-        source_lens : [] i32      = .[ header.count,           source.count ];
-
-        glShaderSource(shader, 2, sources.data, source_lens.data);
-        glCompileShader(shader);
-
-        success: GLint;
-        if glGetShaderiv(shader, GL_COMPILE_STATUS, ^success); success == GL_FALSE {
-            buf_data: [2048] u8;
-            buf := str.{ ~~buf_data, 0 };
-            glGetShaderInfoLog(shader, 2048, ^buf.count, buf.data);
-            println(buf);
-        }
-
-        return shader;
-    }
-
-    link_program :: (vertex_shader, fragment_shader: GLint) -> GLuint {
-        prog := glCreateProgram();
-        glAttachShader(prog, vertex_shader);
-        glAttachShader(prog, fragment_shader);
-        glLinkProgram(prog);
-
-        success: GLint;
-        if glGetProgramiv(prog, GL_LINK_STATUS, ^success); success == GL_FALSE {
-            buf_data: [1024] u8;
-            buf := str.{ ~~buf_data, 0 };
-            glGetProgramInfoLog(prog, 1024, ^buf.count, buf.data);
-            println(buf);
-        }
-
-        return prog;
-    }
-}
-
diff --git a/src/gfx/texture.onyx b/src/gfx/texture.onyx
deleted file mode 100644 (file)
index 5a205d4..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-
-use core
-use opengles
-use stb_image
-
-#local texture_cache: Map(str, Texture);
-
-Texture :: struct {
-    texture: GLint;
-    width, height, channels: i32;
-    filename: str;
-}
-
-texture_lookup :: #match {}
-#match texture_lookup (filename: str) -> (Texture, bool) {
-    if texture_cache->has(filename) {
-        return texture_cache[filename], true;
-    }
-
-    buffer: [512] u8;
-    memory.copy(~~ buffer, filename.data, math.min(filename.count, 511));
-    return texture_lookup(cast(cstr) buffer);
-}
-
-#match texture_lookup (path: cstr) -> (Texture, bool) {
-    filename := string.from_cstr(path);
-    if texture_cache->has(filename) {
-        return texture_cache[filename], true;
-    }
-
-    tex: Texture;
-    tex.filename = filename;
-    pixels := stbi_load(path, ^tex.width, ^tex.height, ^tex.channels, 4);
-    if pixels == null {
-        debug_log(.Warning, "Failed to load texture: {}", filename);
-        return .{}, false;
-    }
-    defer stbi_image_free(pixels);
-
-    glGenTextures(1, ^tex.texture);
-    glBindTexture(GL_TEXTURE_2D, tex.texture);
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
-
-    // Are these sensible defaults?
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
-    glBindTexture(GL_TEXTURE_2D, 0);
-
-    // This assumes that the filename data is going to be persistant forever.
-    // Not a great assumption to make but is it really worth copying it?
-    texture_cache[filename] = tex;
-    debug_log(.Info, "Loaded texture: {}", filename);
-    return tex, true;
-}
-
-texture_free :: (use tex: ^Texture) {
-    glDeleteTextures(1, ^texture);
-}
-
-texture_use :: (use tex: ^Texture, texture_binding := 0) {
-    glActiveTexture(GL_TEXTURE0 + texture_binding);
-    glBindTexture(GL_TEXTURE_2D, texture);
-}
-
-
-Texture_Wrap :: enum {
-    Clamp :: GL_CLAMP_TO_EDGE | 0;
-    Repeat :: GL_REPEAT | 0;
-    Mirrored_Repeat :: GL_MIRRORED_REPEAT | 0;
-}
-
-texture_wrap :: (use tex: ^Texture, wrap: Texture_Wrap) {
-    glBindTexture(GL_TEXTURE_2D, texture);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ~~ wrap);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ~~ wrap);
-}
-
-texture_cache_clear :: () {
-    for^ texture_cache.entries {
-        glDeleteTextures(1, ^it.value.texture);
-    }
-
-    map.clear(^texture_cache);
-}
diff --git a/src/gfx/ui.onyx b/src/gfx/ui.onyx
deleted file mode 100644 (file)
index ae31eaa..0000000
+++ /dev/null
@@ -1,728 +0,0 @@
-//
-// Very simple immediate mode UI
-//
-
-use core
-use opengles
-use glfw3
-
-UI_Id :: u32
-
-ui_end_frame :: () {
-    hot_item_depth_needed = hot_item_depth;
-    if !hot_item_was_set do set_hot_item(0);
-    hot_item_depth = 0;
-    hot_item_was_set = false;
-
-    for^ animation_states.entries {
-        if !it.value.accessed_this_frame || (it.value.click_time == 0 && it.value.hover_time == 0)  {
-            map.delete(^animation_states, it.key);
-        }
-    }
-
-    for^ animation_states.entries {
-        it.value.accessed_this_frame = false;
-    }
-
-    if active_countdown > 0 {
-        active_countdown -= 1;
-        if active_countdown == 0 {
-            set_active_item(0);
-            input_release_keys();
-        }
-    }
-}
-
-//
-// Buttons
-//
-Button_Theme :: struct {
-    use text_theme := Text_Theme.{};
-    use animation_theme := Animation_Theme.{};
-
-    background_color := Color.{ 0.1, 0.1, 0.1 };
-    hover_color      := Color.{ 0.3, 0.3, 0.3 };
-    click_color      := Color.{ 0.5, 0.5, 0.7 };
-
-    border_color := Color.{0.2, 0.2, 0.2};
-    border_width := 2.0f;
-
-    active := false;
-}
-
-#local default_button_theme := Button_Theme.{};
-
-draw_button  :: (use r: Rect, text: str, theme := ^default_button_theme, site := #callsite, increment := 0) -> bool {
-    result := false;
-
-    hash := get_site_hash(site, increment);
-    animation_state := get_animation(hash);
-    mx, my := mouse_get_position();
-
-    contains := Rect.contains(r, .{~~mx, ~~my});
-
-    if is_active_item(hash) {
-        renew_active_item(hash);
-        if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) {
-            if is_hot_item(hash) && contains {
-                result = true;
-                animation_state.click_time = 1.0f;
-            }
-
-            set_active_item(0);
-        }
-    } elseif is_hot_item(hash) {
-        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
-            set_active_item(hash);
-        }
-    }
-
-    if contains {
-        set_hot_item(hash);
-    }
-
-    if is_hot_item(hash) || theme.active {
-        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
-    } else {
-        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
-    }
-
-    border_width := theme.border_width;
-
-    immediate_set_color(theme.border_color);
-    immediate_rectangle(x, y, w, h);
-
-    surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color);
-    surface_color  = color_lerp(animation_state.click_time, surface_color, theme.click_color);
-    immediate_set_color(surface_color);
-    immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2);
-
-    font := font_lookup(.{theme.font_name, theme.font_size});
-    font_height := font_get_height(font, text);
-    font_set_color(theme.text_color);
-    font_draw_centered(font, x, y + (h - font_height) / 2 + font.em - 2, w, text);
-
-    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
-
-    return result;
-}
-
-
-//
-// Sliders
-//
-Slider_Theme :: struct {
-    use text_theme := Text_Theme.{};
-    use animation_theme := Animation_Theme.{};
-
-    box_color        := Color.{ 0.1, 0.1, 0.1 };
-    box_border_color := Color.{ 0.2, 0.2, 0.2 };
-    box_border_width := 4.0f;   // @InPixels
-
-    bar_color                := Color.{ 0.4, 0.4, 0.4 };
-    bar_hover_color          := Color.{ 0, 0, 1 };
-    bar_hover_negative_color := Color.{ 1, 0, 0 }; // The color when value is less than 0
-}
-
-#local default_slider_theme := Slider_Theme.{};
-
-draw_slider :: (use r: Rect, value: ^$T, min_value: T, max_value: T, theme := ^default_slider_theme, site := #callsite, increment := 0) -> bool {
-    result := false;
-
-    hash := get_site_hash(site, increment);
-    state := get_animation(hash);
-    mx, my := mouse_get_position();
-
-    if is_hot_item(hash) {
-        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
-            set_active_item(hash);
-            result = true;
-
-            // Animate this?
-            sx := ~~mx - x;
-
-            if T == i32 || T == i64 || T == u32 || T == u64 {
-                step_width := w / ~~math.abs(max_value - min_value);
-                percent := (sx + step_width / 2) / w;
-                *value = math.lerp(percent, min_value, max_value);
-                *value = math.clamp(*value, min_value, max_value);
-
-            } else {
-                percent := sx / w;
-                *value = math.lerp(percent, min_value, max_value);
-                *value = math.clamp(*value, min_value, max_value);
-            }
-        } else {
-            set_active_item(0);
-        }
-    }
-
-    if Rect.contains(r, .{ ~~mx, ~~my }) {
-        set_hot_item(hash);
-    }
-
-    if is_hot_item(hash) {
-        move_towards(^state.hover_time, 1.0f, theme.hover_speed);
-    } else {
-        move_towards(^state.hover_time, 0.0f, theme.hover_speed);
-    }
-
-    box_border_width := theme.box_border_width;
-
-    bar_color := theme.bar_color;
-    if *value < 0 do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_negative_color);
-    else          do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_color);
-
-    immediate_set_color(theme.box_border_color);
-    immediate_rectangle(x, y, w, h);
-
-    immediate_set_color(theme.box_border_color);
-    immediate_rectangle(x + box_border_width, y + box_border_width, w - box_border_width * 2, h - box_border_width * 2);
-
-    box_width := cast(f32) (*value - min_value) / ~~(max_value - min_value);
-    box_width *= w - box_border_width * 2;
-    box_width = math.clamp(box_width, 0, w - box_border_width * 2);
-    immediate_set_color(bar_color);
-    immediate_rectangle(x + box_border_width, y + box_border_width, box_width, h - box_border_width * 2);
-
-    return result;
-}
-
-//
-// Textbox
-//
-Textbox_Theme :: struct {
-    use text_theme := Text_Theme.{
-        text_color = .{ 0, 0, 0 }
-    };
-
-    use animation_theme := Animation_Theme.{};
-
-    background_color := Color.{ 0.8, 0.8, 0.8 };
-    hover_color      := Color.{ 1.0, 1.0, 1.0 };
-    click_color      := Color.{ 0.5, 0.5, 0.7 };
-
-    border_color := Color.{ 0.2, 0.2, 0.2 };
-    border_width := 6.0f;   // @InPixels
-
-    cursor_color := Color.{ 0.5, 0.5, 0.5 };
-    cursor_width := 4.0f;   // @InPixels
-    cursor_blink_speed := 0.04f;   // Bigger is faster
-
-    placeholder_text_color := Color.{ 0.5, 0.5, 0.5 };
-}
-
-#local {
-    default_textbox_theme := Textbox_Theme.{};
-
-    Textbox_Editing_State :: struct {
-        hash: UI_Id = 0;
-
-        cursor_position: i32 = 0;
-        cursor_animation := 0.0f;
-        cursor_animation_speed := 0.02f;
-
-        // @HACK // Otherwise the action keys are evaluated every frame.
-        action_key_timeout := 0.0f;
-    }
-
-    textbox_editing_state := Textbox_Editing_State.{};
-}
-
-draw_textbox :: (use r: Rect, text_buffer: ^[..] u8, placeholder := null_str, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool {
-    result := false;
-
-    hash := get_site_hash(site, increment);
-    animation_state := get_animation(hash);
-    mx, my := mouse_get_position();
-
-    border_width := theme.border_width;
-    text_color := theme.text_color;
-    text := str.{text_buffer.data, text_buffer.count};
-    if text.count == 0 && placeholder.count > 0 {
-        text = placeholder;
-        text_color = theme.placeholder_text_color;
-    }
-
-    font := font_lookup(.{theme.font_name, theme.font_size});
-    text_width  := font_get_width(font, text);
-    text_height := font_get_height(font, text);
-
-    text_x := x + border_width;
-    text_y := y + font.em + (h - text_height) / 2;
-
-    contains := Rect.contains(r, .{~~mx, ~~my});
-
-    if is_hot_item(hash) && !is_active_item(hash) {
-        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains {
-            set_active_item(hash);
-            input_capture_keys();
-            textbox_editing_state.hash = hash;
-            textbox_editing_state.cursor_animation_speed = theme.cursor_blink_speed;
-        }
-    }
-
-    if is_active_item(hash) {
-        renew_active_item(hash);
-        if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) && !contains {
-            set_active_item(0);
-            input_release_keys();
-            textbox_editing_state.hash = 0;
-            textbox_editing_state.cursor_position = 0;
-        }
-    }
-
-    if contains {
-        set_hot_item(hash);
-    }
-
-    if textbox_editing_state.hash == hash {
-        move_towards(^textbox_editing_state.cursor_animation, 0.0f, textbox_editing_state.cursor_animation_speed);
-        if textbox_editing_state.cursor_animation <= 0.0f do textbox_editing_state.cursor_animation = 1.0f;
-
-        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains {
-            textbox_editing_state.cursor_animation = 1.0f;
-            textbox_editing_state.cursor_position = screen_to_cursor(^font, text_x, text_y, text, ~~mx, ~~my);
-        }
-
-        for key: input_get_keys_this_frame() {
-            switch key.key {
-                case GLFW_KEY_ESCAPE {
-                    set_active_item(0);
-                    input_release_keys();
-                }
-
-                case GLFW_KEY_LEFT  do textbox_editing_state.cursor_position -= 1;
-                case GLFW_KEY_RIGHT do textbox_editing_state.cursor_position += 1;
-                case GLFW_KEY_END   do textbox_editing_state.cursor_position = text_buffer.count;
-                case GLFW_KEY_HOME  do textbox_editing_state.cursor_position = 0;
-
-                case GLFW_KEY_BACKSPACE {
-                    if textbox_editing_state.cursor_position > 0 {
-                        array.delete(text_buffer, textbox_editing_state.cursor_position - 1);
-                    }
-                    textbox_editing_state.cursor_position = math.max(~~0, textbox_editing_state.cursor_position - 1);
-                }
-
-                case GLFW_KEY_DELETE {
-                    array.delete(text_buffer, textbox_editing_state.cursor_position);
-                }
-            }
-        }
-
-        for ch: input_get_chars_this_frame() {
-            array.insert(text_buffer, textbox_editing_state.cursor_position, ~~ch);
-            textbox_editing_state.cursor_position += 1;
-            result = true;
-        }
-
-        textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.count);
-        textbox_editing_state.cursor_animation = 1.0f;
-
-        text = str.{text_buffer.data, text_buffer.count};
-    }
-
-    if is_hot_item(hash) {
-        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
-    } else {
-        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
-    }
-
-    immediate_push_scissor(x, y, w, h);
-    immediate_set_color(theme.border_color);
-    immediate_rectangle(x, y, w, h);
-
-    surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color);
-    surface_color  = color_lerp(animation_state.click_time, surface_color, theme.click_color);
-    immediate_set_color(surface_color);
-    immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2);
-
-    // Draw the cursor on textboxes
-    if is_active_item(hash) {
-        cursor := cursor_to_screen(^font, x + border_width, y + border_width, text, textbox_editing_state.cursor_position);
-        right_edge := x + w - border_width * 2;
-        if cursor.x > right_edge {
-            text_x -= cursor.x - right_edge;
-            cursor.x = right_edge - 2;
-        }
-        immediate_set_color(.{0,0,0});
-        immediate_rectangle(cursor.x, cursor.y + 2, 2, h - border_width * 2 - 4);
-    }
-
-    font_set_color(theme.text_color);
-    font_draw(font, text_x, text_y, text); // This is technically a frame late for updating the text?
-
-    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
-
-    immediate_pop_scissor();
-    return result;
-
-    cursor_to_screen :: (font: ^Font, x, y: f32, text: str, cur_pos: i32) -> Vector2 {
-        cx := x;
-        cy := y;
-
-        for text[0 .. cur_pos] {
-            if it == #char "\n" {
-                cx = 0;
-                cy += font.em + 2;
-            } else {
-                cx += font_get_char_width(*font, it);
-            }
-        }
-
-        return .{ cx, cy };
-    }
-
-    screen_to_cursor :: (font: ^Font, x, y: f32, text: str, mouse_x, mouse_y: f32) -> i32 {
-        mx := mouse_x - x;
-        my := mouse_y - y;
-        
-        cx := 0.0f; 
-        cy := 0.0f;
-        for pos: text.count {
-            if text[pos] == #char "\n" {
-                cx = 0;
-                cy += font.em + 2;
-            } else {
-                cx += font_get_char_width(*font, text[pos]);
-            }
-
-            if cy >= my && cx >= mx {
-                return pos;
-            }
-        }
-
-        return text.count;
-    }
-}
-
-
-
-
-//
-// Checkboxes
-//
-Checkbox_Theme :: struct {
-    use text_theme := Text_Theme.{};
-    use animation_theme := Animation_Theme.{};
-
-    box_color        := Color.{ 0.2, 0.2, 0.2 };
-    box_border_width := 4.0f;    // @InPixels
-    box_size         := 20.0f;   // @InPixels
-
-    checked_color       := Color.{ 1, 0, 0 };
-    checked_hover_color := Color.{ 1, 0.6, 0.6 };
-
-    background_color := Color.{ 0.05, 0.05, 0.05 };  // Background of the checkbox button.
-    hover_color      := Color.{ 0.3, 0.3, 0.3 };
-    click_color      := Color.{ 0.5, 0.5, 0.7 };
-}
-
-#local default_checkbox_theme := Checkbox_Theme.{};
-
-draw_checkbox :: (use r: Rect, value: ^bool, text: str, theme := ^default_checkbox_theme, site := #callsite, increment := 0) -> bool {
-    result := false;
-
-    hash := get_site_hash(site, increment);
-    animation_state := get_animation(hash);
-    mx, my := mouse_get_position();
-
-    contains := Rect.contains(r, .{~~mx, ~~my});
-
-    if is_active_item(hash) {
-        renew_active_item(hash);
-        if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) {
-            if is_hot_item(hash) && contains {
-                result = true;
-                *value = !*value;
-                animation_state.click_time = 1.0f;
-            }
-
-            set_active_item(0);
-        }
-    } elseif is_hot_item(hash) {
-        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
-            set_active_item(hash);
-        }
-    }
-
-    if contains {
-        set_hot_item(hash);
-    }
-
-    if is_hot_item(hash) {
-        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
-    } else {
-        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
-    }
-
-    box_border_width := theme.box_border_width;
-    box_size         := theme.box_size;
-
-    immediate_set_color(theme.box_color);
-    immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size);
-
-    surface_color : Color;
-    if *value {
-        surface_color = theme.checked_color;
-        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color);
-
-    } else {
-        surface_color = theme.background_color;
-        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color);
-    }
-
-    surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color);
-
-    immediate_set_color(surface_color);
-    immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2);
-
-    font := font_lookup(.{theme.font_name, theme.font_size});
-    text_width  := font_get_width(font, text);
-    text_height := font_get_height(font, text);
-
-    font_set_color(theme.text_color);
-    font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text);
-
-    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
-
-    return result;
-}
-
-
-//
-// Radio buttons
-//
-Radio_Theme :: struct {
-    use text_theme := Text_Theme.{};
-    use animation_theme := Animation_Theme.{};
-
-    box_color        := Color.{ 0.2, 0.2, 0.2 };
-    box_border_width := 4.0f;    // @InPixels
-    box_size         := 20.0f;   // @InPixels
-
-    checked_color       := Color.{ 0, 0, 1 };
-    checked_hover_color := Color.{ 0.6, 0.6, 1 };
-
-    background_color := Color.{ 0.05, 0.05, 0.05 };  // Background of the checkbox button.
-    hover_color      := Color.{ 0.3, 0.3, 0.3 };
-    click_color      := Color.{ 0.5, 0.5, 0.7 };
-}
-
-#local default_radio_theme := Radio_Theme.{};
-
-draw_radio :: (use r: Rect, value: ^$T, set_to: T, text: str, theme := ^default_radio_theme, site := #callsite, increment := 0) -> bool {
-    result := false;
-
-    hash := get_site_hash(site, increment);
-    animation_state := get_animation(hash);
-    mx, my := mouse_get_position();
-
-    contains := Rect.contains(r, .{~~mx, ~~my});
-
-    if is_active_item(hash) {
-        renew_active_item(hash);
-        if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) {
-            if is_hot_item(hash) && contains {
-                result = true;
-                *value = set_to;
-                animation_state.click_time = 1.0f;
-            }
-
-            set_active_item(0);
-        }
-    } elseif is_hot_item(hash) {
-        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
-            set_active_item(hash);
-        }
-    }
-
-    if contains {
-        set_hot_item(hash);
-    }
-
-    if is_hot_item(hash) {
-        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
-    } else {
-        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
-    }
-
-    box_border_width := theme.box_border_width;
-    box_size         := theme.box_size;
-
-    immediate_set_color(theme.box_color);
-    immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size);
-
-    surface_color : Color;
-    if *value == set_to {
-        surface_color = theme.checked_color;
-        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color);
-
-    } else {
-        surface_color = theme.background_color;
-        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color);
-    }
-
-    surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color);
-
-    immediate_set_color(surface_color);
-    immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2);
-
-    font := font_lookup(.{theme.font_name, theme.font_size});
-    text_width  := font_get_width(font, text);
-    text_height := font_get_height(font, text);
-
-    font_set_color(theme.text_color);
-    font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text);
-
-    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
-
-    return result;
-}
-
-
-
-
-scrolling_region_start :: (r: Rect, max_y_scroll := 10000.0f, site := #callsite, increment := 0) {
-    hash := get_site_hash(site, increment);
-    mx, my := mouse_get_position();
-    state := map.get_ptr(^scroll_states, hash);
-    if state == null {
-        animation_states[hash] = .{};
-        state = ^scroll_states[hash];
-    }
-
-    contains := Rect.contains(r, .{~~mx, ~~my});
-    if contains {
-        scroll_delta := mouse_get_scroll_vector();
-        state.xscroll -= scroll_delta.x * 20;
-        state.yscroll -= scroll_delta.y * 20;
-
-        state.yscroll = math.clamp(state.yscroll, 0, max_y_scroll);
-    }
-
-    immediate_flush();
-    immediate_push_scissor(r.x, r.y, r.w, r.h);
-    immediate_set_scroll(-state.xscroll, -state.yscroll);
-}
-
-scrolling_region_stop :: () {
-    immediate_flush();
-    immediate_pop_scissor();
-    immediate_set_scroll(0, 0);
-}
-
-
-
-#local {
-    hot_item    : UI_Id = 0
-    hot_item_was_set := false
-
-    hot_item_depth := 0;
-    hot_item_depth_needed := 0;
-
-    active_item : UI_Id = 0
-    active_countdown := 0
-
-    set_active_item :: (id: UI_Id) -> bool {
-        active_item = id;
-        active_countdown = 2;
-        return true;
-    }
-
-    renew_active_item :: (id: UI_Id) {
-        if active_item == id {
-            active_countdown = 2;
-        }
-    }
-
-    set_hot_item :: (id: UI_Id, force := false) -> bool {
-        if active_item != 0 do return false;
-
-        if force {
-            hot_item_was_set = true;
-            hot_item = id;
-            return true;
-        }
-
-        hot_item_depth += 1;
-        if hot_item_depth >= hot_item_depth_needed {
-            hot_item_was_set = true;
-            hot_item = id;
-            return true;
-        }
-
-        return false;
-    }
-
-    is_active_item :: (id: UI_Id) -> bool {
-        return active_item == id;
-    }
-
-    is_hot_item :: (id: UI_Id) -> bool {
-        return hot_item == id;
-    }
-
-    Text_Theme :: struct {
-        text_color := Color.{1, 1, 1};
-        font_name  := "./assets/fonts/calibri.ttf";
-        font_size  := 18;
-    }
-
-    Animation_Theme :: struct {
-        hover_speed       := 0.1f;
-        click_decay_speed := 0.08f;
-    }
-
-    Animation_State :: struct {
-        hover_time := 0.0f;
-        click_time := 0.0f;
-
-        accessed_this_frame := false;
-    }
-
-    animation_states : Map(UI_Id, Animation_State);
-
-    get_animation :: (id: UI_Id) -> ^Animation_State {
-        retval := map.get_ptr(^animation_states, id);
-        if retval == null {
-            animation_states[id] = .{};
-            retval = ^animation_states[id];
-        }
-
-        retval.accessed_this_frame = true;
-        return retval;
-    }
-
-    has_active_animation :: () -> bool {
-        for^ animation_states.entries {
-            if it.value.hover_time != 0.0f || it.value.hover_time != 0.0f do return true;
-            if it.value.click_time != 0.0f || it.value.click_time != 0.0f do return true;
-        }
-
-        return false;
-    }
-
-
-    Scroll_State :: struct {
-        xscroll: f32;
-        yscroll: f32;
-    }
-
-    scroll_states : Map(UI_Id, Scroll_State);
-
-    get_site_hash :: macro (site: CallSite, increment := 0) -> UI_Id {
-        file_hash   := core.hash.to_u32(site.file);
-        line_hash   := core.hash.to_u32(site.line);
-        column_hash := core.hash.to_u32(site.column);
-
-        return ~~ (file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382 + increment);
-    }
-
-    color_lerp :: macro (t: f32, c1, c2: Color) => Color.{
-        r = c1.r * (1 - t) + c2.r * t,
-        g = c1.g * (1 - t) + c2.g * t,
-        b = c1.b * (1 - t) + c2.b * t,
-        a = c1.a * (1 - t) + c2.a * t,
-    };
-}
index d538d19aaba6bb4c33b0c6b865ed92043b98f8a2..05c80aae8bcd46249e3218386a3538129ffe4f25 100644 (file)
@@ -2,29 +2,17 @@
 use core
 use opengles
 use glfw3
+use ogre
 
 DEBUG :: #defined(runtime.vars.DEBUG)
 
-// @GlobalVariable
-window: GLFWwindow_p
-
-// @GlobalVariable
-window_width: u32
-window_height: u32
+window: Window
 
 #if DEBUG { debug_font: Font; }
 
 init :: () {
-    window = create_window();
-    input_bind_glfw_events(window);
-    glInit(glfwGetLoadProcAddress());
-
-    glEnable(GL_TEXTURE);
-    glEnable(GL_CULL_FACE);
-    glFrontFace(GL_CW);
-    glCullFace(GL_BACK);
-    glEnable(GL_BLEND);
-    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+    window = window_create(1200, 900, #cstr "Bar Simulator");
+    window_use(^window);
 
     shaders_init();
     fonts_init();
@@ -32,9 +20,6 @@ init :: () {
     editor_init();
     game_init();
 
-    glfwGetWindowSize(window, ^window_width, ^window_height);
-    update_view_matrix(window_width, window_height);
-
     #if DEBUG { debug_font = font_lookup(.{"./assets/fonts/calibri.ttf", 16}); }
 }
 
@@ -46,7 +31,7 @@ update :: (dt: f32) {
     input_update();
 
     if is_key_just_up(GLFW_KEY_ESCAPE) {
-        glfwSetWindowShouldClose(window, true);
+        glfwSetWindowShouldClose(window.glfw_window, true);
         return;
     }
 
@@ -59,7 +44,7 @@ update :: (dt: f32) {
 
 draw :: () {
     immediate_clear(.{0.15, 0.15, 0.2});
-    defer ui_end_frame();
+    defer ui.ui_end_frame();
     defer input_post_update();
     defer {
         immediate_flush();
@@ -73,10 +58,10 @@ draw :: () {
 
             version_buf : [32] u8;
             version_str := conv.format(version_buf, "Version: {}.{}", runtime.vars.MAJOR_VERSION, runtime.vars.MINOR_VERSION);
-            font_print(debug_font, ~~window_width - font_get_width(debug_font, version_str), 16, version_str);
+            font_print(debug_font, ~~window.width - font_get_width(debug_font, version_str), 16, version_str);
         }
 
-        glfwSwapBuffers(window);
+        glfwSwapBuffers(window.glfw_window);
     }
 
     game_draw();
@@ -100,7 +85,7 @@ run :: () {
     seconds := 0.0;
     frame_count := 0;
 
-    while !glfwWindowShouldClose(window) {
+    while !glfwWindowShouldClose(window.glfw_window) {
         glfwPollEvents();
 
         now = glfwGetTime();
@@ -121,29 +106,6 @@ run :: () {
     }
 }
 
-create_window :: () => {
-    #if runtime.compiler_os == .Linux {
-        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
-        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
-        glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
-    } else {
-        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
-        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
-    }
-    window := glfwCreateWindow(1200, 900, #cstr "Bar simulator");
-
-    glfwMakeContextCurrent(window);
-    glfwSwapInterval(1);
-    glfwSetWindowSizeCallback(window, "on_resize");
-    return window;
-}
-
-#export "on_resize" (window: GLFWwindow_p, width, height: u32) {
-    window_width = width;
-    window_height = height;
-    update_view_matrix(width, height);
-}
-
 main :: () {
     random.set_seed(os.time());
 
diff --git a/src/ogre/canvas.onyx b/src/ogre/canvas.onyx
new file mode 100644 (file)
index 0000000..7e85764
--- /dev/null
@@ -0,0 +1,62 @@
+package ogre
+
+use core
+use opengles
+
+
+Canvas :: struct {
+    width, height: i32;
+
+    framebuffer: GLint;
+    depth_stencil_buffer: GLint;
+    color_texture: GLint;
+}
+
+canvas_make :: (width, height: i32) -> Canvas {
+    canvas: Canvas;
+    canvas.width = width;
+    canvas.height = height;
+
+    glGenFramebuffers(1, ^canvas.framebuffer);
+    glBindFramebuffer(GL_FRAMEBUFFER, canvas.framebuffer);
+
+    glGenTextures(1, ^canvas.color_texture);
+    glBindTexture(GL_TEXTURE_2D, canvas.color_texture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, null);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glBindTexture(GL_TEXTURE_2D, 0);
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, canvas.color_texture, 0);
+
+    glGenRenderbuffers(1, ^canvas.depth_stencil_buffer);
+    glBindRenderbuffer(GL_RENDERBUFFER, canvas.depth_stencil_buffer);
+    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
+    glBindRenderbuffer(GL_RENDERBUFFER, 0);
+    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, canvas.depth_stencil_buffer);
+
+    if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE {
+        printf("[ERROR] Framebuffer is not complete!\n");
+    }
+
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);  
+    return canvas;
+}
+
+canvas_free :: (use canvas: ^Canvas) {
+    glDeleteFramebuffers(1, ^canvas.framebuffer);
+    glDeleteTextures(1, ^canvas.color_texture);
+    glDeleteRenderbuffers(1, ^canvas.depth_stencil_buffer);
+}
+
+canvas_use :: (use canvas: ^Canvas) {
+    if canvas == null {
+        glBindFramebuffer(GL_FRAMEBUFFER, 0);
+        update_view_matrix(global_window.width, global_window.height);
+    } else {
+        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+        update_view_matrix(width, height);
+    }
+}
+
+canvas_to_texture :: (canvas: ^Canvas) => Texture.{ canvas.color_texture, canvas.width, canvas.height, 3, "<canvas>" };
+
diff --git a/src/ogre/colors.onyx b/src/ogre/colors.onyx
new file mode 100644 (file)
index 0000000..9a7cceb
--- /dev/null
@@ -0,0 +1,56 @@
+package ogre
+
+use core
+
+Color :: struct {
+    r, g, b :  f32;
+    a       := 1.0f;
+}
+
+color_to_hsl :: (c: Color) -> (h: f32, s: f32, l: f32) {
+    r := c.r / 255;
+    g := c.g / 255;
+    b := c.b / 255;
+    cmax := math.max(math.max(r, g), b);
+    cmin := math.min(math.min(r, g), b);
+    delta := cmax - cmin;
+
+    h := 0.0f;
+    if cmax == r {
+        m := (g - b) / delta;
+        while m >= 6 do m -= 6;
+        while m < 0  do m += 6;
+        h = 60 * m;
+    }
+    if cmax == g do h = 60 * (b - r) / delta + 2;
+    if cmax == b do h = 60 * (r - g) / delta + 4;
+
+    l := (cmax + cmin) / 2;
+    s := delta / (1 - math.abs(2 * l - 1));
+
+    return h, s, l;
+}
+
+hsl_to_color :: (h, s, l: f32) -> Color {
+    c := (1 - math.abs(2 * l - 1)) * s;
+
+    h_term := h / 60;
+    while h_term < 0  do h_term += 2;
+    while h_term >= 0 do h_term -= 2;
+
+    x := c * (1 - math.abs(h_term - 1));
+    m := l - c / 2;
+
+    r, g, b: f32;
+    if 0   <= h && h < 60  { r = c; g = x; b = 0; }
+    if 60  <= h && h < 120 { r = x; g = c; b = 0; }
+    if 120 <= h && h < 180 { r = 0; g = c; b = x; }
+    if 180 <= h && h < 240 { r = 0; g = x; b = c; }
+    if 240 <= h && h < 300 { r = x; g = 0; b = c; }
+    if 300 <= h && h < 360 { r = c; g = 0; b = x; }
+
+    r = (r + m) * 255;
+    g = (g + m) * 255;
+    b = (b + m) * 255;
+    return .{ r, g, b, 1 };
+}
diff --git a/src/ogre/font.onyx b/src/ogre/font.onyx
new file mode 100644 (file)
index 0000000..9a9f090
--- /dev/null
@@ -0,0 +1,267 @@
+package ogre
+
+use core
+use stb_truetype
+use opengles
+
+#local {
+    font_registry: Map(FontDescriptor, Font);
+    font_vbo:      GLint;
+    font_vao:      GLint;
+
+    font_shader:   Shader;
+    font_color:    Color;
+}
+
+fonts_init :: () {
+    map.init(^font_registry);
+
+    font_shader = shader_make("./assets/shaders/font.glsl");
+    shader_use(font_shader);
+    shader_link_window_matrix_block(font_shader);
+    shader_link_world_matrix_block(font_shader);
+
+    glGenVertexArrays(1, ^font_vao);
+    glBindVertexArray(font_vao);
+
+    font_interp_buffer: GLint;
+    glGenBuffers(1, ^font_interp_buffer);
+    glBindBuffer(GL_ARRAY_BUFFER, font_interp_buffer);
+    font_interp_data := f32.[
+        0.0, 0.0,
+        1.0, 0.0,
+        1.0, 1.0,
+        0.0, 1.0,
+    ];
+    glBufferData(GL_ARRAY_BUFFER, sizeof typeof font_interp_data, ~~^font_interp_data, GL_STATIC_DRAW);
+    glEnableVertexAttribArray(0);
+    glVertexAttribPointer(0, 2, GL_FLOAT, false, 2 * sizeof f32, ~~0);
+
+    font_index_buffer: GLint;
+    glGenBuffers(1, ^font_index_buffer);
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, font_index_buffer);
+    font_index_data := u8.[ 0, 1, 2, 0, 2, 3 ];
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof typeof font_index_data, ~~^font_index_data, GL_STATIC_DRAW);
+
+    glGenBuffers(1, ^font_vbo);
+    glBindBuffer(GL_ARRAY_BUFFER, font_vbo);
+    glBufferData(GL_ARRAY_BUFFER, 1024 * sizeof stbtt_aligned_quad, null, GL_STREAM_DRAW);
+
+    for 1..5 {
+        glEnableVertexAttribArray(it);
+        glVertexAttribDivisor(it, 1);
+    }
+    glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~ 0);
+    glVertexAttribPointer(2, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~16);
+    glVertexAttribPointer(3, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~8);
+    glVertexAttribPointer(4, 2, GL_FLOAT, false, sizeof stbtt_aligned_quad, ~~24);
+
+    glBindBuffer(GL_ARRAY_BUFFER, -1);
+    glBindVertexArray(-1);
+
+    font_set_color(.{0,0,0});
+}
+
+
+Font :: struct {
+    texture: GLint;
+    texture_width, texture_height: i32;
+    chars: [] stbtt_packedchar;
+    em: f32;
+}
+
+font_make :: (fd: FontDescriptor) -> Font {
+    texture_size :: 256;
+
+    char_data := memory.make_slice(stbtt_packedchar, 96);
+
+    ttf_file := os.get_contents(fd.path);
+    if ttf_file.count == 0 {
+        println("Bad font");
+        return .{};
+    }
+    defer cfree(ttf_file.data);
+
+    pixels := calloc(texture_size * texture_size);
+    defer cfree(pixels);
+
+    ctx: stbtt_pack_context;
+    stbtt_PackBegin(^ctx, pixels, texture_size, texture_size, 0, 1);
+    stbtt_PackFontRange(^ctx, ttf_file.data, 0, ~~fd.size, #char " ", 96, char_data.data);
+    stbtt_PackEnd(^ctx);
+
+    texture: GLint;
+    glGenTextures(1, ^texture);
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture_size, texture_size, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glBindTexture(GL_TEXTURE_2D, 0);
+
+    font := Font.{
+        texture = texture,
+        texture_width = texture_size,
+        texture_height = texture_size,
+        chars = char_data,
+        em = ~~fd.size,
+    };
+
+    font_registry[fd] = font;
+
+    return font;
+}
+
+font_set_color :: (color: Color) {
+    font_color = color;
+}
+
+font_print :: (font: Font, x, y: f32, format: str, va: ..any) {
+    buf: [1024] u8;
+    msg := conv.format_va(buf, format, va);
+    font_draw(font, x, y, msg);
+}
+
+font_draw :: (font: Font, x, y: f32, msg: str) {
+    quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad);
+    quad_num := 0;
+
+    x_, y_ := x, y;
+
+    for msg {
+        if it == #char "\n" {
+            x_ = x;
+            y_ += font.em + 2;
+        }
+
+        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
+            ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false);
+
+        quad_num += 1;
+    }
+
+    font_render(font, quads[0 .. quad_num]);
+}
+
+font_draw_centered :: (font: Font, x, y, max_width: f32, msg: str) {
+    quads: ^stbtt_aligned_quad = alloc.from_stack(msg.count * sizeof stbtt_aligned_quad);
+    quad_num := 0;
+
+    width := font_get_width(font, msg);
+    x_, y_ := x, y;
+    x_ = (max_width - width) / 2 + x;
+
+    for msg {
+        if it == #char "\n" {
+            x_ = (max_width - width) / 2 + x;
+            y_ += font.em + 2;
+        }
+
+        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
+            ~~(it - #char " "), ^x_, ^y_, ^quads[quad_num], false);
+
+        quad_num += 1;
+    }
+
+    font_render(font, quads[0 .. quad_num]);
+}
+
+#local font_render :: (font: Font, quads: [] stbtt_aligned_quad) {
+    // If this is being used in conjunction with the immediate
+    // rendering system, make sure the immediate objects are flushed
+    // before trying to render over them.
+    #if #defined(immediate_flush) {
+        immediate_flush();
+    }
+
+    glBindBuffer(GL_ARRAY_BUFFER, font_vbo);
+    glBufferSubData(GL_ARRAY_BUFFER, 0, quads.count * sizeof stbtt_aligned_quad, quads.data);
+    glBindBuffer(GL_ARRAY_BUFFER, -1);
+
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, font.texture);
+    shader_use(font_shader);
+    shader_set_uniform(font_shader, #cstr "u_texture", 0);
+    shader_set_uniform(font_shader, #cstr "u_color", font_color);
+
+    glDisable(GL_DEPTH_TEST);
+    glBindVertexArray(font_vao);
+    glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, ~~0, quads.count);
+    glBindTexture(GL_TEXTURE_2D, -1);
+    glBindVertexArray(-1);
+}
+
+font_get_width :: (font: Font, msg: str) -> f32 {
+    x_, y_ := 0.0f, 0.0f;
+    width := 0.0f;
+
+    quad: stbtt_aligned_quad;
+    for msg {
+        if it == #char "\n" {
+            width = math.max(width, x_);
+            x_ = 0;
+            y_ += font.em + 2;
+            continue;
+        }
+
+        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
+            ~~(it - #char " "), ^x_, ^y_, ^quad, false);
+    }
+
+    return math.max(x_, width);
+}
+
+font_get_height :: (font: Font, msg: str) -> f32 {
+    x_, y_ := 0.0f, 0.0f;
+    width := 0.0f;
+
+    quad: stbtt_aligned_quad;
+    for msg {
+        if it == #char "\n" {
+            width = math.max(width, x_);
+            x_ = 0;
+            y_ += font.em + 2;
+            continue;
+        }
+
+        stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
+            ~~(it - #char " "), ^x_, ^y_, ^quad, false);
+    }
+
+    return y_ + font.em + 2;
+}
+
+font_get_char_width :: (font: Font, ch: u8) -> f32 {
+    x, y := 0.0f, 0.0f;
+    quad: stbtt_aligned_quad;
+
+    stbtt_GetPackedQuad(font.chars.data, font.texture_width, font.texture_height,
+        ~~(ch - #char " "), ^x, ^y, ^quad, false);
+
+    return x;
+}
+
+
+FontDescriptor :: struct {
+    path : str;
+    size : u32;
+}
+
+#match hash.to_u32 (fd: FontDescriptor) => {
+    name_hash := hash.to_u32(fd.path);
+    size_hash := hash.to_u32(fd.size);
+    return name_hash * 13 + size_hash * 17;
+}
+
+#operator == (f1, f2: FontDescriptor) => f1.path == f2.path && f1.size == f2.size;
+
+font_lookup :: (fd := FontDescriptor.{ "assets/calibri.ttf", 12 }) -> Font {
+    if font_registry->has(fd) {
+        return font_registry[fd];
+    }
+
+    font := font_make(fd);
+    font_registry[fd] = font;
+    return font;
+}
diff --git a/src/ogre/immediate.onyx b/src/ogre/immediate.onyx
new file mode 100644 (file)
index 0000000..0cd0505
--- /dev/null
@@ -0,0 +1,219 @@
+package ogre
+
+use core
+use opengles
+use glfw3
+
+immediate_init :: () {
+    memory.alloc_slice(^vertex_data, Maximum_Vertex_Count);
+    vertex_count = 0;
+
+    imgui_shader = shader_make(Shader_Path);
+    shader_use(imgui_shader);
+    shader_link_window_matrix_block(imgui_shader);
+    shader_link_world_matrix_block(imgui_shader);
+    shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 0.0f);
+    shader_set_uniform(imgui_shader, #cstr "u_texture", 0);
+
+    immediate_mesh = mesh_make(vertex_data, .[], GL_DYNAMIC_DRAW);
+    immediate_color = .{0,0,0};
+}
+
+immediate_flush :: (set_shader := true) {
+    if vertex_count == 0 do return;
+
+    if set_shader {
+        shader_use(imgui_shader);
+        shader_set_uniform(imgui_shader, #cstr "u_texture_enabled", 1.0f if rendering_type == .Image else 0.0f);
+    }
+
+    immediate_mesh.vertex_count = vertex_count;
+    mesh_update_verticies(immediate_mesh, vertex_data);
+
+    mesh_draw(immediate_mesh);
+
+    vertex_count = 0;
+    rendering_type = .Plain;
+    immediate_mesh.primitive = GL_TRIANGLES;
+}
+
+immediate_clear :: (color: Color) {
+    glClearColor(color.r, color.g, color.b, color.a);
+    glClear(GL_COLOR_BUFFER_BIT);
+}
+
+immediate_set_color :: (color: Color) {
+    immediate_color = color;
+}
+
+immediate_vertex :: (x, y: f32, t_x := 0.0f, t_y := 0.0f) {
+    if vertex_count >= Maximum_Vertex_Count do immediate_flush();
+    set_rendering_type(.Plain);
+
+    vertex_data[vertex_count] = .{ .{x, y}, .{t_x, t_y}, immediate_color };
+}
+
+immediate_line :: (x1, y1, x2, y2: f32) {
+    if vertex_count >= Maximum_Vertex_Count do immediate_flush();
+    set_rendering_type(.Line);
+    immediate_mesh.primitive = GL_LINES;
+
+    vertex_data[vertex_count + 0] = .{ .{x1, y1}, .{0, 0}, immediate_color };
+    vertex_data[vertex_count + 1] = .{ .{x2, y2}, .{0, 0}, immediate_color };
+    vertex_count += 2;
+}
+
+immediate_triangle :: (x1, x2, x3: Vector2) {
+    if vertex_count + 3 > Maximum_Vertex_Count do immediate_flush();
+    set_rendering_type(.Plain);
+
+    vertex_data[vertex_count + 0] = .{ x1, .{0,0}, immediate_color };
+    vertex_data[vertex_count + 1] = .{ x2, .{0,0}, immediate_color };
+    vertex_data[vertex_count + 2] = .{ x3, .{0,0}, immediate_color };
+    vertex_count += 3;
+}
+
+immediate_rectangle :: (x, y, w, h: f32) {
+    if vertex_count + 6 > Maximum_Vertex_Count do immediate_flush();
+    set_rendering_type(.Plain);
+
+    vertex_data[vertex_count + 0] = .{ .{x,   y},   .{0,0}, immediate_color };
+    vertex_data[vertex_count + 1] = .{ .{x+w, y},   .{0,0}, immediate_color };
+    vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{0,0}, immediate_color };
+    vertex_data[vertex_count + 3] = .{ .{x,   y},   .{0,0}, immediate_color };
+    vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{0,0}, immediate_color };
+    vertex_data[vertex_count + 5] = .{ .{x,   y+h}, .{0,0}, immediate_color };
+    vertex_count += 6;
+}
+
+immediate_image :: (image: ^Texture, x, y, w, h: f32) {
+    if vertex_count > 0 do immediate_flush();
+
+    set_rendering_type(.Image);
+    texture_use(image);
+    shader_use(imgui_shader);
+    shader_set_uniform(imgui_shader, #cstr "u_texture", 0);
+
+    vertex_data[vertex_count + 0] = .{ .{x, y},     .{0,0}, immediate_color };
+    vertex_data[vertex_count + 1] = .{ .{x+w, y},   .{1,0}, immediate_color };
+    vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{1,1}, immediate_color };
+    vertex_data[vertex_count + 3] = .{ .{x, y},     .{0,0}, immediate_color };
+    vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{1,1}, immediate_color };
+    vertex_data[vertex_count + 5] = .{ .{x, y+h},   .{0,1}, immediate_color };
+    vertex_count += 6;
+}
+
+immediate_subimage :: (image: ^Texture, x, y, w, h: f32, tx, ty, tw, th: f32) {
+    if vertex_count > 0 do immediate_flush();
+
+    set_rendering_type(.Image);
+    texture_use(image);
+    shader_use(imgui_shader);
+    shader_set_uniform(imgui_shader, #cstr "u_texture", 0);
+
+    sx := tx / ~~ image.width;
+    sy := ty / ~~ image.height;
+    sw := tw / ~~ image.width;
+    sh := th / ~~ image.height;
+
+    vertex_data[vertex_count + 0] = .{ .{x, y},     .{sx,      sy}, immediate_color };
+    vertex_data[vertex_count + 1] = .{ .{x+w, y},   .{sx+sw,   sy}, immediate_color };
+    vertex_data[vertex_count + 2] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color };
+    vertex_data[vertex_count + 3] = .{ .{x, y},     .{sx,      sy}, immediate_color };
+    vertex_data[vertex_count + 4] = .{ .{x+w, y+h}, .{sx+sw,sy+sh}, immediate_color };
+    vertex_data[vertex_count + 5] = .{ .{x, y+h},   .{sx,   sy+sh}, immediate_color };
+    vertex_count += 6;
+}
+
+immediate_ellipse :: () {}
+
+immediate_push_scissor :: (x, y, w, h: f32) {
+    if vertex_count > 0 do immediate_flush();
+
+    // Assuming that x, y, w, and h are in screen (window) coordinates.
+    x += offset.x;
+    y += offset.y;
+
+    xi := cast(i32) x;
+    yi := cast(i32) y;
+    wi := cast(i32) w;
+    hi := cast(i32) h;
+    scissors << .{xi, yi, wi, hi};
+
+    glEnable(GL_SCISSOR_TEST);
+    glScissor(xi, global_window.height - yi - hi, wi, hi);
+}
+
+immediate_pop_scissor :: () {
+    if scissors.count > 0 {
+        array.pop(^scissors);
+    }
+
+    if scissors.count > 0 {
+        glEnable(GL_SCISSOR_TEST);
+        s := scissors[scissors.count - 1];
+        glScissor(s.x, global_window.height - s.y - s.h, s.w, s.h);
+    } else {
+        glDisable(GL_SCISSOR_TEST);
+    }
+}
+
+immediate_set_scroll :: (scroll_x, scroll_y: f32) {
+    offset = .{ scroll_x, scroll_y };
+    update_model_matrix(offset);
+}
+
+immediate_get_scroll :: () => offset;
+
+Immediate_Vertex :: struct {
+    pos:   Vector2;
+    tex:   Vector2;
+    color: Color;
+}
+
+#local {
+    Shader_Path   :: "./assets/shaders/imgui.glsl"
+    imgui_shader: Shader;
+
+    Maximum_Vertex_Count :: 1023;
+    vertex_count: i32;
+    vertex_data:  [] Immediate_Vertex;
+
+    immediate_color: Color;
+
+    immediate_mesh: ^Mesh(Immediate_Vertex);
+
+    Rendering_Type :: enum {
+        Plain;
+        Line;
+        Image;
+    }
+    rendering_type := Rendering_Type.Plain;
+
+    Scissor :: struct {
+        x, y, w, h: i32;
+    }
+    scissors: [..] Scissor;
+
+    offset: Vector2;
+
+    set_rendering_type :: (new_type: typeof rendering_type) {
+        if rendering_type != new_type {
+            immediate_flush();
+            rendering_type = new_type;
+        }
+    }
+}
+
+
+
+// @Relocate
+move_towards :: (v: ^$T, target: T, diff: T) {
+    if math.abs(target - *v) <= diff {
+        *v = target;
+        return;
+    }
+
+    if *v < target do *v += diff;
+    if *v > target do *v -= diff;
+}
diff --git a/src/ogre/input.onyx b/src/ogre/input.onyx
new file mode 100644 (file)
index 0000000..38d656e
--- /dev/null
@@ -0,0 +1,186 @@
+package ogre
+
+use package core
+use package glfw3
+
+// If you are offseting the mouse coordinate for world space
+// or UI scrolling etc., set this function to be the function
+// to ask for the current offset. Currently scaling the mouse
+// coordinate is not supported, only linear translations.
+Mouse_Offset_Function :: immediate_get_scroll
+
+
+#local {
+    keys_this_frame  : Set(Key_Descriptor)   // Keys currently being pressed this frame
+    keys_last_frame  : Set(Key_Descriptor)   // Keys being pressed in the last frame
+
+    key_codepoints : [..] u32
+
+    buttons_this_frame: [8] bool  // Mouse buttons being pressed this frame
+    buttons_last_frame: [8] bool  // Mouse buttons being pressed last frame
+
+    scroll_x: f64
+    scroll_y: f64
+
+    character_mode := false;
+}
+
+#init () {
+    set.init(^keys_this_frame);
+    set.init(^keys_last_frame);
+}
+
+Key_Descriptor :: struct {
+    key: u32;
+    scancode: u32 = 0;
+    mod: u32      = 0;
+}
+#match hash.to_u32 (use x: Key_Descriptor) => hash.to_u32(key);
+#operator == (x, y: Key_Descriptor) => x.key == y.key;
+
+input_update :: () {
+    glfwGetCursorPos(global_window.glfw_window, ^mouse_x, ^mouse_y);
+}
+
+input_post_update :: () {
+    set.clear(^keys_last_frame);
+    for keys_this_frame.entries do keys_last_frame << it.value;
+
+    for 8 do buttons_last_frame[it] = buttons_this_frame[it];
+
+    last_mouse_x = mouse_x;
+    last_mouse_y = mouse_y;
+    scroll_x = 0;
+    scroll_y = 0;
+
+    array.clear(^key_codepoints);
+}
+
+input_get_chars_this_frame :: () -> [] u32 {
+    return key_codepoints;
+}
+
+input_get_keys_this_frame :: () -> Iterator(Key_Descriptor) {
+    #persist index := 0;
+
+    next :: (_: rawptr) -> (Key_Descriptor, bool) {
+        if index >= keys_this_frame.entries.count do return .{0}, false;
+
+        while keys_last_frame->has(keys_this_frame.entries[index].value) {
+            index += 1;
+            if index >= keys_this_frame.entries.count do return .{0}, false;
+        }
+
+        defer index += 1;
+        return keys_this_frame.entries[index].value, true;
+    }
+
+    index = 0;
+    return .{ null, next };
+}
+
+input_capture_keys :: () {
+    character_mode = true;
+}
+
+input_release_keys :: () {
+    character_mode = false;
+}
+
+is_key_down      :: (key) => !character_mode && set.has(^keys_this_frame, .{key});
+is_key_just_down :: (key) => !character_mode && set.has(^keys_this_frame, .{key}) && !set.has(^keys_last_frame, .{key});
+is_key_just_up   :: (key) => !character_mode && !set.has(^keys_this_frame, .{key}) && set.has(^keys_last_frame, .{key});
+
+is_button_down      :: (button) => buttons_this_frame[button];
+is_button_just_down :: (button) => buttons_this_frame[button] && !buttons_last_frame[button];
+is_button_just_up   :: (button) => !buttons_this_frame[button] && buttons_last_frame[button];
+
+#local {
+    last_mouse_x: f64;
+    last_mouse_y: f64;
+
+    mouse_x: f64;
+    mouse_y: f64;
+}
+
+mouse_get_delta :: () -> (f64, f64) {
+    return mouse_x - last_mouse_x, mouse_y - last_mouse_y;
+}
+
+mouse_get_delta_vector :: () -> Vector2 {
+    dmx, dmy := mouse_get_delta();
+    return .{ ~~dmx, ~~dmy };
+}
+
+mouse_get_position :: () -> (f64, f64) {
+    mx, my := mouse_x, mouse_y;        
+    #if #defined(Mouse_Offset_Function) {
+        scroll := Mouse_Offset_Function();
+        mx -= ~~ scroll.x;
+        my -= ~~ scroll.y;
+    }
+    return mx, my;
+}
+
+mouse_get_position_vector :: () -> Vector2 {
+    mx, my := mouse_x, mouse_y;        
+    #if #defined(Mouse_Offset_Function) {
+        scroll := Mouse_Offset_Function();
+        mx -= ~~ scroll.x;
+        my -= ~~ scroll.y;
+    }
+    return .{ ~~mx, ~~my };
+}
+
+mouse_get_scroll :: () -> (f64, f64) {
+    return scroll_x, scroll_y;
+}
+
+mouse_get_scroll_vector :: () -> Vector2 {
+    return .{ ~~scroll_x, ~~scroll_y };
+}
+
+input_bind_glfw_events :: (window: GLFWwindow_p) {
+    glfwSetKeyCallback(window, #export_name input_key_event);
+    glfwSetCharCallback(window, #export_name input_char_event);
+    glfwSetMouseButtonCallback(window, #export_name input_button_event);
+    glfwSetScrollCallback(window, #export_name input_scroll_event);
+}
+
+#local
+input_button_event :: (window: GLFWwindow_p, button, action, mod: u32) {
+    if action == GLFW_PRESS {
+        buttons_this_frame[button] = true;
+    }
+
+    if action == GLFW_RELEASE {
+        buttons_this_frame[button] = false;
+    }
+}
+
+#local
+input_key_event :: (window: GLFWwindow_p, key, scancode, action, mod: u32) {
+    if action == GLFW_PRESS {
+        keys_this_frame << .{ key, scancode, mod };
+    }
+
+    if action == GLFW_REPEAT {
+        set.remove(^keys_last_frame, .{key});
+    }
+
+    if action == GLFW_RELEASE {
+        set.remove(^keys_this_frame, .{ key });
+    }
+}
+
+#local
+input_char_event :: (window: GLFWwindow_p, codepoint: u32) {
+    if !character_mode do return;
+    key_codepoints << codepoint;       
+}
+
+#local
+input_scroll_event :: (window: GLFWwindow_p, xoff, yoff: f64) {
+    scroll_x = xoff;
+    scroll_y = yoff;
+}
diff --git a/src/ogre/mesh.onyx b/src/ogre/mesh.onyx
new file mode 100644 (file)
index 0000000..ba926aa
--- /dev/null
@@ -0,0 +1,90 @@
+package ogre
+
+use core
+use opengles
+
+Mesh :: struct (Vertex_Type: type_expr) {
+    handle: GLint;
+    vertex_handle: GLint;
+    index_handle: GLint;
+
+    vertex_count: u32;
+    index_count: u32;
+    primitive: GLuint = GL_TRIANGLES;
+}
+
+mesh_make :: (verticies: [] $T, indicies: [] u32, gl_hint := GL_STATIC_DRAW) -> ^Mesh(T) {
+    mesh := new(Mesh(T));
+    mesh.vertex_count = verticies.count;
+    mesh.index_count  = indicies.count;
+
+    glGenVertexArrays(1, ^mesh.handle);
+    glBindVertexArray(mesh.handle);
+
+    glGenBuffers(1, ^mesh.vertex_handle);
+    glBindBuffer(GL_ARRAY_BUFFER, mesh.vertex_handle);
+    glBufferData(GL_ARRAY_BUFFER, sizeof T * verticies.count, verticies.data, gl_hint);
+
+    type_info :: runtime.info
+    vertex_info := cast(^type_info.Type_Info_Struct) type_info.get_type_info(T);
+    vertex_attr := 0;
+    for attr: vertex_info.members {
+        defer vertex_attr += 1;
+        glEnableVertexAttribArray(vertex_attr);
+
+        switch attr.type {
+            case Vector2 do glVertexAttribPointer(vertex_attr, 2, GL_FLOAT, false, sizeof T, ~~attr.offset);
+            case Vector3 do glVertexAttribPointer(vertex_attr, 3, GL_FLOAT, false, sizeof T, ~~attr.offset);
+            case u32 do glVertexAttribIPointer(vertex_attr, 1, GL_UNSIGNED_INT, sizeof T, ~~attr.offset);
+            case i32 do glVertexAttribIPointer(vertex_attr, 1, GL_INT, sizeof T, ~~attr.offset);
+
+            // It would be nice to not have to have all the cases here.
+            // Instead allow for an extensible way of defining them at compile time.
+            case Color do glVertexAttribPointer(vertex_attr, 4, GL_FLOAT, false, sizeof T, ~~attr.offset);
+
+            case #default {
+                buf: [256] u8;
+                msg := conv.str_format(buf, "Unknown type for GL vertex attribute, {}.", attr.type);
+                assert(false, msg);
+            }
+        }
+    }
+
+    if indicies.count > 0 {
+        glGenBuffers(1, ^mesh.index_handle);
+        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.index_handle);
+        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof i32 * indicies.count, indicies.data, gl_hint);
+    }
+
+    glBindVertexArray(-1);
+
+    return mesh;
+}
+
+mesh_update_verticies :: (use mesh: ^Mesh, verticies: [] mesh.Vertex_Type) {
+    // @TODO // Add bounds checking to arrays here.
+
+    glBindBuffer(GL_ARRAY_BUFFER, vertex_handle);
+    glBufferSubData(GL_ARRAY_BUFFER, 0, verticies.count * sizeof mesh.Vertex_Type, verticies.data);
+    glBindBuffer(GL_ARRAY_BUFFER, -1);
+}
+
+mesh_draw :: (use mesh: ^Mesh) {
+    glBindVertexArray(handle);
+    if index_count > 0 {
+        glDrawElements(primitive, index_count, GL_UNSIGNED_INT, ~~0);
+    } else {
+        glDrawArrays(primitive, 0, vertex_count);
+    }
+    glBindVertexArray(-1);
+}
+
+mesh_free :: (mesh: ^Mesh) {
+    glDeleteBuffers(1, ^mesh.vertex_handle);
+    glDeleteBuffers(1, ^mesh.index_handle);
+    glDeleteVertexArrays(1, ^mesh.handle);
+    cfree(mesh);
+}
+
+
+
diff --git a/src/ogre/shader.onyx b/src/ogre/shader.onyx
new file mode 100644 (file)
index 0000000..671e583
--- /dev/null
@@ -0,0 +1,182 @@
+package ogre
+
+use core
+use opengles
+
+Shader :: struct {
+    vs, fs: GLuint;
+    prog: GLuint;
+}
+
+window_matrix_block_buffer: GLuint;
+world_matrix_block_buffer:  GLuint;
+
+shaders_init :: () {
+    glGenBuffers(1, ^window_matrix_block_buffer);
+    glGenBuffers(1, ^world_matrix_block_buffer);
+
+    glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer);
+    glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * 16, null, GL_DYNAMIC_DRAW);
+
+    glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer);
+    glBufferData(GL_UNIFORM_BUFFER, sizeof f32 * (16 + 16), null, GL_DYNAMIC_DRAW);
+
+    glBindBuffer(GL_UNIFORM_BUFFER, -1);
+
+    glBindBufferBase(GL_UNIFORM_BUFFER, WINDOW_MATRIX_BLOCK, window_matrix_block_buffer);
+    glBindBufferBase(GL_UNIFORM_BUFFER, WORLD_MATRIX_BLOCK, world_matrix_block_buffer);
+}
+
+shader_make :: (shader_path: str) -> Shader {
+    shader_source := os.get_contents(shader_path);
+    vs := compile_shader(shader_source, GL_VERTEX_SHADER);
+    fs := compile_shader(shader_source, GL_FRAGMENT_SHADER);
+
+    prog := link_program(vs, fs);
+
+    return Shader.{vs, fs, prog};
+}
+
+shader_use :: (shader: Shader) {
+    glUseProgram(shader.prog);
+}
+
+#local {
+    WINDOW_MATRIX_BLOCK :: 0;
+    WORLD_MATRIX_BLOCK  :: 1;
+}
+
+shader_link_window_matrix_block :: (use shader: Shader) {
+    matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_window_matrix_block");
+    glUniformBlockBinding(prog, matrix_block_index, WINDOW_MATRIX_BLOCK);
+}
+
+shader_link_world_matrix_block :: (use shader: Shader) {
+    matrix_block_index := glGetUniformBlockIndex(prog, #cstr "u_world_matrix_block");
+    glUniformBlockBinding(prog, matrix_block_index, WORLD_MATRIX_BLOCK);
+}
+
+shader_set_uniform :: (shader: Shader, uniform: cstr, value: $T) {
+    glUseProgram(shader.prog);
+    location := glGetUniformLocation(shader.prog, uniform);
+
+    set_uniform_internal(location, value);
+    
+    set_uniform_internal :: #match {
+        macro (location: GLint, value: u32) do glUniform1i(location, value); ,
+        macro (location: GLint, value: f32) do glUniform1f(location, value); ,
+        macro (location: GLint, value: Vector2) do glUniform2f(location, value.x, value.y); ,
+        macro (location: GLint, value: Vector3) do glUniform3f(location, value.x, value.y, value.z); ,
+        macro (location: GLint, value: Color)   do glUniform4f(location, value.r, value.g, value.b, value.a); ,
+
+        macro (location: GLint, value: $T) {
+            buffer: [1024] u8;
+            assert(false, conv.format(buffer, "Bad shader_set_uniform case: {}", T));
+        }
+    }
+}
+
+update_view_matrix :: (width, height: u32) {
+    matrix : [16] f32;
+    top    := 0.0f;
+    left   := 0.0f;
+    right  := cast(f32) width;
+    bottom := cast(f32) height;
+    far    := 10.0f;
+    near   := 0f;
+    
+    matrix[0] = 2 / (right - left);
+    matrix[5] = 2 / (top - bottom);
+    matrix[10] = -2 / (far - near);
+    matrix[12] = -(right + left) / (right - left);
+    matrix[13] = -(top + bottom) / (top - bottom);
+    matrix[14] = -(far + near) / (far - near);
+    matrix[15] = 1;
+
+    glBindBuffer(GL_UNIFORM_BUFFER, window_matrix_block_buffer);
+    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof matrix, ^matrix);
+    glBindBuffer(GL_UNIFORM_BUFFER, -1);
+
+    glViewport(0, 0, width, height);
+}
+
+update_world_matrix :: () {
+    world_mat: [16] f32;
+    world_mat[0] = 1;
+    world_mat[5] = 1;
+    world_mat[10] = 1;
+    world_mat[15] = 1;
+
+    glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer);
+    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof typeof world_mat, ^world_mat);
+    glBindBuffer(GL_UNIFORM_BUFFER, -1);
+}
+
+update_model_matrix :: (v: Vector2) {
+    model_mat: [16] f32;
+    model_mat[0]  = 1;
+    model_mat[5]  = 1;
+    model_mat[10] = 1;
+    model_mat[12] = v.x;
+    model_mat[13] = v.y;
+    model_mat[14] = 0;
+    model_mat[15] = 1;
+
+    glBindBuffer(GL_UNIFORM_BUFFER, world_matrix_block_buffer);
+    glBufferSubData(GL_UNIFORM_BUFFER, 16 * sizeof f32, sizeof typeof model_mat, ^model_mat);
+    glBindBuffer(GL_UNIFORM_BUFFER, -1);
+}
+
+
+#local {
+    compile_shader :: (source: str, type: GLenum) -> GLint {
+        shader := glCreateShader(type);
+
+        #persist VERTEX_HEADER := """
+#version 300 es
+#define VERTEX_SHADER 1
+#define COMM out
+        """;
+
+        #persist FRAGMENT_HEADER := """
+#version 300 es
+#define FRAGMENT_SHADER 1
+#define COMM in
+        """;
+
+        header      := VERTEX_HEADER if type == GL_VERTEX_SHADER else FRAGMENT_HEADER;
+        sources     : [] cptr(u8) = .[ cptr.make(header.data), cptr.make(source.data) ];
+        source_lens : [] i32      = .[ header.count,           source.count ];
+
+        glShaderSource(shader, 2, sources.data, source_lens.data);
+        glCompileShader(shader);
+
+        success: GLint;
+        if glGetShaderiv(shader, GL_COMPILE_STATUS, ^success); success == GL_FALSE {
+            buf_data: [2048] u8;
+            buf := str.{ ~~buf_data, 0 };
+            glGetShaderInfoLog(shader, 2048, ^buf.count, buf.data);
+            println(buf);
+        }
+
+        return shader;
+    }
+
+    link_program :: (vertex_shader, fragment_shader: GLint) -> GLuint {
+        prog := glCreateProgram();
+        glAttachShader(prog, vertex_shader);
+        glAttachShader(prog, fragment_shader);
+        glLinkProgram(prog);
+
+        success: GLint;
+        if glGetProgramiv(prog, GL_LINK_STATUS, ^success); success == GL_FALSE {
+            buf_data: [1024] u8;
+            buf := str.{ ~~buf_data, 0 };
+            glGetProgramInfoLog(prog, 1024, ^buf.count, buf.data);
+            println(buf);
+        }
+
+        return prog;
+    }
+}
+
diff --git a/src/ogre/texture.onyx b/src/ogre/texture.onyx
new file mode 100644 (file)
index 0000000..64773cc
--- /dev/null
@@ -0,0 +1,88 @@
+package ogre
+
+use core
+use opengles
+use stb_image
+
+#local texture_cache: Map(str, Texture);
+
+Texture :: struct {
+    texture: GLint;
+    width, height, channels: i32;
+    filename: str;
+}
+
+texture_lookup :: #match {}
+#match texture_lookup (filename: str) -> (Texture, bool) {
+    if texture_cache->has(filename) {
+        return texture_cache[filename], true;
+    }
+
+    buffer: [512] u8;
+    memory.copy(~~ buffer, filename.data, math.min(filename.count, 511));
+    return texture_lookup(cast(cstr) buffer);
+}
+
+#match texture_lookup (path: cstr) -> (Texture, bool) {
+    filename := string.from_cstr(path);
+    if texture_cache->has(filename) {
+        return texture_cache[filename], true;
+    }
+
+    tex: Texture;
+    tex.filename = filename;
+    pixels := stbi_load(path, ^tex.width, ^tex.height, ^tex.channels, 4);
+    if pixels == null {
+        printf("[WARN ] Failed to load texture: {}\n", filename);
+        return .{}, false;
+    }
+    defer stbi_image_free(pixels);
+
+    glGenTextures(1, ^tex.texture);
+    glBindTexture(GL_TEXTURE_2D, tex.texture);
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex.width, tex.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
+
+    // Are these sensible defaults?
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+    glBindTexture(GL_TEXTURE_2D, 0);
+
+    // This assumes that the filename data is going to be persistant forever.
+    // Not a great assumption to make but is it really worth copying it?
+    texture_cache[filename] = tex;
+    printf("[INFO ] Loaded texture: {}\n", filename);
+    return tex, true;
+}
+
+texture_free :: (use tex: ^Texture) {
+    glDeleteTextures(1, ^texture);
+}
+
+texture_use :: (use tex: ^Texture, texture_binding := 0) {
+    glActiveTexture(GL_TEXTURE0 + texture_binding);
+    glBindTexture(GL_TEXTURE_2D, texture);
+}
+
+
+Texture_Wrap :: enum {
+    Clamp :: GL_CLAMP_TO_EDGE | 0;
+    Repeat :: GL_REPEAT | 0;
+    Mirrored_Repeat :: GL_MIRRORED_REPEAT | 0;
+}
+
+texture_wrap :: (use tex: ^Texture, wrap: Texture_Wrap) {
+    glBindTexture(GL_TEXTURE_2D, texture);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, ~~ wrap);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, ~~ wrap);
+}
+
+texture_cache_clear :: () {
+    for^ texture_cache.entries {
+        glDeleteTextures(1, ^it.value.texture);
+    }
+
+    map.clear(^texture_cache);
+}
diff --git a/src/ogre/ui.onyx b/src/ogre/ui.onyx
new file mode 100644 (file)
index 0000000..1242f11
--- /dev/null
@@ -0,0 +1,731 @@
+package ogre.ui
+
+//
+// Very simple immediate mode UI
+//
+
+use core
+use opengles
+use glfw3
+use ogre
+
+UI_Id :: u32
+
+ui_end_frame :: () {
+    hot_item_depth_needed = hot_item_depth;
+    if !hot_item_was_set do set_hot_item(0);
+    hot_item_depth = 0;
+    hot_item_was_set = false;
+
+    for^ animation_states.entries {
+        if !it.value.accessed_this_frame || (it.value.click_time == 0 && it.value.hover_time == 0)  {
+            map.delete(^animation_states, it.key);
+        }
+    }
+
+    for^ animation_states.entries {
+        it.value.accessed_this_frame = false;
+    }
+
+    if active_countdown > 0 {
+        active_countdown -= 1;
+        if active_countdown == 0 {
+            set_active_item(0);
+            input_release_keys();
+        }
+    }
+}
+
+//
+// Buttons
+//
+Button_Theme :: struct {
+    use text_theme := Text_Theme.{};
+    use animation_theme := Animation_Theme.{};
+
+    background_color := Color.{ 0.1, 0.1, 0.1 };
+    hover_color      := Color.{ 0.3, 0.3, 0.3 };
+    click_color      := Color.{ 0.5, 0.5, 0.7 };
+
+    border_color := Color.{0.2, 0.2, 0.2};
+    border_width := 2.0f;
+
+    active := false;
+}
+
+#local default_button_theme := Button_Theme.{};
+
+draw_button  :: (use r: Rect, text: str, theme := ^default_button_theme, site := #callsite, increment := 0) -> bool {
+    result := false;
+
+    hash := get_site_hash(site, increment);
+    animation_state := get_animation(hash);
+    mx, my := mouse_get_position();
+
+    contains := Rect.contains(r, .{~~mx, ~~my});
+
+    if is_active_item(hash) {
+        renew_active_item(hash);
+        if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) {
+            if is_hot_item(hash) && contains {
+                result = true;
+                animation_state.click_time = 1.0f;
+            }
+
+            set_active_item(0);
+        }
+    } elseif is_hot_item(hash) {
+        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
+            set_active_item(hash);
+        }
+    }
+
+    if contains {
+        set_hot_item(hash);
+    }
+
+    if is_hot_item(hash) || theme.active {
+        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
+    } else {
+        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
+    }
+
+    border_width := theme.border_width;
+
+    immediate_set_color(theme.border_color);
+    immediate_rectangle(x, y, w, h);
+
+    surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color);
+    surface_color  = color_lerp(animation_state.click_time, surface_color, theme.click_color);
+    immediate_set_color(surface_color);
+    immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2);
+
+    font := font_lookup(.{theme.font_name, theme.font_size});
+    font_height := font_get_height(font, text);
+    font_set_color(theme.text_color);
+    font_draw_centered(font, x, y + (h - font_height) / 2 + font.em - 2, w, text);
+
+    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
+
+    return result;
+}
+
+
+//
+// Sliders
+//
+Slider_Theme :: struct {
+    use text_theme := Text_Theme.{};
+    use animation_theme := Animation_Theme.{};
+
+    box_color        := Color.{ 0.1, 0.1, 0.1 };
+    box_border_color := Color.{ 0.2, 0.2, 0.2 };
+    box_border_width := 4.0f;   // @InPixels
+
+    bar_color                := Color.{ 0.4, 0.4, 0.4 };
+    bar_hover_color          := Color.{ 0, 0, 1 };
+    bar_hover_negative_color := Color.{ 1, 0, 0 }; // The color when value is less than 0
+}
+
+#local default_slider_theme := Slider_Theme.{};
+
+draw_slider :: (use r: Rect, value: ^$T, min_value: T, max_value: T, theme := ^default_slider_theme, site := #callsite, increment := 0) -> bool {
+    result := false;
+
+    hash := get_site_hash(site, increment);
+    state := get_animation(hash);
+    mx, my := mouse_get_position();
+
+    if is_hot_item(hash) {
+        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
+            set_active_item(hash);
+            result = true;
+
+            // Animate this?
+            sx := ~~mx - x;
+
+            if T == i32 || T == i64 || T == u32 || T == u64 {
+                step_width := w / ~~math.abs(max_value - min_value);
+                percent := (sx + step_width / 2) / w;
+                *value = math.lerp(percent, min_value, max_value);
+                *value = math.clamp(*value, min_value, max_value);
+
+            } else {
+                percent := sx / w;
+                *value = math.lerp(percent, min_value, max_value);
+                *value = math.clamp(*value, min_value, max_value);
+            }
+        } else {
+            set_active_item(0);
+        }
+    }
+
+    if Rect.contains(r, .{ ~~mx, ~~my }) {
+        set_hot_item(hash);
+    }
+
+    if is_hot_item(hash) {
+        move_towards(^state.hover_time, 1.0f, theme.hover_speed);
+    } else {
+        move_towards(^state.hover_time, 0.0f, theme.hover_speed);
+    }
+
+    box_border_width := theme.box_border_width;
+
+    bar_color := theme.bar_color;
+    if *value < 0 do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_negative_color);
+    else          do bar_color = color_lerp(state.hover_time, bar_color, theme.bar_hover_color);
+
+    immediate_set_color(theme.box_border_color);
+    immediate_rectangle(x, y, w, h);
+
+    immediate_set_color(theme.box_border_color);
+    immediate_rectangle(x + box_border_width, y + box_border_width, w - box_border_width * 2, h - box_border_width * 2);
+
+    box_width := cast(f32) (*value - min_value) / ~~(max_value - min_value);
+    box_width *= w - box_border_width * 2;
+    box_width = math.clamp(box_width, 0, w - box_border_width * 2);
+    immediate_set_color(bar_color);
+    immediate_rectangle(x + box_border_width, y + box_border_width, box_width, h - box_border_width * 2);
+
+    return result;
+}
+
+//
+// Textbox
+//
+Textbox_Theme :: struct {
+    use text_theme := Text_Theme.{
+        text_color = .{ 0, 0, 0 }
+    };
+
+    use animation_theme := Animation_Theme.{};
+
+    background_color := Color.{ 0.8, 0.8, 0.8 };
+    hover_color      := Color.{ 1.0, 1.0, 1.0 };
+    click_color      := Color.{ 0.5, 0.5, 0.7 };
+
+    border_color := Color.{ 0.2, 0.2, 0.2 };
+    border_width := 6.0f;   // @InPixels
+
+    cursor_color := Color.{ 0.5, 0.5, 0.5 };
+    cursor_width := 4.0f;   // @InPixels
+    cursor_blink_speed := 0.04f;   // Bigger is faster
+
+    placeholder_text_color := Color.{ 0.5, 0.5, 0.5 };
+}
+
+#local {
+    default_textbox_theme := Textbox_Theme.{};
+
+    Textbox_Editing_State :: struct {
+        hash: UI_Id = 0;
+
+        cursor_position: i32 = 0;
+        cursor_animation := 0.0f;
+        cursor_animation_speed := 0.02f;
+
+        // @HACK // Otherwise the action keys are evaluated every frame.
+        action_key_timeout := 0.0f;
+    }
+
+    textbox_editing_state := Textbox_Editing_State.{};
+}
+
+draw_textbox :: (use r: Rect, text_buffer: ^[..] u8, placeholder := null_str, theme := ^default_textbox_theme, site := #callsite, increment := 0) -> bool {
+    result := false;
+
+    hash := get_site_hash(site, increment);
+    animation_state := get_animation(hash);
+    mx, my := mouse_get_position();
+
+    border_width := theme.border_width;
+    text_color := theme.text_color;
+    text := str.{text_buffer.data, text_buffer.count};
+    if text.count == 0 && placeholder.count > 0 {
+        text = placeholder;
+        text_color = theme.placeholder_text_color;
+    }
+
+    font := font_lookup(.{theme.font_name, theme.font_size});
+    text_width  := font_get_width(font, text);
+    text_height := font_get_height(font, text);
+
+    text_x := x + border_width;
+    text_y := y + font.em + (h - text_height) / 2;
+
+    contains := Rect.contains(r, .{~~mx, ~~my});
+
+    if is_hot_item(hash) && !is_active_item(hash) {
+        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains {
+            set_active_item(hash);
+            input_capture_keys();
+            textbox_editing_state.hash = hash;
+            textbox_editing_state.cursor_animation_speed = theme.cursor_blink_speed;
+        }
+    }
+
+    if is_active_item(hash) {
+        renew_active_item(hash);
+        if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) && !contains {
+            set_active_item(0);
+            input_release_keys();
+            textbox_editing_state.hash = 0;
+            textbox_editing_state.cursor_position = 0;
+        }
+    }
+
+    if contains {
+        set_hot_item(hash);
+    }
+
+    if textbox_editing_state.hash == hash {
+        move_towards(^textbox_editing_state.cursor_animation, 0.0f, textbox_editing_state.cursor_animation_speed);
+        if textbox_editing_state.cursor_animation <= 0.0f do textbox_editing_state.cursor_animation = 1.0f;
+
+        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) && contains {
+            textbox_editing_state.cursor_animation = 1.0f;
+            textbox_editing_state.cursor_position = screen_to_cursor(^font, text_x, text_y, text, ~~mx, ~~my);
+        }
+
+        for key: input_get_keys_this_frame() {
+            switch key.key {
+                case GLFW_KEY_ESCAPE {
+                    set_active_item(0);
+                    input_release_keys();
+                }
+
+                case GLFW_KEY_LEFT  do textbox_editing_state.cursor_position -= 1;
+                case GLFW_KEY_RIGHT do textbox_editing_state.cursor_position += 1;
+                case GLFW_KEY_END   do textbox_editing_state.cursor_position = text_buffer.count;
+                case GLFW_KEY_HOME  do textbox_editing_state.cursor_position = 0;
+
+                case GLFW_KEY_BACKSPACE {
+                    if textbox_editing_state.cursor_position > 0 {
+                        array.delete(text_buffer, textbox_editing_state.cursor_position - 1);
+                    }
+                    textbox_editing_state.cursor_position = math.max(~~0, textbox_editing_state.cursor_position - 1);
+                }
+
+                case GLFW_KEY_DELETE {
+                    array.delete(text_buffer, textbox_editing_state.cursor_position);
+                }
+            }
+        }
+
+        for ch: input_get_chars_this_frame() {
+            array.insert(text_buffer, textbox_editing_state.cursor_position, ~~ch);
+            textbox_editing_state.cursor_position += 1;
+            result = true;
+        }
+
+        textbox_editing_state.cursor_position = math.clamp(textbox_editing_state.cursor_position, 0, text_buffer.count);
+        textbox_editing_state.cursor_animation = 1.0f;
+
+        text = str.{text_buffer.data, text_buffer.count};
+    }
+
+    if is_hot_item(hash) {
+        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
+    } else {
+        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
+    }
+
+    immediate_push_scissor(x, y, w, h);
+    immediate_set_color(theme.border_color);
+    immediate_rectangle(x, y, w, h);
+
+    surface_color := color_lerp(animation_state.hover_time, theme.background_color, theme.hover_color);
+    surface_color  = color_lerp(animation_state.click_time, surface_color, theme.click_color);
+    immediate_set_color(surface_color);
+    immediate_rectangle(x + border_width, y + border_width, w - border_width * 2, h - border_width * 2);
+
+    // Draw the cursor on textboxes
+    if is_active_item(hash) {
+        cursor := cursor_to_screen(^font, x + border_width, y + border_width, text, textbox_editing_state.cursor_position);
+        right_edge := x + w - border_width * 2;
+        if cursor.x > right_edge {
+            text_x -= cursor.x - right_edge;
+            cursor.x = right_edge - 2;
+        }
+        immediate_set_color(.{0,0,0});
+        immediate_rectangle(cursor.x, cursor.y + 2, 2, h - border_width * 2 - 4);
+    }
+
+    font_set_color(theme.text_color);
+    font_draw(font, text_x, text_y, text); // This is technically a frame late for updating the text?
+
+    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
+
+    immediate_pop_scissor();
+    return result;
+
+    cursor_to_screen :: (font: ^Font, x, y: f32, text: str, cur_pos: i32) -> Vector2 {
+        cx := x;
+        cy := y;
+
+        for text[0 .. cur_pos] {
+            if it == #char "\n" {
+                cx = 0;
+                cy += font.em + 2;
+            } else {
+                cx += font_get_char_width(*font, it);
+            }
+        }
+
+        return .{ cx, cy };
+    }
+
+    screen_to_cursor :: (font: ^Font, x, y: f32, text: str, mouse_x, mouse_y: f32) -> i32 {
+        mx := mouse_x - x;
+        my := mouse_y - y;
+        
+        cx := 0.0f; 
+        cy := 0.0f;
+        for pos: text.count {
+            if text[pos] == #char "\n" {
+                cx = 0;
+                cy += font.em + 2;
+            } else {
+                cx += font_get_char_width(*font, text[pos]);
+            }
+
+            if cy >= my && cx >= mx {
+                return pos;
+            }
+        }
+
+        return text.count;
+    }
+}
+
+
+
+
+//
+// Checkboxes
+//
+Checkbox_Theme :: struct {
+    use text_theme := Text_Theme.{};
+    use animation_theme := Animation_Theme.{};
+
+    box_color        := Color.{ 0.2, 0.2, 0.2 };
+    box_border_width := 4.0f;    // @InPixels
+    box_size         := 20.0f;   // @InPixels
+
+    checked_color       := Color.{ 1, 0, 0 };
+    checked_hover_color := Color.{ 1, 0.6, 0.6 };
+
+    background_color := Color.{ 0.05, 0.05, 0.05 };  // Background of the checkbox button.
+    hover_color      := Color.{ 0.3, 0.3, 0.3 };
+    click_color      := Color.{ 0.5, 0.5, 0.7 };
+}
+
+#local default_checkbox_theme := Checkbox_Theme.{};
+
+draw_checkbox :: (use r: Rect, value: ^bool, text: str, theme := ^default_checkbox_theme, site := #callsite, increment := 0) -> bool {
+    result := false;
+
+    hash := get_site_hash(site, increment);
+    animation_state := get_animation(hash);
+    mx, my := mouse_get_position();
+
+    contains := Rect.contains(r, .{~~mx, ~~my});
+
+    if is_active_item(hash) {
+        renew_active_item(hash);
+        if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) {
+            if is_hot_item(hash) && contains {
+                result = true;
+                *value = !*value;
+                animation_state.click_time = 1.0f;
+            }
+
+            set_active_item(0);
+        }
+    } elseif is_hot_item(hash) {
+        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
+            set_active_item(hash);
+        }
+    }
+
+    if contains {
+        set_hot_item(hash);
+    }
+
+    if is_hot_item(hash) {
+        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
+    } else {
+        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
+    }
+
+    box_border_width := theme.box_border_width;
+    box_size         := theme.box_size;
+
+    immediate_set_color(theme.box_color);
+    immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size);
+
+    surface_color : Color;
+    if *value {
+        surface_color = theme.checked_color;
+        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color);
+
+    } else {
+        surface_color = theme.background_color;
+        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color);
+    }
+
+    surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color);
+
+    immediate_set_color(surface_color);
+    immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2);
+
+    font := font_lookup(.{theme.font_name, theme.font_size});
+    text_width  := font_get_width(font, text);
+    text_height := font_get_height(font, text);
+
+    font_set_color(theme.text_color);
+    font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text);
+
+    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
+
+    return result;
+}
+
+
+//
+// Radio buttons
+//
+Radio_Theme :: struct {
+    use text_theme := Text_Theme.{};
+    use animation_theme := Animation_Theme.{};
+
+    box_color        := Color.{ 0.2, 0.2, 0.2 };
+    box_border_width := 4.0f;    // @InPixels
+    box_size         := 20.0f;   // @InPixels
+
+    checked_color       := Color.{ 0, 0, 1 };
+    checked_hover_color := Color.{ 0.6, 0.6, 1 };
+
+    background_color := Color.{ 0.05, 0.05, 0.05 };  // Background of the checkbox button.
+    hover_color      := Color.{ 0.3, 0.3, 0.3 };
+    click_color      := Color.{ 0.5, 0.5, 0.7 };
+}
+
+#local default_radio_theme := Radio_Theme.{};
+
+draw_radio :: (use r: Rect, value: ^$T, set_to: T, text: str, theme := ^default_radio_theme, site := #callsite, increment := 0) -> bool {
+    result := false;
+
+    hash := get_site_hash(site, increment);
+    animation_state := get_animation(hash);
+    mx, my := mouse_get_position();
+
+    contains := Rect.contains(r, .{~~mx, ~~my});
+
+    if is_active_item(hash) {
+        renew_active_item(hash);
+        if is_button_just_up(GLFW_MOUSE_BUTTON_LEFT) {
+            if is_hot_item(hash) && contains {
+                result = true;
+                *value = set_to;
+                animation_state.click_time = 1.0f;
+            }
+
+            set_active_item(0);
+        }
+    } elseif is_hot_item(hash) {
+        if is_button_down(GLFW_MOUSE_BUTTON_LEFT) {
+            set_active_item(hash);
+        }
+    }
+
+    if contains {
+        set_hot_item(hash);
+    }
+
+    if is_hot_item(hash) {
+        move_towards(^animation_state.hover_time, 1.0f, theme.hover_speed);
+    } else {
+        move_towards(^animation_state.hover_time, 0.0f, theme.hover_speed);
+    }
+
+    box_border_width := theme.box_border_width;
+    box_size         := theme.box_size;
+
+    immediate_set_color(theme.box_color);
+    immediate_rectangle(x + 4, y + (h - box_size) / 2, box_size, box_size);
+
+    surface_color : Color;
+    if *value == set_to {
+        surface_color = theme.checked_color;
+        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.checked_hover_color);
+
+    } else {
+        surface_color = theme.background_color;
+        surface_color = color_lerp(animation_state.hover_time, surface_color, theme.hover_color);
+    }
+
+    surface_color = color_lerp(animation_state.click_time, surface_color, theme.click_color);
+
+    immediate_set_color(surface_color);
+    immediate_rectangle(x + 4 + box_border_width, y + (h - box_size) / 2 + box_border_width, box_size - box_border_width * 2, box_size - box_border_width * 2);
+
+    font := font_lookup(.{theme.font_name, theme.font_size});
+    text_width  := font_get_width(font, text);
+    text_height := font_get_height(font, text);
+
+    font_set_color(theme.text_color);
+    font_draw(font, x + box_size + 4 * 2, y + font.em + (h - text_height) / 2, text);
+
+    move_towards(^animation_state.click_time, 0.0f, theme.click_decay_speed);
+
+    return result;
+}
+
+
+
+
+scrolling_region_start :: (r: Rect, max_y_scroll := 10000.0f, site := #callsite, increment := 0) {
+    hash := get_site_hash(site, increment);
+    mx, my := mouse_get_position();
+    state := ^scroll_states[hash];
+    if state == null {
+        animation_states[hash] = .{};
+        state = ^scroll_states[hash];
+    }
+
+    contains := Rect.contains(r, .{~~mx, ~~my});
+    if contains {
+        scroll_delta := mouse_get_scroll_vector();
+        state.xscroll -= scroll_delta.x * 20;
+        state.yscroll -= scroll_delta.y * 20;
+
+        state.yscroll = math.clamp(state.yscroll, 0, max_y_scroll);
+    }
+
+    immediate_flush();
+    immediate_push_scissor(r.x, r.y, r.w, r.h);
+    immediate_set_scroll(-state.xscroll, -state.yscroll);
+}
+
+scrolling_region_stop :: () {
+    immediate_flush();
+    immediate_pop_scissor();
+    immediate_set_scroll(0, 0);
+}
+
+
+
+#local {
+    hot_item    : UI_Id = 0
+    hot_item_was_set := false
+
+    hot_item_depth := 0;
+    hot_item_depth_needed := 0;
+
+    active_item : UI_Id = 0
+    active_countdown := 0
+
+    set_active_item :: (id: UI_Id) -> bool {
+        active_item = id;
+        active_countdown = 2;
+        return true;
+    }
+
+    renew_active_item :: (id: UI_Id) {
+        if active_item == id {
+            active_countdown = 2;
+        }
+    }
+
+    set_hot_item :: (id: UI_Id, force := false) -> bool {
+        if active_item != 0 do return false;
+
+        if force {
+            hot_item_was_set = true;
+            hot_item = id;
+            return true;
+        }
+
+        hot_item_depth += 1;
+        if hot_item_depth >= hot_item_depth_needed {
+            hot_item_was_set = true;
+            hot_item = id;
+            return true;
+        }
+
+        return false;
+    }
+
+    is_active_item :: (id: UI_Id) -> bool {
+        return active_item == id;
+    }
+
+    is_hot_item :: (id: UI_Id) -> bool {
+        return hot_item == id;
+    }
+
+    Text_Theme :: struct {
+        text_color := Color.{1, 1, 1};
+        font_name  := "./assets/fonts/calibri.ttf";
+        font_size  := 18;
+    }
+
+    Animation_Theme :: struct {
+        hover_speed       := 0.1f;
+        click_decay_speed := 0.08f;
+    }
+
+    Animation_State :: struct {
+        hover_time := 0.0f;
+        click_time := 0.0f;
+
+        accessed_this_frame := false;
+    }
+
+    animation_states : Map(UI_Id, Animation_State);
+
+    get_animation :: (id: UI_Id) -> ^Animation_State {
+        retval := ^animation_states[id];
+        if retval == null {
+            animation_states[id] = .{};
+            retval = ^animation_states[id];
+        }
+
+        retval.accessed_this_frame = true;
+        return retval;
+    }
+
+    has_active_animation :: () -> bool {
+        for^ animation_states.entries {
+            if it.value.hover_time != 0.0f || it.value.hover_time != 0.0f do return true;
+            if it.value.click_time != 0.0f || it.value.click_time != 0.0f do return true;
+        }
+
+        return false;
+    }
+
+
+    Scroll_State :: struct {
+        xscroll: f32;
+        yscroll: f32;
+    }
+
+    scroll_states : Map(UI_Id, Scroll_State);
+
+    get_site_hash :: (site: CallSite, increment := 0) -> UI_Id {
+        file_hash   := core.hash.to_u32(site.file);
+        line_hash   := core.hash.to_u32(site.line);
+        column_hash := core.hash.to_u32(site.column);
+
+        return ~~ (file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382 + increment);
+    }
+
+    color_lerp :: macro (t: f32, c1, c2: Color) => Color.{
+        r = c1.r * (1 - t) + c2.r * t,
+        g = c1.g * (1 - t) + c2.g * t,
+        b = c1.b * (1 - t) + c2.b * t,
+        a = c1.a * (1 - t) + c2.a * t,
+    };
+}
diff --git a/src/ogre/vecmath.onyx b/src/ogre/vecmath.onyx
new file mode 100644 (file)
index 0000000..583dd2c
--- /dev/null
@@ -0,0 +1,161 @@
+package ogre
+
+use core {hash, math, conv, string}
+
+@conv.Custom_Format.{format_vector2i}
+@conv.Custom_Parse.{parse_vector2i}
+Vector2i :: struct {
+    x, y: i32;
+    
+}
+
+@conv.Custom_Format.{format_vector2}
+@conv.Custom_Parse.{parse_vector2}
+Vector2 :: struct {
+    x, y: f32;
+
+    mag :: (v: Vector2) => math.sqrt(v.x * v.x + v.y * v.y);
+
+    square_mag :: (v: Vector2) => v.x * v.x + v.y * v.y;
+
+    norm :: (v: Vector2) -> Vector2 {
+        l := math.sqrt(v.x * v.x + v.y * v.y);
+        return .{ v.x / l, v.y / l };
+    }
+
+
+    Zero :: Vector2.{0, 0}
+}
+
+@conv.Custom_Format.{format_vector3i}
+Vector3i :: struct {
+    x, y, z: i32;
+}
+
+@conv.Custom_Format.{format_vector3}
+Vector3 :: struct {
+    x, y, z: f32;
+
+    mag :: (v: Vector3) => math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
+
+    neg :: (v: Vector3) => Vector3.{ -v.x, -v.y, -v.z };
+
+    dot :: (v1, v2: Vector3) -> f32 {
+        return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
+    }
+
+    norm :: (v: Vector3) -> Vector3 {
+        l := math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
+        return .{ v.x / l, v.y / l, v.z / l };
+    }
+
+    cross :: (v1, v2: Vector3) -> Vector3 {
+        return .{
+            v1.y * v2.z - v1.z * v2.y,
+            v1.z * v2.x - v1.x * v2.z,
+            v1.x * v2.y - v1.y * v2.x,
+        };
+    }
+
+    clamp :: (v: Vector3, min: Vector3, max: Vector3) -> Vector3 {
+        return .{
+            math.clamp(v.x, min.x, max.x),
+            math.clamp(v.y, min.y, max.y),
+            math.clamp(v.z, min.z, max.z),
+        };
+    }
+}
+
+#operator + (v1, v2: Vector2i)    => Vector2i.{ v1.x + v2.x, v1.y + v2.y };
+#operator - (v1, v2: Vector2i)    => Vector2i.{ v1.x - v2.x, v1.y - v2.y };
+#operator * (v: Vector2i, s: i32) => Vector2i.{ v.x * s,     v.y * s     };
+#operator * (v1, v2: Vector2i)    => Vector2i.{ v1.x * v2.x, v1.y * v2.y };
+#operator == (v1, v2: Vector2i)   => v1.x == v2.x && v1.y == v2.y;
+#match hash.to_u32 (v: Vector2i)  => 13 * v.x + 17 * v.y;
+
+#operator + (v1, v2: Vector2)    => Vector2.{ v1.x + v2.x, v1.y + v2.y };
+#operator - (v1, v2: Vector2)    => Vector2.{ v1.x - v2.x, v1.y - v2.y };
+#operator * (v: Vector2, s: f32) => Vector2.{ v.x * s,     v.y * s     };
+#operator * (v1, v2: Vector2)    => Vector2.{ v1.x * v2.x, v1.y * v2.y };
+#operator == (v1, v2: Vector2)   => v1.x == v2.x && v1.y == v2.y;
+
+#operator + (v1, v2: Vector3)    => Vector3.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
+#operator - (v1, v2: Vector3)    => Vector3.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
+#operator * (v: Vector3, s: f32) => Vector3.{ v.x * s,     v.y * s,     v.z * s };
+#operator * (v1, v2: Vector3)    => Vector3.{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z };
+#operator == (v1, v2: Vector3)   => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z;
+
+#operator + (v1, v2: Vector3i)    => Vector3i.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
+#operator - (v1, v2: Vector3i)    => Vector3i.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
+#operator * (v: Vector3i, s: i32) => Vector3i.{ v.x * s,     v.y * s,     v.z * s };
+#operator * (v1, v2: Vector3i)    => Vector3i.{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z };
+#operator == (v1, v2: Vector3i)   => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z;
+
+#local {
+    format_vector2i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2i) {
+        conv.format(output, "({}, {})", v.x, v.y);
+    }
+
+    format_vector2 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2) {
+        conv.format(output, "({}, {})", v.x, v.y);
+    }
+
+    format_vector3 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3) {
+        conv.format(output, "({}, {}, {})", v.x, v.y, v.z);
+    }
+
+    format_vector3i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3i) {
+        conv.format(output, "({}, {}, {})", v.x, v.y, v.z);
+    }
+
+    parse_vector2i :: (output: ^Vector2i, line_: str, string_allocator: Allocator) -> bool {
+        line := line_;
+        xs := string.read_until(^line, #char " ");
+        string.advance(^line, 1);
+        ys := string.read_until(^line, #char " ");
+
+        if xs == "" || ys == "" do return false;
+
+        output.x = ~~ conv.str_to_i64(xs);
+        output.y = ~~ conv.str_to_i64(ys);
+        return true;
+    }
+
+    parse_vector2 :: (output: ^Vector2, line_: str, string_allocator: Allocator) -> bool {
+        line := line_;
+        xs := string.read_until(^line, #char " ");
+        string.advance(^line, 1);
+        ys := string.read_until(^line, #char " ");
+
+        if xs == "" || ys == "" do return false;
+
+        output.x = ~~ conv.str_to_f64(xs);
+        output.y = ~~ conv.str_to_f64(ys);
+        return true;
+    }
+}
+
+
+
+Rect :: struct {
+    x, y, w, h: f32;
+
+    intersects :: (r1, r2: Rect) -> bool {
+        return r1.x < r2.x + r2.w
+            && r1.x + r1.w > r2.x
+            && r1.y < r2.y + r2.h
+            && r1.y + r1.h > r2.y;
+    }
+
+    contains :: (r: Rect, p: Vector2) -> bool {
+        return r.x <= p.x && r.x + r.w >= p.x
+            && r.y <= p.y && r.y + r.h >= p.y;
+    }
+
+    center :: (use r: Rect) => Vector2.{ r.x+r.w/2, r.y+r.h/2 };
+
+    top_left     :: (use r: Rect) => Vector2.{ r.x,   r.y   };
+    top_right    :: (use r: Rect) => Vector2.{ r.x+r.w, r.y   };
+    bottom_left  :: (use r: Rect) => Vector2.{ r.x,   r.y+r.h };
+    bottom_right :: (use r: Rect) => Vector2.{ r.x+r.w, r.y+r.h };
+}
diff --git a/src/ogre/window.onyx b/src/ogre/window.onyx
new file mode 100644 (file)
index 0000000..486cf37
--- /dev/null
@@ -0,0 +1,67 @@
+package ogre
+
+use glfw3
+use opengles
+
+Window :: struct {
+    glfw_window: GLFWwindow_p;
+    width, height: i32;
+}
+
+window_create :: (width, height: i32, name: cstr) -> Window {
+    #if runtime.compiler_os == .Linux {
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
+        glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+    } else {
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+    }
+
+    w := glfwCreateWindow(width, height, name);
+
+    return .{ w, width, height };
+}
+
+window_use :: (w: ^Window) {
+    glfwMakeContextCurrent(w.glfw_window);
+    glfwSwapInterval(1);
+
+    input_bind_glfw_events(w.glfw_window);
+
+    // Provide sensible defaults for OpenGL
+    glInit(glfwGetLoadProcAddress());
+    glEnable(GL_TEXTURE);
+    glEnable(GL_CULL_FACE);
+    glFrontFace(GL_CW);
+    glCullFace(GL_BACK);
+    glEnable(GL_BLEND);
+    glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+
+    glfwGetWindowSize(w.glfw_window, ^w.width, ^w.height);
+    update_view_matrix(w.width, w.height);
+
+    glfwSetWindowSizeCallback(w.glfw_window, #export_name (w: GLFWwindow_p, width, height: u32) {
+        global_window.width = width;
+        global_window.height = height;
+        update_view_matrix(width, height);
+    });
+
+    global_window = w;
+}
+
+//
+// These have to be macros because '#export_name' is used on the argument,
+// which requires that they are compile time known functions.
+//
+// window_size_callback :: macro (w: ^Window, resize: (w: GLFWwindow_p, width, height: u32) -> void) {
+//     glfw3.glfwSetWindowSizeCallback(w.glfw_window, #export_name resize);
+// }
+
+
+
+
+
+#package
+global_window: ^Window;
+
index 970ec857a5e387828ad2e3b392bd2e632fa3dc32..93981375633e6084118ddd447c0c3bdadc011bb7 100644 (file)
@@ -55,7 +55,7 @@ Audio_Manager :: struct {
         }
 
         alBufferData(buffer, al_format, wav_file.data.data, wav_file.data.count, wav_file.sample_rate);
-        memory.free_slice(^wav_file.loaded_file_data);
+        delete(^wav_file.loaded_file_data);
 
         loaded_sounds[path] = .{ buffer };
         return .{ buffer };
@@ -78,15 +78,13 @@ Audio_Manager :: struct {
     }
 
     tick :: () {
-        while i := 0; i < playing_sounds.count {
-            defer i += 1;
-
+        for it: iter.as_iterator(^playing_sounds) {
             state: i32;
-            alGetSourcei(playing_sounds[i], AL_SOURCE_STATE, ^state);
+            alGetSourcei(it, AL_SOURCE_STATE, ^state);
+            
             if state != AL_PLAYING {
-                alDeleteSources(1, ^playing_sounds[i]);
-                array.fast_delete(^playing_sounds, i);
-                i -= 1;
+                alDeleteSources(1, ^it);
+                #remove;
             }
         }
     }
index 72abc9587b689a501c5895422770a8a1f1e31761..ec5f756b7560899731ca005831374e741754e31f 100644 (file)
@@ -1,5 +1,6 @@
 
-use package core
+use core
+use ogre
 
 queue_assets :: (store: ^$T, callsite := #callsite) {
     assets_to_load << .{ T, store, callsite };
diff --git a/src/utils/colors.onyx b/src/utils/colors.onyx
deleted file mode 100644 (file)
index ab6231d..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-
-use package core
-
-Color :: struct {
-    r, g, b :  f32;
-    a       := 1.0f;
-}
-
-color_to_hsl :: (c: Color) -> (h: f32, s: f32, l: f32) {
-    r := c.r / 255;
-    g := c.g / 255;
-    b := c.b / 255;
-    cmax := math.max(math.max(r, g), b);
-    cmin := math.min(math.min(r, g), b);
-    delta := cmax - cmin;
-
-    h := 0.0f;
-    if cmax == r {
-        m := (g - b) / delta;
-        while m >= 6 do m -= 6;
-        while m < 0  do m += 6;
-        h = 60 * m;
-    }
-    if cmax == g do h = 60 * (b - r) / delta + 2;
-    if cmax == b do h = 60 * (r - g) / delta + 4;
-
-    l := (cmax + cmin) / 2;
-    s := delta / (1 - math.abs(2 * l - 1));
-
-    return h, s, l;
-}
-
-hsl_to_color :: (h, s, l: f32) -> Color {
-    c := (1 - math.abs(2 * l - 1)) * s;
-
-    h_term := h / 60;
-    while h_term < 0  do h_term += 2;
-    while h_term >= 0 do h_term -= 2;
-
-    x := c * (1 - math.abs(h_term - 1));
-    m := l - c / 2;
-
-    r, g, b: f32;
-    if 0   <= h && h < 60  { r = c; g = x; b = 0; }
-    if 60  <= h && h < 120 { r = x; g = c; b = 0; }
-    if 120 <= h && h < 180 { r = 0; g = c; b = x; }
-    if 180 <= h && h < 240 { r = 0; g = x; b = c; }
-    if 240 <= h && h < 300 { r = x; g = 0; b = c; }
-    if 300 <= h && h < 360 { r = c; g = 0; b = x; }
-
-    r = (r + m) * 255;
-    g = (g + m) * 255;
-    b = (b + m) * 255;
-    return .{ r, g, b, 1 };
-}
diff --git a/src/utils/input.onyx b/src/utils/input.onyx
deleted file mode 100644 (file)
index 31a9022..0000000
+++ /dev/null
@@ -1,187 +0,0 @@
-use package core
-use package glfw3
-
-// If you are offseting the mouse coordinate for world space
-// or UI scrolling etc., set this function to be the function
-// to ask for the current offset. Currently scaling the mouse
-// coordinate is not supported, only linear translations.
-Mouse_Offset_Function :: immediate_get_scroll
-
-
-#local {
-    keys_this_frame  : Set(Key_Descriptor)   // Keys currently being pressed this frame
-    keys_last_frame  : Set(Key_Descriptor)   // Keys being pressed in the last frame
-
-    key_codepoints : [..] u32
-
-    buttons_this_frame: [8] bool  // Mouse buttons being pressed this frame
-    buttons_last_frame: [8] bool  // Mouse buttons being pressed last frame
-
-    scroll_x: f64
-    scroll_y: f64
-
-    character_mode := false;
-}
-
-#init () {
-    set.init(^keys_this_frame);
-    set.init(^keys_last_frame);
-}
-
-Key_Descriptor :: struct {
-    key: u32;
-    scancode: u32 = 0;
-    mod: u32      = 0;
-}
-#match hash.to_u32 (use x: Key_Descriptor) => hash.to_u32(key);
-#operator == (x, y: Key_Descriptor) => x.key == y.key;
-
-input_update :: () {
-    glfwGetCursorPos(window, ^mouse_x, ^mouse_y);
-}
-
-input_post_update :: () {
-    set.clear(^keys_last_frame);
-    for keys_this_frame.entries do keys_last_frame << it.value;
-
-    for 8 do buttons_last_frame[it] = buttons_this_frame[it];
-
-    last_mouse_x = mouse_x;
-    last_mouse_y = mouse_y;
-    scroll_x = 0;
-    scroll_y = 0;
-
-    array.clear(^key_codepoints);
-}
-
-input_get_chars_this_frame :: () -> [] u32 {
-    return key_codepoints;
-}
-
-input_get_keys_this_frame :: () -> Iterator(Key_Descriptor) {
-    #persist index := 0;
-
-    next :: (_: rawptr) -> (Key_Descriptor, bool) {
-        if index >= keys_this_frame.entries.count do return .{0}, false;
-
-        while keys_last_frame->has(keys_this_frame.entries[index].value) {
-            index += 1;
-            if index >= keys_this_frame.entries.count do return .{0}, false;
-        }
-
-        defer index += 1;
-        return keys_this_frame.entries[index].value, true;
-    }
-
-    index = 0;
-    return .{ null, next };
-}
-
-input_capture_keys :: () {
-    character_mode = true;
-}
-
-input_release_keys :: () {
-    character_mode = false;
-}
-
-is_key_down      :: (key) => !character_mode && set.has(^keys_this_frame, .{key});
-is_key_just_down :: (key) => !character_mode && set.has(^keys_this_frame, .{key}) && !set.has(^keys_last_frame, .{key});
-is_key_just_up   :: (key) => !character_mode && !set.has(^keys_this_frame, .{key}) && set.has(^keys_last_frame, .{key});
-
-is_button_down      :: (button) => buttons_this_frame[button];
-is_button_just_down :: (button) => buttons_this_frame[button] && !buttons_last_frame[button];
-is_button_just_up   :: (button) => !buttons_this_frame[button] && buttons_last_frame[button];
-
-#local {
-    last_mouse_x: f64;
-    last_mouse_y: f64;
-
-    mouse_x: f64;
-    mouse_y: f64;
-}
-
-mouse_get_delta :: () -> (f64, f64) {
-    return mouse_x - last_mouse_x, mouse_y - last_mouse_y;
-}
-
-mouse_get_delta_vector :: () -> Vector2 {
-    dmx, dmy := mouse_get_delta();
-    return .{ ~~dmx, ~~dmy };
-}
-
-mouse_get_position :: () -> (f64, f64) {
-    mx, my := mouse_x, mouse_y;        
-    #if #defined(Mouse_Offset_Function) {
-        scroll := Mouse_Offset_Function();
-        mx -= ~~ scroll.x;
-        my -= ~~ scroll.y;
-    }
-    return mx, my;
-}
-
-mouse_get_position_vector :: () -> Vector2 {
-    mx, my := mouse_x, mouse_y;        
-    #if #defined(Mouse_Offset_Function) {
-        scroll := Mouse_Offset_Function();
-        mx -= ~~ scroll.x;
-        my -= ~~ scroll.y;
-    }
-    return .{ ~~mx, ~~my };
-}
-
-mouse_get_scroll :: () -> (f64, f64) {
-    return scroll_x, scroll_y;
-}
-
-mouse_get_scroll_vector :: () -> Vector2 {
-    return .{ ~~scroll_x, ~~scroll_y };
-}
-
-input_bind_glfw_events :: (window: GLFWwindow_p) {
-    glfwSetKeyCallback(window, INPUT_KEY_EVENT);
-    glfwSetCharCallback(window, INPUT_CHAR_EVENT);
-    glfwSetMouseButtonCallback(window, INPUT_BUTTON_EVENT);
-    glfwSetScrollCallback(window, INPUT_SCROLL_EVENT);
-}
-
-#local {
-    INPUT_BUTTON_EVENT :: "__input_button_event"
-    INPUT_KEY_EVENT    :: "__input_key_event"
-    INPUT_SCROLL_EVENT :: "__input_scroll_event"
-    INPUT_CHAR_EVENT   :: "__input_char_event"
-}
-
-#export INPUT_BUTTON_EVENT (window: GLFWwindow_p, button, action, mod: u32) {
-    if action == GLFW_PRESS {
-        buttons_this_frame[button] = true;
-    }
-
-    if action == GLFW_RELEASE {
-        buttons_this_frame[button] = false;
-    }
-}
-
-#export INPUT_KEY_EVENT (window: GLFWwindow_p, key, scancode, action, mod: u32) {
-    if action == GLFW_PRESS {
-        keys_this_frame << .{ key, scancode, mod };
-    }
-
-    if action == GLFW_REPEAT {
-        set.remove(^keys_last_frame, .{key});
-    }
-
-    if action == GLFW_RELEASE {
-        set.remove(^keys_this_frame, .{ key });
-    }
-}
-
-#export INPUT_CHAR_EVENT (window: GLFWwindow_p, codepoint: u32) {
-    if !character_mode do return;
-    key_codepoints << codepoint;       
-}
-
-#export INPUT_SCROLL_EVENT (window: GLFWwindow_p, xoff, yoff: f64) {
-    scroll_x = xoff;
-    scroll_y = yoff;
-}
diff --git a/src/utils/vecmath.onyx b/src/utils/vecmath.onyx
deleted file mode 100644 (file)
index 83e177f..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-
-use core {hash, math}
-
-#tag conv.Custom_Format.{format_vector2i}
-#tag conv.Custom_Parse.{parse_vector2i}
-Vector2i :: struct {
-    x, y: i32;
-    
-}
-
-#tag conv.Custom_Format.{format_vector2}
-#tag conv.Custom_Parse.{parse_vector2}
-Vector2 :: struct {
-    x, y: f32;
-
-    mag :: (v: Vector2) => math.sqrt(v.x * v.x + v.y * v.y);
-
-    square_mag :: (v: Vector2) => v.x * v.x + v.y * v.y;
-
-    norm :: (v: Vector2) -> Vector2 {
-        l := math.sqrt(v.x * v.x + v.y * v.y);
-        return .{ v.x / l, v.y / l };
-    }
-
-
-    Zero :: Vector2.{0, 0}
-}
-
-#tag conv.Custom_Format.{format_vector3i}
-Vector3i :: struct {
-    x, y, z: i32;
-}
-
-#tag conv.Custom_Format.{format_vector3}
-Vector3 :: struct {
-    x, y, z: f32;
-
-    mag :: (v: Vector3) => math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
-
-    neg :: (v: Vector3) => Vector3.{ -v.x, -v.y, -v.z };
-
-    dot :: (v1, v2: Vector3) -> f32 {
-        return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
-    }
-
-    norm :: (v: Vector3) -> Vector3 {
-        l := math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
-        return .{ v.x / l, v.y / l, v.z / l };
-    }
-
-    cross :: (v1, v2: Vector3) -> Vector3 {
-        return .{
-            v1.y * v2.z - v1.z * v2.y,
-            v1.z * v2.x - v1.x * v2.z,
-            v1.x * v2.y - v1.y * v2.x,
-        };
-    }
-
-    clamp :: (v: Vector3, min: Vector3, max: Vector3) -> Vector3 {
-        return .{
-            math.clamp(v.x, min.x, max.x),
-            math.clamp(v.y, min.y, max.y),
-            math.clamp(v.z, min.z, max.z),
-        };
-    }
-}
-
-#operator + (v1, v2: Vector2i)   => Vector2i.{ v1.x + v2.x, v1.y + v2.y };
-#operator - (v1, v2: Vector2i)   => Vector2i.{ v1.x - v2.x, v1.y - v2.y };
-#operator * (v: Vector2i, s: i32) => Vector2i.{ v.x * s,     v.y * s     };
-#operator * (v1, v2: Vector2i)   => Vector2i.{ v1.x * v2.x, v1.y * v2.y };
-#operator == (v1, v2: Vector2i)  => v1.x == v2.x && v1.y == v2.y;
-#match hash.to_u32 (v: Vector2i) => 13 * v.x + 17 * v.y;
-
-#operator + (v1, v2: Vector2)    => (typeof v1).{ v1.x + v2.x, v1.y + v2.y };
-#operator - (v1, v2: Vector2)    => (typeof v1).{ v1.x - v2.x, v1.y - v2.y };
-#operator * (v: Vector2, s: f32) => (typeof v ).{ v.x * s,     v.y * s     };
-#operator * (v1, v2: Vector2)    => (typeof v1).{ v1.x * v2.x, v1.y * v2.y };
-#operator == (v1, v2: Vector2)   => v1.x == v2.x && v1.y == v2.y;
-
-#operator + (v1, v2: Vector3)    => Vector3.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
-#operator - (v1, v2: Vector3)    => Vector3.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
-#operator * (v: Vector3, s: f32) => Vector3.{ v.x * s,     v.y * s,     v.z * s };
-#operator * (v1, v2: Vector3)    => (typeof v1).{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z };
-#operator == (v1, v2: Vector3)   => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z;
-
-#operator + (v1, v2: Vector3i)    => Vector3i.{ v1.x + v2.x, v1.y + v2.y, v1.z + v2.z };
-#operator - (v1, v2: Vector3i)    => Vector3i.{ v1.x - v2.x, v1.y - v2.y, v1.z - v2.z };
-#operator * (v: Vector3i, s: i32) => Vector3i.{ v.x * s,     v.y * s,     v.z * s };
-#operator * (v1, v2: Vector3i)    => (typeof v1).{ v1.x * v2.x, v1.y * v2.y, v1.z * v2.z };
-#operator == (v1, v2: Vector3i)   => v1.x == v2.x && v1.y == v2.y && v1.z == v2.z;
-
-#local {
-    conv :: package core.conv
-
-    format_vector2i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2i) {
-        conv.format(output, "({}, {})", v.x, v.y);
-    }
-
-    format_vector2 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector2) {
-        conv.format(output, "({}, {})", v.x, v.y);
-    }
-
-    format_vector3 :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3) {
-        conv.format(output, "({}, {}, {})", v.x, v.y, v.z);
-    }
-
-    format_vector3i :: (output: ^conv.Format_Output, format: ^conv.Format, v: ^Vector3i) {
-        conv.format(output, "({}, {}, {})", v.x, v.y, v.z);
-    }
-
-    parse_vector2i :: (output: ^Vector2i, line_: str, string_allocator: Allocator) -> bool {
-        string :: package core.string
-
-        line := line_;
-        xs := string.read_until(^line, #char " ");
-        string.advance(^line, 1);
-        ys := string.read_until(^line, #char " ");
-
-        if xs == "" || ys == "" do return false;
-
-        output.x = ~~ conv.str_to_i64(xs);
-        output.y = ~~ conv.str_to_i64(ys);
-        return true;
-    }
-
-    parse_vector2 :: (output: ^Vector2, line_: str, string_allocator: Allocator) -> bool {
-        string :: package core.string
-
-        line := line_;
-        xs := string.read_until(^line, #char " ");
-        string.advance(^line, 1);
-        ys := string.read_until(^line, #char " ");
-
-        if xs == "" || ys == "" do return false;
-
-        output.x = ~~ conv.str_to_f64(xs);
-        output.y = ~~ conv.str_to_f64(ys);
-        return true;
-    }
-}
-
-
-
-Rect :: struct {
-    x, y, w, h: f32;
-
-    intersects :: (r1, r2: Rect) -> bool {
-        return r1.x < r2.x + r2.w
-            && r1.x + r1.w > r2.x
-            && r1.y < r2.y + r2.h
-            && r1.y + r1.h > r2.y;
-    }
-
-    contains :: (r: Rect, p: Vector2) -> bool {
-        return r.x <= p.x && r.x + r.w >= p.x
-            && r.y <= p.y && r.y + r.h >= p.y;
-    }
-
-    center :: (use r: Rect) => Vector2.{ r.x+r.w/2, r.y+r.h/2 };
-
-    top_left     :: (use r: Rect) => Vector2.{ r.x,   r.y   };
-    top_right    :: (use r: Rect) => Vector2.{ r.x+r.w, r.y   };
-    bottom_left  :: (use r: Rect) => Vector2.{ r.x,   r.y+r.h };
-    bottom_right :: (use r: Rect) => Vector2.{ r.x+r.w, r.y+r.h };
-}