From 6792dbb02c5b1e0d46dabdc14dcc103ecd401ac5 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Sat, 17 Jul 2021 15:45:32 -0400 Subject: [PATCH] Fix background/overlay layout in DOM/HTML renderers (#429) --- .../Modifiers/StyleModifiers.swift | 38 ++++++++--- .../Modifiers/LayoutModifiers.swift | 60 ++++++++++++++++++ .../RenderingTests.swift | 53 ++++++++++++++++ .../RenderingTests/testBackground.1.png | Bin 0 -> 1008 bytes .../RenderingTests/testBackground.2.png | Bin 0 -> 1007 bytes .../RenderingTests/testOverlay.1.png | Bin 0 -> 1060 bytes .../RenderingTests/testOverlay.2.png | Bin 0 -> 1037 bytes 7 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testBackground.1.png create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testBackground.2.png create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testOverlay.1.png create mode 100644 Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testOverlay.2.png diff --git a/Sources/TokamakCore/Modifiers/StyleModifiers.swift b/Sources/TokamakCore/Modifiers/StyleModifiers.swift index 7d691a1b5..048e69f96 100644 --- a/Sources/TokamakCore/Modifiers/StyleModifiers.swift +++ b/Sources/TokamakCore/Modifiers/StyleModifiers.swift @@ -17,6 +17,15 @@ import Foundation +/// Override this View's body to provide a layout that fits the background to the content. +public struct _BackgroundLayout: _PrimitiveView + where Content: View, Background: View +{ + public let content: Content + public let background: Background + public let alignment: Alignment +} + public struct _BackgroundModifier: ViewModifier, EnvironmentReader where Background: View { @@ -30,11 +39,11 @@ public struct _BackgroundModifier: ViewModifier, EnvironmentReader } public func body(content: Content) -> some View { - // FIXME: Clip to bounds of foreground. - ZStack(alignment: alignment) { - background - content - } + _BackgroundLayout( + content: content, + background: background, + alignment: alignment + ) } mutating func setContent(from values: EnvironmentValues) { @@ -113,6 +122,15 @@ public extension View { } } +/// Override this View's body to provide a layout that fits the background to the content. +public struct _OverlayLayout: _PrimitiveView + where Content: View, Overlay: View +{ + public let content: Content + public let overlay: Overlay + public let alignment: Alignment +} + public struct _OverlayModifier: ViewModifier, EnvironmentReader where Overlay: View { @@ -126,11 +144,11 @@ public struct _OverlayModifier: ViewModifier, EnvironmentReader } public func body(content: Content) -> some View { - // FIXME: Clip to content shape. - ZStack(alignment: alignment) { - content - overlay - } + _OverlayLayout( + content: content, + overlay: overlay, + alignment: alignment + ) } mutating func setContent(from values: EnvironmentValues) { diff --git a/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift b/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift index b42c5ad0e..cdf904751 100644 --- a/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift +++ b/Sources/TokamakStaticHTML/Modifiers/LayoutModifiers.swift @@ -156,3 +156,63 @@ extension _AspectRatioLayout: DOMViewModifier { ] } } + +extension _BackgroundLayout: _HTMLPrimitive { + public var renderedBody: AnyView { + AnyView( + HTML( + "div", + ["style": "display: inline-grid; grid-template-columns: auto auto;"] + ) { + HTML( + "div", + ["style": """ + display: flex; + justify-content: \(alignment.horizontal.flexAlignment); + align-items: \(alignment.vertical.flexAlignment); + grid-area: a; + + width: 0; min-width: 100%; + height: 0; min-height: 100%; + overflow: hidden; + """] + ) { + background + } + HTML("div", ["style": "grid-area: a;"]) { + content + } + } + ) + } +} + +extension _OverlayLayout: _HTMLPrimitive { + public var renderedBody: AnyView { + AnyView( + HTML( + "div", + ["style": "display: inline-grid; grid-template-columns: auto auto;"] + ) { + HTML("div", ["style": "grid-area: a;"]) { + content + } + HTML( + "div", + ["style": """ + display: flex; + justify-content: \(alignment.horizontal.flexAlignment); + align-items: \(alignment.vertical.flexAlignment); + grid-area: a; + + width: 0; min-width: 100%; + height: 0; min-height: 100%; + overflow: hidden; + """] + ) { + overlay + } + } + ) + } +} diff --git a/Tests/TokamakStaticHTMLTests/RenderingTests.swift b/Tests/TokamakStaticHTMLTests/RenderingTests.swift index 57a26ef0d..9217cb200 100644 --- a/Tests/TokamakStaticHTMLTests/RenderingTests.swift +++ b/Tests/TokamakStaticHTMLTests/RenderingTests.swift @@ -339,6 +339,59 @@ final class RenderingTests: XCTestCase { timeout: defaultSnapshotTimeout ) } + + func testBackground() { + assertSnapshot( + matching: Rectangle() + .fill(Color.blue) + .opacity(0.5) + .frame(width: 80, height: 80) + .background( + RoundedRectangle(cornerRadius: 10).fill(Color.red) + ), + as: .image(size: .init(width: 100, height: 100)) + ) + + assertSnapshot( + matching: Rectangle() + .fill(Color.blue) + .opacity(0.5) + .frame(width: 80, height: 80) + .background( + RoundedRectangle(cornerRadius: 10) + .fill(Color.red) + .frame(width: 40, height: 40), + alignment: .bottomTrailing + ), + as: .image(size: .init(width: 100, height: 100)) + ) + } + + func testOverlay() { + assertSnapshot( + matching: Rectangle() + .fill(Color.blue) + .frame(width: 80, height: 80) + .overlay( + RoundedRectangle(cornerRadius: 10) + .fill(Color.red.opacity(0.5)) + ), + as: .image(size: .init(width: 100, height: 100)) + ) + + assertSnapshot( + matching: Rectangle() + .fill(Color.blue) + .frame(width: 80, height: 80) + .overlay( + RoundedRectangle(cornerRadius: 10) + .fill(Color.red) + .frame(width: 40, height: 40), + alignment: .bottomTrailing + ), + as: .image(size: .init(width: 100, height: 100)) + ) + } } #endif diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testBackground.1.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testBackground.1.png new file mode 100644 index 0000000000000000000000000000000000000000..2c10c859d1b221b3218053bd0863b6dfdb5cd55b GIT binary patch literal 1008 zcmeAS@N?(olHy`uVBq!ia0vp^DImF{Fa= z?Ob2~U`L5o{thXxi_=a`7rZ;eL!~g?=}v};-kl$lBYu?M`N96jp+{|(nNXRRl1P@) zwJWR|=Nh!Fmb|ZCIWzd(%q0TLzTdO_URf2z)Gc|7pIOR;r7d%k#tBA7PQ_|PkB*iJ zf(qS>+dMcV88=yE!MGDRB{x2+{Pbsr@^yRW+rA5~GVN( zvFg)NzgcXmwInrfd}Ninxn}LMd2cPB=bw1x{@|_GdG6`I8t%qEIII2Oe11aR!PZ09 zCw$}k@NaH*W01T|q-$DVqPWn1ul;|RZrm=8+4rQ~Eo^-}Q{VrB8?9TmeVG~fw*oN zxlfiFt44=AooMlf`5i4NhA{WU0WL{d)owe7LYX&jcxQ4%AI=m}evptZekntQd-3K& za_wbhWr}>KiwasAn&-xzxVoV^wf4~ouU{M08>3`9-t-*SOL?MGp#LVpGROYtjE7(3 z#3Jw3bGVcpd$>57|9$Ir@!sjz64}>e?JF1k?>C=&dxP=AE&Pd>kL*~y)XluH_<7?| z>jPh<@1FlU<0E&c_?-QmF*BLN4lV1Hj<41U+_XGhUp~(-MpC=v@zIteOIJNUu=x0a zbL9(mGUoiNwlF)OHD$)zC0U{~=CP(VbG6}D-k-~i_toZY%+6H1sF2R0qH>el(Md%`vN7fH z3$BS1QdlPSF4lH&nk499*0*j#iqks(xzE4eUfF-;u?^GudDWU}yPPI)nobnZs?(V+ zkmBU4($Xb=T9svHfH3F6N5_je9o4S5IIZ}xF=lWoOZjA3`fAGTDgS?6ymhiLyT1PW z?a9}~{+r$8-Ctg|cdOXMVfiT9ciy z&B{s7t}JbekwLVqBJ=t~YptbctnHid;{E~UwY+D-+&xqr9>obcwY(4Y?r?9N;mIMm zFI-nqPBckP(d9?n)|QHpj+re2538>VJqlDzBSYa;L8IA%hU-?y0j(AIz4|l6O?lrx zOGaKlkW4zR3B*1Z%L z`1Qxh4;QyZIra2&o>VxRa;JX7A&GmjpD!%uylMZU-R}NZW94YOWeM*kza?iqSo|(k z*R=J9v5A6y+&uYdhMHw62l&qGh)tjW|5^NP{n~4(^11By5A6Lk_wv3y)>8^Ou2l0s zEa1tWI>BIyp|qz5&+!ehDk?mwXWWE@4jbl(b8v2{hBAlC ae)7)~O`WMKcy}u(vw6DuxvX)be{26Y z23Ktt_Vza2xZ>QNkV(sz%+dR`ZcF+7@89apdt3il8^7MR>hlDVV`-(L8UBdA#SF@c4V@W4AaZUY)S!w{zb6JzJ|BXRv37&6;^OQAcIbzv=s~ z)M%bNu(h&o#qq4LrO(nZ7Kb!-ghR`SLyV_ zeNKNk`hMJtXwiwjl6U0w%pb>ZZZOU}ak8%Z(LtH(OgT3sxh>ZnZF##_geh%~6I$2| z1Fl-Ahx#n0Zad3DpjXY8EZf%hSg@yo*E(78c4uosriC{s1XilvoUmZNu5{Tkr&B8z zU6I{(B+TpZNl(kIX*OpgI;2aV6#i@bahd+*s>tjGdOaMn4yFP1cg@%Ei2Fu~*=}k^HA`;>vO%D%xf0X`{ Ww&Z~8E$!K$9O~)n=d#Wzp$Pz6)U@FM literal 0 HcmV?d00001 diff --git a/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testOverlay.2.png b/Tests/TokamakStaticHTMLTests/__Snapshots__/RenderingTests/testOverlay.2.png new file mode 100644 index 0000000000000000000000000000000000000000..0382d49f296597df26ff4a3838e4ae8d7e91de2b GIT binary patch literal 1037 zcmeAS@N?(olHy`uVBq!ia0vp^DImgY^i9Wn-)z}%_}IB4VgDO$I4gxE z=`7f=xPe2~?R(y)V^4ba&AdC^_WJ9Y&s59P@9%v7`(8Ek(KlE6|9ME|am*>a+$kj~ zFhL;Qys5>b!QTsM; z4x@!(DlJ{|hgDf-251X89qoT8)a0{5lyl*u;{|j#PV?S!zqds_AEeo5zkKyEZ&%rt zsndQP-+4Jdc@FRXsvDNm%hJRy{k}Q%%fk8^>-}AmwX}kl+JF4M{%=9U!TO&s1EZ{N zu03G*@5B0xYudlAU%%?Sq*uq@WxIYazB}{k6Rv$9-kiRCa960o*XvUPoN~^7Fw3guQhxI6 zp!KO)7fYCL%u3ojwa>umP3hCpD__LiPP}?JEnI%(vh4;=>*myyX)gh>ZvErm!)XHHT+ARa!jlC+jLoYMhzWV(>D3ld1}HYvd#WwZA^wT2dAmcncj`GXLbX2|OZc zo=!8q7~d3{)Obk8lVft7TC|fygk^sHmfN5{F<4=dD0}SIPPV(|VDLi_;vzf@^*A1UL)b p4n$g4IjJ0xWK4hig0oQViMoUO#GOk%uG$33ik_~1F6*2UngDrv!pZ;u literal 0 HcmV?d00001