MAX_VERT_BUFFERS :: 64; UI_Box_Flags :: enum_flags u32 { NONE :: 0; CLICKABLE :: 1; DRAW_BORDER :: 2; DRAW_BACKGROUND :: 4; DRAW_TEXT :: 8; CLIP :: 16; ANIMATE_ON_HOVER :: 32; } Rect :: struct { x: float; y: float; w: float; h: float; } Rect_Instance_Data :: struct { p0 : Vector2; p1 : Vector2; colors : [4] Color; corner_radius : float; edge_softness : float; border_thickness : float; } Interaction_State :: struct { clicked: bool; left_mouse_down: bool; right_mouse_down: bool; left_mouse_pressed: bool; right_mouse_pressed: bool; normalized_local_mouse_coordinates: Vector2; // Coordinates inside the rect in the range [0,1] } UI_Box :: struct { hash: u32; last_used_frame_index: u64; parent: *UI_Box; first_child: *UI_Box; last_child: *UI_Box; num_children: u64; next: *UI_Box; prev: *UI_Box; // Specified per-frame flags : UI_Box_Flags; text: string; alignment_flags: UI_Text_Alignment_Flags; semantic_size: [2] UI_Size; padding_left: float; padding_right: float; padding_top: float; padding_bottom: float; // Computed per-frame rect : Rect; size: Vector2; // Persistent animation_t: float; hover_animation_t: float; interaction : Interaction_State; style : struct { texture: Texture_Handle; background_color: Color; border_color: Color; border_width: float; text_color: Color; } layout : struct { alignment: UI_Child_Alignment; axis: UI_Child_Axis; } } UI_Child_Axis :: enum { HORIZONTAL; VERTICAL; } UI_Child_Alignment :: enum { LEFT; RIGHT; CENTERED; } UI_Text_Alignment_Flags :: enum_flags u8 { LEFT_BOTTOM :: 0; CENTER_HORIZONTALLY :: 1; CENTER_VERTICALLY :: 2; RIGHT_BOTTOM :: 4; } UI_Size_Kind :: enum { PIXELS; TEXT_DIM; PCT; CHILDREN_SUM; } UI_Size :: struct { size_kind: UI_Size_Kind; value: float; strictness: float; } Textured_Vert_Buffer :: struct { vb : Buffer_Handle; verts: [6] Textured_Vert; } UI_State :: struct { begun: bool; frame_index: u64; root: *UI_Box; boxes: Table(u32, UI_Box); last_box: *UI_Box; parent_stack: Stack(*UI_Box); allocator: Allocator; pool: Flat_Pool; // Rendering shaders : struct { ui: Pipeline_State_Handle; ui_rect: Pipeline_State_Handle; ui_rect_textured: Pipeline_State_Handle; text: Pipeline_State_Handle; } fonts : struct { regular: Font_Handle; button: Font_Handle; } rect_vb : Buffer_Handle; colored_verts: [..] Colored_Vert; max_verts: u32; instanced_rects : [..] Rect_Instance_Data; instance_rect_sb : Buffer_Handle; texture_vert_buffers : [MAX_VERT_BUFFERS] Textured_Vert_Buffer; next_available_texture_buffer_index: s64; sampler: Sampler_Handle; } ui_state : UI_State; ui_box :: (flags: UI_Box_Flags, identifier: s64 = 0, loc := #caller_location) -> *UI_Box { return ui_box_make(flags, hash=get_hash(loc, identifier)); } ui_box_make :: (flags: UI_Box_Flags, hash: u32) -> *UI_Box { actual_hash := hash; parent := get_current_parent(); if parent != null { parent.num_children += 1; actual_hash += parent.hash * xx parent.num_children; //actual_hash += xx (parent.num_children*2); } box := get_ui_box_or_create_new(actual_hash); box.last_used_frame_index = ui_state.frame_index; box.first_child = null; box.last_child = null; box.next = null; box.prev = null; box.num_children = 0; box.flags = flags; box.parent = null; // Set the links box.parent = parent; if box.parent != null { if box.parent.first_child == null { box.parent.first_child = box; } if box.parent.last_child != null { box.parent.last_child.next = box; box.prev = box.parent.last_child; } box.parent.last_child = box; } box.text = current_text; box.style.texture = current_texture; box.style.background_color = background_color; box.style.text_color = text_color; box.style.border_color = border_color; box.style.border_width = border_width; box.semantic_size[0] = current_size_x; box.semantic_size[1] = current_size_y; box.padding_left = padding_left; box.padding_right = padding_right; box.padding_top = padding_top; box.padding_bottom = padding_bottom; box.alignment_flags = current_text_alignment; // Reset everything set_properties_to_defaults(); if ui_state.root == null { ui_state.root = box; } ui_state.last_box = box; return box; } get_hash :: (loc: Source_Code_Location, identifier: s64) -> u32 { hash := cast(u32) loc.fully_pathed_filename.data * cast,no_check(u32)(loc.line_number + 1) * cast(u32)(identifier + 1); return hash; } get_current_parent :: () -> *UI_Box { return stack_peek(*ui_state.parent_stack); } ui_push_parent :: (box: *UI_Box, alignment: UI_Child_Alignment = .LEFT, axis: UI_Child_Axis = .HORIZONTAL) { box.layout.alignment = alignment; box.layout.axis = axis; stack_push(*ui_state.parent_stack, box); } ui_pop_parent :: () { stack_pop(*ui_state.parent_stack); } ui_init :: () { a: Allocator; a.proc = flat_pool_allocator_proc; a.data = *ui_state.pool; ui_state.allocator = a; ui_state.colored_verts.allocator = ui_state.allocator; ui_state.boxes.allocator = ui_state.allocator; init(*ui_state.boxes, 1024); ui_state.sampler = create_sampler(engine.renderer); ui_state.fonts.regular = create_font(engine.renderer, "../assets/fonts/roboto/Roboto-Regular.ttf", 12); ui_state.fonts.button = create_font(engine.renderer, "../assets/fonts/roboto/Roboto-Regular.ttf", 12); // ui_rect { vs := create_vertex_shader(engine.renderer, "../assets/shaders/ui_rect.hlsl", "VS"); ps := create_pixel_shader(engine.renderer, "../assets/shaders/ui_rect.hlsl", "PS"); layout : [2] Vertex_Data_Info; layout[0] = .{0,.POSITION2D, 0}; layout[1] = .{0,.COLOR_WITH_ALPHA, 0}; params : [2] Shader_Parameter; params[0].shader = .PIXEL; params[0].type = .SAMPLER; params[0].name = "samp"; params[0].slot = 0; params[0].mapping = .CLAMP_SAMPLER; params[1].shader = .PIXEL; params[1].type = .TEXTURE; params[1].name = "tex"; params[1].slot = 1; ui_state.shaders.ui_rect = create_pipeline_state(engine.renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); } // ui_rect { vs := create_vertex_shader(engine.renderer, "../assets/shaders/ui.hlsl", "VS_Main"); ps := create_pixel_shader(engine.renderer, "../assets/shaders/ui.hlsl", "PS_Main"); layout : [0] Vertex_Data_Info; params : [2] Shader_Parameter; params[0].shader = .PIXEL; params[0].type = .SAMPLER; params[0].name = "samp"; params[0].slot = 0; params[0].mapping = .CLAMP_SAMPLER; params[1].shader = .PIXEL; params[1].type = .TEXTURE; params[1].name = "tex"; params[1].slot = 1; ui_state.shaders.ui = create_pipeline_state(engine.renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); } { vs := create_vertex_shader(engine.renderer, "../assets/shaders/font.hlsl", "VS"); ps := create_pixel_shader(engine.renderer, "../assets/shaders/font.hlsl", "PS"); layout : [3] Vertex_Data_Info; layout[0] = .{0,.POSITION2D, 0}; layout[1] = .{0,.TEXCOORD0, 0}; layout[2] = .{0,.COLOR_WITH_ALPHA, 0}; params : [2] Shader_Parameter; params[0].shader = .PIXEL; params[0].type = .SAMPLER; params[0].name = "ss"; params[0].slot = 0; params[0].mapping = .CLAMP_SAMPLER; params[1].shader = .PIXEL; params[1].type = .TEXTURE; params[1].name = "tex"; params[1].slot = 1; ui_state.shaders.text = create_pipeline_state(engine.renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); } // Make into one shader.... // ui_rect textured { vs := create_vertex_shader(engine.renderer, "../assets/shaders/ui_rect.hlsl", "VS", string.["USE_TEXTURE"]); ps := create_pixel_shader(engine.renderer, "../assets/shaders/ui_rect.hlsl", "PS", string.["USE_TEXTURE"]); layout : [2] Vertex_Data_Info; layout[0] = .{0,.POSITION2D, 0}; layout[1] = .{0,.TEXCOORD0, 0}; params : [2] Shader_Parameter; params[0].shader = .PIXEL; params[0].type = .SAMPLER; params[0].name = "samp"; params[0].slot = 0; params[0].mapping = .CLAMP_SAMPLER; params[1].shader = .PIXEL; params[1].type = .TEXTURE; params[1].name = "tex"; params[1].slot = 1; ui_state.shaders.ui_rect_textured = create_pipeline_state(engine.renderer, vs, ps, layout, params, blend_type=.TRANSPARENT); } ui_state.max_verts = 4096; { dynamic_buffer_size := size_of(Colored_Vert) * ui_state.max_verts; ui_state.rect_vb = create_vertex_buffer(engine.renderer, null, dynamic_buffer_size, stride=size_of(Colored_Vert), mappable=true); for 0..MAX_VERT_BUFFERS-1 { { buffer : Textured_Vert_Buffer; dynamic_buffer_size := size_of(Textured_Vert) * ui_state.max_verts; buffer.vb = create_vertex_buffer(engine.renderer, null, dynamic_buffer_size, stride=size_of(Textured_Vert), mappable=true); ui_state.texture_vert_buffers[it] = buffer; } } } { dynamic_buffer_size := size_of(Rect_Instance_Data) * ui_state.max_verts; ui_state.instance_rect_sb = create_structured_buffer(engine.renderer, null, dynamic_buffer_size, stride=size_of(Rect_Instance_Data), mappable=true); } } // # BEGIN # LAYOUT ALGORITHM ui_figure_out_sizes :: () { // SET ALL PIXEL AND TEXT SIZES for *box : ui_state.boxes { if box.semantic_size[0].size_kind == { case .PIXELS; { box.size.x = box.semantic_size[0].value + box.padding_left + box.padding_right; } case .TEXT_DIM; { text_size := get_text_size(engine.renderer, box.text, ui_state.fonts.button); box.size.x = text_size.x + box.padding_left + box.padding_right; } } if box.semantic_size[1].size_kind == { case .PIXELS; { box.size.y = box.semantic_size[1].value + box.padding_top + box.padding_bottom; } case .TEXT_DIM; { text_size := get_text_size(engine.renderer, box.text, ui_state.fonts.button); box.size.y = text_size.y + box.padding_top + box.padding_bottom; } } } // Upwards-dependent for *box : ui_state.boxes { if box.semantic_size[0].size_kind == .PCT { assert(box.parent != null); box.size.x = box.parent.size.x * box.semantic_size[0].value - (box.parent.padding_left + box.parent.padding_right); } if box.semantic_size[1].size_kind == .PCT { assert(box.parent != null); box.size.y = box.parent.size.y * box.semantic_size[1].value - (box.parent.padding_top + box.parent.padding_bottom); } } // Downwards-dependent for *box : ui_state.boxes { if box.semantic_size[0].size_kind == .CHILDREN_SUM { assert(box.num_children != 0); size : float = 0.0; child := box.first_child; while child != null { defer child = child.next; if box.layout.axis == .HORIZONTAL { size += child.size.x; } else { size = max(child.size.x, size); } } box.size.x = size + box.padding_left + box.padding_right; } if box.semantic_size[1].size_kind == .CHILDREN_SUM { assert(box.num_children != 0); size : float = 0.0; child := box.first_child; while child != null { defer child = child.next; if box.layout.axis == .VERTICAL { size += child.size.y; } else { size = max(child.size.y, size); } } box.size.y = size + box.padding_top + box.padding_bottom; } } // Find final positions for *box : ui_state.boxes { if box.parent == null { box.rect.x = 0.0; box.rect.y = 0.0; box.rect.w = box.size.x; box.rect.h = box.size.y; ui_set_rect_recursively(box); } } for *box : ui_state.boxes { if box.style.texture != 0 { w, h := get_texture_size(engine.renderer, box.style.texture); aspect := cast(float)w/cast(float)h; current_aspect := cast(float)box.rect.w/cast(float)box.rect.h; rect := box.rect; if rect.w < rect.h { box.rect.w = box.rect.h * aspect; scale := rect.w / box.rect.w; // @Note(Daniel): My brain can't think straight so this extra scaling might be circumvented by doing the calculations differently.... if scale < 1 { box.rect.w *= scale; box.rect.h *= scale; } } else { box.rect.h = box.rect.w / aspect; scale := rect.h / box.rect.h; if scale < 1 { box.rect.w *= scale; box.rect.h *= scale; } } } //if box.semantic_size[0].size_kind == .KEEP_ASPECT { // assert(box.style.texture != 0); // w, h := get_texture_size(engine.renderer, box.style.texture); // aspect := cast(float)w/cast(float)h; // box.size.x = box.size.y * aspect; //} // //if box.semantic_size[1].size_kind == .KEEP_ASPECT { // assert(box.style.texture != 0); // w, h := get_texture_size(engine.renderer, box.style.texture); // aspect := cast(float)h/cast(float)w; // box.size.y = box.size.x * aspect; //} } //print("\n"); for *box : ui_state.boxes { //print("RECT % % % % TEXT %\n", box.rect.x, box.rect.y, box.rect.w, box.rect.h, box.text); } //print("\n"); } ui_set_rect_recursively :: (parent: *UI_Box) { starting_offset_x := parent.rect.x + parent.padding_left; starting_offset_y := parent.rect.y + parent.padding_bottom; child := parent.first_child; while child != null { defer child = child.next; child.rect.x = starting_offset_x; child.rect.y = starting_offset_y; child.rect.w = child.size.x; child.rect.h = child.size.y; if parent.layout.axis == { case .HORIZONTAL; { starting_offset_x += child.rect.w; } case .VERTICAL; { starting_offset_y += child.rect.h; } } if child.num_children > 0 { ui_set_rect_recursively(child); } } } // # END # LAYOUT ALGORITHM ui_begin :: () { assert(!ui_state.begun); ui_state.frame_index += 1; ui_state.begun = true; ui_state.colored_verts.count = 0; ui_state.instanced_rects.count = 0; ui_state.next_available_texture_buffer_index = 0; } ui_end :: () { hashes_to_remove : [..] u32; hashes_to_remove.allocator = temp; for * ui_state.boxes { if it.last_used_frame_index != ui_state.frame_index { array_add(*hashes_to_remove, it.hash); } } for hashes_to_remove { table_remove(*ui_state.boxes, it); } // Do all the layouting work ui_figure_out_sizes(); // Check for any input events ui_update_input(); assert(ui_state.begun); ui_state.begun = false; ui_state.parent_stack.values.count = 0; } // #### BOX PROPERTY FUNCTIONS ui_set_next_background_color :: (color: Color) { background_color = color; } ui_set_next_text_color :: (color: Color) { text_color = color; } ui_set_next_border_color :: (color: Color) { border_color = color; } ui_set_next_border_width :: (width: float) { border_width = width; } ui_set_next_texture :: (handle: Texture_Handle) { current_texture = handle; } ui_set_next_padding :: (new_padding: float) { padding_left = new_padding; padding_right = new_padding; padding_top = new_padding; padding_bottom = new_padding; } ui_set_next_padding_left :: (new_padding: float) { padding_left = new_padding; } ui_set_next_padding_right :: (new_padding: float) { padding_right = new_padding; } ui_set_next_padding_top :: (new_padding: float) { padding_top = new_padding; } ui_set_next_padding_bottom :: (new_padding: float) { padding_bottom = new_padding; } ui_set_next_text :: (text: string) { current_text = copy_temporary_string(text); } ui_set_next_text_alignment :: (flags: UI_Text_Alignment_Flags) { current_text_alignment = flags; } ui_set_next_size_x :: (size: UI_Size_Kind, value: float = 0.0, strictness: float = 0.0) { current_size_x = .{size, value, strictness}; } ui_set_next_size_y :: (size: UI_Size_Kind, value: float = 0.0, strictness: float = 0.0) { current_size_y = .{size, value, strictness}; } // # END # BOX PROPERTY FUNCTIONS ui_render :: () { // Render background for * ui_state.boxes { if it.parent == null { ui_render_background_recursively(it); } } if ui_state.colored_verts.count > 0 { //upload_data_to_buffer(engine.renderer, ui_state.rect_vb, ui_state.colored_verts.data, cast(s32)ui_state.colored_verts.count * size_of(Colored_Vert)); //push_cmd_set_draw_mode(engine.renderer, .FILL); //push_cmd_set_depth_write(engine.renderer, false); //push_cmd_set_pipeline_state(engine.renderer, ui_state.shaders.ui_rect); //push_cmd_set_vertex_buffer(engine.renderer, ui_state.rect_vb); //push_cmd_draw(engine.renderer, ui_state.colored_verts.count); } // NEW DRAW TIME if ui_state.instanced_rects.count > 0 { upload_data_to_buffer(engine.renderer, ui_state.instance_rect_sb, ui_state.instanced_rects.data, cast(s32)ui_state.instanced_rects.count * size_of(Rect_Instance_Data)); push_cmd_set_draw_mode(engine.renderer, .FILL); push_cmd_set_depth_write(engine.renderer, false); push_cmd_set_pipeline_state(engine.renderer, ui_state.shaders.ui); push_cmd_set_constant_buffer(engine.renderer, 0, engine.screen_data_buffer, .PIXEL); push_cmd_set_structured_buffer(engine.renderer, 0, ui_state.instance_rect_sb, .VERTEX); push_cmd_draw_instanced(engine.renderer, 4, ui_state.instanced_rects.count, topology=.TRIANGLE_STRIP); } // @Incomplete: this should be part of the same rendering somehow... Although it should happen on a per-texture basis // Render backgrounds with texture for * ui_state.boxes { if it.parent == null { ui_render_texture_background_recursively(it); } } // Render text push_cmd_set_pipeline_state(engine.renderer, ui_state.shaders.text); for * ui_state.boxes { if it.parent == null { ui_render_text_recursively(it); } } } #scope_file set_properties_to_defaults :: () { background_color = .{1,1,1,1}; text_color = .{0,0,0,1}; border_color = .{130.0/255.0,95.0/255.0,38.0/255.0,1}; border_width = 2.0; current_text = ""; current_texture = 0; current_size_x = .{}; current_size_y = .{}; padding_left = 0; padding_right = 0; padding_top = 0; padding_bottom = 0; current_text_alignment = .LEFT_BOTTOM; } get_ui_box_or_create_new :: (hash: u32) -> *UI_Box, bool { ptr := table_find_pointer(*ui_state.boxes, hash); if ptr == null { table_add(*ui_state.boxes, hash, .{}); ptr = table_find_pointer(*ui_state.boxes, hash); } ptr.hash = hash; return ptr, false; } // #### RENDERING make_vert :: (x: float, y: float, color: Color) -> Colored_Vert { vert : Colored_Vert; vert.position.x = x; vert.position.y = y; vert.color = color; return vert; } make_vert_textured :: (x: float, y: float, uv_x: float, uv_y: float) -> Textured_Vert { vert : Textured_Vert; vert.position.x = x; vert.position.y = y; vert.texcoord.x = uv_x; vert.texcoord.y = uv_y; return vert; } should_draw_hover_animation :: (box: *UI_Box) -> bool { return box.flags & .ANIMATE_ON_HOVER && box.hover_animation_t > 0.0; } instance_data_from_rect :: (x: float, y: float, w: float, h: float, corner_radius: float, edge_softness: float, border_thickness: float, color: .. Color) -> Rect_Instance_Data { data : Rect_Instance_Data = ---; data.p0.x = x; data.p0.y = y; data.p1.x = x + w; data.p1.y = y - h; data.corner_radius = corner_radius; data.edge_softness = edge_softness; data.border_thickness = border_thickness; if color.count == 4 { data.colors[0] = color[0]; data.colors[1] = color[1]; data.colors[2] = color[2]; data.colors[3] = color[3]; } else { data.colors[0] = color[0]; data.colors[1] = color[0]; data.colors[2] = color[0]; data.colors[3] = color[0]; } return data; } ui_render_background_recursively :: (box: *UI_Box) { inv_w := 1.0 / cast(float)engine.renderer.render_target_width; inv_h := 1.0 / cast(float)engine.renderer.render_target_height; if box.flags & .DRAW_BACKGROUND && box.style.texture == 0 { x : float = box.rect.x * inv_w * 2.0 - 1.0; y : float = (cast(float)engine.renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0; w : float = box.rect.w * inv_w * 2.0; h : float = box.rect.h * inv_h * 2.0; color : Color = box.style.background_color; instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, 0.0, color); array_add(*ui_state.instanced_rects, instance_data); //array_add(*ui_state.colored_verts, make_vert(x + w, y - h, color)); //array_add(*ui_state.colored_verts, make_vert(x, y - h, color)); //array_add(*ui_state.colored_verts, make_vert(x, y, color)); //array_add(*ui_state.colored_verts, make_vert(x + w, y - h, color)); //array_add(*ui_state.colored_verts, make_vert(x, y, color)); //array_add(*ui_state.colored_verts, make_vert(x + w, y, color)); } if box.flags & .DRAW_BORDER { x : float = box.rect.x * inv_w * 2.0 - 1.0; y : float = (cast(float)engine.renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0; w : float = box.rect.w * inv_w * 2.0; h : float = box.rect.h * inv_h * 2.0; color : Color = box.style.border_color; instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, box.style.border_width, color); array_add(*ui_state.instanced_rects, instance_data); } if should_draw_hover_animation(box) { x : float = box.rect.x * inv_w * 2.0 - 1.0; y : float = (cast(float)engine.renderer.render_target_height - box.rect.y) * inv_h * 2.0 - 1.0; w : float = box.rect.w * inv_w * 2.0; h : float = box.rect.h * inv_h * 2.0; color := Color.{1,1,1, box.hover_animation_t * 0.3}; instance_data := instance_data_from_rect(x, y, w, h, 0.0, 0.0, 0.0, color); array_add(*ui_state.instanced_rects, instance_data); } child := box.first_child; while child != null { defer child = child.next; ui_render_background_recursively(child); } } get_next_available_texture_vert_buffer :: () -> *Textured_Vert_Buffer { index := ui_state.next_available_texture_buffer_index; assert(index < MAX_VERT_BUFFERS); ui_state.next_available_texture_buffer_index += 1; buffer := *ui_state.texture_vert_buffers[index]; return buffer; } ui_render_texture_background_recursively :: (box: *UI_Box) { inv_w := 1.0 / cast(float)engine.renderer.render_target_width; inv_h := 1.0 / cast(float)engine.renderer.render_target_height; if box.flags & .DRAW_BACKGROUND && box.style.texture != 0 { buffer := get_next_available_texture_vert_buffer(); rect := box.rect; inv_w := 1.0 / cast(float)engine.renderer.render_target_width; inv_h := 1.0 / cast(float)engine.renderer.render_target_height; x : float = rect.x * inv_w * 2.0 - 1.0; y : float = (cast(float)engine.renderer.render_target_height - rect.y) * inv_h * 2.0 - 1.0; w : float = rect.w * inv_w * 2.0; h : float = rect.h * inv_h * 2.0; buffer.verts[0] = make_vert_textured(x + w, y - h, 1.0, 1.0); buffer.verts[1] = make_vert_textured(x, y - h, 0, 1.0); buffer.verts[2] = make_vert_textured(x, y, 0.0, 0.0); buffer.verts[3] = make_vert_textured(x + w, y - h, 1.0, 1.0); buffer.verts[4] = make_vert_textured(x, y, 0.0, 0.0); buffer.verts[5] = make_vert_textured(x + w, y, 1.0, 0.0); upload_data_to_buffer(engine.renderer, buffer.vb, buffer.verts.data, 6 * size_of(Textured_Vert)); push_cmd_set_draw_mode(engine.renderer, .FILL); push_cmd_set_depth_write(engine.renderer, false); push_cmd_set_pipeline_state(engine.renderer, ui_state.shaders.ui_rect_textured); push_cmd_set_sampler(engine.renderer, 0, ui_state.sampler); push_cmd_set_texture(engine.renderer, 0, box.style.texture); push_cmd_set_vertex_buffer(engine.renderer, buffer.vb); push_cmd_draw(engine.renderer, 6); } child := box.first_child; while child != null { defer child = child.next; ui_render_texture_background_recursively(child); } } ui_render_text_recursively :: (box: *UI_Box) { inv_w := 1.0 / cast(float)engine.renderer.render_target_width; inv_h := 1.0 / cast(float)engine.renderer.render_target_height; if box.flags & .DRAW_TEXT { font_handle := ui_state.fonts.regular; text_size := get_text_size(engine.renderer, box.text, font_handle); x := box.rect.x + box.padding_left; y := cast(float)engine.renderer.render_target_height - box.rect.y - box.rect.h + box.padding_bottom; if box.alignment_flags & .CENTER_HORIZONTALLY { x += box.rect.w * 0.5 - text_size.x * 0.5; } if box.alignment_flags & .CENTER_VERTICALLY { y += box.rect.h * 0.5 - text_size.y * 0.5; } if box.alignment_flags & .RIGHT_BOTTOM { x += box.rect.w - text_size.x; } render_data := bake_text(engine.renderer, x, y, box.text, font_handle, box.style.text_color); font := *engine.renderer.fonts[font_handle - 1]; push_cmd_set_texture(engine.renderer, 0, font.texture); push_cmd_set_vertex_buffer(engine.renderer, render_data.vb); push_cmd_draw(engine.renderer, render_data.vert_count); } child := box.first_child; while child != null { defer child = child.next; ui_render_text_recursively(child); } } ADJUSTMENT :: 0.1; is_mouse_inside :: (x: float, y: float, rect: Rect) -> bool { return x - ADJUSTMENT >= rect.x && y - ADJUSTMENT >= rect.y && x + ADJUSTMENT <= rect.x + rect.w && y + ADJUSTMENT <= rect.y + rect.h; } ui_update_input :: () { mouse_x := engine.input.mouse.x; mouse_y := engine.input.mouse.y; for *box: ui_state.boxes { if box.flags & .CLICKABLE { normalized_local_mouse_coordinates: Vector2; // Coordinates inside the rect in the range [0,1] box.interaction.normalized_local_mouse_coordinates.x = clamp(mouse_x - box.rect.x, 0.0, box.rect.w) / box.rect.w; box.interaction.normalized_local_mouse_coordinates.y = clamp(mouse_y - box.rect.y, 0.0, box.rect.h) / box.rect.h; if box.interaction.clicked { box.interaction.clicked = false; } if box.interaction.left_mouse_pressed { box.interaction.left_mouse_down = false; if !key_pressed(.MOUSE_LEFT) { box.interaction.clicked = true; box.interaction.left_mouse_pressed = false; } } else { if is_mouse_inside(mouse_x, mouse_y, box.rect) && key_pressed(.MOUSE_LEFT) { box.interaction.left_mouse_pressed = true; box.interaction.left_mouse_down = true; } } } if box.flags & .ANIMATE_ON_HOVER { if is_mouse_inside(mouse_x, mouse_y, box.rect) { box.hover_animation_t += dt * 10; } else { box.hover_animation_t -= dt * 10; } box.hover_animation_t = clamp(box.hover_animation_t, 0.0, 1.0); } } } // style background_color : Color = .{1,1,1,1}; text_color : Color = .{0,0,0,1}; border_color : Color; border_width : float; current_text : string = ""; current_texture : Texture_Handle; current_size_x : UI_Size; current_size_y : UI_Size; current_text_alignment : UI_Text_Alignment_Flags; padding_left : float; padding_right : float; padding_top : float; padding_bottom : float; #load "../core/stack.jai";