diff --git a/code/font/VEFontCache/VEFontCache.odin b/code/font/VEFontCache/VEFontCache.odin index 27e18f4..5a3d79a 100644 --- a/code/font/VEFontCache/VEFontCache.odin +++ b/code/font/VEFontCache/VEFontCache.odin @@ -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 diff --git a/code/font/VEFontCache/parser.odin b/code/font/VEFontCache/parser.odin index 7cc670f..1eaf99b 100644 --- a/code/font/VEFontCache/parser.odin +++ b/code/font/VEFontCache/parser.odin @@ -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 ) diff --git a/code/sectr/collision.odin b/code/sectr/collision.odin index c0ef4ea..50b85c9 100644 --- a/code/sectr/collision.odin +++ b/code/sectr/collision.odin @@ -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 diff --git a/code/sectr/engine/client_api.odin b/code/sectr/engine/client_api.odin index 3560277..8906acb 100644 --- a/code/sectr/engine/client_api.odin +++ b/code/sectr/engine/client_api.odin @@ -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") } diff --git a/code/sectr/engine/render.odin b/code/sectr/engine/render.odin index b934a06..225de87 100644 --- a/code/sectr/engine/render.odin +++ b/code/sectr/engine/render.odin @@ -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.. 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 ) } diff --git a/code/sectr/engine/update.odin b/code/sectr/engine/update.odin index 00ef8f7..317eb96 100644 --- a/code/sectr/engine/update.odin +++ b/code/sectr/engine/update.odin @@ -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 diff --git a/code/sectr/font/provider.odin b/code/sectr/font/provider.odin index c022767..c699b05 100644 --- a/code/sectr/font/provider.odin +++ b/code/sectr/font/provider.odin @@ -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 ] diff --git a/code/sectr/grime/mappings.odin b/code/sectr/grime/mappings.odin index d7a4a4c..c5c478d 100644 --- a/code/sectr/grime/mappings.odin +++ b/code/sectr/grime/mappings.odin @@ -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 diff --git a/code/sectr/input/events.odin b/code/sectr/input/events.odin index 2d726ab..3ba2a44 100644 --- a/code/sectr/input/events.odin +++ b/code/sectr/input/events.odin @@ -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 } } } diff --git a/code/sectr/math/space.odin b/code/sectr/math/space.odin index 133eb68..e972701 100644 --- a/code/sectr/math/space.odin +++ b/code/sectr/math/space.odin @@ -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 diff --git a/code/sectr/ui/core/base.odin b/code/sectr/ui/core/base.odin index d587cb0..f329e09 100644 --- a/code/sectr/ui/core/base.odin +++ b/code/sectr/ui/core/base.odin @@ -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 } diff --git a/code/sectr/ui/core/box.odin b/code/sectr/ui/core/box.odin index f7e08c4..9fc935b 100644 --- a/code/sectr/ui/core/box.odin +++ b/code/sectr/ui/core/box.odin @@ -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, diff --git a/code/sectr/ui/core/layout_compute.odin b/code/sectr/ui/core/layout_compute.odin index 9d87b5a..273d8bd 100644 --- a/code/sectr/ui/core/layout_compute.odin +++ b/code/sectr/ui/core/layout_compute.odin @@ -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 }