265 lines
8.0 KiB
Odin
265 lines
8.0 KiB
Odin
package sectr
|
|
|
|
import "core:math"
|
|
import "core:math/linalg"
|
|
|
|
ui_compute_layout :: proc()
|
|
{
|
|
profile(#procedure)
|
|
state := get_state()
|
|
|
|
root := state.project.workspace.ui.root
|
|
{
|
|
computed := & root.computed
|
|
bounds := & computed.bounds
|
|
style := root.style
|
|
layout := & style.layout
|
|
|
|
bounds.min = layout.pos
|
|
bounds.max = layout.size.min
|
|
|
|
computed.content = bounds^
|
|
computed.padding = {}
|
|
}
|
|
|
|
current := root.first
|
|
for ; current != nil;
|
|
{
|
|
profile("Layout Box")
|
|
parent := current.parent
|
|
parent_content := parent.computed.content
|
|
computed := & current.computed
|
|
|
|
style := current.style
|
|
layout := & style.layout
|
|
|
|
|
|
// 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
|
|
|
|
|
|
margins := range2(
|
|
{ layout.margins.left, -layout.margins.top },
|
|
{ -layout.margins.right, layout.margins.bottom },
|
|
)
|
|
margined_bounds := range2(
|
|
parent_content.p0 + margins.p0,
|
|
parent_content.p1 + margins.p1,
|
|
)
|
|
margined_size := linalg.abs(margined_bounds.p1 - margined_bounds.p0)
|
|
|
|
anchor := & layout.anchor
|
|
// Margins + Anchors Applied
|
|
adjusted_bounds := range2(
|
|
{ margined_bounds.p0.x + margined_size.x * anchor.p0.x, margined_bounds.p0.y + margined_size.y * anchor.p0.y },
|
|
{ margined_bounds.p1.x + margined_size.x * anchor.p1.x, margined_bounds.p1.y + margined_size.y * anchor.p1.y },
|
|
)
|
|
adjusted_bounds_size := linalg.abs(adjusted_bounds.p1 - adjusted_bounds.p0)
|
|
|
|
// Resolves final constrained bounds of the parent for the child box
|
|
// Will be applied to the box after the child's positon is resolved.
|
|
|
|
fixed_pos := Vec2 { fixed_pos_x, fixed_pos_y }
|
|
constraint_min := adjusted_bounds.min //* (1 - fixed_pos) + parent_content.min * fixed_pos
|
|
constraint_max := adjusted_bounds.max //* (1 - fixed_pos) + parent_content.max * fixed_pos
|
|
|
|
// constraint_min_x := adjusted_bounds.min.x //* (1 - fixed_pos_x) + parent_content.min.x * fixed_pos_x
|
|
// constraint_min_y := adjusted_bounds.min.y //* (1 - fixed_pos_y) + parent_content.min.y * fixed_pos_y
|
|
// constraint_max_x := adjusted_bounds.max.x //* (1 - fixed_pos_x) + parent_content.max.x * fixed_pos_x
|
|
// constraint_max_y := adjusted_bounds.max.y //* (1 - fixed_pos_y) + parent_content.max.y * fixed_pos_y
|
|
|
|
constrained_bounds := range2(
|
|
constraint_min,
|
|
constraint_max,
|
|
// { constraint_min_x, constraint_min_y },
|
|
// { constraint_max_x, constraint_max_y },
|
|
)
|
|
constrained_size := linalg.abs(constrained_bounds.p1 - constrained_bounds.p0)
|
|
|
|
|
|
/*
|
|
If fixed position (X or Y):
|
|
* Ignore Margins
|
|
* Ignore Anchors
|
|
|
|
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 not 0:
|
|
* Allow the child box to spread to entire adjusted content bounds.
|
|
*/
|
|
|
|
size_unit_bounds := range2(
|
|
{ 0.0, 0.0 },
|
|
{ 1.0, -1.0 },
|
|
)
|
|
|
|
alignment := layout.alignment
|
|
aligned_unit_bounds := range2(
|
|
size_unit_bounds.p0 + { -alignment.x, alignment.y },
|
|
size_unit_bounds.p1 - { alignment.x, -alignment.y },
|
|
)
|
|
|
|
wtf := range2(
|
|
{ constrained_bounds.p0.x, constrained_bounds.p0.y },
|
|
{ constrained_bounds.p1.x, constrained_bounds.p1.y },
|
|
)
|
|
|
|
// projected_bounds := range2(
|
|
// aligned_unit_bounds.p0 * wtf.p0,
|
|
// aligned_unit_bounds.p1 * wtf.p1,
|
|
// )
|
|
|
|
|
|
constrained_half_size := constrained_size * 0.5
|
|
min_half_size := layout.size.min * 0.5
|
|
max_half_size := layout.size.max * 0.5
|
|
half_size := linalg.max( constrained_half_size, min_half_size )
|
|
half_size = linalg.min( half_size, max_half_size )
|
|
|
|
projected_bounds := range2(
|
|
aligned_unit_bounds.p0 * half_size,
|
|
aligned_unit_bounds.p1 * half_size,
|
|
)
|
|
|
|
rel_projected_bounds := range2(
|
|
layout.pos + projected_bounds.p0,
|
|
layout.pos + projected_bounds.p1,
|
|
)
|
|
|
|
bounds : Range2
|
|
|
|
// Resolve and apply the size constraint based off of positon of box and the constrained bounds
|
|
|
|
// Check to see if left or right side is over
|
|
if ! (.Fixed_Width in style.flags)
|
|
{
|
|
bounds.p0.x = rel_projected_bounds.p0.x < constrained_bounds.p0.x ? constrained_bounds.p0.x : rel_projected_bounds.p0.x
|
|
bounds.p1.x = rel_projected_bounds.p1.x > constrained_bounds.p1.x ? constrained_bounds.p1.x : rel_projected_bounds.p1.x
|
|
}
|
|
else {
|
|
size_unit_bounds := range2(
|
|
{ 0.0, 0.0 },
|
|
{ 1.0, -1.0 },
|
|
)
|
|
|
|
alignment := layout.alignment
|
|
aligned_unit_bounds := range2(
|
|
size_unit_bounds.p0 + { -alignment.x, alignment.y },
|
|
size_unit_bounds.p1 - { alignment.x, -alignment.y },
|
|
)
|
|
|
|
// Apply size.p0.x directly
|
|
bounds.p0.x = aligned_unit_bounds.p0.x * layout.size.min.x
|
|
bounds.p1.x = aligned_unit_bounds.p1.x * layout.size.min.x
|
|
|
|
bounds.p0.x += constrained_bounds.p0.x
|
|
bounds.p1.x += constrained_bounds.p0.x
|
|
|
|
bounds.p0.x += layout.pos.x
|
|
bounds.p1.x += layout.pos.x
|
|
}
|
|
|
|
if ! (.Fixed_Height in style.flags)
|
|
{
|
|
bounds.p0.y = rel_projected_bounds.p0.y > constrained_bounds.p0.y ? constrained_bounds.p0.y : rel_projected_bounds.p0.y
|
|
bounds.p1.y = rel_projected_bounds.p1.y < constrained_bounds.p1.y ? constrained_bounds.p1.y : rel_projected_bounds.p1.y
|
|
}
|
|
else {
|
|
size_unit_bounds := range2(
|
|
{ 0.0, 0.0 },
|
|
{ 1.0, -1.0 },
|
|
)
|
|
|
|
alignment := layout.alignment
|
|
aligned_unit_bounds := range2(
|
|
size_unit_bounds.p0 + { -alignment.x, alignment.y },
|
|
size_unit_bounds.p1 - { alignment.x, -alignment.y },
|
|
)
|
|
|
|
// Apply size.p0.y directly
|
|
bounds.p0.y = aligned_unit_bounds.p0.y * layout.size.min.y
|
|
bounds.p1.y = aligned_unit_bounds.p1.y * layout.size.min.y
|
|
|
|
bounds.p0.y += constrained_bounds.p0.y //+ aligned_unit_bounds
|
|
bounds.p1.y += constrained_bounds.p0.y //+ aligned_unit_bounds
|
|
|
|
bounds.p0.y += layout.pos.y
|
|
bounds.p1.y += layout.pos.y
|
|
}
|
|
|
|
// Enforce the min/max size
|
|
bounds_size := bounds.p1 - bounds.p0
|
|
// if bounds_size > layout.size.max {
|
|
// Enforce max
|
|
|
|
|
|
// }
|
|
|
|
|
|
text_size : Vec2
|
|
// If the computed matches, we already have the size, don't bother.
|
|
if current.first_frame || ! size_to_text || computed.text_size.y != size_range2(computed.bounds).y {
|
|
text_size = cast(Vec2) measure_text_size( current.text.str, style.font, style.font_size, 0 )
|
|
}
|
|
else {
|
|
text_size = computed.text_size
|
|
}
|
|
if size_to_text {
|
|
// size = text_size
|
|
}
|
|
|
|
|
|
computed.bounds = bounds
|
|
|
|
border_offset := Vec2 { layout.border_width, layout.border_width }
|
|
padding := & computed.padding
|
|
(padding^) = range2(
|
|
bounds.p0 + border_offset,
|
|
bounds.p1 + border_offset,
|
|
)
|
|
|
|
content := & computed.content
|
|
(content^) = range2(
|
|
bounds.p0 + { layout.padding.left, -layout.padding.top },
|
|
bounds.p1 + { -layout.padding.right, layout.padding.bottom },
|
|
)
|
|
|
|
// Text
|
|
if len(current.text.str) > 0
|
|
{
|
|
// profile("Text")
|
|
top_left := content.p0
|
|
bottom_right := content.p1
|
|
|
|
content_size := Vec2 { top_left.x - bottom_right.x, top_left.y - bottom_right.y }
|
|
text_pos : Vec2
|
|
text_pos = top_left
|
|
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 )
|
|
}
|
|
}
|