From 98a107f7fe64f7301c103a5ece1c806f373d6270 Mon Sep 17 00:00:00 2001 From: Carson Katri Date: Tue, 30 Jun 2020 12:29:06 -0400 Subject: [PATCH] Add ScrollView (#128) * Add ScrollView * Root styles for ScrollView and Stack --- Sources/TokamakCore/Tokens/Axis.swift | 31 +++++++++++++ Sources/TokamakCore/Views/ScrollView.swift | 38 +++++++++++++++ Sources/TokamakDOM/DOMRenderer.swift | 9 +++- .../Modifiers/LayoutModifiers.swift | 4 ++ .../TokamakDOM/Resources/TokamakStyles.swift | 27 +++++++++++ Sources/TokamakDOM/Views/HStack.swift | 1 + Sources/TokamakDOM/Views/ScrollView.swift | 46 +++++++++++++++++++ Sources/TokamakDOM/Views/VStack.swift | 1 + Sources/TokamakDemo/main.swift | 37 ++++++++------- docs/progress.md | 2 +- 10 files changed, 178 insertions(+), 18 deletions(-) create mode 100644 Sources/TokamakCore/Tokens/Axis.swift create mode 100644 Sources/TokamakCore/Views/ScrollView.swift create mode 100644 Sources/TokamakDOM/Resources/TokamakStyles.swift create mode 100644 Sources/TokamakDOM/Views/ScrollView.swift diff --git a/Sources/TokamakCore/Tokens/Axis.swift b/Sources/TokamakCore/Tokens/Axis.swift new file mode 100644 index 00000000..aca72c79 --- /dev/null +++ b/Sources/TokamakCore/Tokens/Axis.swift @@ -0,0 +1,31 @@ +// Copyright 2020 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Carson Katri on 06/29/2020. +// + +public enum Axis: Int8, CaseIterable { + case horizontal + case vertical + + public struct Set: OptionSet { + public let rawValue: Int8 + public init(rawValue: Int8) { + self.rawValue = rawValue + } + + public static let horizontal: Axis.Set = .init(rawValue: 1 << 0) + public static let vertical: Axis.Set = .init(rawValue: 1 << 1) + } +} diff --git a/Sources/TokamakCore/Views/ScrollView.swift b/Sources/TokamakCore/Views/ScrollView.swift new file mode 100644 index 00000000..a3049d9c --- /dev/null +++ b/Sources/TokamakCore/Views/ScrollView.swift @@ -0,0 +1,38 @@ +// Copyright 2020 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Carson Katri on 06/29/2020. +// + +public struct ScrollView: View where Content: View { + public let content: Content + public let axes: Axis.Set + public let showsIndicators: Bool + + public init(_ axes: Axis.Set = .vertical, showsIndicators: Bool = true, @ViewBuilder content: () -> Content) { + self.axes = axes + self.showsIndicators = showsIndicators + self.content = content() + } + + public var body: Never { + neverBody("ScrollView") + } +} + +extension ScrollView: ParentView { + public var children: [AnyView] { + (content as? GroupView)?.children ?? [AnyView(content)] + } +} diff --git a/Sources/TokamakDOM/DOMRenderer.swift b/Sources/TokamakDOM/DOMRenderer.swift index 8cf8f96f..afc29a07 100644 --- a/Sources/TokamakDOM/DOMRenderer.swift +++ b/Sources/TokamakDOM/DOMRenderer.swift @@ -49,6 +49,8 @@ public final class DOMNode: Target { } let log = JSObjectRef.global.console.object!.log.function! +let document = JSObjectRef.global.document.object! +let head = document.head.object! public final class DOMRenderer: Renderer { public private(set) var reconciler: StackReconciler? @@ -57,7 +59,12 @@ public final class DOMRenderer: Renderer { public init(_ view: V, _ ref: JSObjectRef) { rootRef = ref - rootRef.style = "display: flex; width: 100%; height: 100%; justify-content: center; align-items: center;" + rootRef.style = "display: flex; width: 100%; height: 100%; justify-content: center; align-items: center; overflow: hidden;" + + let rootStyle = document.createElement!("style").object! + rootStyle.innerHTML = .string(tokamakStyles) + _ = head.appendChild!(rootStyle) + reconciler = StackReconciler( view: view, target: DOMNode(view, ref), diff --git a/Sources/TokamakDOM/Modifiers/LayoutModifiers.swift b/Sources/TokamakDOM/Modifiers/LayoutModifiers.swift index 454a36e5..89f2b449 100644 --- a/Sources/TokamakDOM/Modifiers/LayoutModifiers.swift +++ b/Sources/TokamakDOM/Modifiers/LayoutModifiers.swift @@ -32,6 +32,8 @@ extension _FrameLayout: DOMViewModifier { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex-grow: 0; + flex-shrink: 0; """] } } @@ -48,6 +50,8 @@ extension _FlexFrameLayout: DOMViewModifier { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + flex-grow: 0; + flex-shrink: 0; """] } } diff --git a/Sources/TokamakDOM/Resources/TokamakStyles.swift b/Sources/TokamakDOM/Resources/TokamakStyles.swift new file mode 100644 index 00000000..57066b8d --- /dev/null +++ b/Sources/TokamakDOM/Resources/TokamakStyles.swift @@ -0,0 +1,27 @@ +// Copyright 2020 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +let tokamakStyles = """ +._tokamak-stack > * { + flex-shrink: 0; +} +._tokamak-scrollview-hideindicators { + scrollbar-color: transparent; + scrollbar-width: 0; +} +._tokamak-scrollview-hideindicators::-webkit-scrollbar { + width: 0; + height: 0; +} +""" diff --git a/Sources/TokamakDOM/Views/HStack.swift b/Sources/TokamakDOM/Views/HStack.swift index 5a1d9649..049978ba 100644 --- a/Sources/TokamakDOM/Views/HStack.swift +++ b/Sources/TokamakDOM/Views/HStack.swift @@ -39,6 +39,7 @@ extension HStack: ViewDeferredToRenderer, SpacerContainer { \(hasSpacer ? "width: 100%;" : "") \(fillCrossAxis ? "height: 100%;" : "") """, + "class": "_tokamak-stack", ]) { content }) } } diff --git a/Sources/TokamakDOM/Views/ScrollView.swift b/Sources/TokamakDOM/Views/ScrollView.swift new file mode 100644 index 00000000..85b70b75 --- /dev/null +++ b/Sources/TokamakDOM/Views/ScrollView.swift @@ -0,0 +1,46 @@ +// Copyright 2020 Tokamak contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Created by Carson Katri on 06/29/2020. +// + +import TokamakCore + +public typealias ScrollView = TokamakCore.ScrollView + +extension ScrollView: ViewDeferredToRenderer, SpacerContainer { + var axis: SpacerContainerAxis { + if axes.contains(.horizontal) { + return .horizontal + } else { + return .vertical + } + } + + public var deferredBody: AnyView { + let scrollX = axes.contains(.horizontal) + let scrollY = axes.contains(.vertical) + return AnyView(HTML("div", [ + "style": """ + \(scrollX ? "overflow-x: auto; width: 100%;" : "overflow-x: hidden;") + \(scrollY ? "overflow-y: auto; height: 100%;" : "overflow-y: hidden;") + \(fillCrossAxis && scrollX ? "height: 100%;" : "") + \(fillCrossAxis && scrollY ? "width: 100%;" : "") + """, + "class": !showsIndicators ? "_tokamak-scrollview-hideindicators" : "", + ]) { + content + }) + } +} diff --git a/Sources/TokamakDOM/Views/VStack.swift b/Sources/TokamakDOM/Views/VStack.swift index af40f169..885b9af6 100644 --- a/Sources/TokamakDOM/Views/VStack.swift +++ b/Sources/TokamakDOM/Views/VStack.swift @@ -39,6 +39,7 @@ extension VStack: ViewDeferredToRenderer, SpacerContainer { \(hasSpacer ? "height: 100%;" : "") \(fillCrossAxis ? "width: 100%;" : "") """, + "class": "_tokamak-stack", ]) { content }) } } diff --git a/Sources/TokamakDemo/main.swift b/Sources/TokamakDemo/main.swift index 38fcc6dd..a3d5a8b1 100644 --- a/Sources/TokamakDemo/main.swift +++ b/Sources/TokamakDemo/main.swift @@ -31,23 +31,28 @@ struct CustomModifier: ViewModifier { let div = document.createElement!("div").object! let renderer = DOMRenderer( - VStack { - Counter(count: 5, limit: 15) - ZStack { - Text("I'm on bottom") - Text("I'm forced to the top") - .zIndex(1) - Text("I'm on top") + ScrollView(showsIndicators: false) { + HStack { + Spacer() + } + VStack { + Counter(count: 5, limit: 15) + ZStack { + Text("I'm on bottom") + Text("I'm forced to the top") + .zIndex(1) + Text("I'm on top") + } + .padding(20) + ForEachDemo() + TextDemo() + SVGCircle() + .frame(width: 25, height: 25) + TextFieldDemo() + SpacerDemo() + Spacer() + Text("Forced to bottom.") } - .padding(20) - ForEachDemo() - TextDemo() - SVGCircle() - .frame(width: 25, height: 25) - TextFieldDemo() - SpacerDemo() - Spacer() - Text("Forced to bottom.") }, div ) diff --git a/docs/progress.md b/docs/progress.md index fd3197b6..21467698 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -85,7 +85,7 @@ Table columns: | --- | ------------------------------------------------------------------------------------------ | :-: | | | [List](https://developer.apple.com/documentation/swiftui/list) | | | 🚧 | [ForEach](https://developer.apple.com/documentation/swiftui/foreach) | | -| | [ScrollView](https://developer.apple.com/documentation/swiftui/scrollview) | | +| 🚧 | [ScrollView](https://developer.apple.com/documentation/swiftui/scrollview) | | | | [ScrollViewReader](https://developer.apple.com/documentation/swiftui/scrollviewreader) | β | | | [ScrollViewProxy](https://developer.apple.com/documentation/swiftui/scrollviewproxy) | β | | | [DynamicViewContent](https://developer.apple.com/documentation/swiftui/dynamicviewcontent) | β |