Add `GeometryReader` implementation (#239)
This is just an empty API at the moment. I hope it can be implemented purely in the `deferredBody` of `GeometryReader` with [the ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver) without requiring any tweaks in the `Renderer` protocol or the reconciler. Seems like I need the `domRef` modifier that writes `JSObjectRef` to a given binding working first, as discussed in #231.
This commit is contained in:
parent
c43d2db1b3
commit
b7434a2e54
|
@ -45,6 +45,8 @@
|
|||
D1B4229324B3B9BB00682F74 /* OutlineGroupDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */; };
|
||||
D1C726F324CB63C6003B576D /* ButtonStyleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */; };
|
||||
D1C726F424CB63C6003B576D /* ButtonStyleDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */; };
|
||||
D1D6B62324D817350041E1D9 /* GeometryReaderDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D6B62224D817350041E1D9 /* GeometryReaderDemo.swift */; };
|
||||
D1D6B62424D817350041E1D9 /* GeometryReaderDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1D6B62224D817350041E1D9 /* GeometryReaderDemo.swift */; };
|
||||
D1E5FDAD24C1D57000E7485E /* TokamakShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1E5FDAC24C1D57000E7485E /* TokamakShim.swift */; };
|
||||
D1E5FDAF24C1D58E00E7485E /* libTokamakShim.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D1E5FDA424C1D54B00E7485E /* libTokamakShim.a */; };
|
||||
D1E5FDB224C1D59400E7485E /* libTokamakShim.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D1E5FDA424C1D54B00E7485E /* libTokamakShim.a */; };
|
||||
|
@ -107,6 +109,7 @@
|
|||
D1B4228E24B3B9BB00682F74 /* ListDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListDemo.swift; sourceTree = "<group>"; };
|
||||
D1B4228F24B3B9BB00682F74 /* OutlineGroupDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutlineGroupDemo.swift; sourceTree = "<group>"; };
|
||||
D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonStyleDemo.swift; sourceTree = "<group>"; };
|
||||
D1D6B62224D817350041E1D9 /* GeometryReaderDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeometryReaderDemo.swift; sourceTree = "<group>"; };
|
||||
D1E5FDA424C1D54B00E7485E /* libTokamakShim.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTokamakShim.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
D1E5FDAC24C1D57000E7485E /* TokamakShim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokamakShim.swift; sourceTree = "<group>"; };
|
||||
D1EE7EA624C0DD2100C0D127 /* PickerDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PickerDemo.swift; sourceTree = "<group>"; };
|
||||
|
@ -168,6 +171,7 @@
|
|||
85ED189924AD425E0085DFA0 /* TokamakDemo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D1D6B62224D817350041E1D9 /* GeometryReaderDemo.swift */,
|
||||
D1C726F224CB63C6003B576D /* ButtonStyleDemo.swift */,
|
||||
B5C76E4924C73ED4003EABB2 /* AppStorageDemo.swift */,
|
||||
B56F22DF24BC89FD001738DF /* ColorDemo.swift */,
|
||||
|
@ -332,6 +336,7 @@
|
|||
85ED186A24AD38F20085DFA0 /* UIAppDelegate.swift in Sources */,
|
||||
B56F22E324BD1C26001738DF /* GridDemo.swift in Sources */,
|
||||
D1B4229224B3B9BB00682F74 /* OutlineGroupDemo.swift in Sources */,
|
||||
D1D6B62324D817350041E1D9 /* GeometryReaderDemo.swift in Sources */,
|
||||
B5DBA22B24D509B4003D3347 /* RedactDemo.swift in Sources */,
|
||||
B56F22E024BC89FD001738DF /* ColorDemo.swift in Sources */,
|
||||
B51F215024B920B400CF2583 /* PathDemo.swift in Sources */,
|
||||
|
@ -358,6 +363,7 @@
|
|||
85ED18AA24AD425E0085DFA0 /* TokamakDemo.swift in Sources */,
|
||||
B56F22E424BD1C26001738DF /* GridDemo.swift in Sources */,
|
||||
D1B4229324B3B9BB00682F74 /* OutlineGroupDemo.swift in Sources */,
|
||||
D1D6B62424D817350041E1D9 /* GeometryReaderDemo.swift in Sources */,
|
||||
B5DBA22C24D509B4003D3347 /* RedactDemo.swift in Sources */,
|
||||
B56F22E124BC89FD001738DF /* ColorDemo.swift in Sources */,
|
||||
B51F215124B920B400CF2583 /* PathDemo.swift in Sources */,
|
||||
|
|
|
@ -21,10 +21,6 @@ protocol AppearanceActionType {
|
|||
struct _AppearanceActionModifier: ViewModifier {
|
||||
var appear: (() -> ())?
|
||||
var disappear: (() -> ())?
|
||||
init(appear: (() -> ())? = nil, disappear: (() -> ())? = nil) {
|
||||
self.appear = appear
|
||||
self.disappear = disappear
|
||||
}
|
||||
|
||||
typealias Body = Never
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// 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.
|
||||
|
||||
// FIXME: these should have standalone implementations
|
||||
extension View {
|
||||
public func _onMount(perform action: (() -> ())? = nil) -> some View {
|
||||
modifier(_AppearanceActionModifier(appear: action))
|
||||
}
|
||||
|
||||
public func _onUnmount(perform action: (() -> ())? = nil) -> some View {
|
||||
modifier(_AppearanceActionModifier(disappear: action))
|
||||
}
|
||||
}
|
|
@ -22,10 +22,6 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
|
|||
override func mount(with reconciler: StackReconciler<R>) {
|
||||
let childBody = reconciler.render(compositeView: self)
|
||||
|
||||
if let appearanceAction = view.view as? AppearanceActionType {
|
||||
appearanceAction.appear?()
|
||||
}
|
||||
|
||||
let child: MountedElement<R> = childBody.makeMountedView(parentTarget, environmentValues)
|
||||
mountedChildren = [child]
|
||||
child.mount(with: reconciler)
|
||||
|
@ -44,6 +40,13 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
|
|||
targetRef.target = hostDescendant.target
|
||||
view.view = targetRef
|
||||
}
|
||||
|
||||
// FIXME: this has to be implemented in a render-specific way, otherwise it's equivalent to
|
||||
// `_onMount` and `_onUnmount` at the moment,
|
||||
// see https://github.com/swiftwasm/Tokamak/issues/175 for more details
|
||||
if let appearanceAction = view.view as? AppearanceActionType {
|
||||
appearanceAction.appear?()
|
||||
}
|
||||
}
|
||||
|
||||
override func unmount(with reconciler: StackReconciler<R>) {
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
// 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.
|
||||
|
||||
public typealias StateObject = ObservedObject
|
|
@ -1,6 +1,17 @@
|
|||
// Copyright 2020 Tokamak contributors
|
||||
//
|
||||
// File.swift
|
||||
// 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 6/28/20.
|
||||
//
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// 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.
|
||||
|
||||
public struct GeometryProxy {
|
||||
public let size: CGSize
|
||||
}
|
||||
|
||||
public func makeProxy(from size: CGSize) -> GeometryProxy {
|
||||
.init(size: size)
|
||||
}
|
||||
|
||||
// FIXME: to be implemented
|
||||
// public enum CoordinateSpace {
|
||||
// case global
|
||||
// case local
|
||||
// case named(AnyHashable)
|
||||
// }
|
||||
|
||||
// public struct Anchor<Value> {
|
||||
// let box: AnchorValueBoxBase<Value>
|
||||
// public struct Source {
|
||||
// private var box: AnchorBoxBase<Value>
|
||||
// }
|
||||
// }
|
||||
|
||||
// extension GeometryProxy {
|
||||
// public let safeAreaInsets: EdgeInsets
|
||||
// public func frame(in coordinateSpace: CoordinateSpace) -> CGRect
|
||||
// public subscript<T>(anchor: Anchor<T>) -> T {}
|
||||
// }
|
||||
|
||||
public struct GeometryReader<Content>: View where Content: View {
|
||||
public let content: (GeometryProxy) -> Content
|
||||
public init(@ViewBuilder content: @escaping (GeometryProxy) -> Content) {
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public var body: Never {
|
||||
neverBody("GeometryReader")
|
||||
}
|
||||
}
|
|
@ -87,6 +87,7 @@ public typealias Button = TokamakCore.Button
|
|||
public typealias DisclosureGroup = TokamakCore.DisclosureGroup
|
||||
public typealias Divider = TokamakCore.Divider
|
||||
public typealias ForEach = TokamakCore.ForEach
|
||||
public typealias GeometryReader = TokamakCore.GeometryReader
|
||||
public typealias GridItem = TokamakCore.GridItem
|
||||
public typealias Group = TokamakCore.Group
|
||||
public typealias HStack = TokamakCore.HStack
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// 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.
|
||||
|
||||
import JavaScriptKit
|
||||
import TokamakCore
|
||||
import TokamakStaticHTML
|
||||
|
||||
private let ResizeObserver = JSObjectRef.global.ResizeObserver.function!
|
||||
|
||||
extension GeometryReader: ViewDeferredToRenderer {
|
||||
public var deferredBody: AnyView {
|
||||
AnyView(_GeometryReader(content: content))
|
||||
}
|
||||
}
|
||||
|
||||
struct _GeometryReader<Content: View>: View {
|
||||
final class State: ObservableObject {
|
||||
/** Holds a strong reference to a `JSClosure` instance that has to stay alive as long as
|
||||
the `_GeometryReader` owner is alive.
|
||||
*/
|
||||
var closure: JSClosure?
|
||||
|
||||
/// A reference to a DOM node being observed for size updates.
|
||||
var observedNodeRef: JSObjectRef?
|
||||
|
||||
/// A reference to a `ResizeObserver` instance.
|
||||
var observerRef: JSObjectRef?
|
||||
|
||||
/// The last known size of the `observedNodeRef` DOM node.
|
||||
@Published var size: CGSize?
|
||||
}
|
||||
|
||||
let content: (GeometryProxy) -> Content
|
||||
|
||||
@StateObject private var state = State()
|
||||
|
||||
var body: some View {
|
||||
HTML("div", ["class": "_tokamak-geometryreader"]) {
|
||||
if let size = state.size {
|
||||
content(makeProxy(from: size))
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
._domRef($state.observedNodeRef)
|
||||
._onMount {
|
||||
let closure = JSClosure { [weak state] args in
|
||||
// FIXME: `JSArrayRef` is not a `RandomAccessCollection` for some reason, which forces
|
||||
// us to use a string subscript
|
||||
guard
|
||||
let rect = args[0].object?[dynamicMember: "0"].object?.contentRect.object,
|
||||
let width = rect.width.number,
|
||||
let height = rect.height.number
|
||||
else { return .undefined }
|
||||
|
||||
state?.size = .init(width: width, height: height)
|
||||
|
||||
return .undefined
|
||||
}
|
||||
state.closure = closure
|
||||
|
||||
let observerRef = ResizeObserver.new(closure)
|
||||
|
||||
_ = observerRef.observe!(state.observedNodeRef!)
|
||||
|
||||
state.observerRef = observerRef
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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.
|
||||
|
||||
import TokamakShim
|
||||
|
||||
struct GeometryReaderDemo: View {
|
||||
var body: some View {
|
||||
GeometryReader {
|
||||
Text("\(String(describing: $0.size))")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -115,6 +115,7 @@ struct TokamakDemoView: View {
|
|||
.zIndex(1)
|
||||
Text("I'm on top")
|
||||
}.padding(20))
|
||||
NavItem("GeometryReader", destination: GeometryReaderDemo())
|
||||
}
|
||||
Section(header: Text("Selectors")) {
|
||||
NavItem("Picker", destination: PickerDemo())
|
||||
|
|
|
@ -31,7 +31,6 @@ public let tokamakStyles = """
|
|||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
._tokamak-disclosuregroup-label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -76,7 +75,13 @@ public let tokamakStyles = """
|
|||
height: 1.2em;
|
||||
border-radius: .1em;
|
||||
}
|
||||
|
||||
._tokamak-geometryreader {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
._tokamak-navigationview {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
Loading…
Reference in New Issue