SectrPrototype/code/ui_layout.odin

181 lines
5.3 KiB
Odin

package sectr
import "core:math"
import "core:math/linalg"
// Note(Ed): This is naturally pretty expensive
ui_compute_layout :: proc()
{
profile(#procedure)
state := get_state()
root := state.project.workspace.ui.root
{
computed := & root.computed
style := root.style
layout := & style.layout
computed.bounds.min = layout.pos
computed.bounds.max = layout.size.min
computed.content = computed.bounds
}
current := root.first
for ; current != nil;
{
// profile("Layout Box")
style := current.style
// These are used to choose via multiplication weather to apply
// position & size constraints of the parent.
// The parent's unadjusted content bounds however are enforced for position,
// they cannot be ignored. The user may bypass them by doing the
// relative offset math vs world/screen space if they desire.
fixed_pos_x : f32 = cast(f32) int(.Fixed_Position_X in style.flags)
fixed_pos_y : f32 = cast(f32) int(.Fixed_Position_Y in style.flags)
fixed_width : f32 = cast(f32) int(.Fixed_Width in style.flags)
fixed_height : f32 = cast(f32) int(.Fixed_Height in style.flags)
size_to_text : bool = .Size_To_Text in style.flags
parent := current.parent
computed := & current.computed
parent_content := parent.computed.content
parent_content_size := parent_content.max - parent_content.min
parent_center := parent_content.min + parent_content_size * 0.5
layout := & style.layout
/*
If fixed position (X or Y):
* Ignore Margins
* Ignore Anchors
If clampped position (X or Y):
* Positon cannot exceed the anchors/margins bounds.
If fixed size (X or Y):
* Ignore Parent constraints (can only be clipped)
If auto-sized:
* Enforce parent size constraint of bounds relative to
where the adjusted content bounds are after applying margins & anchors.
The 'side' conflicting with the bounds will end at that bound side instead of clipping.
If size.min is not 0:
* Ignore parent constraints if the bounds go below that value.
If size.max is 0:
* Allow the child box to spread to entire adjusted content bounds, otherwise clampped to max size.
*/
// 1. Anchors
anchor := & layout.anchor
anchored_bounds := range2(
parent_content.min + parent_content_size * anchor.min,
parent_content.max - parent_content_size * anchor.max,
)
// anchored_bounds_origin := (anchored_bounds.min + anchored_bounds.max) * 0.5
// 2. Apply Margins
margins := range2(
{ layout.margins.left, layout.margins.bottom },
{ layout.margins.right, layout.margins.top },
)
margined_bounds := range2(
anchored_bounds.min + margins.min,
anchored_bounds.max - margins.max,
)
margined_bounds_origin := (margined_bounds.min + margined_bounds.max) * 0.5
margined_size := margined_bounds.max - margined_bounds.min
// 3. Enforce Min/Max Size Constraints
adjusted_max_size_x := layout.size.max.x > 0 ? min( margined_size.x, layout.size.max.x ) : margined_size.x
adjusted_max_size_y := layout.size.max.y > 0 ? min( margined_size.y, layout.size.max.y ) : margined_size.y
adjusted_size : Vec2
adjusted_size.x = max( adjusted_max_size_x, layout.size.min.x)
adjusted_size.y = max( adjusted_max_size_y, layout.size.min.y)
if .Fixed_Width in style.flags {
adjusted_size.x = layout.size.min.x
}
if .Fixed_Height in style.flags {
adjusted_size.y = layout.size.min.y
}
text_size : Vec2
if style.font_size == computed.text_size.y {
text_size = computed.text_size
}
else {
text_size = cast(Vec2) measure_text_size( current.text.str, style.font, style.font_size, 0 )
}
if size_to_text {
adjusted_size = text_size
}
// 5. Determine relative position
origin_center := margined_bounds_origin
origin_top_left := Vec2 { margined_bounds.min.x, margined_bounds.max.y }
origin := .Origin_At_Anchor_Center in style.flags ? origin_center : origin_top_left
rel_pos := origin + layout.pos
if .Fixed_Position_X in style.flags {
rel_pos.x = origin.x + layout.pos.x
}
if .Fixed_Position_Y in style.flags {
rel_pos.y = origin.y + layout.pos.y
}
vec2_one := Vec2 { 1, 1 }
// 6. Determine the box bounds
// Adjust Alignment of pivot position
alignment := layout.alignment
bounds := range2(
rel_pos - adjusted_size * alignment,
rel_pos + adjusted_size * (vec2_one - alignment),
)
// Determine Padding's outer bounds
border_offset := Vec2 { layout.border_width, layout.border_width }
padding_bounds := range2(
bounds.min + border_offset,
bounds.min - border_offset,
)
// Determine Content Bounds
content_bounds := range2(
bounds.min + { layout.padding.left, layout.padding.bottom },
bounds.max - { layout.padding.right, layout.padding.top },
)
computed.anchors = anchored_bounds
computed.margins = margined_bounds
computed.bounds = bounds
computed.padding = padding_bounds
computed.content = content_bounds
if len(current.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.x += ( content_size.x - text_size.x ) * layout.text_alignment.x
text_pos.y += ( content_size.y - text_size.y ) * layout.text_alignment.y
computed.text_size = text_size
computed.text_pos = { text_pos.x, -text_pos.y }
}
current = ui_box_tranverse_next( current )
}
}