From 5089fc1a48ce8c9ec2819bb34937cfa9ea0a3756 Mon Sep 17 00:00:00 2001 From: Brendan Hansen Date: Sun, 13 Feb 2022 21:14:06 -0600 Subject: [PATCH] refactoring code; pulling new code from other project --- run_tree/assets/shaders/world_fragment.glsl | 24 - run_tree/assets/shaders/world_vertex.glsl | 31 -- src/build.onyx | 12 +- src/gfx/canvas.onyx | 24 + src/{ => gfx}/font.onyx | 20 + src/{ => gfx}/immediate.onyx | 52 +++ src/{ => gfx}/mesh.onyx | 0 src/gfx/shader.onyx | 178 ++++++++ src/{ => gfx}/texture.onyx | 0 src/gfx/ui.onyx | 471 ++++++++++++++++++++ src/input.onyx | 81 +++- src/main.onyx | 24 +- 12 files changed, 822 insertions(+), 95 deletions(-) delete mode 100644 run_tree/assets/shaders/world_fragment.glsl delete mode 100644 run_tree/assets/shaders/world_vertex.glsl create mode 100644 src/gfx/canvas.onyx rename src/{ => gfx}/font.onyx (93%) rename src/{ => gfx}/immediate.onyx (69%) rename src/{ => gfx}/mesh.onyx (100%) create mode 100644 src/gfx/shader.onyx rename src/{ => gfx}/texture.onyx (100%) create mode 100644 src/gfx/ui.onyx diff --git a/run_tree/assets/shaders/world_fragment.glsl b/run_tree/assets/shaders/world_fragment.glsl deleted file mode 100644 index dc1ff92..0000000 --- a/run_tree/assets/shaders/world_fragment.glsl +++ /dev/null @@ -1,24 +0,0 @@ -#version 300 es -precision mediump float; - -uniform sampler2D u_sampler; - -uniform vec3 u_fog_color; -uniform float u_fog_start; -uniform float u_fog_range; - - -in vec4 v_color; -in vec2 v_tex; -in float v_tex_enabled; -in float v_dist; - -out vec4 fragColor; -void main() { - vec4 tile_color = vec4((v_color * mix(vec4(1), texture(u_sampler, v_tex), v_tex_enabled)).xyz, 1); - - // Not going to rely on clamp() existing. - float fog = min(1.0f, max(0.0f, (v_dist - u_fog_start) / u_fog_range)); - - fragColor = mix(tile_color, vec4(u_fog_color, 1), fog); -} diff --git a/run_tree/assets/shaders/world_vertex.glsl b/run_tree/assets/shaders/world_vertex.glsl deleted file mode 100644 index ff7709c..0000000 --- a/run_tree/assets/shaders/world_vertex.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#version 300 es -layout(location = 0) in vec3 a_pos; -layout(location = 1) in vec2 a_tex; -layout(location = 2) in uint a_data; - -layout(std140) uniform u_world_matrix_block { - mat4 u_view; - mat4 u_world; - mat4 u_model; -}; - -out vec4 v_color; -out vec2 v_tex; -out float v_tex_enabled; -out float v_dist; - -void main() { - vec4 pos = u_world * u_model * vec4(a_pos, 1); - v_dist = length(pos); - gl_Position = u_view * pos; - - vec3 block_color = vec3( - float((a_data & 0x0000FU) >> 0U) / 15.0, - float((a_data & 0x000F0U) >> 4U) / 15.0, - float((a_data & 0x00F00U) >> 8U) / 15.0 - ) * (float((a_data & 0x0F000U) >> 12U) / 15.0); - - v_color = vec4(block_color, 1); - v_tex = a_tex; - v_tex_enabled = float((a_data & 0x10000U) >> 16U); -} diff --git a/src/build.onyx b/src/build.onyx index 858725b..3dd0e44 100644 --- a/src/build.onyx +++ b/src/build.onyx @@ -10,20 +10,24 @@ #load "camera" #load "chunk" #load "config" -#load "font" -#load "immediate" #load "input" #load "main" -#load "mesh" #load "physics" #load "player" #load "shader" -#load "texture" #load "utils" #load "vecmath" #load "world" #load "worldgen" +#load "gfx/canvas" +#load "gfx/font" +#load "gfx/immediate" +#load "gfx/mesh" +// #load "gfx/shader" +#load "gfx/texture" +#load "gfx/ui" + // Onyx library code #load "stb_truetype" #load "stb_image" diff --git a/src/gfx/canvas.onyx b/src/gfx/canvas.onyx new file mode 100644 index 0000000..d2f08e4 --- /dev/null +++ b/src/gfx/canvas.onyx @@ -0,0 +1,24 @@ + +use package core +use package opengles + + +Canvas :: struct { + framebuffer: GLint; + depth_stencil_buffer: GLint; + color_texture: GLint; +} + +canvas_make :: () -> Canvas { + canvas: Canvas; +} + +canvas_use :: (use canvas: ^Canvas) { + if canvas == null { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } else { + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + } +} + + diff --git a/src/font.onyx b/src/gfx/font.onyx similarity index 93% rename from src/font.onyx rename to src/gfx/font.onyx index 3e32076..094760e 100644 --- a/src/font.onyx +++ b/src/gfx/font.onyx @@ -211,6 +211,26 @@ font_get_width :: (font: Font, msg: str) -> f32 { 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; +} + FontDescriptor :: struct { path : str; diff --git a/src/immediate.onyx b/src/gfx/immediate.onyx similarity index 69% rename from src/immediate.onyx rename to src/gfx/immediate.onyx index 65f6ce1..23dd4eb 100644 --- a/src/immediate.onyx +++ b/src/gfx/immediate.onyx @@ -3,6 +3,9 @@ use package core use package opengles use package glfw3 +#local window_width :: camera.window_width +#local window_height :: camera.window_height + immediate_init :: () { memory.alloc_slice(^vertex_data, Maximum_Vertex_Count); vertex_count = 0; @@ -88,8 +91,52 @@ immediate_image :: (image: ^Texture, x, y, w, h: f32) { 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) { + // Assuming that x, y, w, and h are in screen (window) coordinates. + + scissors << .{x, y, w, h}; + glEnable(GL_SCISSOR_TEST); + glScissor(~~x, ~~(window_height - (y + h)), ~~w, ~~h); +} + +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); + } +} + Color :: struct { r, g, b : f32; a := 1.0f; @@ -119,6 +166,11 @@ Immediate_Vertex :: struct { } rendering_type := Rendering_Type.Plain; + Scissor :: struct { + x, y, w, h: f32; + } + scissors: [..] Scissor; + set_rendering_type :: (new_type: typeof rendering_type) { if rendering_type != new_type { immediate_flush(); diff --git a/src/mesh.onyx b/src/gfx/mesh.onyx similarity index 100% rename from src/mesh.onyx rename to src/gfx/mesh.onyx diff --git a/src/gfx/shader.onyx b/src/gfx/shader.onyx new file mode 100644 index 0000000..246c3d1 --- /dev/null +++ b/src/gfx/shader.onyx @@ -0,0 +1,178 @@ + +use package core +use package 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: 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 :: () { + matrix : [16] f32; + top := 0.0f; + left := 0.0f; + right := cast(f32) window_width; + bottom := cast(f32) window_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); +} + +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 : [] ^u8 = .[ header.data, source.data ]; + source_lens : [] i32 = .[ header.count, source.count ]; + + glShaderSource(shader, 2, sources.data, source_lens.data); + glCompileShader(shader); + + success: GLint; + if glGetShaderiv(shader, GL_COMPILE_STATUS, ^success); success == GL_FALSE { + buf_data: [2048] u8; + buf := str.{ ~~buf_data, 0 }; + glGetShaderInfoLog(shader, 2048, ^buf.count, buf.data); + println(buf); + } + + return shader; + } + + link_program :: (vertex_shader, fragment_shader: GLint) -> GLuint { + prog := glCreateProgram(); + glAttachShader(prog, vertex_shader); + glAttachShader(prog, fragment_shader); + glLinkProgram(prog); + + success: GLint; + if glGetProgramiv(prog, GL_LINK_STATUS, ^success); success == GL_FALSE { + buf_data: [1024] u8; + buf := str.{ ~~buf_data, 0 }; + glGetProgramInfoLog(prog, 1024, ^buf.count, buf.data); + println(buf); + } + + return prog; + } +} + diff --git a/src/texture.onyx b/src/gfx/texture.onyx similarity index 100% rename from src/texture.onyx rename to src/gfx/texture.onyx diff --git a/src/gfx/ui.onyx b/src/gfx/ui.onyx new file mode 100644 index 0000000..32264af --- /dev/null +++ b/src/gfx/ui.onyx @@ -0,0 +1,471 @@ +// +// Very simple immediate mode UI +// + +use package core +use package opengles +use package 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; + } +} + +// +// 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) { + 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; +} + + +// +// 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; + } + + 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); + textbox_editing_state.hash = hash; + textbox_editing_state.cursor_animation_speed = theme.cursor_blink_speed; + } + } + + if is_active_item(hash) { + if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) && !contains { + set_active_item(0); + 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 = get_cursor_position(text_buffer, text_x, text_y, theme.font_size, ~~mx, ~~my); + } + + keys := input_get_keys_this_frame(); + if keys.count > 0 { + for key: keys { + switch key { + 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 { + array.pop(text_buffer); + 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); + } + + case #default { + if key >= #char " " && key <= 128 { + array.push(text_buffer, ~~key); + textbox_editing_state.cursor_position += 1; + } + } + } + } + + 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 + + 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; +} + + + + +// +// 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) { + 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; +} + +#if !#defined(Rect) { + 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; + } + } +} + +#if !#defined(move_towards) { + 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; + } +} + + +#local { + hot_item : UI_Id = 0 + active_item : UI_Id = 0 + hot_item_was_set := false + + hot_item_depth := 0; + hot_item_depth_needed := 0; + + set_active_item :: (id: UI_Id) -> bool { + active_item = id; + return true; + } + + 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; + } + + get_site_hash :: macro (site: CallSite, increment := 0) -> UI_Id { + hash :: package core.hash + file_hash := hash.to_u32(site.file); + line_hash := hash.to_u32(site.line); + column_hash := hash.to_u32(site.column); + + return ~~ (file_hash * 0x472839 + line_hash * 0x6849210 + column_hash * 0x1248382 + increment); + } + + color_lerp :: macro (t: f32, c1, c2: Color) => Color.{ + r = c1.r * (1 - t) + c2.r * t, + g = c1.g * (1 - t) + c2.g * t, + b = c1.b * (1 - t) + c2.b * t, + a = c1.a * (1 - t) + c2.a * t, + }; +} diff --git a/src/input.onyx b/src/input.onyx index a05c3f5..2a5f217 100644 --- a/src/input.onyx +++ b/src/input.onyx @@ -2,18 +2,27 @@ use package core use package glfw3 #local { - keys_this_frame: [..] u32 - keys_last_frame: [..] u32 + keys_this_frame : [..] u32 // Keys currently being pressed this frame + keys_pulse_frame : [..] u32 // Keys pressed during this frame, only set once per keypress + keys_last_frame : [..] u32 // Keys being pressed in the last frame - buttons_this_frame: [8] bool - buttons_last_frame: [8] bool + buttons_this_frame: [8] bool // Mouse buttons being pressed this frame + buttons_last_frame: [8] bool // Mouse buttons being pressed last frame } input_update :: () { glfwGetCursorPos(window, ^mouse_x, ^mouse_y); + + array.clear(^keys_pulse_frame); + for keys_this_frame { + if !array.contains(keys_last_frame, it) { + keys_pulse_frame << it; + } + } } input_post_update :: () { + array.clear(^keys_pulse_frame); array.clear(^keys_last_frame); for keys_this_frame do keys_last_frame << it; @@ -23,30 +32,14 @@ input_post_update :: () { last_mouse_y = mouse_y; } -handle_key_event :: (key, scancode, action, mod: u32) { - if action == GLFW_PRESS { - keys_this_frame << key; - } - - if action == GLFW_RELEASE { - array.remove(^keys_this_frame, key); - } +input_get_keys_this_frame :: () -> [] u32 { + return keys_pulse_frame; } is_key_down :: (key) => array.contains(keys_this_frame, key); is_key_just_down :: (key) => array.contains(keys_this_frame, key) && !array.contains(keys_last_frame, key); is_key_just_up :: (key) => !array.contains(keys_this_frame, key) && array.contains(keys_last_frame, key); -handle_button_event :: (button, action, mod: u32) { - if action == GLFW_PRESS { - buttons_this_frame[button] = true; - } - - if action == GLFW_RELEASE { - buttons_this_frame[button] = false; - } -} - 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]; @@ -62,3 +55,47 @@ is_button_just_up :: (button) => !buttons_this_frame[button] && buttons_last_f 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) { + return mouse_x, mouse_y; +} + +mouse_get_position_vector :: () -> Vector2 { + return .{ ~~mouse_x, ~~mouse_y }; +} + + +input_bind_glfw_events :: (window: GLFWwindow_p) { + glfwSetKeyCallback(window, INPUT_KEY_EVENT); + glfwSetMouseButtonCallback(window, INPUT_BUTTON_EVENT); +} + +#local { + INPUT_BUTTON_EVENT :: "__input_button_event" + INPUT_KEY_EVENT :: "__input_key_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; + } + + if action == GLFW_RELEASE { + array.remove(^keys_this_frame, key); + } +} diff --git a/src/main.onyx b/src/main.onyx index 781da77..c530db1 100644 --- a/src/main.onyx +++ b/src/main.onyx @@ -45,19 +45,6 @@ create_window :: () => { update_window_matrix(); } -#export "on_key" (window: GLFWwindow_p, key, scancode, action, mod: u32) { - if key == GLFW_KEY_ESCAPE && action == GLFW_PRESS { - if cursor_grabbed do toggle_cursor_grabbed(); - else do glfwSetWindowShouldClose(window, true); - } - - handle_key_event(key, scancode, action, mod); -} - -#export "on_mouse_button" (window: GLFWwindow_p, button, action, mod: u32) { - handle_button_event(button, action, mod); -} - cursor_grabbed := false; toggle_cursor_grabbed :: () { cursor_grabbed = !cursor_grabbed; @@ -80,6 +67,8 @@ toggle_cursor_grabbed :: () { setup_opengl :: () { glInit(glfwGetLoadProcAddress()); + input_bind_glfw_events(window); + glEnable(GL_TEXTURE); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); @@ -122,6 +111,14 @@ update :: (dt: f32) { input_update(); defer input_post_update(); + if is_key_just_down(GLFW_KEY_ESCAPE) { + if cursor_grabbed { + toggle_cursor_grabbed(); + } else { + glfwSetWindowShouldClose(window, true); + } + } + if !cursor_grabbed { if is_button_just_down(GLFW_MOUSE_BUTTON_LEFT) { toggle_cursor_grabbed(); @@ -170,7 +167,6 @@ draw_scene :: () { if debug_screen { immediate_set_color(.{1, 0, 1, 0.5}); immediate_rectangle(0, 0, 528, 220); - immediate_flush(); player_chunk := world_position_to_chunk(world, player.body.pos); font_print(font, 0, 32, "FPS: {}", game_fps); -- 2.25.1