#import "modules/std.sx"; #import "modules/allocators.sx"; #import "modules/opengl.sx"; #import "ui/types.sx"; #import "ui/render.sx"; #import "ui/events.sx"; #import "ui/view.sx"; #import "ui/renderer.sx"; UIPipeline :: struct { renderer: UIRenderer; render_tree: RenderTree; font: GlyphCache; screen_width: f32; screen_height: f32; root: ViewChild; has_root: bool; // Frame arena infrastructure arena_a: Arena; arena_b: Arena; frame_index: s64; body: Closure() -> View; has_body: bool; parent_allocator: Allocator; init :: (self: *UIPipeline, width: f32, height: f32) { self.render_tree = RenderTree.init(); self.renderer.init(); self.screen_width = width; self.screen_height = height; self.has_root = false; self.has_body = false; self.frame_index = 0; } init_font :: (self: *UIPipeline, path: [:0]u8, size: f32, dpi_scale: f32) { self.font.init(path, size); self.font.set_dpi_scale(dpi_scale); self.renderer.dpi_scale = dpi_scale; set_global_font(@self.font); } set_root :: (self: *UIPipeline, view: View) { self.root = ViewChild.{ view = view }; self.has_root = true; } set_body :: (self: *UIPipeline, body_fn: Closure() -> View) { self.body = body_fn; self.has_body = true; self.parent_allocator = context.allocator; // Initialize both arenas (256KB initial, grows automatically) self.arena_a.create(self.parent_allocator, 262144); self.arena_b.create(self.parent_allocator, 262144); self.frame_index = 0; } resize :: (self: *UIPipeline, width: f32, height: f32) { self.screen_width = width; self.screen_height = height; } // Process a single event through the view tree dispatch_event :: (self: *UIPipeline, event: *Event) -> bool { if self.has_root == false { return false; } self.root.view.handle_event(event, self.root.computed_frame); } // Run one frame: layout → render → commit tick :: (self: *UIPipeline) { if self.has_body { self.tick_with_body(); return; } if self.has_root == false { return; } proposal := ProposedSize.fixed(self.screen_width, self.screen_height); // Layout root_size := self.root.view.size_that_fits(proposal); self.root.computed_frame = Frame.{ origin = Point.zero(), size = root_size }; self.root.view.layout(self.root.computed_frame); // Render to tree self.render_tree.clear(); ctx := RenderContext.init(@self.render_tree); self.root.view.render(@ctx, self.root.computed_frame); // Commit to GPU self.commit_gpu(); } tick_with_body :: (self: *UIPipeline) { build_arena : *Arena = if self.frame_index & 1 == 0 then @self.arena_a else @self.arena_b; build_arena.reset(); // Reset render_tree nodes (backing is stale after arena reset) self.render_tree.nodes.items = xx 0; self.render_tree.nodes.len = 0; self.render_tree.nodes.cap = 0; push Context.{ allocator = xx build_arena, data = context.data } { // Workaround: self.body() crashes through struct field (issue-0010) body_fn := self.body; root_view := body_fn(); self.root = ViewChild.{ view = root_view }; self.has_root = true; proposal := ProposedSize.fixed(self.screen_width, self.screen_height); root_size := self.root.view.size_that_fits(proposal); self.root.computed_frame = Frame.{ origin = Point.zero(), size = root_size }; self.root.view.layout(self.root.computed_frame); self.render_tree.clear(); ctx := RenderContext.init(@self.render_tree); self.root.view.render(@ctx, self.root.computed_frame); self.commit_gpu(); } self.frame_index += 1; } commit_gpu :: (self: *UIPipeline) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); self.renderer.begin(self.screen_width, self.screen_height, self.font.texture_id); self.renderer.process(@self.render_tree); self.renderer.flush(); glDisable(GL_BLEND); } }