Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add _targetRef and _domRef modifiers #240

Merged
merged 5 commits into from
Aug 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Sources/TokamakCore/Modifiers/AppearanceActionModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

protocol AppearanceActionProtocol {
protocol AppearanceActionType {
var appear: (() -> ())? { get }
var disappear: (() -> ())? { get }
}
Expand All @@ -29,7 +29,7 @@ struct _AppearanceActionModifier: ViewModifier {
typealias Body = Never
}

extension ModifiedContent: AppearanceActionProtocol
extension ModifiedContent: AppearanceActionType
where Content: View, Modifier == _AppearanceActionModifier {
var appear: (() -> ())? { modifier.appear }
var disappear: (() -> ())? { modifier.disappear }
Expand Down
5 changes: 4 additions & 1 deletion Sources/TokamakCore/Modifiers/StyleModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ public struct _BackgroundModifier<Background>: ViewModifier where Background: Vi
extension _BackgroundModifier: Equatable where Background: Equatable {}

extension View {
public func background<Background>(_ background: Background, alignment: Alignment = .center) -> some View where Background: View {
public func background<Background>(
_ background: Background,
alignment: Alignment = .center
) -> some View where Background: View {
modifier(_BackgroundModifier(background: background, alignment: alignment))
}
}
Expand Down
19 changes: 17 additions & 2 deletions Sources/TokamakCore/MountedViews/MountedCompositeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,34 @@ 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? AppearanceActionProtocol {
if let appearanceAction = view.view as? AppearanceActionType {
appearanceAction.appear?()
}

let child: MountedElement<R> = childBody.makeMountedView(parentTarget, environmentValues)
mountedChildren = [child]
child.mount(with: reconciler)

// `_TargetRef` is a composite view, so it's enough to check for it only here
if var targetRef = view.view as? TargetRefType {
// `_TargetRef` body is not always a host view that has a target, need to traverse
// all descendants to find a `MountedHostView<R>` instance.
var descendant: MountedElement<R>? = child
while descendant != nil && !(descendant is MountedHostView<R>) {
descendant = descendant?.mountedChildren.first
}

guard let hostDescendant = descendant as? MountedHostView<R> else { return }

targetRef.target = hostDescendant.target
view.view = targetRef
}
}

override func unmount(with reconciler: StackReconciler<R>) {
mountedChildren.forEach { $0.unmount(with: reconciler) }

if let appearanceAction = view.view as? AppearanceActionProtocol {
if let appearanceAction = view.view as? AppearanceActionType {
appearanceAction.disappear?()
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/TokamakCore/MountedViews/MountedHostView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
private let parentTarget: R.TargetType

/// Target of this host view supplied by a renderer after mounting has completed.
private var target: R.TargetType?
private(set) var target: R.TargetType?

init(_ view: AnyView, _ parentTarget: R.TargetType, _ environmentValues: EnvironmentValues) {
self.parentTarget = parentTarget
Expand Down
11 changes: 9 additions & 2 deletions Sources/TokamakCore/Shapes/Shape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ extension Shape {
OffsetShape(shape: self, offset: .init(width: x, height: y))
}

public func scale(x: CGFloat = 1, y: CGFloat = 1, anchor: UnitPoint = .center) -> ScaledShape<Self> {
public func scale(
x: CGFloat = 1,
y: CGFloat = 1,
anchor: UnitPoint = .center
) -> ScaledShape<Self> {
ScaledShape(shape: self,
scale: CGSize(width: x, height: y), anchor: anchor)
}
Expand Down Expand Up @@ -121,7 +125,10 @@ extension Shape {
}

extension Shape {
public func fill<S>(_ content: S, style: FillStyle = FillStyle()) -> some View where S: ShapeStyle {
public func fill<S>(
_ content: S,
style: FillStyle = FillStyle()
) -> some View where S: ShapeStyle {
_ShapeView(shape: self, style: content, fillStyle: style)
}

Expand Down
19 changes: 15 additions & 4 deletions Sources/TokamakCore/Shapes/ShapeModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,35 @@
//

extension InsettableShape {
public func strokeBorder<S>(_ content: S, style: StrokeStyle, antialiased: Bool = true) -> some View where S: ShapeStyle {
public func strokeBorder<S>(
_ content: S,
style: StrokeStyle,
antialiased: Bool = true
) -> some View where S: ShapeStyle {
inset(by: style.lineWidth / 2)
.stroke(style: style)
.fill(content, style: FillStyle(antialiased: antialiased))
}

@inlinable public func strokeBorder(style: StrokeStyle, antialiased: Bool = true) -> some View {
@inlinable
public func strokeBorder(style: StrokeStyle, antialiased: Bool = true) -> some View {
inset(by: style.lineWidth / 2)
.stroke(style: style)
.fill(style: FillStyle(antialiased: antialiased))
}

@inlinable public func strokeBorder<S>(_ content: S, lineWidth: CGFloat = 1, antialiased: Bool = true) -> some View where S: ShapeStyle {
@inlinable
public func strokeBorder<S>(
_ content: S,
lineWidth: CGFloat = 1,
antialiased: Bool = true
) -> some View where S: ShapeStyle {
strokeBorder(content, style: StrokeStyle(lineWidth: lineWidth),
antialiased: antialiased)
}

@inlinable public func strokeBorder(lineWidth: CGFloat = 1, antialiased: Bool = true) -> some View {
@inlinable
public func strokeBorder(lineWidth: CGFloat = 1, antialiased: Bool = true) -> some View {
strokeBorder(style: StrokeStyle(lineWidth: lineWidth),
antialiased: antialiased)
}
Expand Down
39 changes: 39 additions & 0 deletions Sources/TokamakCore/State/TargetRef.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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.

protocol TargetRefType {
var target: Target? { get set }
}

public struct _TargetRef<V: View, T>: View, TargetRefType {
let binding: Binding<T?>

let view: V

var target: Target? {
get { binding.wrappedValue as? Target }

set { binding.wrappedValue = newValue as? T }
}

public var body: V { view }
}

extension View {
/** Allows capturing target instance of aclosest descendant host view. The resulting instance
is written to a given `binding`. */
public func _targetRef<T: Target>(_ binding: Binding<T?>) -> _TargetRef<Self, T> {
.init(binding: binding, view: self)
}
}
5 changes: 1 addition & 4 deletions Sources/TokamakCore/Tokens/Edge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ public struct EdgeInsets: Equatable {
public var bottom: CGFloat
public var trailing: CGFloat

public init(top: CGFloat,
leading: CGFloat,
bottom: CGFloat,
trailing: CGFloat) {
public init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) {
self.top = top
self.leading = leading
self.bottom = bottom
Expand Down
30 changes: 30 additions & 0 deletions Sources/TokamakDOM/DOMRef.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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

extension View {
/** Allows capturing DOM references of host views. The resulting reference is written
to a given `binding`.
*/
public func _domRef(_ binding: Binding<JSObjectRef?>) -> some View {
// Convert `Binding<JSObjectRef?>` to `Binding<DOMNode?>` first.
let targetBinding = Binding(
get: { binding.wrappedValue.map(DOMNode.init) },
set: { binding.wrappedValue = $0?.ref }
)
return _targetRef(targetBinding)
}
}
2 changes: 1 addition & 1 deletion Sources/TokamakDOM/DOMRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ let document = JSObjectRef.global.document.object!
let body = document.body.object!
let head = document.head.object!

let timeoutScheduler = { (closure: @escaping () -> ()) in
private let timeoutScheduler = { (closure: @escaping () -> ()) in
let fn = JSClosure { _ in
closure()
return .undefined
Expand Down
28 changes: 28 additions & 0 deletions Sources/TokamakDemo/DOMRefDemo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 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.

#if os(WASI)
import JavaScriptKit
import TokamakShim

struct DOMRefDemo: View {
@State var button: JSObjectRef?

var body: some View {
Button("Click me") {
button?.innerHTML = "This text was set directly through a DOM reference"
}._domRef($button)
}
}
#endif
67 changes: 37 additions & 30 deletions Sources/TokamakDemo/TokamakDemo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,38 +95,45 @@ var redactDemo: NavItem {
}
}

var links: [NavItem] {
[
NavItem("Counter", destination:
Counter(count: Count(value: 5), limit: 15)
.padding()
.background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 1.0))
.border(Color.red, width: 3)),
NavItem("ZStack", destination: ZStack {
Text("I'm on bottom")
Text("I'm forced to the top")
.zIndex(1)
Text("I'm on top")
}.padding(20)),
NavItem("ButtonStyle", destination: ButtonStyleDemo()),
NavItem("ForEach", destination: ForEachDemo()),
NavItem("Text", destination: TextDemo()),
NavItem("Toggle", destination: ToggleDemo()),
NavItem("Path", destination: PathDemo()),
NavItem("TextField", destination: TextFieldDemo()),
NavItem("Spacer", destination: SpacerDemo()),
NavItem("Environment", destination: EnvironmentDemo().font(.system(size: 8))),
NavItem("Picker", destination: PickerDemo()),
NavItem("List", destination: listDemo),
sidebarDemo,
outlineGroupDemo,
NavItem("Color", destination: ColorDemo()),
appStorageDemo,
gridDemo,
redactDemo,
]
var domRefDemo: NavItem {
#if os(WASI)
return NavItem("DOM reference", destination: DOMRefDemo())
#else
return NavItem(unavailable: "DOM reference")
#endif
}

let links = [
NavItem("Counter", destination:
Counter(count: Count(value: 5), limit: 15)
.padding()
.background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 1.0))
.border(Color.red, width: 3)),
NavItem("ZStack", destination: ZStack {
Text("I'm on bottom")
Text("I'm forced to the top")
.zIndex(1)
Text("I'm on top")
}.padding(20)),
NavItem("ButtonStyle", destination: ButtonStyleDemo()),
NavItem("ForEach", destination: ForEachDemo()),
NavItem("Text", destination: TextDemo()),
NavItem("Toggle", destination: ToggleDemo()),
NavItem("Path", destination: PathDemo()),
NavItem("TextField", destination: TextFieldDemo()),
NavItem("Spacer", destination: SpacerDemo()),
NavItem("Environment", destination: EnvironmentDemo().font(.system(size: 8))),
NavItem("Picker", destination: PickerDemo()),
NavItem("List", destination: listDemo),
sidebarDemo,
outlineGroupDemo,
NavItem("Color", destination: ColorDemo()),
appStorageDemo,
gridDemo,
redactDemo,
domRefDemo,
]

struct TokamakDemoView: View {
var body: some View {
NavigationView { () -> AnyView in
Expand Down