"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"
}
]
}
run) onyx-run $dest ;;
debug) onyx-run --debug $dest ;;
*) onyx run -V -I ../src build $@ ;;
-esac
\ No newline at end of file
+esac
#load_all "./entity"
#load_all "./entity/components"
#load_all "./entity/schematics"
-#load_all "./gfx"
#load_all "./sfx"
#load_all "./utils"
#load "./../lib/openal/module"
#load "./../lib/stb_truetype/module"
#load "./../lib/stb_image/module"
+#load_all "./ogre"
use core
+use ogre
BackgroundComponent :: struct {
use component: Component;
use core
+use ogre
CollisionMaskComponent :: struct {
use component: Component;
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;
}
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;
}
}
}
use core
+use ogre
//
// Currently, DispenserComponents only dispense Item_Entity's, and no
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) {
use core
+use ogre
EntrywayComponent :: struct {
use component: Component;
use core
+use ogre
#local Transaction :: struct {
amount: i32;
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) {
use core
use glfw3
+use ogre
Facing :: enum {
use core
+use ogre
#local Patron_State :: enum {
Walking_To_Seat;
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) {
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) {
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 {}
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();
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;
{ // Draw menu bar
x := 0.0f;
- w := cast(f32) window_width;
+ w := cast(f32) window.width;
h := menubar_height;
y := h * (editor_openness - 1);
{ // 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);
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 {
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) {
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});
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;
-}
use core
+use ogre
+use ogre.ui
Item :: struct {
return items[id];
}
-#tag Entity_Schematic.{create}
+@Entity_Schematic.{create}
#local Item_Entity :: struct {
create :: (scene) => {
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 {
use core
-use glfw3
+use ogre
+use ogre.ui
Entity_Nothing :: cast(Entity_ID) 0
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;
}
RenderComponent :: struct {
use base: Component;
- #tag Editor_Hidden, Entity_Store.Skip
+ @Editor_Hidden, Entity_Store.Skip
func : (e: ^Entity) -> void;
layer := 0;
}
}
-#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;
id: Entity_ID;
flags: Entity_Flags;
- #tag Entity_Store.Skip
+ @Entity_Store.Skip
schematic: type_expr;
nickname: str;
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);
// #local background_texture: Texture;
-#tag Entity_Schematic.{ Background.create }
+@Entity_Schematic.{ Background.create }
Background :: struct {
create :: (scene) => {
this := scene->make();
use core
-#tag Entity_Schematic.{ Entryway.create }
+@Entity_Schematic.{ Entryway.create }
Entryway :: struct {
create :: (scene) => {
this := scene->make();
use core
+use ogre
-#tag Entity_Schematic.{
+@Entity_Schematic.{
(scene) => Furniture.create(scene, .{0, 0})
}
Furniture :: struct {
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 {
use core
use glfw3
+use ogre
Player_Controls :: struct {
up : i32;
}
-#tag Entity_Schematic.{
+@Entity_Schematic.{
(scene: ^Scene) => Player.create(scene, .{0,0})
}
Player :: struct {
}
#persist assets: struct {
- #tag "assets/images/player.png"
- #tag Texture_Wrap.Clamp
+ @"assets/images/player.png"
+ @Texture_Wrap.Clamp
texture: Texture;
}
}
+use ogre
-#tag Entity_Schematic.{
+@Entity_Schematic.{
(scene) => Tap.create(scene, .{0,0}, .{0,0})
}
Tap :: struct {
use core
use glfw3
use opengles
+use ogre
//
// Game Global Variables
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();
+++ /dev/null
-
-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>" };
-
+++ /dev/null
-
-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;
-}
+++ /dev/null
-
-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;
- }
- }
-}
+++ /dev/null
-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);
-}
-
-
-
+++ /dev/null
-
-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;
- }
-}
-
+++ /dev/null
-
-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);
-}
+++ /dev/null
-//
-// 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,
- };
-}
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();
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}); }
}
input_update();
if is_key_just_up(GLFW_KEY_ESCAPE) {
- glfwSetWindowShouldClose(window, true);
+ glfwSetWindowShouldClose(window.glfw_window, true);
return;
}
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();
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();
seconds := 0.0;
frame_count := 0;
- while !glfwWindowShouldClose(window) {
+ while !glfwWindowShouldClose(window.glfw_window) {
glfwPollEvents();
now = glfwGetTime();
}
}
-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());
--- /dev/null
+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>" };
+
--- /dev/null
+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 };
+}
--- /dev/null
+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;
+}
--- /dev/null
+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;
+}
--- /dev/null
+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;
+}
--- /dev/null
+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);
+}
+
+
+
--- /dev/null
+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;
+ }
+}
+
--- /dev/null
+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);
+}
--- /dev/null
+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,
+ };
+}
--- /dev/null
+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 };
+}
--- /dev/null
+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;
+
}
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 };
}
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;
}
}
}
-use package core
+use core
+use ogre
queue_assets :: (store: ^$T, callsite := #callsite) {
assets_to_load << .{ T, store, callsite };
+++ /dev/null
-
-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 };
-}
+++ /dev/null
-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;
-}
+++ /dev/null
-
-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 };
-}