GR :: #import "GetRect_LeftHanded"()(Type_Indicator = Ui_Type_Indicator); Input :: #import "Input"; #load "component_themes.jai"; #load "autoedit.jai"; // vw is 1/100 of view width vw : float; // vh is 1/100 of view height vh : float; // These are for making minimum widths and heights. // nvh is 1/100 of 1080; nvh : float : 1080 / 100; // nvw is 1/100 of 1920; nvw : float : 1920 / 100; ui_w :: (normal_w : int, min_w: int) -> float { min_width := min_w * nvw; normal_width := normal_w * vw; if min_width > normal_width then return min_width; return normal_width; } ui_h :: (normal_h : int, min_h: int) -> float { min_height := min_h * nvh; normal_height := normal_h * vh; if min_height > normal_height then return min_height; return normal_height; } default_texture : *Ui_Texture = null; Ui_Font_Glyph :: struct { advance : u32 = 1; }; Ui_Font :: struct { em_width : int = 1; character_height : int = 30; typical_descender : int = 1; typical_ascender : int = 1; fons_font : s32 = 0; temporary_glyphs : [..]Ui_Font_Glyph; temporary_glyphs_byte_offsets : [..]u32; temporary_glyphs_width_in_pixels : u32; }; ui_init_font_fields :: (font: *Ui_Font) { m_str := "M"; fonsSetFont(state.fons, xx font.fons_font); fonsSetSize(state.fons, xx font.character_height); ascender, descender, line_h : float; fonsVertMetrics(state.fons, *ascender, *descender, *line_h); font.typical_descender = cast(int) descender; font.typical_ascender = cast(int) (ascender * 0.85); // @stupid fix to make the ascender not be so big. w := fonsTextBounds(state.fons, 0.0, 0.0, m_str.data, m_str.data + m_str.count, null); font.em_width = xx w; } Ui_Texture :: struct { tex: sg_image; } Ui_Rect :: struct { x, y, w, h: s32; }; Ui_Type_Indicator :: struct { Texture : Type : Ui_Texture; Window_Type : Type : s32; Font : Type : Ui_Font; Font_Effects : Type : u32; }; Font :: Ui_Font; defaultFont: Font; ui_texture_counter : u32 = 0; #scope_file ui_mouse_occluders : [..]GR.Rect; #scope_export ui_add_mouse_occluder :: (r: GR.Rect) { array_add(*ui_mouse_occluders, r); } ui_clear_mouse_occluders :: () { array_reset_keeping_memory(*ui_mouse_occluders); } ui_is_mouse_in_occluder :: (mpos: Vector2) -> bool { for ui_mouse_occluders { if (mpos.x >= it.x && mpos.x <= it.x + it.w && mpos.y >= it.y && mpos.y <= it.y + it.h) { return true; }; } return false; } get_mouse_state :: (kc: Key_Code) -> Key_Current_State { if ui_is_mouse_in_occluder(.{input_mouse_x, input_mouse_y}) then return .NONE; return input_button_states[kc]; } texture_load_from_memory :: (texture: *Ui_Texture, memory: []u8, srgb: bool, build_mipmaps: bool) -> bool { x : s32; y : s32; channels : s32; data := stbi.stbi_load_from_memory(memory.data, xx memory.count, *x, *y, *channels, 4); img := sg_alloc_image(); subimg : [6][16]sg_range; subimg[0][0] = .{ ptr = data, size = xx (x * y * 4) }; sg_init_image(img, *(sg_image_desc.{ width = x, height = y, pixel_format = sg_pixel_format.RGBA8, data = .{ subimage = subimg } })); stbi.stbi_image_free(data); texture.tex = img; if !default_texture then default_texture = texture; return true; } gScissor : Ui_Rect; gScissorActive : bool = false; set_scissor :: (x0: s32, y0: s32, x1: s32, y1: s32) { arb_tri_command_add(.{ type = .SET_SCISSOR, scissor = .{x0, y0, x1 - x0, y1 - y0}}); } clear_scissor :: () { arb_tri_command_add(.{type = .REMOVE_SCISSOR}); } gCurrentTexture : *Ui_Texture = null; add_uvs : bool = false; set_shader_for_color :: (enable_blend := false) { arb_tri_command_add(.{ type = .REMOVE_TEXTURE }); add_uvs = false; immediate_flush(); } set_shader_for_images :: (texture: *Ui_Texture) { arb_tri_command_add(.{ type = .SET_TEXTURE, texture = texture }); add_uvs = true; immediate_flush(); } gPreppedText: string; gPreppedTextWidth : s32; prepare_text :: (font: *Ui_Type_Indicator.Font, text: string, effects: Ui_Type_Indicator.Font_Effects = 0) -> s64 { if text.count < 1 { gPreppedText = ""; gPreppedTextWidth = 0; return 0; } // print("Font: %\n", font); fonsSetFont(state.fons, state.font_default.fons_font); fonsSetSize(state.fons, xx font.character_height); w := fonsTextBounds(state.fons, 0.0, 0.0, text.data, text.data + text.count, null); gPreppedText = text; gPreppedTextWidth = cast(s32) w; // @Memory: there is a bug if we actually free these during subwindow popup draw. // I'm not completely sure why, but it should be fine/better if we just reset keeping memory. array_reset_keeping_memory(*font.temporary_glyphs); array_reset_keeping_memory(*font.temporary_glyphs_byte_offsets); font.temporary_glyphs_width_in_pixels = 0; for 0..(text.count-1) { glyph : Ui_Font_Glyph; glyph.advance = cast(u32) fonsTextBounds(state.fons, 0.0, 0.0, text.data + it, text.data + it + 1, null); font.temporary_glyphs_width_in_pixels += glyph.advance; array_add(*font.temporary_glyphs, glyph); array_add(*font.temporary_glyphs_byte_offsets, cast(u32) it); } return cast(s64) w; } draw_prepared_text :: (font: *Ui_Type_Indicator.Font, x: s64, y: s64, text_color: Vector4, effects: Ui_Type_Indicator.Font_Effects = 0) { if gPreppedText.count < 1 then return; color := sfons_rgba(xx (255.0 * text_color.x), xx (255.0 * text_color.y), xx (255.0 * text_color.z), xx (255.0 * text_color.w)); fonsSetColor(state.fons, color); result := cast(*u8) temporary_alloc(gPreppedText.count + 1); // Add 1 for the zero. memcpy(result, gPreppedText.data, gPreppedText.count); result[gPreppedText.count] = 0; sgl_layer(layer); fonsDrawText(state.fons, xx x, xx y, result, null); fonsPushState(state.fons); arb_tri_command_add(.{ type = .DRAW_TEXT, layer = layer }); layer += 1; } get_mouse_pointer_position :: (window: Ui_Type_Indicator.Window_Type, right_handed: bool) -> (x: int, y: int, success: bool) { return xx input_mouse_x, xx input_mouse_y, true; } get_font_at_size :: (memory: [] u8, pixel_height: int) -> *Font { f : *Font = New(Font); f.character_height = cast(u32) pixel_height; ui_init_font_fields(f); return f; } // TODO: Figure out what to do with the normal? immediate_triangle :: (p0: Vector3, p1: Vector3, p2: Vector3, c0 := Vector4.{1,1,1,1}, c1 := Vector4.{1,1,1,1}, c2 := Vector4.{1,1,1,1}, uv0 := Vector2.{}, uv1 := Vector2.{}, uv2 := Vector2.{}, normal := Vector3.{z=1}) { tri: Arb_Tri; tri.pos[0] = p0; tri.pos[1] = p1; tri.pos[2] = p2; tri.col[0] = c0; tri.col[1] = c1; tri.col[2] = c2; // This UV symbolizes that the sampler should not be used. nullUV : Vector2 = .{-4, -2}; if !add_uvs { tri.uv[0] = nullUV; tri.uv[1] = nullUV; tri.uv[2] = nullUV; } else { tri.uv[0] = flip_y_if_plat(uv0); tri.uv[1] = flip_y_if_plat(uv1); tri.uv[2] = flip_y_if_plat(uv2); } arb_tri_add(tri); } immediate_rect :: (x: float, y: float, w: float, h: float, rotation: float = 0) { if rotation == 0 { immediate_quad(.{xx x, xx y}, .{xx x + w, xx y}, .{xx x + w, xx y + h}, .{xx x, xx y + h}); return; } // 1. Find the center of the rectangle, which will be our pivot point. center_x := x + w / 2.0; center_y := y + h / 2.0; // 2. Pre-calculate the sine and cosine of the rotation angle. // The angle is assumed to be in radians. s := sin(rotation); c := cos(rotation); // 3. Define the corners relative to the origin (0,0) before rotation. half_w := w / 2.0; half_h := h / 2.0; // Relative positions of the four corners p1_rel_x := -half_w; p1_rel_y := -half_h; // Top-left p2_rel_x := half_w; p2_rel_y := -half_h; // Top-right p3_rel_x := half_w; p3_rel_y := half_h; // Bottom-right p4_rel_x := -half_w; p4_rel_y := half_h; // Bottom-left // 4. Apply the 2D rotation formula to each corner and add the center offset. // The formula is: // x' = x * cos(θ) - y * sin(θ) // y' = x * sin(θ) + y * cos(θ) p1x := center_x + p1_rel_x * c - p1_rel_y * s; p1y := center_y + p1_rel_x * s + p1_rel_y * c; p2x := center_x + p2_rel_x * c - p2_rel_y * s; p2y := center_y + p2_rel_x * s + p2_rel_y * c; p3x := center_x + p3_rel_x * c - p3_rel_y * s; p3y := center_y + p3_rel_x * s + p3_rel_y * c; p4x := center_x + p4_rel_x * c - p4_rel_y * s; p4y := center_y + p4_rel_x * s + p4_rel_y * c; // 5. Draw the quad using the final, rotated corner coordinates. immediate_quad( .{xx p1x, xx p1y}, .{xx p2x, xx p2y}, .{xx p3x, xx p3y}, .{xx p4x, xx p4y} ); } immediate_quad :: (p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, color := Vector4.{1,1,1,1}, uv0 := Vector2.{0,0}, uv1 := Vector2.{1,0}, uv2 := Vector2.{1,1}, uv3 := Vector2.{0, 1}) { to_3d_vec :: (v: Vector2) -> Vector3 { return .{v.x, v.y, 0.0}; } immediate_triangle(to_3d_vec(p0), to_3d_vec(p1), to_3d_vec(p2), color, color, color, uv0, uv1, uv2); immediate_triangle(to_3d_vec(p0), to_3d_vec(p2), to_3d_vec(p3), color, color, color, uv0, uv2, uv3); } immediate_flush :: () { arb_tri_command_add(.{ type = .FLUSH_TRI, tri_offset = arbTriState.latest_flush, tri_count = arbTriState.trilist.count - arbTriState.latest_flush }); arbTriState.latest_flush = arbTriState.trilist.count; } init_ui :: () { dp : GR.Draw_Procs = .{ texture_load_from_memory = texture_load_from_memory, // implemented set_scissor = set_scissor, clear_scissor = clear_scissor, set_shader_for_color = set_shader_for_color, // implemented set_shader_for_images = set_shader_for_images, prepare_text = prepare_text, // implemented draw_prepared_text = draw_prepared_text, // implemented get_mouse_pointer_position = get_mouse_pointer_position, // implemented get_font_at_size = get_font_at_size, // implemented immediate_triangle = immediate_triangle, // implemented immediate_quad = immediate_quad, // implemented immediate_flush = immediate_flush // implemented }; GR.ui_init("", *dp); } ui_events : [..]Input.Event; add_ui_event :: (event: Input.Event) { array_add(*ui_events, event); } tick_ui :: () { w,h := get_window_size(); for ui_events { GR.getrect_handle_event(it); } vw = (cast(float) w)/100.0; vh = (cast(float) h)/100.0; array_reset_keeping_memory(*ui_events); GR.ui_per_frame_update(1, xx w, xx h, get_time()); tick_editor_ui(); } checkboxTest : bool = false; get_font_at_size :: (pixel_height: int) -> *Font { list : []u8; return get_font_at_size(list, pixel_height); } test_color : Vector3 = .{1.0, 0.0, 1.0}; draw_bg_rectangle :: (r: GR.Rect, theme: GR.Overall_Theme) { draw_rectangle(r, theme.background_color); } draw_rectangle :: (r: GR.Rect, color: Vector4) { set_shader_for_color(); immediate_quad(.{r.x, r.y}, .{r.x + r.w, r.y}, .{r.x + r.w, r.y + r.h}, .{r.x, r.y + r.h}, color); immediate_flush(); } draw_ui_rect_animation:: (r: GR.Rect, animationPlayer: *Animation_Player) { tex := New(Ui_Texture ,,temp); animation_player_tick(animationPlayer); animation := animationPlayer.current_animation; tex.tex = animation.sheet; set_shader_for_images(tex); frame := animation.frames[animationPlayer.current_frame]; uv := Vector4.{ cast(float) frame.x / cast(float)animation.sheet_w, cast(float) frame.y / cast(float)animation.sheet_h, cast(float) frame.w / cast(float)animation.sheet_w, cast(float) frame.h / cast(float)animation.sheet_h, }; uv0 := flip_y_if_plat(Vector2.{uv.x, uv.y}); uv1 := flip_y_if_plat(Vector2.{uv.x + uv.z, uv.y}); uv2 := flip_y_if_plat(Vector2.{uv.x + uv.z, uv.y + uv.w}); uv3 := flip_y_if_plat(Vector2.{uv.x, uv.y + uv.w}); immediate_quad(.{r.x, r.y}, .{r.x + r.w, r.y}, .{r.x + r.w, r.y + r.h}, .{r.x, r.y + r.h}, .{0.9,0.9,0.9,1}, uv0, uv1, uv2, uv3); immediate_flush(); set_shader_for_color(); } font_boundary :: () { arb_tri_command_add(.{type = .FONT_BOUNDARY}); } render_ui :: () { proc := GR.default_theme_procs[0]; my_theme := proc(); GR.set_default_theme(my_theme); draw_editor_ui(*my_theme); if !in_editor_view then game_ui(*my_theme); } ui_pass :: () { render_ui(); // Generates commands that are handled in arb_tri_flush }