Skip to content

Commit c3bd75f

Browse files
committed
Fixed child alignment
1 parent 06434bc commit c3bd75f

3 files changed

Lines changed: 178 additions & 27 deletions

File tree

examples/flyui/flyui_demo.php

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use VISU\FlyUI\FUIButtonGroup;
99
use VISU\FlyUI\FUILayoutFlow;
1010
use VISU\FlyUI\FUILayoutSizing;
11+
use VISU\FlyUI\FUILayoutAlignment;
1112
use VISU\FlyUI\Theme\FUIButtonStyle;
1213
use VISU\FlyUI\Theme\FUIButtonGroupStyle;
1314
use VISU\Graphics\Rendering\RenderContext;
@@ -37,6 +38,7 @@ class FlyUiDemoState {
3738
public FUILayoutFlow $stackFlow = FUILayoutFlow::vertical;
3839
public FUILayoutSizing $verticalStackSizing = FUILayoutSizing::fill;
3940
public FUILayoutSizing $horizontalStackSizing = FUILayoutSizing::fill;
41+
public FUILayoutAlignment $stackAlignment = FUILayoutAlignment::topLeft;
4042
}
4143

4244
$state = new FlyUiDemoState;
@@ -69,6 +71,7 @@ function UIDemo(string $name, \Closure $func) : void {
6971
FlyUI::beginLayout()
7072
->verticalFill()
7173
->flow($state->stackFlow)
74+
->align($state->stackAlignment)
7275
->spacing(5);
7376

7477
for ($i = 0; $i < 10; $i++) {
@@ -134,16 +137,64 @@ function UIDemo(string $name, \Closure $func) : void {
134137
}
135138
});
136139

137-
// FlyUI::buttonGroup(['fill' => 'Fill', 'fit' => 'Fit'], $state->stackSizing->name, function(string $option) use(&$state) {
138-
// var_dump($option);
139-
// if ($option === 'fill') {
140-
// $state->stackSizing = FUILayoutSizing::fill;
141-
// } else {
142-
// $state->stackSizing = FUILayoutSizing::fit;
143-
// }
144-
// });
140+
FlyUI::spaceY(10);
141+
142+
// alignment
143+
FlyUI::beginSection('Stack Alignment');
144+
145+
// horizontal alignment
146+
$horizontalAlignment = match($state->stackAlignment) {
147+
FUILayoutAlignment::topLeft, FUILayoutAlignment::centerLeft, FUILayoutAlignment::bottomLeft => 'left',
148+
FUILayoutAlignment::topCenter, FUILayoutAlignment::center, FUILayoutAlignment::bottomCenter => 'center',
149+
FUILayoutAlignment::topRight, FUILayoutAlignment::centerRight, FUILayoutAlignment::bottomRight => 'right',
150+
};
151+
152+
FlyUI::buttonGroup('Horizontal', [
153+
'left' => 'Left',
154+
'center' => 'Center',
155+
'right' => 'Right',
156+
], $horizontalAlignment, function(string $option) use(&$state) {
157+
$state->stackAlignment = match([$state->stackAlignment, $option]) {
158+
[FUILayoutAlignment::topLeft, 'left'], [FUILayoutAlignment::topCenter, 'left'], [FUILayoutAlignment::topRight, 'left'] => FUILayoutAlignment::topLeft,
159+
[FUILayoutAlignment::topLeft, 'center'], [FUILayoutAlignment::topCenter, 'center'], [FUILayoutAlignment::topRight, 'center'] => FUILayoutAlignment::topCenter,
160+
[FUILayoutAlignment::topLeft, 'right'], [FUILayoutAlignment::topCenter, 'right'], [FUILayoutAlignment::topRight, 'right'] => FUILayoutAlignment::topRight,
161+
[FUILayoutAlignment::centerLeft, 'left'], [FUILayoutAlignment::center, 'left'], [FUILayoutAlignment::centerRight, 'left'] => FUILayoutAlignment::centerLeft,
162+
[FUILayoutAlignment::centerLeft, 'center'], [FUILayoutAlignment::center, 'center'], [FUILayoutAlignment::centerRight, 'center'] => FUILayoutAlignment::center,
163+
[FUILayoutAlignment::centerLeft, 'right'], [FUILayoutAlignment::center, 'right'], [FUILayoutAlignment::centerRight, 'right'] => FUILayoutAlignment::centerRight,
164+
[FUILayoutAlignment::bottomLeft, 'left'], [FUILayoutAlignment::bottomCenter, 'left'], [FUILayoutAlignment::bottomRight, 'left'] => FUILayoutAlignment::bottomLeft,
165+
[FUILayoutAlignment::bottomLeft, 'center'], [FUILayoutAlignment::bottomCenter, 'center'], [FUILayoutAlignment::bottomRight, 'center'] => FUILayoutAlignment::bottomCenter,
166+
[FUILayoutAlignment::bottomLeft, 'right'], [FUILayoutAlignment::bottomCenter, 'right'], [FUILayoutAlignment::bottomRight, 'right'] => FUILayoutAlignment::bottomRight,
167+
};
168+
})->setId('h-align-btn-group');
169+
170+
// vertical alignment
171+
$verticalAlignment = match($state->stackAlignment) {
172+
FUILayoutAlignment::topLeft, FUILayoutAlignment::topCenter, FUILayoutAlignment::topRight => 'top',
173+
FUILayoutAlignment::centerLeft, FUILayoutAlignment::center, FUILayoutAlignment::centerRight => 'center',
174+
FUILayoutAlignment::bottomLeft, FUILayoutAlignment::bottomCenter, FUILayoutAlignment::bottomRight => 'bottom',
175+
};
176+
177+
FlyUI::buttonGroup('Vertical', [
178+
'top' => 'Top',
179+
'center' => 'Center',
180+
'bottom' => 'Bottom',
181+
], $verticalAlignment, function(string $option) use(&$state) {
182+
$state->stackAlignment = match([$state->stackAlignment, $option]) {
183+
[FUILayoutAlignment::topLeft, 'top'], [FUILayoutAlignment::centerLeft, 'top'], [FUILayoutAlignment::bottomLeft, 'top'] => FUILayoutAlignment::topLeft,
184+
[FUILayoutAlignment::topCenter, 'top'], [FUILayoutAlignment::center, 'top'], [FUILayoutAlignment::bottomCenter, 'top'] => FUILayoutAlignment::topCenter,
185+
[FUILayoutAlignment::topRight, 'top'], [FUILayoutAlignment::centerRight, 'top'], [FUILayoutAlignment::bottomRight, 'top'] => FUILayoutAlignment::topRight,
186+
[FUILayoutAlignment::topLeft, 'center'], [FUILayoutAlignment::centerLeft, 'center'], [FUILayoutAlignment::bottomLeft, 'center'] => FUILayoutAlignment::centerLeft,
187+
[FUILayoutAlignment::topCenter, 'center'], [FUILayoutAlignment::center, 'center'], [FUILayoutAlignment::bottomCenter, 'center'] => FUILayoutAlignment::center,
188+
[FUILayoutAlignment::topRight, 'center'], [FUILayoutAlignment::centerRight, 'center'], [FUILayoutAlignment::bottomRight, 'center'] => FUILayoutAlignment::centerRight,
189+
[FUILayoutAlignment::topLeft, 'bottom'], [FUILayoutAlignment::centerLeft, 'bottom'], [FUILayoutAlignment::bottomLeft, 'bottom'] => FUILayoutAlignment::bottomLeft,
190+
[FUILayoutAlignment::topCenter, 'bottom'], [FUILayoutAlignment::center, 'bottom'], [FUILayoutAlignment::bottomCenter, 'bottom'] => FUILayoutAlignment::bottomCenter,
191+
[FUILayoutAlignment::topRight, 'bottom'], [FUILayoutAlignment::centerRight, 'bottom'], [FUILayoutAlignment::bottomRight, 'bottom'] => FUILayoutAlignment::bottomRight,
192+
};
193+
})->setId('v-align-btn-group');
194+
195+
FlyUI::end(); // end alignment section
145196

146-
FlyUI::end(); // end right settings area
197+
FlyUI::end(); // end right settings area
147198

148199
FlyUI::end(); // end main container
149200

src/FlyUI/FUILayout.php

Lines changed: 102 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,6 @@
55
use GL\Math\Vec2;
66
use GL\VectorGraphics\VGColor;
77

8-
enum FUILayoutAlignment
9-
{
10-
case topLeft;
11-
case topCenter;
12-
case topRight;
13-
case centerLeft;
14-
case center;
15-
case centerRight;
16-
case bottomLeft;
17-
case bottomCenter;
18-
case bottomRight;
19-
}
20-
218
class FUILayout extends FUIView
229
{
2310
/**
@@ -82,6 +69,15 @@ public function flow(FUILayoutFlow $flow) : self
8269
return $this;
8370
}
8471

72+
/**
73+
* Sets the alignment of the view
74+
*/
75+
public function align(FUILayoutAlignment $alignment) : self
76+
{
77+
$this->alignment = $alignment;
78+
return $this;
79+
}
80+
8581
/**
8682
* Sets the a fixed height for the layout
8783
* Note: This will override the vertical sizing mode
@@ -179,6 +175,58 @@ public function backgroundColor(VGColor $color, ?float $cornerRadius = null) : s
179175
return $this;
180176
}
181177

178+
/**
179+
* Calculate the offset for alignment within available space
180+
*/
181+
private function calculateAlignmentOffset(Vec2 $totalChildrenSize, Vec2 $availableSize) : Vec2
182+
{
183+
$offset = new Vec2(0.0, 0.0);
184+
185+
// calculate horizontal offset
186+
switch ($this->alignment) {
187+
case FUILayoutAlignment::topCenter:
188+
case FUILayoutAlignment::center:
189+
case FUILayoutAlignment::bottomCenter:
190+
$offset->x = ($availableSize->x - $totalChildrenSize->x) / 2.0;
191+
break;
192+
193+
case FUILayoutAlignment::topRight:
194+
case FUILayoutAlignment::centerRight:
195+
case FUILayoutAlignment::bottomRight:
196+
$offset->x = $availableSize->x - $totalChildrenSize->x;
197+
break;
198+
199+
default:
200+
$offset->x = 0.0; // left alignment (default)
201+
break;
202+
}
203+
204+
// calculate vertical offset
205+
switch ($this->alignment) {
206+
case FUILayoutAlignment::centerLeft:
207+
case FUILayoutAlignment::center:
208+
case FUILayoutAlignment::centerRight:
209+
$offset->y = ($availableSize->y - $totalChildrenSize->y) / 2.0;
210+
break;
211+
212+
case FUILayoutAlignment::bottomLeft:
213+
case FUILayoutAlignment::bottomCenter:
214+
case FUILayoutAlignment::bottomRight:
215+
$offset->y = $availableSize->y - $totalChildrenSize->y;
216+
break;
217+
218+
default:
219+
$offset->y = 0.0; // top alignment (default)
220+
break;
221+
}
222+
223+
// ensure offsets are never negative
224+
$offset->x = max(0.0, $offset->x);
225+
$offset->y = max(0.0, $offset->y);
226+
227+
return $offset;
228+
}
229+
182230
/**
183231
* Calculate the sizes of all children based on their sizing modes
184232
* This implements Figma-style fill behavior where fill children share available space equally
@@ -366,10 +414,40 @@ public function getEstimatedSize(FUIRenderContext $ctx) : Vec2
366414

367415
protected function renderContent(FUIRenderContext $ctx) : void
368416
{
369-
$containerOrigin = $ctx->origin->copy();
370-
371417
// calculate all children sizes once using optimized method
372418
$childrenSizes = $this->calculateChildrenSizes($ctx);
419+
420+
if (empty($childrenSizes)) {
421+
return;
422+
}
423+
424+
// calculate total size of all children including spacing
425+
$totalChildrenSize = new Vec2(0.0, 0.0);
426+
$totalSpacing = $this->spacing * max(0, count($childrenSizes) - 1);
427+
428+
if ($this->flow === FUILayoutFlow::horizontal) {
429+
foreach ($childrenSizes as $childSize) {
430+
$totalChildrenSize = new Vec2(
431+
$totalChildrenSize->x + $childSize->x,
432+
max($totalChildrenSize->y, $childSize->y)
433+
);
434+
}
435+
$totalChildrenSize = new Vec2($totalChildrenSize->x + $totalSpacing, $totalChildrenSize->y);
436+
} else {
437+
foreach ($childrenSizes as $childSize) {
438+
$totalChildrenSize = new Vec2(
439+
max($totalChildrenSize->x, $childSize->x),
440+
$totalChildrenSize->y + $childSize->y
441+
);
442+
}
443+
$totalChildrenSize = new Vec2($totalChildrenSize->x, $totalChildrenSize->y + $totalSpacing);
444+
}
445+
446+
// calculate alignment offset
447+
$alignmentOffset = $this->calculateAlignmentOffset($totalChildrenSize, $ctx->containerSize);
448+
449+
// start rendering from the aligned position
450+
$containerOrigin = $ctx->origin + $alignmentOffset;
373451

374452
foreach($this->children as $index => $child)
375453
{
@@ -389,14 +467,20 @@ protected function renderContent(FUIRenderContext $ctx) : void
389467
$ctx->origin = $originalOrigin;
390468
$ctx->containerSize = $originalSize;
391469

392-
// move to the next position based on flow direction (reuse containerOrigin)
470+
// move to the next position based on flow direction
393471
if ($this->flow === FUILayoutFlow::horizontal)
394472
{
395-
$containerOrigin->x = $containerOrigin->x + $childSize->x + $this->spacing;
473+
$containerOrigin = new Vec2(
474+
$containerOrigin->x + $childSize->x + $this->spacing,
475+
$containerOrigin->y
476+
);
396477
}
397478
else
398479
{
399-
$containerOrigin->y = $containerOrigin->y + $childSize->y + $this->spacing;
480+
$containerOrigin = new Vec2(
481+
$containerOrigin->x,
482+
$containerOrigin->y + $childSize->y + $this->spacing
483+
);
400484
}
401485
}
402486
}

src/FlyUI/FUILayoutAlignment.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace VISU\FlyUI;
4+
5+
enum FUILayoutAlignment
6+
{
7+
case topLeft;
8+
case topCenter;
9+
case topRight;
10+
case centerLeft;
11+
case center;
12+
case centerRight;
13+
case bottomLeft;
14+
case bottomCenter;
15+
case bottomRight;
16+
}

0 commit comments

Comments
 (0)