// Copyright 2026 Ling71671
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Subsystem managing cursor positioning, layout scopes (TopDown, LeftToRight, Wrapped),
/// available width, and automatic ID sequencing.
pub struct LayoutEngine {
layout_stack : Array[LayoutScope]
mut cursor : @math.Vec2
mut available_width : Double
mut item_spacing : @math.Vec2
mut auto_id_counter : Int
} derive(Eq, Debug)
///|
pub fn LayoutEngine::new() -> LayoutEngine {
{
layout_stack: [
{
dir: TopDown,
start_x: 0.0,
start_y: 0.0,
max_cross_size: 0.0,
current_row_h: 0.0,
wrap_width: 0.0,
},
],
cursor: @math.Vec2::zero(),
available_width: 800.0,
item_spacing: @math.Vec2::new(8.0, 8.0),
auto_id_counter: 0,
}
}
///|
pub fn LayoutEngine::begin_frame(
self : LayoutEngine,
screen_w : Double,
) -> Unit {
self.cursor = @math.Vec2::zero()
self.available_width = if screen_w > 0.0 { screen_w } else { 800.0 }
self.auto_id_counter = 0
self.layout_stack.clear()
self.layout_stack.push({
dir: TopDown,
start_x: 0.0,
start_y: 0.0,
max_cross_size: 0.0,
current_row_h: 0.0,
wrap_width: 0.0,
})
}
///|
pub fn LayoutEngine::current_scope(self : LayoutEngine) -> LayoutScope? {
if self.layout_stack.is_empty() {
None
} else {
Some(self.layout_stack[self.layout_stack.length() - 1])
}
}
///|
pub fn LayoutEngine::push_scope(
self : LayoutEngine,
scope : LayoutScope,
) -> Unit {
self.layout_stack.push(scope)
}
///|
pub fn LayoutEngine::pop_scope(self : LayoutEngine) -> LayoutScope? {
if self.layout_stack.is_empty() {
None
} else {
self.layout_stack.pop()
}
}
///|
pub fn LayoutEngine::allocate_rect(
self : LayoutEngine,
size : @math.Vec2,
) -> @math.Rect {
let current_scope = self.current_scope()
// Pre-allocation line wrapping for LeftToRightWrapped
match current_scope {
Some(scope) =>
if scope.dir == LeftToRightWrapped {
let wrap_w = if scope.wrap_width > 0.0 {
scope.wrap_width
} else {
self.available_width
}
if self.cursor.x > scope.start_x &&
self.cursor.x + size.x > scope.start_x + wrap_w {
let next_y = self.cursor.y + scope.current_row_h + self.item_spacing.y
scope.max_cross_size = scope.max_cross_size +
scope.current_row_h +
self.item_spacing.y
scope.current_row_h = 0.0
self.cursor = @math.Vec2::new(scope.start_x, next_y)
}
}
None => ()
}
let item_y = match current_scope {
Some(scope) =>
if scope.dir == LeftToRight {
scope.start_y
} else {
self.cursor.y
}
None => self.cursor.y
}
let rect = @math.Rect::new(self.cursor.x, item_y, size.x, size.y)
match current_scope {
Some(scope) =>
match scope.dir {
TopDown => {
if size.x > scope.max_cross_size {
scope.max_cross_size = size.x
}
self.cursor = @math.Vec2::new(
self.cursor.x,
self.cursor.y + size.y + self.item_spacing.y,
)
}
LeftToRight => {
if size.y > scope.max_cross_size {
scope.max_cross_size = size.y
}
self.cursor = @math.Vec2::new(
self.cursor.x + size.x + self.item_spacing.x,
scope.start_y,
)
}
LeftToRightWrapped => {
if size.y > scope.current_row_h {
scope.current_row_h = size.y
}
self.cursor = @math.Vec2::new(
self.cursor.x + size.x + self.item_spacing.x,
self.cursor.y,
)
}
}
None =>
self.cursor = @math.Vec2::new(
self.cursor.x,
self.cursor.y + size.y + self.item_spacing.y,
)
}
rect
}
///|
pub fn LayoutEngine::cursor(self : LayoutEngine) -> @math.Vec2 {
self.cursor
}
///|
pub fn LayoutEngine::set_cursor(self : LayoutEngine, pos : @math.Vec2) -> Unit {
self.cursor = pos
}
///|
pub fn LayoutEngine::available_width(self : LayoutEngine) -> Double {
self.available_width
}
///|
pub fn LayoutEngine::set_available_width(
self : LayoutEngine,
w : Double,
) -> Unit {
self.available_width = w
}
///|
pub fn LayoutEngine::item_spacing(self : LayoutEngine) -> @math.Vec2 {
self.item_spacing
}
///|
pub fn LayoutEngine::set_item_spacing(
self : LayoutEngine,
spacing : @math.Vec2,
) -> Unit {
self.item_spacing = spacing
}
///|
pub fn LayoutEngine::next_auto_id_counter(self : LayoutEngine) -> Int {
self.auto_id_counter = self.auto_id_counter + 1
self.auto_id_counter
}
///|
pub fn LayoutEngine::layout_stack(self : LayoutEngine) -> Array[LayoutScope] {
self.layout_stack
}