wip : trying to get layered text rendering working

This commit is contained in:
Edward R. Gonzalez 2024-06-23 20:22:36 -04:00
parent 55b80da8e5
commit 7d41fcc335
13 changed files with 311 additions and 113 deletions

View File

@ -413,7 +413,7 @@ configure_snap :: proc( ctx : ^Context, snap_width, snap_height : u32 ) {
}
// ve_fontcache_load
load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32 ) -> FontID
load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32 ) -> (font_id : FontID)
{
assert( ctx != nil )
assert( len(data) > 0 )
@ -421,6 +421,7 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32
context.allocator = backing
id : i32 = -1
for index : i32 = 0; index < i32(entries.num); index += 1 {
if entries.data[index].used do continue
id = index
@ -435,6 +436,7 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32
entry := & entries.data[ id ]
{
using entry
parser_info = parser_load_font( & parser_ctx, label, data )
// assert( parser_info != nil, "VEFontCache.load_font: Failed to load font info from parser" )
@ -448,9 +450,12 @@ load_font :: proc( ctx : ^Context, label : string, data : []byte, size_px : f32
shaper_info = shaper_load_font( & shaper_ctx, label, data, transmute(rawptr) id )
// assert( shaper_info != nil, "VEFontCache.load_font: Failed to load font from shaper")
return id
}
entry.id = FontID(id)
ctx.entries.data[ id ].id = FontID(id)
font_id = FontID(id)
return
}
// ve_fontcache_unload
@ -740,7 +745,8 @@ measure_text_size :: proc( ctx : ^Context, font : FontID, text_utf8 : string ) -
cast(f32) cast(u32) (f32(bounds_width) * entry.size_scale - f32(atlas.glyph_padding)),
cast(f32) cast(u32) (f32(bounds_height) * entry.size_scale - f32(atlas.glyph_padding)),
}
measured += bounds
measured.x += bounds.x
measured.y = max(measured.y, bounds.y)
}
return measured

View File

@ -208,6 +208,9 @@ parser_get_glyph_box :: proc( font : ^ParserFontInfo, glyph_index : Glyph ) -> (
case .STB_TrueType:
x0, y0, x1, y1 : i32
// {
// success := stbtt.InitFont( & font.stbtt_info, raw_data(data), 0 )
// }
success := cast(bool) stbtt.GetGlyphBox( & font.stbtt_info, i32(glyph_index), & x0, & y0, & x1, & y1 )
assert( success )

View File

@ -19,6 +19,22 @@ intersects_range2 :: #force_inline proc "contextless" ( a, b: Range2 ) -> bool
return true;
}
// AABB: Separating Axis Theorem
overlap_range2 :: #force_inline proc "contextless" ( a, b: Range2 ) -> bool
{
// Check if there's no overlap on the x-axis
if a.max.x <= b.min.x || b.max.x <= a.min.x {
return false; // No overlap on x-axis means no intersection
}
// Check if there's no overlap on the y-axis
if a.max.y <= b.min.y || b.max.y <= a.min.y {
return false; // No overlap on y-axis means no intersection
}
// If neither of the above conditions are true, there's at least a partial overlap
return true;
}
// TODO(Ed): Do we need this? Also does it even work (looks unfinished)?
is_within_screenspace :: #force_inline proc "contextless" ( pos : Vec2 ) -> b32 {
state := get_state(); using state

View File

@ -276,13 +276,15 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
path_arial_unicode_ms := strings.concatenate( { Path_Assets, "Arial Unicode MS.ttf" } )
font_arial_unicode_ms = font_load( path_arial_unicode_ms, 24.0, "Arial_Unicode_MS" )
default_font = font_firacode
default_font = font_rec_mono_semicasual_reg
log( "Default font loaded" )
}
// Setup the screen ui state
if true
{
profile("screen ui")
ui_startup( & screen_ui.base, cache_allocator = persistent_slab_allocator() )
ui_floating_startup( & screen_ui.floating, 1 * Kilobyte, 1 * Kilobyte, persistent_slab_allocator(), "screen ui floating manager" )
@ -298,6 +300,7 @@ startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem
// TODO(Ed): This will eventually have to occur when the user either creates or loads a workspace. I don't know
if true
{
profile("project setup")
using project
path = str_intern("./")
name = str_intern( "First Project" )
@ -437,7 +440,7 @@ hot_reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_
slab_reload( frame_slab, frame_allocator())
slab_reload( transient_slab, transient_allocator())
// ui_reload( & get_state().project.workspace.ui, cache_allocator = persistent_slab_allocator() )
ui_reload( & get_state().project.workspace.ui, cache_allocator = persistent_slab_allocator() )
log("Module reloaded")
}

View File

@ -1,6 +1,7 @@
package sectr
import "core:math"
import lalg "core:math/linalg"
import "core:time"
import ve "codebase:font/VEFontCache"
@ -18,6 +19,8 @@ RenderState :: struct {
pass_actions : PassActions,
}
#region("Draw Helpers")
gp_set_color :: #force_inline proc( color : RGBA8 ) {
color := normalize_rgba8(color);
gp.set_color( color.r, color.g, color.b, color.a )
@ -26,14 +29,14 @@ gp_set_color :: #force_inline proc( color : RGBA8 ) {
draw_filled_circle :: proc(x, y, radius: f32, edges: int) {
if edges < 3 do return // Need at least 3 edges to form a shape
triangles := make([]gp.Triangle, edges)
// defer delete(triangles)
center := gp.Point{x, y}
for i in 0..<edges {
angle1 := 2 * math.PI * f32(i) / f32(edges)
angle2 := 2 * math.PI * f32(i+1) / f32(edges)
triangles := make([]gp.Triangle, edges)
center := gp.Point{x, y}
edge_quotient := 1 / f32(edges)
angle_factor := 2 * math.PI * edge_quotient
for edge_id in 0..< edges
{
angle1 := f32(edge_id ) * angle_factor
angle2 := f32(edge_id +1) * angle_factor
p1 := gp.Point{
x + radius * math.cos(angle1),
@ -43,21 +46,34 @@ draw_filled_circle :: proc(x, y, radius: f32, edges: int) {
x + radius * math.cos(angle2),
y + radius * math.sin(angle2),
}
triangles[i] = gp.Triangle{center, p1, p2}
triangles[edge_id] = gp.Triangle{center, p1, p2}
}
gp.draw_filled_triangles(raw_data(triangles), u32(len(triangles)))
}
// Draw text using a string and normalized screen coordinates
draw_rect_border :: proc( rect : Range2, border_width: f32)
{
rect_size := rect.max - rect.min
border_width := lalg.min(border_width, min(rect_size.x, rect_size.y) * 0.5)
top := gp.Rect{ rect.min.x, rect.min.y, rect_size.x, border_width }
bottom := gp.Rect{ rect.min.x, rect.max.y - border_width, rect_size.x, border_width }
left := gp.Rect{ rect.min.x, rect.min.y + border_width, border_width, rect_size.y - 2 * border_width }
right := gp.Rect{ rect.max.x - border_width, rect.min.y + border_width, border_width, rect_size.y - 2 * border_width }
borders := []gp.Rect{ top, bottom, left, right }
gp.draw_filled_rects( raw_data(borders), u32(len(borders)) )
}
// Draw text using a string and normalized render coordinates
draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White )
{
state := get_state(); using state
width := app_window.extent.x * 2
height := app_window.extent.y * 2
ve_id := font_provider_resolve_draw_id( id )
ve_id := font_provider_resolve_draw_id( id, size )
color_norm := normalize_rgba8(color)
ve.set_colour( & font_provider_data.ve_font_cache, color_norm )
@ -68,18 +84,26 @@ draw_text_string_pos_norm :: proc( content : string, id : FontID, size : f32, po
// Draw text using a string and extent-based screen coordinates
draw_text_string_pos_extent :: proc( content : string, id : FontID, size : f32, pos : Vec2, color := Color_White )
{
state := get_state(); using state
extent := app_window.extent
normalized_pos := pos / extent
profile(#procedure)
state := get_state(); using state
screen_size := app_window.extent * 2
render_pos := screen_to_render_pos(pos)
normalized_pos := render_pos * (1.0 / screen_size)
draw_text_string_pos_norm( content, id, size, normalized_pos, color )
}
#endregion("Draw Helpers")
render :: proc()
{
profile(#procedure)
state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack
ve.flush_draw_list( & font_provider_data.ve_font_cache )
font_provider_data.vbuf_layer_offset = 0
font_provider_data.ibuf_layer_offset = 0
font_provider_data.calls_layer_offset = 0
clear_pass := gfx.Pass { action = render_data.pass_actions.bg_clear_black, swapchain = sokol_glue.swapchain() }
clear_pass.action.colors[0].clear_value = transmute(gfx.Color) normalize_rgba8( config.color_theme.bg )
@ -91,7 +115,6 @@ render :: proc()
render_mode_screenspace()
gfx.commit()
ve.flush_draw_list( & font_provider_data.ve_font_cache )
}
// TODO(Ed): Eventually this needs to become a 'viewport within a UI'
@ -108,21 +131,25 @@ render_mode_2d_workspace :: proc()
cam_zoom_ratio := 1.0 / cam.zoom
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
Render_Reference_Dots:
{
profile("render_reference_dots (workspace)")
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom )
gp.scale( cam.zoom, cam.zoom )
gp.translate( cam.position.x * cam.zoom, cam.position.y * cam.zoom )
gp.scale( cam.zoom, cam.zoom )
gp_set_color(Color_White)
draw_filled_circle(0, 0, 4, 24)
gp_set_color(Color_White)
draw_filled_circle(0, 0, 4, 24)
// Enqueue gp pass to sokol gfx
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
// Enqueue gp pass to sokol gfx
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
}
}
render_mode_screenspace :: proc()
@ -143,6 +170,7 @@ render_mode_screenspace :: proc()
Render_Reference_Dots:
{
profile("render_reference_dots (screen)")
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
@ -163,7 +191,6 @@ render_mode_screenspace :: proc()
gfx.end_pass()
}
debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color := Color_White, font : FontID = Font_Default )
{
state := get_state(); using state
@ -194,27 +221,30 @@ render_mode_screenspace :: proc()
screen_corners := screen_get_corners()
position := screen_corners.top_left
position.x = 0
position.y -= debug.draw_debug_text_y
content := str_fmt_buffer( draw_text_scratch[:], format, ..args )
debug_draw_text( content, position, 12.0 )
debug.draw_debug_text_y += 12
text_size := measure_text_size( content, default_font, 14.0, 0.0 )
debug_draw_text( content, position, 14.0 )
debug.draw_debug_text_y += text_size.y + 4
}
debug.debug_text_vis = true
if debug.debug_text_vis
{
profile("debug_text_vis")
fps_size : f32 = 14.0
fps_msg := str_fmt( "FPS: %0.2f", fps_avg)
fps_msg_width := measure_text_size( fps_msg, default_font, 12.0, 0.0 ).x
fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 } - { 5, 5 }
debug_draw_text( fps_msg, fps_msg_pos, 38.0, color = Color_Red )
fps_msg_size := measure_text_size( fps_msg, default_font, fps_size, 0.0 )
fps_msg_pos := screen_get_corners().top_right - { fps_msg_size.x * 2, fps_msg_size.y }
debug_draw_text( fps_msg, fps_msg_pos, fps_size, color = Color_Red )
debug_text( "Screen Width : %v", screen_size.x )
debug_text( "Screen Height: %v", screen_size.y )
debug_text( "frametime_target_ms : %f ms", frametime_target_ms )
debug_text( "frametime : %0.3f ms", frametime_delta32() )
debug_text( "frametime (work) : %0.3f ms", frametime_delta_ms )
debug_text( "frametime_last_elapsed_ms : %f ms", frametime_elapsed_ms )
if replay.mode == ReplayMode.Record {
debug_text( "Recording Input")
@ -224,7 +254,7 @@ render_mode_screenspace :: proc()
}
debug_text("Zoom Target: %v", project.workspace.zoom_target)
if true
if false
{
using input_events
@ -294,12 +324,99 @@ render_screen_ui :: proc()
profile(#procedure)
state := get_state(); using state // TODO(Ed): Prefer passing static context to through the callstack
screen_extent := app_window.extent
screen_size := app_window.extent * 2
screen_ratio := screen_size.x * ( 1.0 / screen_size.y )
ui := & screen_ui
render_list := array_to_slice( ui.render_list )
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
text_enqueued : b32 = false
shape_enqueued : b32 = false
for entry, id in render_list
{
if entry.layer_signal
{
profile("render ui layer")
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
if text_enqueued {
render_text_layer()
}
gp.begin( i32(screen_size.x), i32(screen_size.y) )
gp.viewport(0, 0, i32(screen_size.x), i32(screen_size.y))
gp.project( -screen_extent.x, screen_extent.x, screen_extent.y, -screen_extent.y )
continue
}
using entry
profile("enqueue box")
GP_Render:
{
profile("draw_shapes")
draw_rect :: proc( rect : Range2, color : RGBA8 ) {
using rect
gp_set_color( color )
size := max - min
position := min
gp.draw_filled_rect( position.x, position.y, size.x, size.y )
}
if style.bg_color.a != 0
{
draw_rect( bounds, style.bg_color )
shape_enqueued = true
}
if style.border_color.a != 0 && border_width > 0 {
gp_set_color( style.border_color )
draw_rect_border( bounds, border_width )
shape_enqueued = true
}
gp_set_color(Color_Red)
draw_filled_circle(bounds.min.x, bounds.min.y, 3, 24)
shape_enqueued = true
gp_set_color(Color_Blue)
draw_filled_circle(bounds.max.x, bounds.max.y, 3, 24)
shape_enqueued = true
}
if len(text.str) > 0 && style.font.key != 0 {
draw_text_string_pos_extent( text.str, default_font, font_size, computed.text_pos, style.text_color )
text_enqueued = true
}
}
if shape_enqueued {
gfx.begin_pass( gfx.Pass { action = render_data.pass_actions.empty_action, swapchain = sokol_glue.swapchain() })
gp.flush()
gp.end()
gfx.end_pass()
}
if text_enqueued {
render_text_layer()
}
}
render_text_layer :: proc()
{
profile("VEFontCache: render frame")
profile("VEFontCache: render text layer")
Bindings :: gfx.Bindings
Range :: gfx.Range
@ -309,28 +426,38 @@ render_text_layer :: proc()
font_provider := state.font_provider_data
using font_provider
ve.optimize_draw_list( & ve_font_cache )
// ve.optimize_draw_list( & ve_font_cache )
draw_list := ve.get_draw_list( & ve_font_cache )
draw_list_vert_slice := array_to_slice(draw_list.vertices)
draw_list_index_slice := array_to_slice(draw_list.indices)
draw_list_calls_slice := array_to_slice(draw_list.calls)
gfx.update_buffer( draw_list_vbuf, Range{ draw_list.vertices.data, draw_list.vertices.num * size_of(ve.Vertex) })
gfx.update_buffer( draw_list_ibuf, Range{ draw_list.indices.data, draw_list.indices.num * size_of(u32) })
vbuf_layer_slice := draw_list_vert_slice [ vbuf_layer_offset : ]
ibuf_layer_slice := draw_list_index_slice[ ibuf_layer_offset : ]
calls_layer_slice := draw_list_calls_slice[ calls_layer_offset : ]
draw_list_call_slice := array_to_slice(draw_list.calls)
for & draw_call in array_to_slice(draw_list.calls)
gfx.append_buffer( draw_list_vbuf, Range{ raw_data(vbuf_layer_slice), cast(u64) len(vbuf_layer_slice) * size_of(ve.Vertex) })
gfx.append_buffer( draw_list_ibuf, Range{ raw_data(ibuf_layer_slice), cast(u64) len(ibuf_layer_slice) * size_of(u32) })
vbuf_layer_offset = cast(u64) len(draw_list_vert_slice)
ibuf_layer_offset = cast(u64) len(draw_list_index_slice)
calls_layer_offset = cast(u64) len(draw_list_calls_slice)
for & draw_call in calls_layer_slice
{
watch := draw_call
// profile("VEFontCache: draw call")
num_indices := draw_call.end_index - draw_call.start_index
switch draw_call.pass
{
// 1. Do the glyph rendering pass
// Glyphs are first rendered to an intermediate 2k x 512px R8 texture
case .Glyph:
// profile("VEFontCache: draw call: glyph")
if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw {
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
@ -366,7 +493,7 @@ render_text_layer :: proc()
// A simple 16-tap box downsample shader is then used to blit from this intermediate texture to the final atlas location
case .Atlas:
// profile("VEFontCache: draw call: atlas")
if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw {
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
@ -407,7 +534,7 @@ render_text_layer :: proc()
case .None: fallthrough
case .Target: fallthrough
case .Target_Uncached:
if (draw_call.end_index - draw_call.start_index) == 0 && ! draw_call.clear_before_draw {
if num_indices == 0 && ! draw_call.clear_before_draw {
continue
}
@ -455,8 +582,7 @@ render_text_layer :: proc()
})
}
if (draw_call.end_index - draw_call.start_index) != 0 {
num_indices := draw_call.end_index - draw_call.start_index
if num_indices != 0 {
gfx.draw( draw_call.start_index, num_indices, 1 )
}

View File

@ -195,7 +195,7 @@ update :: proc( delta_time : f64 ) -> b32
if debug_actions.cam_mouse_pan
{
if is_within_screenspace(input.mouse.pos) {
pan_velocity := input.mouse.delta * vec2(1, -1) * ( 1 / cam.zoom )
pan_velocity := input.mouse.delta * ( 1 / cam.zoom )
cam.position += pan_velocity
}
}
@ -204,7 +204,7 @@ update :: proc( delta_time : f64 ) -> b32
// TODO(Ed): We need input buffer so that we can consume input actions based on the UI with priority
// ui_screen_tick()
ui_screen_tick()
//region WorkspaceImgui Tick
if false

View File

@ -37,6 +37,10 @@ FontProviderData :: struct
draw_list_vbuf : sokol_gfx.Buffer,
draw_list_ibuf : sokol_gfx.Buffer,
vbuf_layer_offset : u64,
ibuf_layer_offset : u64,
calls_layer_offset : u64,
glyph_shader : sokol_gfx.Shader,
atlas_shader : sokol_gfx.Shader,
screen_shader : sokol_gfx.Shader,
@ -547,6 +551,11 @@ font_load :: proc(path_file : string,
def, set_error := hmap_chained_set(font_cache, key, FontDef{})
verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" )
default_size := default_size
if default_size < 0 {
default_size = Font_Default_Point_Size
}
def.path_file = path_file
def.default_size = default_size
@ -555,7 +564,8 @@ font_load :: proc(path_file : string,
// logf("Loading at size %v", font_size)
id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval)
ve_id := & def.size_table[id - 1]
ve_id^ = ve.load_font( & provider_data.ve_font_cache, desired_id, font_data, 14.0 )
ve_ret_id := ve.load_font( & provider_data.ve_font_cache, desired_id, font_data, f32(font_size) )
(ve_id^) = ve_ret_id
}
fid := FontID { key, desired_id }
@ -570,8 +580,9 @@ font_provider_resolve_draw_id :: proc( id : FontID, size := Font_Use_Default_Siz
def := hmap_chained_get( font_provider_data.font_cache, id.key )
size := size == 0.0 ? f32(def.default_size) : size
// size :f32 = 14.0
even_size := math.round(size * (1.0 / f32(Font_Size_Interval))) * f32(Font_Size_Interval)
resolved_size := clamp( i32( even_size), 2, Font_Largest_Px_Size )
resolved_size := clamp( i32( even_size), 14, Font_Largest_Px_Size )
id := (resolved_size / Font_Size_Interval) + (resolved_size % Font_Size_Interval)
ve_id := def.size_table[ id - 1 ]

View File

@ -164,6 +164,7 @@ import "codebase:grime"
array_to_slice :: grime.array_to_slice
array_init :: grime.array_init
array_append :: grime.array_append
array_append_value :: grime.array_append_value
array_append_array :: grime.array_append_array
array_append_at :: grime.array_append_at
array_clear :: grime.array_clear

View File

@ -279,7 +279,7 @@ poll_input_events :: proc( input, prev_input : ^InputState, input_events : Input
input.mouse.raw_pos = event.pos
input.mouse.pos = render_to_screen_pos( event.pos )
input.mouse.delta = event.delta
input.mouse.delta = event.delta * { 1, -1 }
}
}

View File

@ -189,7 +189,7 @@ view_get_corners :: #force_inline proc "contextless"() -> BoundsCorners2 {
render_to_screen_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 {
extent := & get_state().app_window.extent
result := Vec2 {
pos.x - extent.x,
pos.x - extent.x,
pos.y * -1 + extent.y
}
return result
@ -209,7 +209,7 @@ screen_to_ws_view_pos :: #force_inline proc "contextless" (pos: Vec2) -> Vec2 {
// Centered screen space to conventional screen space used for rendering
screen_to_render_pos :: #force_inline proc "contextless" (pos : Vec2) -> Vec2 {
screen_extent := transmute(Vec2) get_state().app_window.extent
return pos * {1, -1} + { screen_extent.x, screen_extent.y }
return pos * {1, 1} + { screen_extent.x, screen_extent.y }
}
// TODO(Ed): These should assume a cam_context or have the ability to provide it in params

View File

@ -72,7 +72,17 @@ UI_RenderEntry :: struct {
layer_id : i32,
}
UI_RenderLayer :: DLL_NodeFL(UI_RenderEntry)
UI_RenderLayer :: DLL_NodeFL(UI_RenderEntry)\
UI_RenderBoxInfo :: struct {
using computed : UI_Computed,
using style : UI_Style,
text : StrRunesPair,
font_size : UI_Scalar,
border_width : UI_Scalar,
label : StrRunesPair,
layer_signal : b32,
}
// TODO(Ed): Rename to UI_Context
UI_State :: struct {
@ -131,8 +141,12 @@ ui_startup :: proc( ui : ^ UI_State, cache_allocator : Allocator /* , cache_rese
ui.prev_cache = (& ui.caches[0])
allocation_error : AllocatorError
ui.render_queue, allocation_error = make( Array(UI_RenderLayer), 32, cache_allocator )
verify( allocation_error == AllocatorError.None, "Failed to allcate render_queue")
ui.render_list, allocation_error = make( Array(UI_RenderBoxInfo), UI_Built_Boxes_Array_Size, cache_allocator, fixed_cap = true )
verify( allocation_error == AllocatorError.None, "Failed to allocate render queue" )
verify( allocation_error == AllocatorError.None, "Failed to allocate rener_list" )
log("ui_startup completed")
}
@ -143,7 +157,8 @@ ui_reload :: proc( ui : ^ UI_State, cache_allocator : Allocator )
for & cache in ui.caches {
hmap_zpl_reload( & cache, cache_allocator)
}
ui.render_list.backing = cache_allocator
ui.render_queue.backing = cache_allocator
ui.render_list.backing = cache_allocator
}
// TODO(Ed) : Is this even needed?
@ -160,6 +175,7 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
stack_clear( & layout_combo_stack )
stack_clear( & style_combo_stack )
array_clear( render_queue )
array_clear( render_list )
curr_cache, prev_cache = swap( curr_cache, prev_cache )
@ -215,6 +231,8 @@ ui_graph_build_end :: proc( ui : ^UI_State )
current.text,
current.layout.font_size,
current.layout.border_width,
current.label,
false,
},
layer_id = current.ancestors -1,
}
@ -224,15 +242,17 @@ ui_graph_build_end :: proc( ui : ^UI_State )
render_queue = array_to_slice(ui.render_queue)
}
// else if layer.last == nil {
// layer.first.next = entry
// entry.prev = layer.first
// layer.last = entry
// }
// push_back to next layer
layer := & render_queue[entry.layer_id]
if layer.first == nil {
layer.first = entry
}
else if layer.last == nil {
layer.first.next = entry
entry.prev = layer.first
layer.last = entry
layer.last = entry
}
else {
layer.last.next = entry
@ -248,34 +268,48 @@ ui_graph_build_end :: proc( ui : ^UI_State )
parent_entry = parent_layer.last
entry.parent = parent_entry
// dll_fl_append( parent_entry, entry )
if parent_entry.first == nil {
parent_entry.first = entry
parent_entry.last = entry
}
else {
parent_entry.last = entry
}
// dll_fl_append( parent_entry, entry )
}
}
// render_queue overlap corrections & render_list generation
render_queue = array_to_slice(ui.render_queue)
for & layer in render_queue
for layer_id : i32 = 0; layer_id < i32(ui.render_queue.num); layer_id += 1
{
layer := & ui.render_queue.data[ layer_id ]
append( & ui.render_list, UI_RenderBoxInfo { layer_signal = true })
to_increment, error := make( Array(^UI_RenderEntry), 4 * Kilo )
verify( error == .None, "Faied to make to_increment array.")
to_inc_last_iterated : i32 = 0
for entry := layer.first; entry != nil; entry = entry.next
{
for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next
{
if ! intersects_range2( entry.info.computed.bounds, neighbor.info.computed.bounds) do continue
if ! overlap_range2( entry.info.computed.bounds, neighbor.info.computed.bounds) do continue
append( & to_increment, neighbor )
} // for neighbor := entry.next; neighbor != nil; neighbor = neighbor.next
if entry == to_increment.data[ to_inc_last_iterated ] {
to_inc_last_iterated += 1
}
else {
// This entry stayed in this layer, we can append the value
array_append_value( & ui.render_list, entry.info )
}
} // for entry := layer.first; entry != nil; entry = entry.next
// Move overlaping entries & their children's by 1 layer
to_inc_slice := array_to_slice(to_increment)
for entry in to_inc_slice
{
@ -287,7 +321,6 @@ ui_graph_build_end :: proc( ui : ^UI_State )
}
push_layer := render_queue[entry.layer_id]
// pop entry from layer
prev := entry.prev
prev.next = entry.next
@ -298,12 +331,7 @@ ui_graph_build_end :: proc( ui : ^UI_State )
// push entry to next layer
if push_layer.first == nil {
push_layer.first = entry
}
else if push_layer.last == nil {
push_layer.last = entry
entry.prev = push_layer.first
push_layer.first.next = entry
entry.next = nil
push_layer.last = entry
}
else {
push_layer.last.next = entry
@ -311,6 +339,12 @@ ui_graph_build_end :: proc( ui : ^UI_State )
push_layer.last = entry
entry.next = nil
}
// else if push_layer.last == nil {
// push_layer.last = entry
// entry.prev = push_layer.first
// push_layer.first.next = entry
// entry.next = nil
// }
// increment children's layers
if entry.first != nil
@ -337,23 +371,29 @@ ui_graph_build_end :: proc( ui : ^UI_State )
// push_back to next layer
if push_layer.first == nil {
push_layer.first = child
}
else if push_layer.last == nil {
push_layer.first.next = child
child.prev = push_layer.first
push_layer.last = child
push_layer.last = child
}
else {
push_layer.last.next = child
child.prev = push_layer.last
push_layer.last = child
}
// else if push_layer.last == nil {
// push_layer.first.next = child
// child.prev = push_layer.first
// push_layer.last = child
// }
} // for child := neighbor.first; child != nil; child = ui_render_entry_traverse_depth( child )
} // if entry.first != nil
} // for entry in to_inc_slice
} // for & layer in render_queue
}
render_queue := array_to_slice(ui.render_queue)
render_list := array_to_slice(ui.render_list)
get_state().ui_context = nil
}

View File

@ -19,14 +19,6 @@ UI_NavLinks :: struct {
left, right, up, down : ^UI_Box,
}
UI_RenderBoxInfo :: struct {
using computed : UI_Computed,
using style : UI_Style,
text : StrRunesPair,
font_size : UI_Scalar,
border_width : UI_Scalar,
}
UI_Box :: struct {
// Cache ID
key : UI_Key,

View File

@ -70,17 +70,17 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
adjusted_size.x = max( adjusted_max_size_x, layout.size.min.x)
adjusted_size.y = max( adjusted_max_size_y, layout.size.min.y)
// text_size : Vec2
// if layout.font_size == computed.text_size.y {
// text_size = computed.text_size
// }
// else {
// text_size = cast(Vec2) measure_text_size( box.text.str, style.font, layout.font_size, 0 )
// }
text_size : Vec2
if layout.font_size == computed.text_size.y {
text_size = computed.text_size
}
else {
text_size = cast(Vec2) measure_text_size( box.text.str, style.font, layout.font_size, 0 )
}
// if size_to_text {
// adjusted_size = text_size
// }
if size_to_text {
adjusted_size = text_size
}
if .Scale_Width_By_Height_Ratio in layout.flags {
adjusted_size.x = adjusted_size.y * layout.size.min.x
@ -161,16 +161,16 @@ ui_box_compute_layout :: proc( box : ^UI_Box,
computed.content = content_bounds
// 8. Text position & size
// if len(box.text.str) > 0
// {
// content_size := content_bounds.max - content_bounds.min
// text_pos : Vec2
// text_pos = content_bounds.min + { 0, text_size.y }
// text_pos += (content_size - text_size) * layout.text_alignment
if len(box.text.str) > 0
{
content_size := content_bounds.max - content_bounds.min
text_pos : Vec2
text_pos = content_bounds.min + { 0, text_size.y }
text_pos += (content_size - text_size) * layout.text_alignment
// computed.text_size = text_size
// computed.text_pos = text_pos
// }
computed.text_size = text_size
computed.text_pos = text_pos
}
computed.fresh = true && !dont_mark_fresh
}