about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2025-03-14 22:16:54 +0100
committerbptato <nincsnevem662@gmail.com>2025-03-14 22:36:24 +0100
commit3f1b6edb306ecb31fc44fe5d391ea8d3bcafe88c (patch)
tree07788d775f2e5c5b50f31ccd289f95d55a174eed
parent42a61c98034cef298303effab0fe0a531fdc68c4 (diff)
downloadchawan-3f1b6edb306ecb31fc44fe5d391ea8d3bcafe88c.tar.gz
layout: implement cross auto margins in flex
Also removes the computation of underflow on the end margin for blocks;
as far as I can tell, this was never used.
-rw-r--r--src/css/cssvalues.nim3
-rw-r--r--src/css/layout.nim64
-rw-r--r--test/layout/flex-cross-margin-auto.expected12
-rw-r--r--test/layout/flex-cross-margin-auto.html13
4 files changed, 71 insertions, 21 deletions
diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim
index 8762e864..9d78f851 100644
--- a/src/css/cssvalues.nim
+++ b/src/css/cssvalues.nim
@@ -716,6 +716,9 @@ when defined(debug):
   func `$`*(val: CSSValue): string =
     return val.serialize()
 
+proc getLength*(vals: CSSValues; p: CSSPropertyType): CSSLength =
+  return vals.words[p].length
+
 macro `{}`*(vals: CSSValues; s: static string): untyped =
   let t = propertyType(s).get
   let vs = ident($valueType(t))
diff --git a/src/css/layout.nim b/src/css/layout.nim
index 3ec833da..e5b06cd8 100644
--- a/src/css/layout.nim
+++ b/src/css/layout.nim
@@ -1046,26 +1046,30 @@ func spx(l: CSSLength; p: SizeConstraint; computed: CSSValues;
     return max(u - padding, 0)
   return max(u, 0)
 
-proc resolveUnderflow(sizes: var ResolvedSizes; parentWidth: SizeConstraint;
+const MarginStartMap = [
+  dtHorizontal: cptMarginLeft,
+  dtVertical: cptMarginTop
+]
+
+const MarginEndMap = [
+  dtHorizontal: cptMarginRight,
+  dtVertical: cptMarginBottom
+]
+
+proc resolveUnderflow(sizes: var ResolvedSizes; parentSize: SizeConstraint;
     computed: CSSValues) =
+  let dim = dtHorizontal
   # width must be definite, so that conflicts can be resolved
-  if sizes.space.w.isDefinite() and parentWidth.t == scStretch:
-    let total = sizes.space.w.u + sizes.margin[dtHorizontal].sum() +
-      sizes.padding[dtHorizontal].sum()
-    let underflow = parentWidth.u - total
-    if underflow > 0:
-      if computed{"margin-left"}.u != clAuto and
-          computed{"margin-right"}.u != clAuto:
-        sizes.margin[dtHorizontal].send += underflow
-      elif computed{"margin-left"}.u != clAuto and
-          computed{"margin-right"}.u == clAuto:
-        sizes.margin[dtHorizontal].send = underflow
-      elif computed{"margin-left"}.u == clAuto and
-          computed{"margin-right"}.u != clAuto:
-        sizes.margin[dtHorizontal].start = underflow
+  if sizes.space[dim].isDefinite() and parentSize.t == scStretch:
+    let start = computed.getLength(MarginStartMap[dim])
+    let send = computed.getLength(MarginEndMap[dim])
+    let underflow = parentSize.u - sizes.space[dim].u -
+      sizes.margin[dim].sum() - sizes.padding[dim].sum()
+    if underflow > 0 and start.u == clAuto:
+      if send.u != clAuto:
+        sizes.margin[dim].start = underflow
       else:
-        sizes.margin[dtHorizontal].start = underflow div 2
-        sizes.margin[dtHorizontal].send = underflow div 2
+        sizes.margin[dim].start = underflow div 2
 
 proc resolveMargins(lctx: LayoutContext; availableWidth: SizeConstraint;
     computed: CSSValues): RelativeRect =
@@ -1160,7 +1164,7 @@ func resolveBounds(lctx: LayoutContext; space: AvailableSpace; padding: Size;
           res.mi[dtVertical].send = px
   return res
 
-const CvalSizeMap = [dtHorizontal: cptWidth, dtVertical: cptHeight]
+const SizeMap = [dtHorizontal: cptWidth, dtVertical: cptHeight]
 
 proc resolveAbsoluteWidth(sizes: var ResolvedSizes; size: Size;
     positioned: RelativeRect; computed: CSSValues; lctx: LayoutContext) =
@@ -1222,7 +1226,7 @@ proc resolveFloatSizes(lctx: LayoutContext; space: AvailableSpace;
   )
   sizes.space.h = maxContent()
   for dim in DimensionType:
-    let length = computed.words[CvalSizeMap[dim]].length
+    let length = computed.getLength(SizeMap[dim])
     if length.canpx(space[dim]):
       let u = length.spx(space[dim], computed, paddingSum[dim])
       sizes.space[dim] = stretch(minClamp(u, sizes.bounds.a[dim]))
@@ -1243,7 +1247,7 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace;
   )
   if dim != dtHorizontal:
     sizes.space.h = maxContent()
-  let length = computed.words[CvalSizeMap[dim]].length
+  let length = computed.getLength(SizeMap[dim])
   if length.canpx(space[dim]):
     let u = length.spx(space[dim], computed, paddingSum[dim])
       .minClamp(sizes.bounds.a[dim])
@@ -1259,7 +1263,7 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace;
     # been specified.
     sizes.space[dim] = maxContent()
   let odim = dim.opposite()
-  let olength = computed.words[CvalSizeMap[odim]].length
+  let olength = computed.getLength(SizeMap[odim])
   if olength.canpx(space[odim]):
     let u = olength.spx(space[odim], computed, paddingSum[odim])
       .minClamp(sizes.bounds.a[odim])
@@ -1273,6 +1277,9 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace;
       t: sizes.space[odim].t,
       u: minClamp(u, sizes.bounds.a[odim])
     )
+    if computed.getLength(MarginStartMap[odim]).u == clAuto or
+        computed.getLength(MarginEndMap[odim]).u == clAuto:
+      sizes.space[odim].t = scFitContent
   elif sizes.bounds.a[odim].send < LUnit.high:
     sizes.space[odim] = fitContent(sizes.bounds.a[odim].max())
   return sizes
@@ -2767,6 +2774,21 @@ proc flushMain(fctx: var FlexContext; mctx: var FlexMainContext;
       lctx.layoutFlexItem(it.child, it.sizes)
     offset[dim] += it.sizes.margin[dim].start
     it.child.state.offset[dim] += offset[dim]
+    # resolve auto cross margins for shrink-to-fit items
+    if sizes.space[odim].t == scStretch:
+      let start = it.child.computed.getLength(MarginStartMap[odim])
+      let send = it.child.computed.getLength(MarginEndMap[odim])
+      # We can get by without adding offset, because flex items are
+      # always layouted at (0, 0).
+      let underflow = sizes.space[odim].u - it.child.state.size[odim] -
+        it.sizes.margin[odim].sum()
+      if underflow > 0 and start.u == clAuto:
+        # we don't really care about the end margin, because that is
+        # already taken into account by AvailableSpace
+        if send.u != clAuto:
+          it.sizes.margin[odim].start = underflow
+        else:
+          it.sizes.margin[odim].start = underflow div 2
     # margins are added here, since they belong to the flex item.
     it.child.state.offset[odim] += offset[odim] + it.sizes.margin[odim].start
     offset[dim] += it.child.state.size[dim]
diff --git a/test/layout/flex-cross-margin-auto.expected b/test/layout/flex-cross-margin-auto.expected
new file mode 100644
index 00000000..2d715081
--- /dev/null
+++ b/test/layout/flex-cross-margin-auto.expected
@@ -0,0 +1,12 @@
+      test
+
+
+
+
+test
+      test
+
+
+
+
+test
diff --git a/test/layout/flex-cross-margin-auto.html b/test/layout/flex-cross-margin-auto.html
new file mode 100644
index 00000000..7f5d8aee
--- /dev/null
+++ b/test/layout/flex-cross-margin-auto.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<div style="display: flex; flex-direction: column; width: 10ch">
+<div style="margin-left: auto; background: blue; width: 4ch">test</div>
+</div>
+<div style="display: flex; height: 10ch">
+<div style="margin-top: auto; background: blue; height: 1em">test</div>
+</div>
+<div style="display: flex; flex-direction: column; width: 10ch">
+<div style="margin-left: auto; background: blue">test</div>
+</div>
+<div style="display: flex; height: 10ch">
+<div style="margin-top: auto; background: blue">test</div>
+</div>