about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-04-19 19:51:45 +0200
committerbptato <nincsnevem662@gmail.com>2024-04-19 19:57:09 +0200
commit485fff96292f737879c92973ca3c06755f95ffdf (patch)
tree531b925f48dc5666573aa3a52bd597f5c911c169
parentd4d34ea8b5257d6f0ddd40807a9b6b684df24811 (diff)
downloadchawan-485fff96292f737879c92973ca3c06755f95ffdf.tar.gz
layout: float sizing fixes
Turns out our shrink-to-fit emulation was inadequate: it assumed all
floats are positioned on a separate line, which is the *opposite* of
what we want, as that would be the behavior of min-content.

Now we instead sum together the width of all floats and the widest
non-floating child, and then clamp that to the target fitContent width.
This correctly gives us max-content width in the first pass, still
without having to actually position the floats.
-rw-r--r--src/layout/engine.nim29
1 files changed, 21 insertions, 8 deletions
diff --git a/src/layout/engine.nim b/src/layout/engine.nim
index 2cf2cf7c..3cfaf10f 100644
--- a/src/layout/engine.nim
+++ b/src/layout/engine.nim
@@ -1162,12 +1162,16 @@ proc layoutFlex(bctx: var BlockContext; box: BlockBox; builder: BlockBoxBuilder;
 
 # Note: padding must still be applied after this.
 proc applyWidth(box: BlockBox; sizes: ResolvedSizes;
-    maxChildWidth: LayoutUnit) =
+    maxChildWidth: LayoutUnit; space: AvailableSpace) =
   # Make the box as small/large as the content's width or specified width.
-  box.size.w = maxChildWidth.applySizeConstraint(sizes.space.w)
+  box.size.w = maxChildWidth.applySizeConstraint(space.w)
   # Then, clamp it to minWidth and maxWidth (if applicable).
   box.size.w = clamp(box.size.w, sizes.minWidth, sizes.maxWidth)
 
+proc applyWidth(box: BlockBox; sizes: ResolvedSizes;
+    maxChildWidth: LayoutUnit) =
+  box.applyWidth(sizes, maxChildWidth, sizes.space)
+
 proc applyHeight(box: BlockBox; sizes: ResolvedSizes;
     maxChildHeight: LayoutUnit) =
   # Make the box as small/large as the content's width or specified width.
@@ -1250,6 +1254,7 @@ type
   BlockState = object
     offset: Offset
     maxChildWidth: LayoutUnit
+    totalFloatWidth: LayoutUnit # used for re-layouts
     nested: seq[BlockBox]
     space: AvailableSpace
     xminwidth: LayoutUnit
@@ -2420,7 +2425,7 @@ func isParentResolved(state: BlockState; bctx: BlockContext): bool =
 # same block formatting context depends on knowing where the box offset is
 # (because of floats).
 proc layoutBlockChildren(state: var BlockState; bctx: var BlockContext;
-    children: seq[BoxBuilder]) =
+    children: seq[BoxBuilder]; parent: BlockBox) =
   for builder in children:
     var dy: LayoutUnit = 0 # delta
     var child: BlockBox
@@ -2454,20 +2459,28 @@ proc layoutBlockChildren(state: var BlockState; bctx: var BlockContext;
       # plus height.
       dy = child.offset.y - state.offset.y + child.size.h
     let childWidth = child.margin.left + child.size.w + child.margin.right
-    state.maxChildWidth = max(state.maxChildWidth, childWidth)
     state.xminwidth = max(state.xminwidth, child.xminwidth)
     if child.computed{"position"} != PositionAbsolute and not isfloat:
       # Not absolute, and not a float.
+      state.maxChildWidth = max(state.maxChildWidth, childWidth)
       state.offset.y += dy
     elif isfloat:
       if state.space.w.t == scFitContent:
         # Float position depends on the available width, but in this case
         # the parent width is not known.
+        #
         # Set the "re-layout" flag, and skip this box.
         # (If child boxes with fit-content have floats, those will be
         # re-layouted too first, so we do not have to consider those here.)
         state.needsReLayout = true
+        # Since we emulate max-content here, the float will not contribute to
+        # maxChildWidth in this iteration; instead, its outer width will be
+        # summed up in totalFloatWidth and added to maxChildWidth in
+        # initReLayout.
+        state.totalFloatWidth += child.size.w + child.margin.left +
+          child.margin.right
         continue
+      state.maxChildWidth = max(state.maxChildWidth, childWidth)
       # Two cases exist:
       # a) The float cannot be positioned, because `box' has not resolved
       #    its y offset yet. (e.g. if float comes before the first child,
@@ -2528,7 +2541,7 @@ proc initReLayout(state: var BlockState; bctx: var BlockContext;
   state.nested.setLen(0)
   bctx.exclusions.setLen(state.oldExclusionsLen)
   state.offset = Offset(x: sizes.padding.left, y: sizes.padding.top)
-  box.applyWidth(sizes, state.maxChildWidth)
+  box.applyWidth(sizes, state.maxChildWidth + state.totalFloatWidth)
   state.space.w = stretch(box.size.w)
 
 # Re-position the children.
@@ -2560,16 +2573,16 @@ proc layoutBlock(bctx: var BlockContext; box: BlockBox;
     oldExclusionsLen: bctx.exclusions.len
   )
   state.initBlockPositionStates(bctx, box)
-  state.layoutBlockChildren(bctx, builder.children)
+  state.layoutBlockChildren(bctx, builder.children, box)
   if state.needsReLayout:
     state.initReLayout(bctx, box, sizes)
-    state.layoutBlockChildren(bctx, builder.children)
+    state.layoutBlockChildren(bctx, builder.children, box)
   if state.nested.len > 0:
     let lastNested = state.nested[^1]
     box.baseline = lastNested.offset.y + lastNested.baseline
   # Apply width then move the inline offset of children that still need
   # further relative positioning.
-  box.applyWidth(sizes, state.maxChildWidth)
+  box.applyWidth(sizes, state.maxChildWidth, state.space)
   state.repositionChildren(box, lctx)
   # Set the inner height to the last y offset minus the starting offset
   # (that is, top padding).