Merge branch main

This commit is contained in:
Carson Katri 2020-08-03 17:08:57 -04:00
commit 4b05fd8523
13 changed files with 185 additions and 48 deletions

View File

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
protocol AppearanceActionProtocol { protocol AppearanceActionType {
var appear: (() -> ())? { get } var appear: (() -> ())? { get }
var disappear: (() -> ())? { get } var disappear: (() -> ())? { get }
} }
@ -29,7 +29,7 @@ struct _AppearanceActionModifier: ViewModifier {
typealias Body = Never typealias Body = Never
} }
extension ModifiedContent: AppearanceActionProtocol extension ModifiedContent: AppearanceActionType
where Content: View, Modifier == _AppearanceActionModifier { where Content: View, Modifier == _AppearanceActionModifier {
var appear: (() -> ())? { modifier.appear } var appear: (() -> ())? { modifier.appear }
var disappear: (() -> ())? { modifier.disappear } var disappear: (() -> ())? { modifier.disappear }

View File

@ -43,7 +43,10 @@ extension _BackgroundModifier: Equatable where Background: Equatable {
} }
extension View { 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)) modifier(_BackgroundModifier(background: background, alignment: alignment))
} }
} }

View File

@ -22,19 +22,34 @@ final class MountedCompositeView<R: Renderer>: MountedCompositeElement<R> {
override func mount(with reconciler: StackReconciler<R>) { override func mount(with reconciler: StackReconciler<R>) {
let childBody = reconciler.render(compositeView: self) let childBody = reconciler.render(compositeView: self)
if let appearanceAction = view.view as? AppearanceActionProtocol { if let appearanceAction = view.view as? AppearanceActionType {
appearanceAction.appear?() appearanceAction.appear?()
} }
let child: MountedElement<R> = childBody.makeMountedView(parentTarget, environmentValues) let child: MountedElement<R> = childBody.makeMountedView(parentTarget, environmentValues)
mountedChildren = [child] mountedChildren = [child]
child.mount(with: reconciler) 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>) { override func unmount(with reconciler: StackReconciler<R>) {
mountedChildren.forEach { $0.unmount(with: reconciler) } mountedChildren.forEach { $0.unmount(with: reconciler) }
if let appearanceAction = view.view as? AppearanceActionProtocol { if let appearanceAction = view.view as? AppearanceActionType {
appearanceAction.disappear?() appearanceAction.disappear?()
} }
} }

View File

@ -29,7 +29,7 @@ public final class MountedHostView<R: Renderer>: MountedElement<R> {
private let parentTarget: R.TargetType private let parentTarget: R.TargetType
/// Target of this host view supplied by a renderer after mounting has completed. /// 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) { init(_ view: AnyView, _ parentTarget: R.TargetType, _ environmentValues: EnvironmentValues) {
self.parentTarget = parentTarget self.parentTarget = parentTarget

View File

@ -83,7 +83,11 @@ extension Shape {
OffsetShape(shape: self, offset: .init(width: x, height: y)) 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, ScaledShape(shape: self,
scale: CGSize(width: x, height: y), anchor: anchor) scale: CGSize(width: x, height: y), anchor: anchor)
} }
@ -122,7 +126,10 @@ extension Shape {
} }
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) _ShapeView(shape: self, style: content, fillStyle: style)
} }

View File

@ -16,24 +16,35 @@
// //
extension InsettableShape { 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) inset(by: style.lineWidth / 2)
.stroke(style: style) .stroke(style: style)
.fill(content, style: FillStyle(antialiased: antialiased)) .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) inset(by: style.lineWidth / 2)
.stroke(style: style) .stroke(style: style)
.fill(style: FillStyle(antialiased: antialiased)) .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), strokeBorder(content, style: StrokeStyle(lineWidth: lineWidth),
antialiased: antialiased) 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), strokeBorder(style: StrokeStyle(lineWidth: lineWidth),
antialiased: antialiased) antialiased: antialiased)
} }

View File

@ -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)
}
}

View File

@ -48,10 +48,7 @@ public struct EdgeInsets: Equatable {
public var bottom: CGFloat public var bottom: CGFloat
public var trailing: CGFloat public var trailing: CGFloat
public init(top: CGFloat, public init(top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat) {
leading: CGFloat,
bottom: CGFloat,
trailing: CGFloat) {
self.top = top self.top = top
self.leading = leading self.leading = leading
self.bottom = bottom self.bottom = bottom

View File

@ -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)
}
}

View File

@ -63,7 +63,7 @@ let document = global.document.object!
let body = document.body.object! let body = document.body.object!
let head = document.head.object! let head = document.head.object!
let timeoutScheduler = { (closure: @escaping () -> ()) in private let timeoutScheduler = { (closure: @escaping () -> ()) in
let fn = JSClosure { _ in let fn = JSClosure { _ in
closure() closure()
return .undefined return .undefined

View File

@ -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

View File

@ -95,39 +95,46 @@ var redactDemo: NavItem {
} }
} }
var links: [NavItem] { var domRefDemo: NavItem {
[ #if os(WASI)
NavItem("Counter", destination: return NavItem("DOM reference", destination: DOMRefDemo())
Counter(count: Count(value: 5), limit: 15) #else
.padding() return NavItem(unavailable: "DOM reference")
.background(Color(red: 0.9, green: 0.9, blue: 0.9, opacity: 1.0)) #endif
.border(Color.red, width: 3)
.foregroundColor(.black)),
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,
]
} }
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)
.foregroundColor(.black)),
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 { struct TokamakDemoView: View {
var body: some View { var body: some View {
NavigationView { () -> AnyView in NavigationView { () -> AnyView in