Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions src/vaev-engine/layout/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export module Vaev.Engine:layout.block;
import :values;
import :layout.base;
import :layout.layout;
import :layout.floats;

namespace Vaev::Layout {

Expand Down Expand Up @@ -170,6 +171,7 @@ struct BlockFormatingContext : FormatingContext {
bool blockWasCompletelyLaidOut = false;

Au lastMarginBottom = 0_au;
FloatsContainer floats{input.position, inlineSize};
for (usize i = startAt; i < endChildren; ++i) {
auto& c = box.children()[i];

Expand All @@ -182,9 +184,30 @@ struct BlockFormatingContext : FormatingContext {
)
);

// TODO: Implement floating
// if (c.style->float_ != Float::NONE)
// continue;
if (c.style->float_ != Float::NONE) {
Input floatInput = {
.intrinsic = IntrinsicSize::MAX_CONTENT,
.availableSpace = {input.availableSpace.x, 0_au},
.containingBlock = {inlineSize, input.knownSize.y.unwrapOr(0_au)},
};
auto size = layout(tree, c, floatInput).size;
auto margins = computeMargins(tree, c, floatInput);
auto bound = floats.place(size + margins.all(), c.style->float_, lastMarginBottom).shrink(margins);
if (input.fragment) {
layout(
tree,
c,
{
.fragment = input.fragment,
.knownSize = bound.size().cast<Opt<Au>>(),
.position = bound.topStart(),
.availableSpace = {input.availableSpace.x, 0_au},
.containingBlock = {inlineSize, input.knownSize.y.unwrapOr(0_au)},
}
);
}
continue;
}

Input childInput = {
.fragment = input.fragment,
Expand Down
6 changes: 5 additions & 1 deletion src/vaev-engine/layout/builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,11 @@ static void _buildChildDefaultDisplay(BuilderContext bc, Gc::Ref<Dom::Element> c
}

// NOTE: Flow for From::BLOCK and From::INLINE
if (display == Display::Outside::BLOCK) {
// https://www.w3.org/TR/css-display-3/#valdef-display-flow
if (display == Display::Outside::BLOCK or
childStyle->float_ != Float::NONE or
childStyle->position != Position::STATIC) {

bc.flushRootInlineBoxIntoAnonymousBox();
_innerDisplayDispatchCreationOfBlockLevelBox(bc, child, childStyle, display);
} else {
Expand Down
129 changes: 129 additions & 0 deletions src/vaev-engine/layout/floats.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
module;

#include <karm-math/au.h>

export module Vaev.Engine:layout.floats;

import :values;

namespace Vaev::Layout {

export struct FloatsContainer {
struct Placed {
RectAu rect;
Float side;
};

Vec<Placed> _placed;
Vec2Au _origin;
Au _inlineSize;

FloatsContainer(Vec2Au origin, Au inlineSize)
: _origin(origin), _inlineSize(inlineSize) {}
Comment on lines +21 to +22
Copy link
Preview

Copilot AI Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructors should be O(1) and lightweight. The current constructors are already simple, but consider making the parameterized constructor explicit to prevent unintended implicit conversions.

Copilot generated this review using guidance from repository custom instructions.


FloatsContainer() : _origin({0_au, 0_au}), _inlineSize(0_au) {}
Copy link
Preview

Copilot AI Sep 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constructors should be O(1) and lightweight. The current constructors are already simple, but consider making the parameterized constructor explicit to prevent unintended implicit conversions.

Copilot generated this review using guidance from repository custom instructions.


static bool _verticallyOverlaps(RectAu const& r, Au bandY, Au bandH) {
Au r0 = r.y;
Au r1 = r.y + r.height;
Au b0 = bandY;
Au b1 = bandY + bandH;
return not(b1 <= r0 or r1 <= b0);
}

// Compute available inline range [start, end) in container coords at band y..y+h
Pair<Au> inlineAvailable(Au y, Au h) const {
// Fast path: no floats
if (_placed.len() == 0)
return {0_au, _inlineSize};

Au left = 0_au; // consumed from Start
Au right = _inlineSize; // open end (exclusive)
for (auto const& pf : _placed) {
if (not _verticallyOverlaps(pf.rect, _origin.y + y, h))
continue;
if (pf.side == Float::INLINE_START or pf.side == Float::LEFT) {
Au used = (pf.rect.x + pf.rect.width) - _origin.x;
if (used > left)
left = used;
} else if (pf.side == Float::INLINE_END or pf.side == Float::RIGHT) {
Au used = pf.rect.x - _origin.x;
if (used < right)
right = used;
}
}
if (left > right)
left = right; // no space => collapsed interval
return {left, right};
}

Au clear(Clear clr, Au fromY) const {
bool start = clr == Clear::BOTH or clr == Clear::INLINE_START or clr == Clear::LEFT;
bool end = clr == Clear::BOTH or clr == Clear::INLINE_END or clr == Clear::RIGHT;

Au y = fromY;
bool changed = true;
while (changed) {
changed = false;
for (auto const& pf : _placed) {
if (not _verticallyOverlaps(pf.rect, _origin.y + y, 1_au))
continue;
bool isStart = (pf.side == Float::INLINE_START or pf.side == Float::LEFT);
bool isEnd = (pf.side == Float::INLINE_END or pf.side == Float::RIGHT);
if ((start and isStart) or (end and isEnd)) {
Au ny = (pf.rect.y + pf.rect.height) - _origin.y;
if (ny > y) {
y = ny;
changed = true;
}
}
}
}
return y;
}

// Place a float box with size (inline=w, block=h) at or after y
RectAu place(Vec2Au size, Float side, Au y) {
Au w = size.x;
Au h = size.y;
if (w <= 0_au or h <= 0_au)
return {_origin.x, _origin.y + y, 0_au, 0_au};

Au curY = y;
while (true) {
auto [availStart, availEnd] = inlineAvailable(curY, h);
Au availW = availEnd - availStart;
if (availW >= w) {
Au x = (side == Float::INLINE_START or side == Float::LEFT)
? (_origin.x + availStart)
: (_origin.x + (availEnd - w));
RectAu rect{x, _origin.y + curY, w, h};
_placed.pushBack({rect, side});
return rect;
}

// Not enough room on this slice; push below the lowest overlapping edge
Au nextY = curY;
for (auto const& pf : _placed) {
if (_verticallyOverlaps(pf.rect, _origin.y + curY, h)) {
Au ny = (pf.rect.y + pf.rect.height) - _origin.y;
if (ny > nextY)
nextY = ny;
}
}
if (nextY == curY) {
// Wider than container: clamp to container width and place at side
Au clampedW = (w > _inlineSize) ? _inlineSize : w;
Au x = (side == Float::INLINE_START or side == Float::LEFT)
? _origin.x
: _origin.x + (_inlineSize - clampedW);
RectAu rect{x, _origin.y + curY, clampedW, h};
_placed.pushBack({rect, side});
return rect;
}
curY = nextY;
}
}
};

} // namespace Vaev::Layout
1 change: 1 addition & 0 deletions src/vaev-engine/layout/mod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export import :layout.base;
export import :layout.block;
export import :layout.builder;
export import :layout.flex;
export import :layout.floats;
export import :layout.grid;
export import :layout.inline_;
export import :layout.layout;
Expand Down
3 changes: 2 additions & 1 deletion src/vaev-engine/layout/paint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,8 @@ static void _paintChildren(Frag& frag, Scene::Stack& stack, auto predicate) {

// NOTE: Positioned elements act as if they establish a stacking context
auto position = s.position;
if (position != Position::STATIC) {
auto float_ = s.float_;
if (position != Position::STATIC or float_ != Float::NONE) {
if (predicate(s))
_paintStackingContext(c, stack);
continue;
Expand Down
2 changes: 1 addition & 1 deletion src/vaev-engine/values/insets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export enum struct Position {
};

export bool impliesRemovingFromFlow(Position position) {
return position == Position::ABSOLUTE || position == Position::FIXED;
return position == Position::ABSOLUTE or position == Position::FIXED;
}

export template <>
Expand Down
Loading